/**
 * Created by IntelliJ IDEA. User: lex Date: Jun 4, 2003 Time: 12:45:56 PM To change this template
 * use Options | File Templates.
 */
public abstract class DebuggerStateManager {
  private final EventDispatcher<DebuggerContextListener> myEventDispatcher =
      EventDispatcher.create(DebuggerContextListener.class);

  public abstract DebuggerContextImpl getContext();

  public abstract void setState(
      DebuggerContextImpl context,
      DebuggerSession.State state,
      DebuggerSession.Event event,
      String description);

  // we allow add listeners inside DebuggerContextListener.changeEvent
  public void addListener(DebuggerContextListener listener) {
    myEventDispatcher.addListener(listener);
  }

  // we allow remove listeners inside DebuggerContextListener.changeEvent
  public void removeListener(DebuggerContextListener listener) {
    myEventDispatcher.removeListener(listener);
  }

  protected void fireStateChanged(DebuggerContextImpl newContext, DebuggerSession.Event event) {
    myEventDispatcher.getMulticaster().changeEvent(newContext, event);
  }
}
 @Override
 public <F extends Facet> void addListener(
     final Class<F> facetClass, final FacetPointerListener<F> listener) {
   EventDispatcher<FacetPointerListener> dispatcher = myDispatchers.get(facetClass);
   if (dispatcher == null) {
     dispatcher = EventDispatcher.create(FacetPointerListener.class);
     myDispatchers.put(facetClass, dispatcher);
   }
   dispatcher.addListener(listener);
 }
/**
 * @author yole
 * @since 28.11.2006
 */
public class MultipleChangeListBrowser extends ChangesBrowser {
  private final ChangeListChooser myChangeListChooser;
  private final ChangeListListener myChangeListListener = new MyChangeListListener();
  private final boolean myShowingAllChangeLists;
  private final EventDispatcher<SelectedListChangeListener> myDispatcher =
      EventDispatcher.create(SelectedListChangeListener.class);
  private final ChangesBrowserExtender myExtender;
  private final Disposable myParentDisposable;
  private final Runnable myRebuildListListener;
  private Collection<Change> myAllChanges;
  private Map<Change, LocalChangeList> myChangeListsMap;
  private boolean myInRebuildList;

  // todo terrible constructor
  public MultipleChangeListBrowser(
      Project project,
      List<? extends ChangeList> changeLists,
      List<Change> changes,
      Disposable parentDisposable,
      ChangeList initialListSelection,
      boolean capableOfExcludingChanges,
      boolean highlightProblems,
      Runnable rebuildListListener,
      @Nullable Runnable inclusionListener,
      AnAction... additionalActions) {
    super(
        project,
        changeLists,
        changes,
        initialListSelection,
        capableOfExcludingChanges,
        highlightProblems,
        inclusionListener,
        MyUseCase.LOCAL_CHANGES,
        null);
    myParentDisposable = parentDisposable;
    myRebuildListListener = rebuildListListener;

    myChangeListChooser = new ChangeListChooser(changeLists);
    myHeaderPanel.add(myChangeListChooser, BorderLayout.EAST);
    myShowingAllChangeLists =
        Comparing.haveEqualElements(
            changeLists, ChangeListManager.getInstance(project).getChangeLists());
    ChangeListManager.getInstance(myProject).addChangeListListener(myChangeListListener);

    myExtender = new Extender(project, this, additionalActions);

    ActionManager actionManager = ActionManager.getInstance();
    final AnAction moveAction = actionManager.getAction(IdeActions.MOVE_TO_ANOTHER_CHANGE_LIST);
    actionManager.addAnActionListener(
        new AnActionListener.Adapter() {
          @Override
          public void afterActionPerformed(
              AnAction action, DataContext dataContext, AnActionEvent event) {
            if (moveAction.equals(action)) {
              rebuildList();
            }
          }
        },
        myParentDisposable);
  }

  @Override
  protected void setInitialSelection(
      List<? extends ChangeList> changeLists,
      List<Change> changes,
      ChangeList initialListSelection) {
    myAllChanges = new ArrayList<Change>();
    mySelectedChangeList = initialListSelection;

    for (ChangeList list : changeLists) {
      if (list instanceof LocalChangeList) {
        myAllChanges.addAll(list.getChanges());
        if (initialListSelection == null) {
          for (Change c : list.getChanges()) {
            if (changes.contains(c)) {
              mySelectedChangeList = list;
              break;
            }
          }
        }
      }
    }

    if (mySelectedChangeList == null) {
      for (ChangeList list : changeLists) {
        if (list instanceof LocalChangeList && ((LocalChangeList) list).isDefault()) {
          mySelectedChangeList = list;
          break;
        }
      }
      if (mySelectedChangeList == null && !changeLists.isEmpty()) {
        mySelectedChangeList = changeLists.get(0);
      }
    }
  }

  @Override
  public void dispose() {
    ChangeListManager.getInstance(myProject).removeChangeListListener(myChangeListListener);
  }

  public Collection<Change> getAllChanges() {
    return myAllChanges;
  }

  public ChangesBrowserExtender getExtender() {
    return myExtender;
  }

  public void addSelectedListChangeListener(SelectedListChangeListener listener) {
    myDispatcher.addListener(listener);
  }

  private void setSelectedList(final ChangeList list) {
    mySelectedChangeList = list;
    rebuildList();
    myDispatcher.getMulticaster().selectedListChanged();
  }

  @Override
  public void rebuildList() {
    if (myInRebuildList) return;
    try {
      myInRebuildList = true;
      if (myChangesToDisplay == null) {
        // changes set not fixed === local changes
        final ChangeListManager manager = ChangeListManager.getInstance(myProject);
        myChangeListsMap = new HashMap<Change, LocalChangeList>();
        final List<LocalChangeList> lists = manager.getChangeListsCopy();
        Collection<Change> allChanges = new ArrayList<Change>();
        for (LocalChangeList list : lists) {
          final Collection<Change> changes = list.getChanges();
          allChanges.addAll(changes);
          for (Change change : changes) {
            myChangeListsMap.put(change, list);
          }
        }
        myAllChanges = allChanges;
        // refresh selected list also
        updateListsInChooser();
      }

      super.rebuildList();
      if (myRebuildListListener != null) {
        myRebuildListListener.run();
      }
    } finally {
      myInRebuildList = false;
    }
  }

  @Override
  public List<Change> getCurrentDisplayedChanges() {
    if (myChangesToDisplay == null) {
      return sortChanges(filterBySelectedChangeList(myAllChanges));
    }
    return super.getCurrentDisplayedChanges();
  }

  @NotNull
  public List<Change> getCurrentIncludedChanges() {
    return filterBySelectedChangeList(myViewer.getIncludedChanges());
  }

  @NotNull
  public Collection<Change> getChangesIncludedInAllLists() {
    return myViewer.getIncludedChanges();
  }

  private List<Change> filterBySelectedChangeList(final Collection<Change> changes) {
    List<Change> filtered = new ArrayList<Change>();
    for (Change change : changes) {
      if (Comparing.equal(getList(change), mySelectedChangeList)) {
        filtered.add(change);
      }
    }
    return filtered;
  }

  private ChangeList getList(final Change change) {
    return myChangeListsMap.get(change);
  }

  @Override
  protected void buildToolBar(final DefaultActionGroup toolBarGroup) {
    super.buildToolBar(toolBarGroup);

    EmptyAction.registerWithShortcutSet(
        IdeActions.MOVE_TO_ANOTHER_CHANGE_LIST, CommonShortcuts.getMove(), myViewer);
    toolBarGroup.add(ActionManager.getInstance().getAction(IdeActions.MOVE_TO_ANOTHER_CHANGE_LIST));
  }

  @Override
  protected List<AnAction> createDiffActions() {
    List<AnAction> actions = super.createDiffActions();
    actions.add(new MoveAction());
    return actions;
  }

  private void updateListsInChooser() {
    Runnable runnable =
        new Runnable() {
          public void run() {
            if (myChangeListChooser != null && myShowingAllChangeLists) {
              myChangeListChooser.updateLists(
                  ChangeListManager.getInstance(myProject).getChangeListsCopy());
            }
          }
        };
    if (SwingUtilities.isEventDispatchThread()) {
      runnable.run();
    } else {
      ApplicationManager.getApplication()
          .invokeLater(runnable, ModalityState.stateForComponent(this));
    }
  }

  private static class Extender implements ChangesBrowserExtender {
    private final Project myProject;
    private final MultipleChangeListBrowser myBrowser;
    private final AnAction[] myAdditionalActions;

    private Extender(
        final Project project,
        final MultipleChangeListBrowser browser,
        AnAction[] additionalActions) {
      myProject = project;
      myBrowser = browser;
      myAdditionalActions = additionalActions;
    }

    public void addToolbarActions(final DialogWrapper dialogWrapper) {
      final Icon icon = AllIcons.Actions.Refresh;
      if (myBrowser.myChangesToDisplay == null) {
        myBrowser.addToolbarAction(
            new AnAction("Refresh Changes") {
              @Override
              public void actionPerformed(AnActionEvent e) {
                myBrowser.rebuildList();
              }

              @Override
              public void update(AnActionEvent e) {
                e.getPresentation().setIcon(icon);
              }
            });
      }
      RollbackDialogAction rollback = new RollbackDialogAction();
      EmptyAction.setupAction(rollback, IdeActions.CHANGES_VIEW_ROLLBACK, myBrowser);
      myBrowser.addToolbarAction(rollback);

      final EditSourceForDialogAction editSourceAction = new EditSourceForDialogAction(myBrowser);
      editSourceAction.registerCustomShortcutSet(CommonShortcuts.getEditSource(), myBrowser);
      myBrowser.addToolbarAction(editSourceAction);

      myBrowser.addToolbarAction(
          ActionManager.getInstance().getAction("Vcs.CheckinProjectToolbar"));

      final List<AnAction> actions =
          AdditionalLocalChangeActionsInstaller.calculateActions(
              myProject, myBrowser.getAllChanges());
      if (actions != null) {
        for (AnAction action : actions) {
          myBrowser.addToolbarAction(action);
        }
      }
      if (myAdditionalActions != null && myAdditionalActions.length > 0) {
        for (AnAction action : myAdditionalActions) {
          myBrowser.addToolbarAction(action);
        }
      }
    }

    public void addSelectedListChangeListener(final SelectedListChangeListener listener) {
      myBrowser.addSelectedListChangeListener(listener);
    }

    public Collection<AbstractVcs> getAffectedVcses() {
      final ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(myProject);
      final Set<AbstractVcs> vcses =
          new HashSet<AbstractVcs>(Arrays.asList(vcsManager.getAllActiveVcss()));
      final Set<AbstractVcs> result = new HashSet<AbstractVcs>();
      for (Change change : myBrowser.myAllChanges) {
        if (vcses.isEmpty()) break;
        final AbstractVcs vcs = ChangesUtil.getVcsForChange(change, myBrowser.myProject);
        if (vcs != null) {
          result.add(vcs);
          vcses.remove(vcs);
        }
      }
      return result;
    }

    public List<Change> getCurrentIncludedChanges() {
      return myBrowser.getCurrentIncludedChanges();
    }
  }

  private class ChangeListChooser extends JPanel {
    private static final int MAX_LEN = 35;
    private final JComboBox myChooser;

    public ChangeListChooser(List<? extends ChangeList> lists) {
      super(new BorderLayout(4, 2));
      myChooser = new JComboBox();
      //noinspection unchecked
      myChooser.setRenderer(
          new ColoredListCellRendererWrapper<LocalChangeList>() {
            @Override
            protected void doCustomize(
                JList list, LocalChangeList value, int index, boolean selected, boolean hasFocus) {
              if (value != null) {
                String name = value.getName().trim();
                if (name.length() > MAX_LEN) {
                  name = name.substring(0, MAX_LEN - 3) + "...";
                }
                append(
                    name,
                    value.isDefault()
                        ? SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES
                        : SimpleTextAttributes.REGULAR_ATTRIBUTES);
              }
            }
          });

      myChooser.addItemListener(
          new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
              if (e.getStateChange() == ItemEvent.SELECTED) {
                final LocalChangeList changeList = (LocalChangeList) myChooser.getSelectedItem();
                setSelectedList(changeList);
                myChooser.setToolTipText(changeList == null ? "" : (changeList.getName()));
              }
            }
          });

      updateLists(lists);
      myChooser.setEditable(false);
      add(myChooser, BorderLayout.CENTER);

      JLabel label = new JLabel(VcsBundle.message("commit.dialog.changelist.label"));
      label.setLabelFor(myChooser);
      add(label, BorderLayout.WEST);
    }

    public void updateLists(List<? extends ChangeList> lists) {
      //noinspection unchecked
      myChooser.setModel(new DefaultComboBoxModel(lists.toArray()));
      myChooser.setEnabled(lists.size() > 1);
      if (lists.contains(mySelectedChangeList)) {
        myChooser.setSelectedItem(mySelectedChangeList);
      } else {
        if (myChooser.getItemCount() > 0) {
          myChooser.setSelectedIndex(0);
        }
      }
      mySelectedChangeList = (ChangeList) myChooser.getSelectedItem();
    }
  }

  private class MyChangeListListener extends ChangeListAdapter {
    public void changeListAdded(ChangeList list) {
      updateListsInChooser();
    }
  }

  private class MoveAction extends MoveChangesToAnotherListAction {
    @Override
    protected boolean isEnabled(AnActionEvent e) {
      Change change = e.getData(VcsDataKeys.CURRENT_CHANGE);
      if (change == null) return false;
      return super.isEnabled(e);
    }

    public void actionPerformed(AnActionEvent e) {
      Change change = e.getData(VcsDataKeys.CURRENT_CHANGE);
      askAndMove(myProject, Collections.singletonList(change), null);
    }
  }
}
public class BreakpointManager {
  private static final Logger LOG =
      Logger.getInstance("#com.intellij.debugger.ui.breakpoints.BreakpointManager");

  @NonNls private static final String MASTER_BREAKPOINT_TAGNAME = "master_breakpoint";
  @NonNls private static final String SLAVE_BREAKPOINT_TAGNAME = "slave_breakpoint";

  @NonNls
  private static final String DEFAULT_SUSPEND_POLICY_ATTRIBUTE_NAME = "default_suspend_policy";

  @NonNls
  private static final String DEFAULT_CONDITION_STATE_ATTRIBUTE_NAME = "default_condition_enabled";

  @NonNls private static final String RULES_GROUP_NAME = "breakpoint_rules";
  private static final String CONVERTED_PARAM = "converted";

  private final Project myProject;
  private final Map<XBreakpoint, Breakpoint> myBreakpoints =
      new HashMap<XBreakpoint, Breakpoint>(); // breakpoints storage, access should be synchronized

  @Nullable
  private List<Breakpoint> myBreakpointsListForIteration =
      null; // another list for breakpoints iteration, unsynchronized access ok

  private final Map<String, String> myUIProperties = new LinkedHashMap<String, String>();
  // private final Map<Key<? extends Breakpoint>, BreakpointDefaults> myBreakpointDefaults = new
  // LinkedHashMap<Key<? extends Breakpoint>, BreakpointDefaults>();

  private final EventDispatcher<BreakpointManagerListener> myDispatcher =
      EventDispatcher.create(BreakpointManagerListener.class);

  private final StartupManager myStartupManager;

  private void update(@NotNull List<BreakpointWithHighlighter> breakpoints) {
    final TIntHashSet intHash = new TIntHashSet();
    for (BreakpointWithHighlighter breakpoint : breakpoints) {
      SourcePosition sourcePosition = breakpoint.getSourcePosition();
      breakpoint.reload();

      if (breakpoint.isValid()) {
        if (sourcePosition == null
            || breakpoint.getSourcePosition().getLine() != sourcePosition.getLine()) {
          fireBreakpointChanged(breakpoint);
        }

        if (intHash.contains(breakpoint.getLineIndex())) {
          remove(breakpoint);
        } else {
          intHash.add(breakpoint.getLineIndex());
        }
      } else {
        remove(breakpoint);
      }
    }
  }

  private void remove(final BreakpointWithHighlighter breakpoint) {
    DebuggerInvocationUtil.invokeLater(
        myProject,
        new Runnable() {
          @Override
          public void run() {
            removeBreakpoint(breakpoint);
          }
        });
  }

  public BreakpointManager(
      @NotNull Project project,
      @NotNull StartupManager startupManager,
      @NotNull DebuggerManagerImpl debuggerManager) {
    myProject = project;
    myStartupManager = startupManager;
    debuggerManager
        .getContextManager()
        .addListener(
            new DebuggerContextListener() {
              private DebuggerSession myPreviousSession;

              @Override
              public void changeEvent(@NotNull DebuggerContextImpl newContext, int event) {
                if (newContext.getDebuggerSession() != myPreviousSession
                    || event == DebuggerSession.EVENT_DETACHED) {
                  updateBreakpointsUI();
                  myPreviousSession = newContext.getDebuggerSession();
                }
              }
            });
  }

  public void init() {
    XBreakpointManager manager = XDebuggerManager.getInstance(myProject).getBreakpointManager();
    manager.addBreakpointListener(
        new XBreakpointListener() {
          @Override
          public void breakpointAdded(@NotNull XBreakpoint xBreakpoint) {
            if (isJavaType(xBreakpoint)) {
              onBreakpointAdded(xBreakpoint);
            }
          }

          @Override
          public void breakpointRemoved(@NotNull XBreakpoint xBreakpoint) {
            onBreakpointRemoved(xBreakpoint);
          }

          @Override
          public void breakpointChanged(@NotNull XBreakpoint xBreakpoint) {
            Breakpoint breakpoint = myBreakpoints.get(xBreakpoint);
            if (breakpoint != null) {
              fireBreakpointChanged(breakpoint);
            }
          }
        });
  }

  private XBreakpointManager getXBreakpointManager() {
    return XDebuggerManager.getInstance(myProject).getBreakpointManager();
  }

  public void editBreakpoint(final Breakpoint breakpoint, final Editor editor) {
    DebuggerInvocationUtil.swingInvokeLater(
        myProject,
        new Runnable() {
          @Override
          public void run() {
            XBreakpoint xBreakpoint = breakpoint.myXBreakpoint;
            if (xBreakpoint instanceof XLineBreakpointImpl) {
              RangeHighlighter highlighter = ((XLineBreakpointImpl) xBreakpoint).getHighlighter();
              if (highlighter != null) {
                GutterIconRenderer renderer = highlighter.getGutterIconRenderer();
                if (renderer != null) {
                  DebuggerSupport.getDebuggerSupport(JavaDebuggerSupport.class)
                      .getEditBreakpointAction()
                      .editBreakpoint(myProject, editor, breakpoint.myXBreakpoint, renderer);
                }
              }
            }
          }
        });
  }

  // @NotNull
  // public BreakpointDefaults getBreakpointDefaults(Key<? extends Breakpoint> category) {
  //  BreakpointDefaults defaults = myBreakpointDefaults.get(category);
  //  if (defaults == null) {
  //    defaults = new BreakpointDefaults();
  //  }
  //  return defaults;
  // }

  public void setBreakpointDefaults(
      Key<? extends Breakpoint> category, BreakpointDefaults defaults) {
    Class typeCls = null;
    if (LineBreakpoint.CATEGORY.toString().equals(category.toString())) {
      typeCls = JavaLineBreakpointType.class;
    } else if (MethodBreakpoint.CATEGORY.toString().equals(category.toString())) {
      typeCls = JavaMethodBreakpointType.class;
    } else if (FieldBreakpoint.CATEGORY.toString().equals(category.toString())) {
      typeCls = JavaFieldBreakpointType.class;
    } else if (ExceptionBreakpoint.CATEGORY.toString().equals(category.toString())) {
      typeCls = JavaExceptionBreakpointType.class;
    }
    if (typeCls != null) {
      XBreakpointType<XBreakpoint<?>, ?> type =
          XDebuggerUtil.getInstance().findBreakpointType(typeCls);
      ((XBreakpointManagerImpl) getXBreakpointManager())
          .getBreakpointDefaults(type)
          .setSuspendPolicy(Breakpoint.transformSuspendPolicy(defaults.getSuspendPolicy()));
    }
    // myBreakpointDefaults.put(category, defaults);
  }

  @Nullable
  public RunToCursorBreakpoint addRunToCursorBreakpoint(
      Document document, int lineIndex, final boolean ignoreBreakpoints) {
    return RunToCursorBreakpoint.create(myProject, document, lineIndex, ignoreBreakpoints);
  }

  @Nullable
  public StepIntoBreakpoint addStepIntoBreakpoint(@NotNull BreakpointStepMethodFilter filter) {
    return StepIntoBreakpoint.create(myProject, filter);
  }

  @Nullable
  public LineBreakpoint addLineBreakpoint(Document document, int lineIndex) {
    ApplicationManager.getApplication().assertIsDispatchThread();
    if (!LineBreakpoint.canAddLineBreakpoint(myProject, document, lineIndex)) {
      return null;
    }
    XLineBreakpoint xLineBreakpoint =
        addXLineBreakpoint(JavaLineBreakpointType.class, document, lineIndex);
    LineBreakpoint breakpoint = LineBreakpoint.create(myProject, xLineBreakpoint);
    if (breakpoint == null) {
      return null;
    }

    addBreakpoint(breakpoint);
    return breakpoint;
  }

  // @Nullable
  // public FieldBreakpoint addFieldBreakpoint(Field field, ObjectReference object) {
  //  ApplicationManager.getApplication().assertIsDispatchThread();
  //  final FieldBreakpoint fieldBreakpoint = FieldBreakpoint.create(myProject, field, object,
  // null);
  //  if (fieldBreakpoint != null) {
  //    addBreakpoint(fieldBreakpoint);
  //  }
  //  return fieldBreakpoint;
  // }

  @Nullable
  public FieldBreakpoint addFieldBreakpoint(@NotNull Document document, int offset) {
    PsiField field = FieldBreakpoint.findField(myProject, document, offset);
    if (field == null) {
      return null;
    }

    int line = document.getLineNumber(offset);

    if (document.getLineNumber(field.getNameIdentifier().getTextOffset()) < line) {
      return null;
    }

    return addFieldBreakpoint(document, line, field.getName());
  }

  @Nullable
  public FieldBreakpoint addFieldBreakpoint(Document document, int lineIndex, String fieldName) {
    ApplicationManager.getApplication().assertIsDispatchThread();
    XLineBreakpoint xBreakpoint =
        addXLineBreakpoint(JavaFieldBreakpointType.class, document, lineIndex);
    FieldBreakpoint fieldBreakpoint = FieldBreakpoint.create(myProject, fieldName, xBreakpoint);
    if (fieldBreakpoint != null) {
      addBreakpoint(fieldBreakpoint);
    }
    return fieldBreakpoint;
  }

  @NotNull
  public ExceptionBreakpoint addExceptionBreakpoint(
      @NotNull final String exceptionClassName, final String packageName) {
    ApplicationManager.getApplication().assertIsDispatchThread();
    final JavaExceptionBreakpointType type =
        (JavaExceptionBreakpointType)
            XDebuggerUtil.getInstance().findBreakpointType(JavaExceptionBreakpointType.class);
    return ApplicationManager.getApplication()
        .runWriteAction(
            new Computable<ExceptionBreakpoint>() {
              @Override
              public ExceptionBreakpoint compute() {
                XBreakpoint<JavaExceptionBreakpointProperties> xBreakpoint =
                    XDebuggerManager.getInstance(myProject)
                        .getBreakpointManager()
                        .addBreakpoint(
                            type,
                            new JavaExceptionBreakpointProperties(exceptionClassName, packageName));
                ExceptionBreakpoint breakpoint =
                    new ExceptionBreakpoint(
                        myProject, exceptionClassName, packageName, xBreakpoint);
                addBreakpoint(breakpoint);
                if (LOG.isDebugEnabled()) {
                  LOG.debug("ExceptionBreakpoint Added");
                }
                return breakpoint;
              }
            });
  }

  @Nullable
  public MethodBreakpoint addMethodBreakpoint(Document document, int lineIndex) {
    ApplicationManager.getApplication().assertIsDispatchThread();

    XLineBreakpoint xBreakpoint =
        addXLineBreakpoint(JavaMethodBreakpointType.class, document, lineIndex);
    MethodBreakpoint breakpoint = MethodBreakpoint.create(myProject, xBreakpoint);
    if (breakpoint == null) {
      return null;
    }

    XDebugSessionImpl.NOTIFICATION_GROUP
        .createNotification(
            "Method breakpoints may dramatically slow down debugging", MessageType.WARNING)
        .notify(myProject);

    addBreakpoint(breakpoint);
    return breakpoint;
  }

  private <B extends XBreakpoint<?>> XLineBreakpoint addXLineBreakpoint(
      Class<? extends XBreakpointType<B, ?>> typeCls, Document document, final int lineIndex) {
    final XBreakpointType<B, ?> type = XDebuggerUtil.getInstance().findBreakpointType(typeCls);
    final VirtualFile file = FileDocumentManager.getInstance().getFile(document);
    return ApplicationManager.getApplication()
        .runWriteAction(
            new Computable<XLineBreakpoint>() {
              @Override
              public XLineBreakpoint compute() {
                return XDebuggerManager.getInstance(myProject)
                    .getBreakpointManager()
                    .addLineBreakpoint(
                        (XLineBreakpointType) type,
                        file.getUrl(),
                        lineIndex,
                        ((XLineBreakpointType) type).createBreakpointProperties(file, lineIndex));
              }
            });
  }

  @Nullable
  public WildcardMethodBreakpoint addMethodBreakpoint(String classPattern, String methodName) {
    ApplicationManager.getApplication().assertIsDispatchThread();
    WildcardMethodBreakpoint breakpoint =
        WildcardMethodBreakpoint.create(myProject, classPattern, methodName, null);
    if (breakpoint == null) {
      return null;
    }
    addBreakpoint(breakpoint);
    return breakpoint;
  }

  /** @return null if not found or a breakpoint object */
  @NotNull
  public List<BreakpointWithHighlighter> findBreakpoints(
      final Document document, final int offset) {
    LinkedList<BreakpointWithHighlighter> result = new LinkedList<BreakpointWithHighlighter>();
    ApplicationManager.getApplication().assertIsDispatchThread();
    for (final Breakpoint breakpoint : getBreakpoints()) {
      if (breakpoint instanceof BreakpointWithHighlighter
          && ((BreakpointWithHighlighter) breakpoint).isAt(document, offset)) {
        result.add((BreakpointWithHighlighter) breakpoint);
      }
    }

    return result;
  }

  @NotNull
  public List<BreakpointWithHighlighter> findBreakpoints(
      @NotNull Document document, @NotNull TextRange textRange) {
    ApplicationManager.getApplication().assertIsDispatchThread();
    List<BreakpointWithHighlighter> result = new ArrayList<BreakpointWithHighlighter>();
    int startLine = document.getLineNumber(textRange.getStartOffset());
    int endLine = document.getLineNumber(textRange.getEndOffset()) + 1;
    TextRange lineRange = new TextRange(startLine, endLine);
    for (final Breakpoint breakpoint : getBreakpoints()) {
      if (breakpoint instanceof BreakpointWithHighlighter
          && lineRange.contains(((BreakpointWithHighlighter) breakpoint).getLineIndex())) {
        result.add((BreakpointWithHighlighter) breakpoint);
      }
    }

    return result;
  }

  /** @param category breakpoint category, null if the category does not matter */
  @Nullable
  public <T extends BreakpointWithHighlighter> T findBreakpoint(
      final Document document, final int offset, @Nullable final Key<T> category) {
    for (final Breakpoint breakpoint : getBreakpoints()) {
      if (breakpoint instanceof BreakpointWithHighlighter
          && ((BreakpointWithHighlighter) breakpoint).isAt(document, offset)) {
        if (category == null || category.equals(breakpoint.getCategory())) {
          //noinspection CastConflictsWithInstanceof,unchecked
          return (T) breakpoint;
        }
      }
    }
    return null;
  }

  @Nullable
  public static Breakpoint findBreakpoint(@NotNull XBreakpoint xBreakpoint) {
    Project project = ((XBreakpointBase) xBreakpoint).getProject();
    BreakpointManager breakpointManager =
        DebuggerManagerEx.getInstanceEx(project).getBreakpointManager();
    return breakpointManager.myBreakpoints.get(xBreakpoint);
  }

  private List<Element> myOriginalBreakpointsNodes = new ArrayList<Element>();

  public void readExternal(@NotNull final Element parentNode) {
    // save old breakpoints
    for (Element element : parentNode.getChildren()) {
      myOriginalBreakpointsNodes.add(element.clone());
    }
    if (myProject.isOpen()) {
      doRead(parentNode);
    } else {
      myStartupManager.registerPostStartupActivity(
          new Runnable() {
            @Override
            public void run() {
              doRead(parentNode);
            }
          });
    }
  }

  private void doRead(@NotNull final Element parentNode) {
    ApplicationManager.getApplication()
        .runReadAction(
            new Runnable() {
              @Override
              @SuppressWarnings({"HardCodedStringLiteral"})
              public void run() {
                final Map<String, Breakpoint> nameToBreakpointMap =
                    new THashMap<String, Breakpoint>();
                try {
                  final List groups = parentNode.getChildren();
                  for (final Object group1 : groups) {
                    final Element group = (Element) group1;
                    if (group.getName().equals(RULES_GROUP_NAME)) {
                      continue;
                    }
                    // skip already converted
                    if (group.getAttribute(CONVERTED_PARAM) != null) {
                      continue;
                    }
                    final String categoryName = group.getName();
                    final Key<Breakpoint> breakpointCategory =
                        BreakpointCategory.lookup(categoryName);
                    final String defaultPolicy =
                        group.getAttributeValue(DEFAULT_SUSPEND_POLICY_ATTRIBUTE_NAME);
                    final boolean conditionEnabled =
                        Boolean.parseBoolean(
                            group.getAttributeValue(
                                DEFAULT_CONDITION_STATE_ATTRIBUTE_NAME, "true"));
                    setBreakpointDefaults(
                        breakpointCategory,
                        new BreakpointDefaults(defaultPolicy, conditionEnabled));
                    Element anyExceptionBreakpointGroup;
                    if (!AnyExceptionBreakpoint.ANY_EXCEPTION_BREAKPOINT.equals(
                        breakpointCategory)) {
                      // for compatibility with previous format
                      anyExceptionBreakpointGroup =
                          group.getChild(
                              AnyExceptionBreakpoint.ANY_EXCEPTION_BREAKPOINT.toString());
                      // final BreakpointFactory factory =
                      // BreakpointFactory.getInstance(breakpointCategory);
                      // if (factory != null) {
                      for (Element breakpointNode : group.getChildren("breakpoint")) {
                        // Breakpoint breakpoint = factory.createBreakpoint(myProject,
                        // breakpointNode);
                        Breakpoint breakpoint = createBreakpoint(categoryName, breakpointNode);
                        breakpoint.readExternal(breakpointNode);
                        nameToBreakpointMap.put(breakpoint.getDisplayName(), breakpoint);
                      }
                      // }
                    } else {
                      anyExceptionBreakpointGroup = group;
                    }

                    if (anyExceptionBreakpointGroup != null) {
                      final Element breakpointElement = group.getChild("breakpoint");
                      if (breakpointElement != null) {
                        XBreakpointManager manager =
                            XDebuggerManager.getInstance(myProject).getBreakpointManager();
                        JavaExceptionBreakpointType type =
                            (JavaExceptionBreakpointType)
                                XDebuggerUtil.getInstance()
                                    .findBreakpointType(JavaExceptionBreakpointType.class);
                        XBreakpoint<JavaExceptionBreakpointProperties> xBreakpoint =
                            manager.getDefaultBreakpoint(type);
                        Breakpoint breakpoint = createJavaBreakpoint(xBreakpoint);
                        breakpoint.readExternal(breakpointElement);
                        addBreakpoint(breakpoint);
                      }
                    }
                  }
                } catch (InvalidDataException ignored) {
                }

                final Element rulesGroup = parentNode.getChild(RULES_GROUP_NAME);
                if (rulesGroup != null) {
                  final List<Element> rules = rulesGroup.getChildren("rule");
                  for (Element rule : rules) {
                    // skip already converted
                    if (rule.getAttribute(CONVERTED_PARAM) != null) {
                      continue;
                    }
                    final Element master = rule.getChild(MASTER_BREAKPOINT_TAGNAME);
                    if (master == null) {
                      continue;
                    }
                    final Element slave = rule.getChild(SLAVE_BREAKPOINT_TAGNAME);
                    if (slave == null) {
                      continue;
                    }
                    final Breakpoint masterBreakpoint =
                        nameToBreakpointMap.get(master.getAttributeValue("name"));
                    if (masterBreakpoint == null) {
                      continue;
                    }
                    final Breakpoint slaveBreakpoint =
                        nameToBreakpointMap.get(slave.getAttributeValue("name"));
                    if (slaveBreakpoint == null) {
                      continue;
                    }

                    boolean leaveEnabled =
                        "true".equalsIgnoreCase(rule.getAttributeValue("leaveEnabled"));
                    XDependentBreakpointManager dependentBreakpointManager =
                        ((XBreakpointManagerImpl) getXBreakpointManager())
                            .getDependentBreakpointManager();
                    dependentBreakpointManager.setMasterBreakpoint(
                        slaveBreakpoint.myXBreakpoint,
                        masterBreakpoint.myXBreakpoint,
                        leaveEnabled);
                    // addBreakpointRule(new EnableBreakpointRule(BreakpointManager.this,
                    // masterBreakpoint, slaveBreakpoint, leaveEnabled));
                  }
                }

                DebuggerInvocationUtil.invokeLater(
                    myProject,
                    new Runnable() {
                      @Override
                      public void run() {
                        updateBreakpointsUI();
                      }
                    });
              }
            });

    myUIProperties.clear();
    final Element props = parentNode.getChild("ui_properties");
    if (props != null) {
      final List children = props.getChildren("property");
      for (Object child : children) {
        Element property = (Element) child;
        final String name = property.getAttributeValue("name");
        final String value = property.getAttributeValue("value");
        if (name != null && value != null) {
          myUIProperties.put(name, value);
        }
      }
    }
  }

  private Breakpoint createBreakpoint(String category, Element breakpointNode)
      throws InvalidDataException {
    XBreakpoint xBreakpoint = null;
    if (category.equals(LineBreakpoint.CATEGORY.toString())) {
      xBreakpoint = createXLineBreakpoint(JavaLineBreakpointType.class, breakpointNode);
    } else if (category.equals(MethodBreakpoint.CATEGORY.toString())) {
      if (breakpointNode.getAttribute("url") != null) {
        xBreakpoint = createXLineBreakpoint(JavaMethodBreakpointType.class, breakpointNode);
      } else {
        xBreakpoint = createXBreakpoint(JavaWildcardMethodBreakpointType.class, breakpointNode);
      }
    } else if (category.equals(FieldBreakpoint.CATEGORY.toString())) {
      xBreakpoint = createXLineBreakpoint(JavaFieldBreakpointType.class, breakpointNode);
    } else if (category.equals(ExceptionBreakpoint.CATEGORY.toString())) {
      xBreakpoint = createXBreakpoint(JavaExceptionBreakpointType.class, breakpointNode);
    }
    if (xBreakpoint == null) {
      throw new IllegalStateException("Unknown breakpoint category " + category);
    }
    return myBreakpoints.get(xBreakpoint);
  }

  private <B extends XBreakpoint<?>> XBreakpoint createXBreakpoint(
      Class<? extends XBreakpointType<B, ?>> typeCls, Element breakpointNode)
      throws InvalidDataException {
    final XBreakpointType<B, ?> type = XDebuggerUtil.getInstance().findBreakpointType(typeCls);
    return ApplicationManager.getApplication()
        .runWriteAction(
            new Computable<XBreakpoint>() {
              @Override
              public XBreakpoint compute() {
                return XDebuggerManager.getInstance(myProject)
                    .getBreakpointManager()
                    .addBreakpoint((XBreakpointType) type, type.createProperties());
              }
            });
  }

  private <B extends XBreakpoint<?>> XLineBreakpoint createXLineBreakpoint(
      Class<? extends XBreakpointType<B, ?>> typeCls, Element breakpointNode)
      throws InvalidDataException {
    final String url = breakpointNode.getAttributeValue("url");
    VirtualFile vFile = VirtualFileManager.getInstance().findFileByUrl(url);
    if (vFile == null) {
      throw new InvalidDataException(
          DebuggerBundle.message("error.breakpoint.file.not.found", url));
    }
    final Document doc = FileDocumentManager.getInstance().getDocument(vFile);
    if (doc == null) {
      throw new InvalidDataException(
          DebuggerBundle.message("error.cannot.load.breakpoint.file", url));
    }

    final int line;
    try {
      //noinspection HardCodedStringLiteral
      line = Integer.parseInt(breakpointNode.getAttributeValue("line"));
    } catch (Exception e) {
      throw new InvalidDataException("Line number is invalid for breakpoint");
    }
    return addXLineBreakpoint(typeCls, doc, line);
  }

  // used in Fabrique
  public synchronized void addBreakpoint(@NotNull Breakpoint breakpoint) {
    myBreakpoints.put(breakpoint.myXBreakpoint, breakpoint);
    myBreakpointsListForIteration = null;
    breakpoint.updateUI();
    RequestManagerImpl.createRequests(breakpoint);
    myDispatcher.getMulticaster().breakpointsChanged();
    if (breakpoint instanceof MethodBreakpoint || breakpoint instanceof WildcardMethodBreakpoint) {
      XDebugSessionImpl.NOTIFICATION_GROUP
          .createNotification(
              "Method breakpoints may dramatically slow down debugging", MessageType.WARNING)
          .notify(myProject);
    }
  }

  private synchronized void onBreakpointAdded(XBreakpoint xBreakpoint) {
    Breakpoint breakpoint = createJavaBreakpoint(xBreakpoint);
    addBreakpoint(breakpoint);
  }

  public void removeBreakpoint(@Nullable final Breakpoint breakpoint) {
    if (breakpoint == null) {
      return;
    }
    ApplicationManager.getApplication()
        .runWriteAction(
            new Runnable() {
              @Override
              public void run() {
                getXBreakpointManager().removeBreakpoint(breakpoint.myXBreakpoint);
              }
            });
  }

  private synchronized void onBreakpointRemoved(@Nullable final XBreakpoint xBreakpoint) {
    ApplicationManager.getApplication().assertIsDispatchThread();
    if (xBreakpoint == null) {
      return;
    }

    Breakpoint breakpoint = myBreakpoints.remove(xBreakpoint);
    if (breakpoint != null) {
      // updateBreakpointRules(breakpoint);
      myBreakpointsListForIteration = null;
      // we delete breakpoints inside release, so gutter will not fire events to deleted breakpoints
      breakpoint.delete();

      RequestManagerImpl.deleteRequests(breakpoint);
      myDispatcher.getMulticaster().breakpointsChanged();
    }
  }

  public void writeExternal(@NotNull final Element parentNode) {
    // restore old breakpoints
    for (Element group : myOriginalBreakpointsNodes) {
      if (group.getAttribute(CONVERTED_PARAM) == null) {
        group.setAttribute(CONVERTED_PARAM, "true");
      }
      group.detach();
    }

    parentNode.addContent(myOriginalBreakpointsNodes);
    // ApplicationManager.getApplication().runReadAction(new Runnable() {
    //  @Override
    //  public void run() {
    //    removeInvalidBreakpoints();
    //    final Map<Key<? extends Breakpoint>, Element> categoryToElementMap = new THashMap<Key<?
    // extends Breakpoint>, Element>();
    //    for (Key<? extends Breakpoint> category : myBreakpointDefaults.keySet()) {
    //      final Element group = getCategoryGroupElement(categoryToElementMap, category,
    // parentNode);
    //      final BreakpointDefaults defaults = getBreakpointDefaults(category);
    //      group.setAttribute(DEFAULT_SUSPEND_POLICY_ATTRIBUTE_NAME,
    // String.valueOf(defaults.getSuspendPolicy()));
    //      group.setAttribute(DEFAULT_CONDITION_STATE_ATTRIBUTE_NAME,
    // String.valueOf(defaults.isConditionEnabled()));
    //    }
    //    // don't store invisible breakpoints
    //    for (Breakpoint breakpoint : getBreakpoints()) {
    //      if (breakpoint.isValid() &&
    //          (!(breakpoint instanceof BreakpointWithHighlighter) ||
    // ((BreakpointWithHighlighter)breakpoint).isVisible())) {
    //        writeBreakpoint(getCategoryGroupElement(categoryToElementMap,
    // breakpoint.getCategory(), parentNode), breakpoint);
    //      }
    //    }
    //    final AnyExceptionBreakpoint anyExceptionBreakpoint = getAnyExceptionBreakpoint();
    //    final Element group = getCategoryGroupElement(categoryToElementMap,
    // anyExceptionBreakpoint.getCategory(), parentNode);
    //    writeBreakpoint(group, anyExceptionBreakpoint);
    //
    //    final Element rules = new Element(RULES_GROUP_NAME);
    //    parentNode.addContent(rules);
    //    //for (EnableBreakpointRule myBreakpointRule : myBreakpointRules) {
    //    //  writeRule(myBreakpointRule, rules);
    //    //}
    //  }
    // });
    //
    // final Element uiProperties = new Element("ui_properties");
    // parentNode.addContent(uiProperties);
    // for (final String name : myUIProperties.keySet()) {
    //  Element property = new Element("property");
    //  uiProperties.addContent(property);
    //  property.setAttribute("name", name);
    //  property.setAttribute("value", myUIProperties.get(name));
    // }
  }

  // @SuppressWarnings({"HardCodedStringLiteral"})
  // private static void writeRule(@NotNull final EnableBreakpointRule enableBreakpointRule,
  // @NotNull Element element) {
  //  Element rule = new Element("rule");
  //  if (enableBreakpointRule.isLeaveEnabled()) {
  //    rule.setAttribute("leaveEnabled", Boolean.toString(true));
  //  }
  //  element.addContent(rule);
  //  writeRuleBreakpoint(rule, MASTER_BREAKPOINT_TAGNAME,
  // enableBreakpointRule.getMasterBreakpoint());
  //  writeRuleBreakpoint(rule, SLAVE_BREAKPOINT_TAGNAME,
  // enableBreakpointRule.getSlaveBreakpoint());
  // }

  // @SuppressWarnings({"HardCodedStringLiteral"}) private static void writeRuleBreakpoint(@NotNull
  // final Element element, final String tagName, @NotNull final Breakpoint breakpoint) {
  //  Element master = new Element(tagName);
  //  element.addContent(master);
  //  master.setAttribute("name", breakpoint.getDisplayName());
  // }

  // @SuppressWarnings({"HardCodedStringLiteral"})
  // private static void writeBreakpoint(@NotNull final Element group, @NotNull final Breakpoint
  // breakpoint) {
  //  Element breakpointNode = new Element("breakpoint");
  //  group.addContent(breakpointNode);
  //  try {
  //    breakpoint.writeExternal(breakpointNode);
  //  }
  //  catch (WriteExternalException e) {
  //    LOG.error(e);
  //  }
  // }

  private static <T extends Breakpoint> Element getCategoryGroupElement(
      @NotNull final Map<Key<? extends Breakpoint>, Element> categoryToElementMap,
      @NotNull final Key<T> category,
      @NotNull final Element parentNode) {
    Element group = categoryToElementMap.get(category);
    if (group == null) {
      group = new Element(category.toString());
      categoryToElementMap.put(category, group);
      parentNode.addContent(group);
    }
    return group;
  }

  private void removeInvalidBreakpoints() {
    ArrayList<Breakpoint> toDelete = new ArrayList<Breakpoint>();

    for (Breakpoint breakpoint : getBreakpoints()) {
      if (!breakpoint.isValid()) {
        toDelete.add(breakpoint);
      }
    }

    for (final Breakpoint aToDelete : toDelete) {
      removeBreakpoint(aToDelete);
    }
  }

  /**
   * @return breakpoints of one of the category: LINE_BREAKPOINTS, EXCEPTION_BREAKPOINTS,
   *     FIELD_BREAKPOINTS, METHOD_BREAKPOINTS
   */
  public <T extends Breakpoint> Breakpoint[] getBreakpoints(@NotNull final Key<T> category) {
    ApplicationManager.getApplication().assertIsDispatchThread();
    removeInvalidBreakpoints();

    final ArrayList<Breakpoint> breakpoints = new ArrayList<Breakpoint>();
    for (Breakpoint breakpoint : getBreakpoints()) {
      if (category.equals(breakpoint.getCategory())) {
        breakpoints.add(breakpoint);
      }
    }

    return breakpoints.toArray(new Breakpoint[breakpoints.size()]);
  }

  @NotNull
  public synchronized List<Breakpoint> getBreakpoints() {
    if (myBreakpointsListForIteration == null) {
      myBreakpointsListForIteration = new ArrayList<Breakpoint>(myBreakpoints.size());

      XBreakpoint<?>[] xBreakpoints =
          ApplicationManager.getApplication()
              .runReadAction(
                  new Computable<XBreakpoint<?>[]>() {
                    public XBreakpoint<?>[] compute() {
                      return getXBreakpointManager().getAllBreakpoints();
                    }
                  });
      for (XBreakpoint<?> xBreakpoint : xBreakpoints) {
        if (isJavaType(xBreakpoint)) {
          Breakpoint breakpoint = myBreakpoints.get(xBreakpoint);
          if (breakpoint == null) {
            breakpoint = createJavaBreakpoint(xBreakpoint);
            myBreakpoints.put(xBreakpoint, breakpoint);
          }
        }
      }

      myBreakpointsListForIteration.addAll(myBreakpoints.values());
    }
    return myBreakpointsListForIteration;
  }

  private boolean isJavaType(XBreakpoint xBreakpoint) {
    return xBreakpoint.getType() instanceof JavaBreakpointType;
  }

  private Breakpoint createJavaBreakpoint(XBreakpoint xBreakpoint) {
    if (xBreakpoint.getType() instanceof JavaBreakpointType) {
      return ((JavaBreakpointType) xBreakpoint.getType())
          .createJavaBreakpoint(myProject, xBreakpoint);
    }
    throw new IllegalStateException("Unsupported breakpoint type:" + xBreakpoint.getType());
  }

  // interaction with RequestManagerImpl
  public void disableBreakpoints(@NotNull final DebugProcessImpl debugProcess) {
    final List<Breakpoint> breakpoints = getBreakpoints();
    if (!breakpoints.isEmpty()) {
      final RequestManagerImpl requestManager = debugProcess.getRequestsManager();
      for (Breakpoint breakpoint : breakpoints) {
        breakpoint.markVerified(requestManager.isVerified(breakpoint));
        requestManager.deleteRequest(breakpoint);
      }
      SwingUtilities.invokeLater(
          new Runnable() {
            @Override
            public void run() {
              updateBreakpointsUI();
            }
          });
    }
  }

  public void enableBreakpoints(final DebugProcessImpl debugProcess) {
    final List<Breakpoint> breakpoints = getBreakpoints();
    if (!breakpoints.isEmpty()) {
      for (Breakpoint breakpoint : breakpoints) {
        breakpoint.markVerified(false); // clean cached state
        breakpoint.createRequest(debugProcess);
      }
      SwingUtilities.invokeLater(
          new Runnable() {
            @Override
            public void run() {
              updateBreakpointsUI();
            }
          });
    }
  }

  public void applyThreadFilter(
      @NotNull final DebugProcessImpl debugProcess, @Nullable ThreadReference newFilterThread) {
    final RequestManagerImpl requestManager = debugProcess.getRequestsManager();
    final ThreadReference oldFilterThread = requestManager.getFilterThread();
    if (Comparing.equal(newFilterThread, oldFilterThread)) {
      // the filter already added
      return;
    }
    requestManager.setFilterThread(newFilterThread);
    if (newFilterThread == null || oldFilterThread != null) {
      final List<Breakpoint> breakpoints = getBreakpoints();
      for (Breakpoint breakpoint : breakpoints) {
        if (LineBreakpoint.CATEGORY.equals(breakpoint.getCategory())
            || MethodBreakpoint.CATEGORY.equals(breakpoint.getCategory())) {
          requestManager.deleteRequest(breakpoint);
          breakpoint.createRequest(debugProcess);
        }
      }
    } else {
      // important! need to add filter to _existing_ requests, otherwise Requestor->Request mapping
      // will be lost
      // and debugger trees will not be restored to original state
      abstract class FilterSetter<T extends EventRequest> {
        void applyFilter(@NotNull final List<T> requests, final ThreadReference thread) {
          for (T request : requests) {
            try {
              final boolean wasEnabled = request.isEnabled();
              if (wasEnabled) {
                request.disable();
              }
              addFilter(request, thread);
              if (wasEnabled) {
                request.enable();
              }
            } catch (InternalException e) {
              LOG.info(e);
            }
          }
        }

        protected abstract void addFilter(final T request, final ThreadReference thread);
      }

      final EventRequestManager eventRequestManager = requestManager.getVMRequestManager();

      new FilterSetter<BreakpointRequest>() {
        @Override
        protected void addFilter(
            @NotNull final BreakpointRequest request, final ThreadReference thread) {
          request.addThreadFilter(thread);
        }
      }.applyFilter(eventRequestManager.breakpointRequests(), newFilterThread);

      new FilterSetter<MethodEntryRequest>() {
        @Override
        protected void addFilter(
            @NotNull final MethodEntryRequest request, final ThreadReference thread) {
          request.addThreadFilter(thread);
        }
      }.applyFilter(eventRequestManager.methodEntryRequests(), newFilterThread);

      new FilterSetter<MethodExitRequest>() {
        @Override
        protected void addFilter(
            @NotNull final MethodExitRequest request, final ThreadReference thread) {
          request.addThreadFilter(thread);
        }
      }.applyFilter(eventRequestManager.methodExitRequests(), newFilterThread);
    }
  }

  public void updateAllRequests() {
    ApplicationManager.getApplication().assertIsDispatchThread();

    List<Breakpoint> breakpoints = getBreakpoints();
    for (Breakpoint breakpoint : breakpoints) {
      fireBreakpointChanged(breakpoint);
    }
  }

  public void updateBreakpointsUI() {
    ApplicationManager.getApplication().assertIsDispatchThread();
    for (Breakpoint breakpoint : getBreakpoints()) {
      breakpoint.updateUI();
    }
  }

  public void reloadBreakpoints() {
    ApplicationManager.getApplication().assertIsDispatchThread();

    for (Breakpoint breakpoint : getBreakpoints()) {
      breakpoint.reload();
    }
  }

  public void addBreakpointManagerListener(@NotNull BreakpointManagerListener listener) {
    myDispatcher.addListener(listener);
  }

  public void removeBreakpointManagerListener(@NotNull BreakpointManagerListener listener) {
    myDispatcher.removeListener(listener);
  }

  private boolean myAllowMulticasting = true;
  private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);

  public void fireBreakpointChanged(Breakpoint breakpoint) {
    breakpoint.reload();
    breakpoint.updateUI();
    RequestManagerImpl.updateRequests(breakpoint);
    if (myAllowMulticasting) {
      // can be invoked from non-AWT thread
      myAlarm.cancelAllRequests();
      final Runnable runnable =
          new Runnable() {
            @Override
            public void run() {
              myAlarm.addRequest(
                  new Runnable() {
                    @Override
                    public void run() {
                      myDispatcher.getMulticaster().breakpointsChanged();
                    }
                  },
                  100);
            }
          };
      if (ApplicationManager.getApplication().isDispatchThread()) {
        runnable.run();
      } else {
        SwingUtilities.invokeLater(runnable);
      }
    }
  }

  public void setBreakpointEnabled(@NotNull final Breakpoint breakpoint, final boolean enabled) {
    if (breakpoint.isEnabled() != enabled) {
      breakpoint.setEnabled(enabled);
      // fireBreakpointChanged(breakpoint);
      // breakpoint.updateUI();
    }
  }

  public void addBreakpointRule(@NotNull EnableBreakpointRule rule) {
    // rule.init();
    // myBreakpointRules.add(rule);
  }

  public boolean removeBreakpointRule(@NotNull EnableBreakpointRule rule) {
    // final boolean removed = myBreakpointRules.remove(rule);
    // if (removed) {
    //  rule.dispose();
    // }
    // return removed;
    return false;
  }

  public boolean removeBreakpointRule(@NotNull Breakpoint slaveBreakpoint) {
    // for (final EnableBreakpointRule rule : myBreakpointRules) {
    //  if (slaveBreakpoint.equals(rule.getSlaveBreakpoint())) {
    //    removeBreakpointRule(rule);
    //    return true;
    //  }
    // }
    return false;
  }

  // private void updateBreakpointRules(@NotNull Breakpoint removedBreakpoint) {
  //  for (Iterator<EnableBreakpointRule> it = myBreakpointRules.iterator(); it.hasNext();) {
  //    final EnableBreakpointRule rule = it.next();
  //    if (removedBreakpoint.equals(rule.getMasterBreakpoint()) ||
  // removedBreakpoint.equals(rule.getSlaveBreakpoint())) {
  //      it.remove();
  //    }
  //  }
  // }

  // copied from XDebugSessionImpl processDependencies
  public void processBreakpointHit(@NotNull final Breakpoint breakpoint) {
    XDependentBreakpointManager dependentBreakpointManager =
        ((XBreakpointManagerImpl) getXBreakpointManager()).getDependentBreakpointManager();
    XBreakpoint xBreakpoint = breakpoint.myXBreakpoint;
    if (!dependentBreakpointManager.isMasterOrSlave(xBreakpoint)) {
      return;
    }
    List<XBreakpoint<?>> breakpoints = dependentBreakpointManager.getSlaveBreakpoints(xBreakpoint);
    for (final XBreakpoint<?> slaveBreakpoint : breakpoints) {
      DebuggerInvocationUtil.invokeLater(
          myProject,
          new Runnable() {
            @Override
            public void run() {
              slaveBreakpoint.setEnabled(true);
            }
          });
    }

    if (dependentBreakpointManager.getMasterBreakpoint(xBreakpoint) != null
        && !dependentBreakpointManager.isLeaveEnabled(xBreakpoint)) {
      DebuggerInvocationUtil.invokeLater(
          myProject,
          new Runnable() {
            @Override
            public void run() {
              breakpoint.setEnabled(false);
            }
          });
      // myDebuggerManager.getBreakpointManager().getLineBreakpointManager().queueBreakpointUpdate(breakpoint);
    }
  }

  public void setInitialBreakpointsState() {
    // myAllowMulticasting = false;
    // for (final EnableBreakpointRule myBreakpointRule : myBreakpointRules) {
    //  myBreakpointRule.init();
    // }
    // myAllowMulticasting = true;
    // if (!myBreakpointRules.isEmpty()) {
    //  IJSwingUtilities.invoke(new Runnable() {
    //    @Override
    //    public void run() {
    //      myDispatcher.getMulticaster().breakpointsChanged();
    //    }
    //  });
    // }
  }

  @Nullable
  public Breakpoint findMasterBreakpoint(@NotNull Breakpoint dependentBreakpoint) {
    XDependentBreakpointManager dependentBreakpointManager =
        ((XBreakpointManagerImpl) getXBreakpointManager()).getDependentBreakpointManager();
    return myBreakpoints.get(
        dependentBreakpointManager.getMasterBreakpoint(dependentBreakpoint.myXBreakpoint));
  }

  @Nullable
  public EnableBreakpointRule findBreakpointRule(@NotNull Breakpoint dependentBreakpoint) {
    // for (final EnableBreakpointRule rule : myBreakpointRules) {
    //  if (dependentBreakpoint.equals(rule.getSlaveBreakpoint())) {
    //    return rule;
    //  }
    // }
    return null;
  }

  public String getProperty(String name) {
    return myUIProperties.get(name);
  }

  public String setProperty(String name, String value) {
    return myUIProperties.put(name, value);
  }
}
@State(
    name = "DebuggerManager",
    storages = {@Storage(file = StoragePathMacros.WORKSPACE_FILE)})
public class DebuggerManagerImpl extends DebuggerManagerEx
    implements PersistentStateComponent<Element> {
  private static final Logger LOG =
      Logger.getInstance("#com.intellij.debugger.impl.DebuggerManagerImpl");

  private final Project myProject;
  private final HashMap<ProcessHandler, DebuggerSession> mySessions =
      new HashMap<ProcessHandler, DebuggerSession>();
  private final BreakpointManager myBreakpointManager;
  private final List<NameMapper> myNameMappers = ContainerUtil.createLockFreeCopyOnWriteList();
  private final List<Function<DebugProcess, PositionManager>> myCustomPositionManagerFactories =
      new ArrayList<Function<DebugProcess, PositionManager>>();

  private final EventDispatcher<DebuggerManagerListener> myDispatcher =
      EventDispatcher.create(DebuggerManagerListener.class);
  private final MyDebuggerStateManager myDebuggerStateManager = new MyDebuggerStateManager();

  private final DebuggerContextListener mySessionListener =
      new DebuggerContextListener() {
        @Override
        public void changeEvent(DebuggerContextImpl newContext, DebuggerSession.Event event) {

          final DebuggerSession session = newContext.getDebuggerSession();
          if (event == DebuggerSession.Event.PAUSE
              && myDebuggerStateManager.myDebuggerSession != session) {
            // if paused in non-active session; switch current session
            myDebuggerStateManager.setState(
                newContext,
                session != null ? session.getState() : DebuggerSession.State.DISPOSED,
                event,
                null);
            return;
          }

          if (myDebuggerStateManager.myDebuggerSession == session) {
            myDebuggerStateManager.fireStateChanged(newContext, event);
          }
          if (event == DebuggerSession.Event.ATTACHED) {
            myDispatcher.getMulticaster().sessionAttached(session);
          } else if (event == DebuggerSession.Event.DETACHED) {
            myDispatcher.getMulticaster().sessionDetached(session);
          } else if (event == DebuggerSession.Event.DISPOSE) {
            dispose(session);
            if (myDebuggerStateManager.myDebuggerSession == session) {
              myDebuggerStateManager.setState(
                  DebuggerContextImpl.EMPTY_CONTEXT,
                  DebuggerSession.State.DISPOSED,
                  DebuggerSession.Event.DISPOSE,
                  null);
            }
          }
        }
      };
  @NonNls private static final String DEBUG_KEY_NAME = "idea.xdebug.key";

  @Override
  public void addClassNameMapper(final NameMapper mapper) {
    myNameMappers.add(mapper);
  }

  @Override
  public void removeClassNameMapper(final NameMapper mapper) {
    myNameMappers.remove(mapper);
  }

  @Override
  public String getVMClassQualifiedName(@NotNull final PsiClass aClass) {
    for (NameMapper nameMapper : myNameMappers) {
      final String qName = nameMapper.getQualifiedName(aClass);
      if (qName != null) {
        return qName;
      }
    }
    return aClass.getQualifiedName();
  }

  @Override
  public void addDebuggerManagerListener(DebuggerManagerListener listener) {
    myDispatcher.addListener(listener);
  }

  @Override
  public void removeDebuggerManagerListener(DebuggerManagerListener listener) {
    myDispatcher.removeListener(listener);
  }

  public DebuggerManagerImpl(
      Project project, StartupManager startupManager, EditorColorsManager colorsManager) {
    myProject = project;
    myBreakpointManager = new BreakpointManager(myProject, startupManager, this);
    if (!project.isDefault()) {
      colorsManager.addEditorColorsListener(
          new EditorColorsListener() {
            @Override
            public void globalSchemeChange(EditorColorsScheme scheme) {
              getBreakpointManager().updateBreakpointsUI();
            }
          },
          project);
    }
  }

  @Override
  public DebuggerSession getSession(DebugProcess process) {
    ApplicationManager.getApplication().assertIsDispatchThread();
    for (final DebuggerSession debuggerSession : getSessions()) {
      if (process == debuggerSession.getProcess()) return debuggerSession;
    }
    return null;
  }

  @Override
  public Collection<DebuggerSession> getSessions() {
    synchronized (mySessions) {
      final Collection<DebuggerSession> values = mySessions.values();
      return values.isEmpty()
          ? Collections.<DebuggerSession>emptyList()
          : new ArrayList<DebuggerSession>(values);
    }
  }

  @Override
  public void disposeComponent() {}

  @Override
  public void initComponent() {}

  @Override
  public void projectClosed() {}

  @Override
  public void projectOpened() {
    myBreakpointManager.init();
  }

  @Nullable
  @Override
  public Element getState() {
    Element state = new Element("state");
    myBreakpointManager.writeExternal(state);
    return state;
  }

  @Override
  public void loadState(Element state) {
    myBreakpointManager.readExternal(state);
  }

  public void writeExternal(Element element) throws WriteExternalException {
    myBreakpointManager.writeExternal(element);
  }

  @Override
  @Nullable
  public DebuggerSession attachVirtualMachine(@NotNull DebugEnvironment environment)
      throws ExecutionException {
    ApplicationManager.getApplication().assertIsDispatchThread();
    final DebugProcessEvents debugProcess = new DebugProcessEvents(myProject);
    debugProcess.addDebugProcessListener(
        new DebugProcessAdapter() {
          @Override
          public void processAttached(final DebugProcess process) {
            process.removeDebugProcessListener(this);
            for (Function<DebugProcess, PositionManager> factory :
                myCustomPositionManagerFactories) {
              final PositionManager positionManager = factory.fun(process);
              if (positionManager != null) {
                process.appendPositionManager(positionManager);
              }
            }
            for (PositionManagerFactory factory :
                Extensions.getExtensions(PositionManagerFactory.EP_NAME, myProject)) {
              final PositionManager manager = factory.createPositionManager(debugProcess);
              if (manager != null) {
                process.appendPositionManager(manager);
              }
            }
          }

          @Override
          public void processDetached(final DebugProcess process, final boolean closedByUser) {
            debugProcess.removeDebugProcessListener(this);
          }

          @Override
          public void attachException(
              final RunProfileState state,
              final ExecutionException exception,
              final RemoteConnection remoteConnection) {
            debugProcess.removeDebugProcessListener(this);
          }
        });
    DebuggerSession session =
        DebuggerSession.create(environment.getSessionName(), debugProcess, environment);
    ExecutionResult executionResult = session.getProcess().getExecutionResult();
    if (executionResult == null) {
      return null;
    }
    session.getContextManager().addListener(mySessionListener);
    getContextManager()
        .setState(
            DebuggerContextUtil.createDebuggerContext(
                session, session.getContextManager().getContext().getSuspendContext()),
            session.getState(),
            DebuggerSession.Event.CONTEXT,
            null);

    final ProcessHandler processHandler = executionResult.getProcessHandler();

    synchronized (mySessions) {
      mySessions.put(processHandler, session);
    }

    if (!(processHandler instanceof RemoteDebugProcessHandler)) {
      // add listener only to non-remote process handler:
      // on Unix systems destroying process does not cause VMDeathEvent to be generated,
      // so we need to call debugProcess.stop() explicitly for graceful termination.
      // RemoteProcessHandler on the other hand will call debugProcess.stop() as a part of
      // destroyProcess() and detachProcess() implementation,
      // so we shouldn't add the listener to avoid calling stop() twice
      processHandler.addProcessListener(
          new ProcessAdapter() {
            @Override
            public void processWillTerminate(ProcessEvent event, boolean willBeDestroyed) {
              final DebugProcessImpl debugProcess = getDebugProcess(event.getProcessHandler());
              if (debugProcess != null) {
                // if current thread is a "debugger manager thread", stop will execute synchronously
                // it is KillableColoredProcessHandler responsibility to terminate VM
                debugProcess.stop(
                    willBeDestroyed
                        && !(event.getProcessHandler() instanceof KillableColoredProcessHandler));

                // wait at most 10 seconds: the problem is that debugProcess.stop() can hang if
                // there are troubles in the debuggee
                // if processWillTerminate() is called from AWT thread debugProcess.waitFor() will
                // block it and the whole app will hang
                if (!DebuggerManagerThreadImpl.isManagerThread()) {
                  if (SwingUtilities.isEventDispatchThread()) {
                    ProgressManager.getInstance()
                        .runProcessWithProgressSynchronously(
                            new Runnable() {
                              @Override
                              public void run() {
                                ProgressManager.getInstance()
                                    .getProgressIndicator()
                                    .setIndeterminate(true);
                                debugProcess.waitFor(10000);
                              }
                            },
                            "Waiting For Debugger Response",
                            false,
                            debugProcess.getProject());
                  } else {
                    debugProcess.waitFor(10000);
                  }
                }
              }
            }
          });
    }
    myDispatcher.getMulticaster().sessionCreated(session);
    return session;
  }

  @Override
  public DebugProcessImpl getDebugProcess(final ProcessHandler processHandler) {
    synchronized (mySessions) {
      DebuggerSession session = mySessions.get(processHandler);
      return session != null ? session.getProcess() : null;
    }
  }

  @SuppressWarnings("UnusedDeclaration")
  @Nullable
  public DebuggerSession getDebugSession(final ProcessHandler processHandler) {
    synchronized (mySessions) {
      return mySessions.get(processHandler);
    }
  }

  @Override
  public void addDebugProcessListener(
      final ProcessHandler processHandler, final DebugProcessListener listener) {
    DebugProcessImpl debugProcess = getDebugProcess(processHandler);
    if (debugProcess != null) {
      debugProcess.addDebugProcessListener(listener);
    } else {
      processHandler.addProcessListener(
          new ProcessAdapter() {
            @Override
            public void startNotified(ProcessEvent event) {
              DebugProcessImpl debugProcess = getDebugProcess(processHandler);
              if (debugProcess != null) {
                debugProcess.addDebugProcessListener(listener);
              }
              processHandler.removeProcessListener(this);
            }
          });
    }
  }

  @Override
  public void removeDebugProcessListener(
      final ProcessHandler processHandler, final DebugProcessListener listener) {
    DebugProcessImpl debugProcess = getDebugProcess(processHandler);
    if (debugProcess != null) {
      debugProcess.removeDebugProcessListener(listener);
    } else {
      processHandler.addProcessListener(
          new ProcessAdapter() {
            @Override
            public void startNotified(ProcessEvent event) {
              DebugProcessImpl debugProcess = getDebugProcess(processHandler);
              if (debugProcess != null) {
                debugProcess.removeDebugProcessListener(listener);
              }
              processHandler.removeProcessListener(this);
            }
          });
    }
  }

  @Override
  public boolean isDebuggerManagerThread() {
    return DebuggerManagerThreadImpl.isManagerThread();
  }

  @Override
  @NotNull
  public String getComponentName() {
    return "DebuggerManager";
  }

  @Override
  public BreakpointManager getBreakpointManager() {
    return myBreakpointManager;
  }

  @Override
  public DebuggerContextImpl getContext() {
    return getContextManager().getContext();
  }

  @Override
  public DebuggerStateManager getContextManager() {
    return myDebuggerStateManager;
  }

  @Override
  public void registerPositionManagerFactory(
      final Function<DebugProcess, PositionManager> factory) {
    myCustomPositionManagerFactories.add(factory);
  }

  @Override
  public void unregisterPositionManagerFactory(
      final Function<DebugProcess, PositionManager> factory) {
    myCustomPositionManagerFactories.remove(factory);
  }

  private static boolean hasWhitespace(String string) {
    int length = string.length();
    for (int i = 0; i < length; i++) {
      if (Character.isWhitespace(string.charAt(i))) {
        return true;
      }
    }
    return false;
  }

  /* Remoting */
  private static void checkTargetJPDAInstalled(JavaParameters parameters)
      throws ExecutionException {
    final Sdk jdk = parameters.getJdk();
    if (jdk == null) {
      throw new ExecutionException(DebuggerBundle.message("error.jdk.not.specified"));
    }
    final JavaSdkVersion version = JavaSdk.getInstance().getVersion(jdk);
    String versionString = jdk.getVersionString();
    if (version == JavaSdkVersion.JDK_1_0 || version == JavaSdkVersion.JDK_1_1) {
      throw new ExecutionException(
          DebuggerBundle.message("error.unsupported.jdk.version", versionString));
    }
    if (SystemInfo.isWindows && version == JavaSdkVersion.JDK_1_2) {
      final VirtualFile homeDirectory = jdk.getHomeDirectory();
      if (homeDirectory == null || !homeDirectory.isValid()) {
        throw new ExecutionException(
            DebuggerBundle.message("error.invalid.jdk.home", versionString));
      }
      //noinspection HardCodedStringLiteral
      File dllFile =
          new File(
              homeDirectory.getPath().replace('/', File.separatorChar)
                  + File.separator
                  + "bin"
                  + File.separator
                  + "jdwp.dll");
      if (!dllFile.exists()) {
        GetJPDADialog dialog = new GetJPDADialog();
        dialog.show();
        throw new ExecutionException(DebuggerBundle.message("error.debug.libraries.missing"));
      }
    }
  }

  /** for Target JDKs versions 1.2.x - 1.3.0 the Classic VM should be used for debugging */
  private static boolean shouldForceClassicVM(Sdk jdk) {
    if (SystemInfo.isMac) {
      return false;
    }
    if (jdk == null) return false;

    String version = JdkUtil.getJdkMainAttribute(jdk, Attributes.Name.IMPLEMENTATION_VERSION);
    if (version != null) {
      if (version.compareTo("1.4") >= 0) {
        return false;
      }
      if (version.startsWith("1.2") && SystemInfo.isWindows) {
        return true;
      }
      version += ".0";
      if (version.startsWith("1.3.0") && SystemInfo.isWindows) {
        return true;
      }
      if ((version.startsWith("1.3.1_07") || version.startsWith("1.3.1_08"))
          && SystemInfo.isWindows) {
        return false; // fixes bug for these JDKs that it cannot start with -classic option
      }
    }

    return DebuggerSettings.getInstance().FORCE_CLASSIC_VM;
  }

  @SuppressWarnings({"HardCodedStringLiteral"})
  public static RemoteConnection createDebugParameters(
      final JavaParameters parameters,
      final boolean debuggerInServerMode,
      int transport,
      final String debugPort,
      boolean checkValidity)
      throws ExecutionException {
    if (checkValidity) {
      checkTargetJPDAInstalled(parameters);
    }

    final boolean useSockets = transport == DebuggerSettings.SOCKET_TRANSPORT;

    String address = "";
    if (StringUtil.isEmptyOrSpaces(debugPort)) {
      try {
        address = DebuggerUtils.getInstance().findAvailableDebugAddress(useSockets);
      } catch (ExecutionException e) {
        if (checkValidity) {
          throw e;
        }
      }
    } else {
      address = debugPort;
    }

    final TransportServiceWrapper transportService =
        TransportServiceWrapper.getTransportService(useSockets);
    final String debugAddress =
        debuggerInServerMode && useSockets ? "127.0.0.1:" + address : address;
    String debuggeeRunProperties =
        "transport=" + transportService.transportId() + ",address=" + debugAddress;
    if (debuggerInServerMode) {
      debuggeeRunProperties += ",suspend=y,server=n";
    } else {
      debuggeeRunProperties += ",suspend=n,server=y";
    }

    if (hasWhitespace(debuggeeRunProperties)) {
      debuggeeRunProperties = "\"" + debuggeeRunProperties + "\"";
    }
    final String _debuggeeRunProperties = debuggeeRunProperties;

    ApplicationManager.getApplication()
        .runReadAction(
            new Runnable() {
              @Override
              @SuppressWarnings({"HardCodedStringLiteral"})
              public void run() {
                JavaSdkUtil.addRtJar(parameters.getClassPath());

                final Sdk jdk = parameters.getJdk();
                final boolean forceClassicVM = shouldForceClassicVM(jdk);
                final boolean forceNoJIT = shouldForceNoJIT(jdk);
                final String debugKey = System.getProperty(DEBUG_KEY_NAME, "-Xdebug");
                final boolean needDebugKey =
                    shouldAddXdebugKey(jdk)
                        || !"-Xdebug".equals(debugKey) /*the key is non-standard*/;

                if (forceClassicVM || forceNoJIT || needDebugKey || !isJVMTIAvailable(jdk)) {
                  parameters
                      .getVMParametersList()
                      .replaceOrPrepend("-Xrunjdwp:", "-Xrunjdwp:" + _debuggeeRunProperties);
                } else {
                  // use newer JVMTI if available
                  parameters.getVMParametersList().replaceOrPrepend("-Xrunjdwp:", "");
                  parameters
                      .getVMParametersList()
                      .replaceOrPrepend(
                          "-agentlib:jdwp=", "-agentlib:jdwp=" + _debuggeeRunProperties);
                }

                if (forceNoJIT) {
                  parameters
                      .getVMParametersList()
                      .replaceOrPrepend("-Djava.compiler=", "-Djava.compiler=NONE");
                  parameters.getVMParametersList().replaceOrPrepend("-Xnoagent", "-Xnoagent");
                }

                if (needDebugKey) {
                  parameters.getVMParametersList().replaceOrPrepend(debugKey, debugKey);
                } else {
                  // deliberately skip outdated parameter because it can disable full-speed
                  // debugging for some jdk builds
                  // see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6272174
                  parameters.getVMParametersList().replaceOrPrepend("-Xdebug", "");
                }

                parameters
                    .getVMParametersList()
                    .replaceOrPrepend("-classic", forceClassicVM ? "-classic" : "");
              }
            });

    return new RemoteConnection(useSockets, "127.0.0.1", address, debuggerInServerMode);
  }

  private static boolean shouldForceNoJIT(Sdk jdk) {
    if (DebuggerSettings.getInstance().DISABLE_JIT) {
      return true;
    }
    if (jdk != null) {
      final String version =
          JdkUtil.getJdkMainAttribute(jdk, Attributes.Name.IMPLEMENTATION_VERSION);
      if (version != null && (version.startsWith("1.2") || version.startsWith("1.3"))) {
        return true;
      }
    }
    return false;
  }

  private static boolean shouldAddXdebugKey(Sdk jdk) {
    if (jdk == null) {
      return true; // conservative choice
    }
    if (DebuggerSettings.getInstance().DISABLE_JIT) {
      return true;
    }

    // if (ApplicationManager.getApplication().isUnitTestMode()) {
    // need this in unit tests to avoid false alarms when comparing actual output with expected
    // output
    // return true;
    // }

    final String version = JdkUtil.getJdkMainAttribute(jdk, Attributes.Name.IMPLEMENTATION_VERSION);
    return version == null
        ||
        // version.startsWith("1.5") ||
        version.startsWith("1.4")
        || version.startsWith("1.3")
        || version.startsWith("1.2")
        || version.startsWith("1.1")
        || version.startsWith("1.0");
  }

  private static boolean isJVMTIAvailable(Sdk jdk) {
    if (jdk == null) {
      return false; // conservative choice
    }

    final String version = JdkUtil.getJdkMainAttribute(jdk, Attributes.Name.IMPLEMENTATION_VERSION);
    if (version == null) {
      return false;
    }
    return !(version.startsWith("1.4")
        || version.startsWith("1.3")
        || version.startsWith("1.2")
        || version.startsWith("1.1")
        || version.startsWith("1.0"));
  }

  public static RemoteConnection createDebugParameters(
      final JavaParameters parameters,
      GenericDebuggerRunnerSettings settings,
      boolean checkValidity)
      throws ExecutionException {
    return createDebugParameters(
        parameters,
        settings.LOCAL,
        settings.getTransport(),
        settings.getDebugPort(),
        checkValidity);
  }

  private static class MyDebuggerStateManager extends DebuggerStateManager {
    private DebuggerSession myDebuggerSession;

    @Override
    public DebuggerContextImpl getContext() {
      return myDebuggerSession == null
          ? DebuggerContextImpl.EMPTY_CONTEXT
          : myDebuggerSession.getContextManager().getContext();
    }

    @Override
    public void setState(
        final DebuggerContextImpl context,
        DebuggerSession.State state,
        DebuggerSession.Event event,
        String description) {
      ApplicationManager.getApplication().assertIsDispatchThread();
      myDebuggerSession = context.getDebuggerSession();
      if (myDebuggerSession != null) {
        myDebuggerSession.getContextManager().setState(context, state, event, description);
      } else {
        fireStateChanged(context, event);
      }
    }
  }

  private void dispose(DebuggerSession session) {
    ProcessHandler processHandler = session.getProcess().getProcessHandler();
    synchronized (mySessions) {
      DebuggerSession removed = mySessions.remove(processHandler);
      LOG.assertTrue(removed != null);
      myDispatcher.getMulticaster().sessionRemoved(session);
    }
  }
}
 public SvnLineCommand(
     Project project, File workingDirectory, @NotNull SvnCommandName commandName) {
   super(project, workingDirectory, commandName);
   myLineListeners = EventDispatcher.create(LineProcessEventListener.class);
 }
/** @author nik */
public class JpsLibraryTableImpl implements LibraryTable, Disposable {
  private final JpsLibrariesModel myModel;
  private final EventDispatcher<Listener> myDispatcher = EventDispatcher.create(Listener.class);
  private final String myTableLevel;
  private LibraryTablePresentation myPresentation;

  public JpsLibraryTableImpl(JpsLibraryCollection libraryCollection, String level) {
    myTableLevel = level;
    myModel = new JpsLibrariesModel(libraryCollection);
  }

  @NotNull
  @Override
  public Library[] getLibraries() {
    return myModel.getLibraries();
  }

  @NotNull
  @Override
  public Iterator<Library> getLibraryIterator() {
    return myModel.getLibraryIterator();
  }

  @Override
  public Library getLibraryByName(@NotNull String name) {
    return myModel.getLibraryByName(name);
  }

  @Override
  public void addListener(Listener listener) {
    myDispatcher.addListener(listener);
  }

  @Override
  public void addListener(Listener listener, Disposable parentDisposable) {
    myDispatcher.addListener(listener, parentDisposable);
  }

  @Override
  public void removeListener(Listener listener) {
    myDispatcher.removeListener(listener);
  }

  @Override
  public Library createLibrary() {
    return createLibrary(null);
  }

  @Override
  public Library createLibrary(@NonNls String name) {
    final ModifiableModel model = getModifiableModel();
    final Library library = model.createLibrary(name);
    model.commit();
    return library;
  }

  @Override
  public void removeLibrary(@NotNull Library library) {
    final ModifiableModel model = getModifiableModel();
    model.removeLibrary(library);
    model.commit();
  }

  @Override
  public void dispose() {
    for (Library library : getLibraries()) {
      Disposer.dispose(library);
    }
  }

  @Override
  public ModifiableModel getModifiableModel() {
    return new JpsLibrariesModel(myModel.myJpsLibraries);
  }

  @Override
  public boolean isEditable() {
    return true;
  }

  @Override
  public String getTableLevel() {
    return myTableLevel;
  }

  @Override
  public LibraryTablePresentation getPresentation() {
    return myPresentation;
  }

  private class JpsLibrariesModel implements LibraryTableBase.ModifiableModelEx {
    private final JpsLibraryCollection myJpsLibraries;
    private final List<JpsLibraryDelegate> myLibraries;

    private JpsLibrariesModel(JpsLibraryCollection libraryCollection) {
      myLibraries = new ArrayList<JpsLibraryDelegate>();
      myJpsLibraries = libraryCollection;
      for (JpsLibrary library : libraryCollection.getLibraries()) {
        myLibraries.add(new JpsLibraryDelegate(library, JpsLibraryTableImpl.this));
      }
    }

    @Override
    public Library createLibrary(String name) {
      return createLibrary(name, null);
    }

    @Override
    public Library createLibrary(String name, @Nullable PersistentLibraryKind type) {
      throw new UnsupportedOperationException(
          "'createLibrary' not implemented in " + getClass().getName());
    }

    @NotNull
    @Override
    public Iterator<Library> getLibraryIterator() {
      return Collections.<Library>unmodifiableList(myLibraries).iterator();
    }

    @Override
    public void removeLibrary(@NotNull Library library) {
      throw new UnsupportedOperationException();
    }

    @NotNull
    @Override
    public Library[] getLibraries() {
      return myLibraries.toArray(new Library[myLibraries.size()]);
    }

    @Override
    public Library getLibraryByName(@NotNull String name) {
      for (JpsLibraryDelegate library : myLibraries) {
        if (name.equals(library.getName())) {
          return library;
        }
      }
      return null;
    }

    @Override
    public void commit() {
      throw new UnsupportedOperationException();
    }

    @Override
    public boolean isChanged() {
      return false;
    }
  }
}
public class FontOptions extends JPanel implements OptionsPanel {
  private static final FontInfoRenderer RENDERER =
      new FontInfoRenderer() {
        @Override
        protected AntialiasingType getAntialiasingType() {
          return UISettings.getShadowInstance().EDITOR_AA_TYPE;
        }
      };

  private final EventDispatcher<ColorAndFontSettingsListener> myDispatcher =
      EventDispatcher.create(ColorAndFontSettingsListener.class);

  @NotNull private final ColorAndFontOptions myOptions;

  @NotNull private final JTextField myEditorFontSizeField = new JTextField(4);
  @NotNull private final JTextField myLineSpacingField = new JTextField(4);
  private final FontComboBox myPrimaryCombo = new FontComboBox();
  private final JCheckBox myUseSecondaryFontCheckbox =
      new JCheckBox(ApplicationBundle.message("secondary.font"));
  private final JCheckBox myEnableLigaturesCheckbox =
      new JCheckBox(ApplicationBundle.message("use.ligatures"));
  private final JLabel myLigaturesInfoLinkLabel;
  private final FontComboBox mySecondaryCombo = new FontComboBox();

  @NotNull
  private final JBCheckBox myOnlyMonospacedCheckBox =
      new JBCheckBox(ApplicationBundle.message("checkbox.show.only.monospaced.fonts"));

  private boolean myIsInSchemeChange;

  public FontOptions(ColorAndFontOptions options) {
    this(options, ApplicationBundle.message("group.editor.font"));
  }

  protected FontOptions(@NotNull ColorAndFontOptions options, final String title) {
    setLayout(new MigLayout("ins 0, gap 5, flowx"));
    Insets borderInsets =
        new Insets(
            IdeBorderFactory.TITLED_BORDER_TOP_INSET,
            IdeBorderFactory.TITLED_BORDER_LEFT_INSET,
            0,
            IdeBorderFactory.TITLED_BORDER_RIGHT_INSET);
    setBorder(IdeBorderFactory.createTitledBorder(title, false, borderInsets));
    myOptions = options;
    add(myOnlyMonospacedCheckBox, "sgx b, sx 2");

    add(new JLabel(ApplicationBundle.message("primary.font")), "newline, ax right");
    add(myPrimaryCombo, "sgx b");
    add(new JLabel(ApplicationBundle.message("editbox.font.size")), "gapleft 20");
    add(myEditorFontSizeField);
    add(new JLabel(ApplicationBundle.message("editbox.line.spacing")), "gapleft 20");
    add(myLineSpacingField);

    add(
        new JLabel(
            ApplicationBundle.message("label.fallback.fonts.list.description"),
            MessageType.INFO.getDefaultIcon(),
            SwingConstants.LEFT),
        "newline, sx 5");
    add(myUseSecondaryFontCheckbox, "newline, ax right");
    add(mySecondaryCombo, "sgx b");
    JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
    myEnableLigaturesCheckbox.setBorder(null);
    panel.add(myEnableLigaturesCheckbox);
    myLigaturesInfoLinkLabel =
        new LinkLabel<Void>(
            ApplicationBundle.message("ligatures.more.info"),
            null,
            new LinkListener<Void>() {
              @Override
              public void linkSelected(LinkLabel aSource, Void aLinkData) {
                BrowserUtil.browse(
                    "https://confluence.jetbrains.com/display/IDEADEV/Support+for+Ligatures+in+Editor");
              }
            });
    myLigaturesInfoLinkLabel.setBorder(new EmptyBorder(0, 5, 0, 0));
    panel.add(myLigaturesInfoLinkLabel);
    add(panel, "newline, sx 2");

    myOnlyMonospacedCheckBox.setBorder(null);
    myUseSecondaryFontCheckbox.setBorder(null);
    mySecondaryCombo.setEnabled(false);

    myOnlyMonospacedCheckBox.setSelected(
        EditorColorsManager.getInstance().isUseOnlyMonospacedFonts());
    myOnlyMonospacedCheckBox.addActionListener(
        new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent e) {
            EditorColorsManager.getInstance()
                .setUseOnlyMonospacedFonts(myOnlyMonospacedCheckBox.isSelected());
            myPrimaryCombo.setMonospacedOnly(myOnlyMonospacedCheckBox.isSelected());
            mySecondaryCombo.setMonospacedOnly(myOnlyMonospacedCheckBox.isSelected());
          }
        });
    myPrimaryCombo.setMonospacedOnly(myOnlyMonospacedCheckBox.isSelected());
    myPrimaryCombo.setRenderer(RENDERER);

    mySecondaryCombo.setMonospacedOnly(myOnlyMonospacedCheckBox.isSelected());
    mySecondaryCombo.setRenderer(RENDERER);

    myUseSecondaryFontCheckbox.addActionListener(
        new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent e) {
            mySecondaryCombo.setEnabled(myUseSecondaryFontCheckbox.isSelected());
            syncFontFamilies();
          }
        });
    ItemListener itemListener =
        new ItemListener() {
          @Override
          public void itemStateChanged(ItemEvent e) {
            if (e.getStateChange() == ItemEvent.SELECTED) {
              syncFontFamilies();
            }
          }
        };
    myPrimaryCombo.addItemListener(itemListener);
    mySecondaryCombo.addItemListener(itemListener);

    ActionListener actionListener =
        new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent e) {
            syncFontFamilies();
          }
        };
    myPrimaryCombo.addActionListener(actionListener);
    mySecondaryCombo.addActionListener(actionListener);

    myEditorFontSizeField
        .getDocument()
        .addDocumentListener(
            new DocumentAdapter() {
              @Override
              public void textChanged(DocumentEvent event) {
                if (myIsInSchemeChange || !SwingUtilities.isEventDispatchThread()) return;
                String selectedFont = myPrimaryCombo.getFontName();
                if (selectedFont != null) {
                  FontPreferences fontPreferences = getFontPreferences();
                  fontPreferences.register(selectedFont, getFontSizeFromField());
                }
                updateDescription(true);
              }
            });
    myEditorFontSizeField.addKeyListener(
        new KeyAdapter() {
          @Override
          public void keyPressed(KeyEvent e) {
            if (e.getKeyCode() != KeyEvent.VK_UP && e.getKeyCode() != KeyEvent.VK_DOWN) return;
            boolean up = e.getKeyCode() == KeyEvent.VK_UP;
            try {
              int value = Integer.parseInt(myEditorFontSizeField.getText());
              value += (up ? 1 : -1);
              value =
                  Math.min(
                      OptionsConstants.MAX_EDITOR_FONT_SIZE,
                      Math.max(OptionsConstants.MIN_EDITOR_FONT_SIZE, value));
              myEditorFontSizeField.setText(String.valueOf(value));
            } catch (NumberFormatException ignored) {
            }
          }
        });

    myLineSpacingField
        .getDocument()
        .addDocumentListener(
            new DocumentAdapter() {
              @Override
              public void textChanged(DocumentEvent event) {
                if (myIsInSchemeChange) return;
                float lineSpacing = getLineSpacingFromField();
                if (getLineSpacing() != lineSpacing) {
                  setCurrentLineSpacing(lineSpacing);
                }
                updateDescription(true);
              }
            });
    myLineSpacingField.addKeyListener(
        new KeyAdapter() {
          @Override
          public void keyPressed(KeyEvent e) {
            if (e.getKeyCode() != KeyEvent.VK_UP && e.getKeyCode() != KeyEvent.VK_DOWN) return;
            boolean up = e.getKeyCode() == KeyEvent.VK_UP;
            try {
              float value = Float.parseFloat(myLineSpacingField.getText());
              value += (up ? 1 : -1) * .1F;
              value =
                  Math.min(
                      OptionsConstants.MAX_EDITOR_LINE_SPACING,
                      Math.max(OptionsConstants.MIN_EDITOR_LINE_SPACING, value));
              myLineSpacingField.setText(String.format(Locale.ENGLISH, "%.1f", value));
            } catch (NumberFormatException ignored) {
            }
          }
        });
    myEnableLigaturesCheckbox.addActionListener(
        new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent e) {
            getFontPreferences().setUseLigatures(myEnableLigaturesCheckbox.isSelected());
          }
        });
  }

  private int getFontSizeFromField() {
    try {
      return Math.min(
          OptionsConstants.MAX_EDITOR_FONT_SIZE,
          Math.max(
              OptionsConstants.MIN_EDITOR_FONT_SIZE,
              Integer.parseInt(myEditorFontSizeField.getText())));
    } catch (NumberFormatException e) {
      return OptionsConstants.DEFAULT_EDITOR_FONT_SIZE;
    }
  }

  private float getLineSpacingFromField() {
    try {
      return Math.min(
          OptionsConstants.MAX_EDITOR_LINE_SPACING,
          Math.max(
              OptionsConstants.MIN_EDITOR_LINE_SPACING,
              Float.parseFloat(myLineSpacingField.getText())));
    } catch (NumberFormatException e) {
      return OptionsConstants.DEFAULT_EDITOR_LINE_SPACING;
    }
  }

  private void syncFontFamilies() {
    if (myIsInSchemeChange) {
      return;
    }
    FontPreferences fontPreferences = getFontPreferences();
    fontPreferences.clearFonts();
    String primaryFontFamily = myPrimaryCombo.getFontName();
    String secondaryFontFamily =
        mySecondaryCombo.isEnabled() ? mySecondaryCombo.getFontName() : null;
    int fontSize = getFontSizeFromField();
    if (primaryFontFamily != null) {
      if (!FontPreferences.DEFAULT_FONT_NAME.equals(primaryFontFamily)) {
        fontPreferences.addFontFamily(primaryFontFamily);
      }
      fontPreferences.register(primaryFontFamily, JBUI.scale(fontSize));
    }
    if (secondaryFontFamily != null) {
      if (!FontPreferences.DEFAULT_FONT_NAME.equals(secondaryFontFamily)) {
        fontPreferences.addFontFamily(secondaryFontFamily);
      }
      fontPreferences.register(secondaryFontFamily, JBUI.scale(fontSize));
    }
    updateDescription(true);
  }

  public static void showReadOnlyMessage(JComponent parent, final boolean sharedScheme) {
    if (!sharedScheme) {
      Messages.showMessageDialog(
          parent,
          ApplicationBundle.message("error.readonly.scheme.cannot.be.modified"),
          ApplicationBundle.message("title.cannot.modify.readonly.scheme"),
          Messages.getInformationIcon());
    } else {
      Messages.showMessageDialog(
          parent,
          ApplicationBundle.message("error.shared.scheme.cannot.be.modified"),
          ApplicationBundle.message("title.cannot.modify.readonly.scheme"),
          Messages.getInformationIcon());
    }
  }

  @Override
  public void updateOptionsList() {
    myIsInSchemeChange = true;

    myLineSpacingField.setText(Float.toString(getLineSpacing()));
    FontPreferences fontPreferences = getFontPreferences();
    List<String> fontFamilies = fontPreferences.getEffectiveFontFamilies();
    myPrimaryCombo.setFontName(fontPreferences.getFontFamily());
    boolean isThereSecondaryFont = fontFamilies.size() > 1;
    myUseSecondaryFontCheckbox.setSelected(isThereSecondaryFont);
    mySecondaryCombo.setFontName(isThereSecondaryFont ? fontFamilies.get(1) : null);
    myEditorFontSizeField.setText(
        String.valueOf(fontPreferences.getSize(fontPreferences.getFontFamily())));

    boolean readOnly = ColorAndFontOptions.isReadOnly(myOptions.getSelectedScheme());
    myPrimaryCombo.setEnabled(!readOnly);
    mySecondaryCombo.setEnabled(isThereSecondaryFont && !readOnly);
    myOnlyMonospacedCheckBox.setEnabled(!readOnly);
    myLineSpacingField.setEnabled(!readOnly);
    myEditorFontSizeField.setEnabled(!readOnly);
    myUseSecondaryFontCheckbox.setEnabled(!readOnly);

    myEnableLigaturesCheckbox.setEnabled(!readOnly);
    myLigaturesInfoLinkLabel.setEnabled(!readOnly);
    myEnableLigaturesCheckbox.setSelected(fontPreferences.useLigatures());

    myIsInSchemeChange = false;
  }

  @NotNull
  protected FontPreferences getFontPreferences() {
    return getCurrentScheme().getFontPreferences();
  }

  protected float getLineSpacing() {
    return getCurrentScheme().getLineSpacing();
  }

  protected void setCurrentLineSpacing(float lineSpacing) {
    getCurrentScheme().setLineSpacing(lineSpacing);
  }

  @Override
  @Nullable
  public Runnable showOption(final String option) {
    return null;
  }

  @Override
  public void applyChangesToScheme() {}

  @Override
  public void selectOption(final String typeToSelect) {}

  protected EditorColorsScheme getCurrentScheme() {
    return myOptions.getSelectedScheme();
  }

  public boolean updateDescription(boolean modified) {
    EditorColorsScheme scheme = myOptions.getSelectedScheme();

    if (modified
        && (ColorAndFontOptions.isReadOnly(scheme) || ColorSettingsUtil.isSharedScheme(scheme))) {
      showReadOnlyMessage(this, ColorSettingsUtil.isSharedScheme(scheme));
      return false;
    }

    myDispatcher.getMulticaster().fontChanged();

    return true;
  }

  @Override
  public void addListener(ColorAndFontSettingsListener listener) {
    myDispatcher.addListener(listener);
  }

  @Override
  public JPanel getPanel() {
    return this;
  }

  @Override
  public Set<String> processListOptions() {
    return new HashSet<String>();
  }
}
Beispiel #9
0
/** @author Eugene Zhuravlev Date: Dec 29, 2003 */
@Deprecated
public class ModuleTypeStep extends ModuleWizardStep {
  private final JPanel myPanel;
  private final JRadioButton myRbCreateNewModule;
  private final JRadioButton myRbImportModule;
  private final FieldPanel myModulePathFieldPanel;
  private final JList myTypesList;
  private final JEditorPane myModuleDescriptionPane;

  private ModuleType myModuleType = StdModuleTypes.JAVA;
  private Runnable myDoubleClickAction = null;

  final EventDispatcher<UpdateListener> myEventDispatcher =
      EventDispatcher.create(UpdateListener.class);
  private final ButtonGroup myButtonGroup;

  public static interface UpdateListener extends EventListener {
    void moduleTypeSelected(ModuleType type);

    void importModuleOptionSelected(boolean selected);
  }

  public ModuleTypeStep(boolean createNewProject) {
    myPanel = new JPanel(new GridBagLayout());
    myPanel.setBorder(BorderFactory.createEtchedBorder());

    myModuleDescriptionPane = new JEditorPane();
    myModuleDescriptionPane.setContentType(UIUtil.HTML_MIME);
    myModuleDescriptionPane.addHyperlinkListener(
        new HyperlinkListener() {
          public void hyperlinkUpdate(HyperlinkEvent e) {
            if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
              try {
                BrowserUtil.launchBrowser(e.getURL().toString());
              } catch (IllegalThreadStateException ex) {
                // it's nnot a problem
              }
            }
          }
        });
    myModuleDescriptionPane.setEditable(false);

    final ModuleType[] allModuleTypes = ModuleTypeManager.getInstance().getRegisteredTypes();

    myTypesList = new JList(allModuleTypes);
    myTypesList.setSelectionModel(new PermanentSingleSelectionModel());
    myTypesList.setCellRenderer(new ModuleTypesListCellRenderer());
    myTypesList.addListSelectionListener(
        new ListSelectionListener() {
          public void valueChanged(ListSelectionEvent e) {
            if (e.getValueIsAdjusting()) {
              return;
            }
            final ModuleType typeSelected = (ModuleType) myTypesList.getSelectedValue();
            myModuleType = typeSelected;
            //noinspection HardCodedStringLiteral
            myModuleDescriptionPane.setText(
                "<html><body><font face=\"verdana\" size=\"-1\">"
                    + typeSelected.getDescription()
                    + "</font></body></html>");
            myEventDispatcher.getMulticaster().moduleTypeSelected(typeSelected);
          }
        });
    myTypesList.setSelectedIndex(0);
    myTypesList.addMouseListener(
        new MouseAdapter() {
          public void mouseClicked(MouseEvent e) {
            if (e.getClickCount() == 2) {
              if (myDoubleClickAction != null) {
                if (myTypesList.getSelectedValue() != null) {
                  myDoubleClickAction.run();
                }
              }
            }
          }
        });

    myRbCreateNewModule = new JRadioButton(IdeBundle.message("radio.create.new.module"), true);
    myRbImportModule = new JRadioButton(IdeBundle.message("radio.import.existing.module"));
    myButtonGroup = new ButtonGroup();
    myButtonGroup.add(myRbCreateNewModule);
    myButtonGroup.add(myRbImportModule);
    ModulesRbListener listener = new ModulesRbListener();
    myRbCreateNewModule.addItemListener(listener);
    myRbImportModule.addItemListener(listener);

    JTextField tfModuleFilePath = new JTextField();
    final String productName = ApplicationNamesInfo.getInstance().getProductName();
    myModulePathFieldPanel =
        createFieldPanel(
            tfModuleFilePath,
            IdeBundle.message("label.path.to.module.file", productName),
            new BrowseFilesListener(
                tfModuleFilePath,
                IdeBundle.message("prompt.select.module.file.to.import", productName),
                null,
                new ModuleFileChooserDescriptor()));
    myModulePathFieldPanel.setEnabled(false);

    if (createNewProject) {
      final JLabel moduleTypeLabel = new JLabel(IdeBundle.message("label.select.module.type"));
      moduleTypeLabel.setFont(UIUtil.getLabelFont().deriveFont(Font.BOLD));
      myPanel.add(moduleTypeLabel, LABEL_CONSTRAINT);
    } else {
      myPanel.add(
          myRbCreateNewModule,
          new GridBagConstraints(
              0,
              GridBagConstraints.RELATIVE,
              1,
              1,
              0.0,
              0.0,
              GridBagConstraints.NORTHWEST,
              GridBagConstraints.NONE,
              new Insets(8, 10, 8, 10),
              0,
              0));
    }
    final JLabel descriptionLabel = new JLabel(IdeBundle.message("label.description"));
    descriptionLabel.setFont(UIUtil.getLabelFont().deriveFont(Font.BOLD));
    myPanel.add(
        descriptionLabel,
        new GridBagConstraints(
            1,
            GridBagConstraints.RELATIVE,
            1,
            1,
            0.0,
            0.0,
            GridBagConstraints.SOUTHWEST,
            GridBagConstraints.NONE,
            new Insets(0, 0, 0, 0),
            0,
            0));

    final JScrollPane typesListScrollPane = ScrollPaneFactory.createScrollPane(myTypesList);
    final Dimension preferredSize = calcTypeListPreferredSize(allModuleTypes);
    typesListScrollPane.setPreferredSize(preferredSize);
    typesListScrollPane.setMinimumSize(preferredSize);
    myPanel.add(
        typesListScrollPane,
        new GridBagConstraints(
            0,
            GridBagConstraints.RELATIVE,
            1,
            1,
            0.2,
            (createNewProject ? 1.0 : 0.0),
            GridBagConstraints.NORTHWEST,
            GridBagConstraints.BOTH,
            new Insets(0, createNewProject ? 10 : 30, 0, 10),
            0,
            0));

    final JScrollPane descriptionScrollPane =
        ScrollPaneFactory.createScrollPane(myModuleDescriptionPane);
    descriptionScrollPane.setPreferredSize(
        new Dimension(preferredSize.width * 3, preferredSize.height));
    myPanel.add(
        descriptionScrollPane,
        new GridBagConstraints(
            1,
            GridBagConstraints.RELATIVE,
            1,
            1,
            0.8,
            (createNewProject ? 1.0 : 0.0),
            GridBagConstraints.CENTER,
            GridBagConstraints.BOTH,
            new Insets(0, 0, 0, 10),
            0,
            0));

    if (!createNewProject) {
      myPanel.add(
          myRbImportModule,
          new GridBagConstraints(
              0,
              GridBagConstraints.RELATIVE,
              2,
              1,
              1.0,
              0.0,
              GridBagConstraints.NORTHWEST,
              GridBagConstraints.NONE,
              new Insets(16, 10, 0, 10),
              0,
              0));
      myPanel.add(
          myModulePathFieldPanel,
          new GridBagConstraints(
              0,
              GridBagConstraints.RELATIVE,
              2,
              1,
              1.0,
              1.0,
              GridBagConstraints.NORTHWEST,
              GridBagConstraints.HORIZONTAL,
              new Insets(8, 30, 0, 10),
              0,
              0));
    }
  }

  private Dimension calcTypeListPreferredSize(final ModuleType[] allModuleTypes) {
    int width = 0;
    int height = 0;
    final FontMetrics fontMetrics = myTypesList.getFontMetrics(myTypesList.getFont());
    final int fontHeight = fontMetrics.getMaxAscent() + fontMetrics.getMaxDescent();
    for (final ModuleType type : allModuleTypes) {
      final Icon icon = type.getBigIcon();
      final int iconHeight = icon != null ? icon.getIconHeight() : 0;
      final int iconWidth = icon != null ? icon.getIconWidth() : 0;
      height += Math.max(iconHeight, fontHeight) + 6;
      width = Math.max(width, iconWidth + fontMetrics.stringWidth(type.getName()) + 10);
    }
    return new Dimension(width, height);
  }

  public String getHelpId() {
    return "project.creatingModules.page1";
  }

  public void setModuleListDoubleClickAction(Runnable runnable) {
    myDoubleClickAction = runnable;
  }

  public JComponent getComponent() {
    return myPanel;
  }

  public Icon getIcon() {
    return ICON;
  }

  public boolean validate() {
    if (myRbImportModule.isSelected()) {
      final String path = myModulePathFieldPanel.getText().trim();
      if (path.length() == 0) {
        Messages.showErrorDialog(
            IdeBundle.message(
                "error.please.specify.path.to.module.file",
                ApplicationNamesInfo.getInstance().getProductName()),
            IdeBundle.message("title.module.file.path.not.specified"));
        myModulePathFieldPanel.getTextField().requestFocus();
        return false;
      }
      final File file = new File(path);
      if (!file.exists()) {
        Messages.showErrorDialog(
            IdeBundle.message("error.module.file.does.not.exist"),
            IdeBundle.message("title.module.file.does.not.exist"));
        myModulePathFieldPanel.getTextField().requestFocus();
        return false;
      }
      if (!StdFileTypes.IDEA_MODULE.equals(
          FileTypeManager.getInstance().getFileTypeByFileName(file.getName()))) {
        Messages.showErrorDialog(
            IdeBundle.message(
                "error.module.not.iml", path, ApplicationNamesInfo.getInstance().getProductName()),
            IdeBundle.message("title.incorrect.file.type"));
        myModulePathFieldPanel.getTextField().requestFocus();
        return false;
      }
    }
    return true;
  }

  public boolean isNextButtonEnabled() {
    return !myRbImportModule.isSelected();
  }

  public boolean isCreateNewModule() {
    return myRbCreateNewModule.isSelected();
  }

  public boolean isImportExistingModule() {
    return myRbImportModule.isSelected();
  }

  public String getModuleFilePath() {
    return myModulePathFieldPanel.getText().trim().replace(File.separatorChar, '/');
  }

  public ModuleType getModuleType() {
    return myModuleType;
  }

  public void addUpdateListener(UpdateListener listener) {
    myEventDispatcher.addListener(listener);
  }

  public void removeUpdateListener(UpdateListener listener) {
    myEventDispatcher.removeListener(listener);
  }

  public void updateDataModel() {}

  public JComponent getPreferredFocusedComponent() {
    return myTypesList;
  }

  private class ModulesRbListener implements ItemListener {
    public void itemStateChanged(ItemEvent e) {
      final JComponent toFocus;
      ButtonModel selection = myButtonGroup.getSelection();
      setControlsEnabled(selection);
      if (selection == myRbCreateNewModule.getModel()) {
        toFocus = myTypesList;
        myEventDispatcher.getMulticaster().importModuleOptionSelected(false);
      } else if (selection == myRbImportModule.getModel()) { // import existing
        toFocus = myModulePathFieldPanel.getTextField();
        myEventDispatcher.getMulticaster().importModuleOptionSelected(true);
      } else {
        toFocus = null;
      }

      if (toFocus != null) {
        SwingUtilities.invokeLater(
            new Runnable() {
              public void run() {
                toFocus.requestFocus();
              }
            });
      }
    }
  }

  private void setControlsEnabled(ButtonModel selection) {
    boolean newModuleEnabled = selection == myRbCreateNewModule.getModel();
    myTypesList.setEnabled(newModuleEnabled);
    myModuleDescriptionPane.setEnabled(newModuleEnabled);

    boolean importModuleEnabled = selection == myRbImportModule.getModel();
    myModulePathFieldPanel.setEnabled(importModuleEnabled);
  }

  private static class ModuleFileChooserDescriptor extends FileChooserDescriptor {
    public ModuleFileChooserDescriptor() {
      super(true, false, false, false, false, false);
      setHideIgnored(false);
    }

    public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) {
      final boolean isVisible = super.isFileVisible(file, showHiddenFiles);
      if (!isVisible || file.isDirectory()) {
        return isVisible;
      }
      return StdFileTypes.IDEA_MODULE.equals(FileTypeManager.getInstance().getFileTypeByFile(file));
    }
  }

  private static class ModuleTypesListCellRenderer extends DefaultListCellRenderer {
    public Component getListCellRendererComponent(
        JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
      final Component rendererComponent =
          super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
      final ModuleType moduleType = (ModuleType) value;
      setIcon(moduleType.getBigIcon());
      setDisabledIcon(moduleType.getBigIcon());
      setText(moduleType.getName());
      return rendererComponent;
    }
  }

  private static class PermanentSingleSelectionModel extends DefaultListSelectionModel {
    public PermanentSingleSelectionModel() {
      super.setSelectionMode(SINGLE_SELECTION);
    }

    public final void setSelectionMode(int selectionMode) {}

    public final void removeSelectionInterval(int index0, int index1) {}
  }
}
/** @author Dmitry Avdeev */
@State(
    name = "TaskManager",
    storages = {@Storage(file = StoragePathMacros.WORKSPACE_FILE)})
public class TaskManagerImpl extends TaskManager
    implements ProjectComponent,
        PersistentStateComponent<TaskManagerImpl.Config>,
        ChangeListDecorator {

  private static final Logger LOG = Logger.getInstance("#com.intellij.tasks.impl.TaskManagerImpl");

  private static final DecimalFormat LOCAL_TASK_ID_FORMAT = new DecimalFormat("LOCAL-00000");
  public static final Comparator<Task> TASK_UPDATE_COMPARATOR =
      new Comparator<Task>() {
        public int compare(Task o1, Task o2) {
          int i = Comparing.compare(o2.getUpdated(), o1.getUpdated());
          return i == 0 ? Comparing.compare(o2.getCreated(), o1.getCreated()) : i;
        }
      };
  private static final Convertor<Task, String> KEY_CONVERTOR =
      new Convertor<Task, String>() {
        @Override
        public String convert(Task o) {
          return o.getId();
        }
      };
  static final String TASKS_NOTIFICATION_GROUP = "Task Group";

  private final Project myProject;

  private final WorkingContextManager myContextManager;

  private final Map<String, Task> myIssueCache =
      Collections.synchronizedMap(new LinkedHashMap<String, Task>());

  private final Map<String, LocalTask> myTasks =
      Collections.synchronizedMap(
          new LinkedHashMap<String, LocalTask>() {
            @Override
            public LocalTask put(String key, LocalTask task) {
              LocalTask result = super.put(key, task);
              if (size() > myConfig.taskHistoryLength) {
                ArrayList<LocalTask> list = new ArrayList<LocalTask>(values());
                Collections.sort(list, TASK_UPDATE_COMPARATOR);
                for (LocalTask oldest : list) {
                  if (!oldest.isDefault()) {
                    remove(oldest);
                    break;
                  }
                }
              }
              return result;
            }
          });

  @NotNull private LocalTask myActiveTask = createDefaultTask();
  private Timer myCacheRefreshTimer;

  private volatile boolean myUpdating;
  private final Config myConfig = new Config();
  private final ChangeListAdapter myChangeListListener;
  private final ChangeListManager myChangeListManager;

  private final List<TaskRepository> myRepositories = new ArrayList<TaskRepository>();
  private final EventDispatcher<TaskListener> myDispatcher =
      EventDispatcher.create(TaskListener.class);
  private Set<TaskRepository> myBadRepositories = new ConcurrentHashSet<TaskRepository>();

  public TaskManagerImpl(
      Project project,
      WorkingContextManager contextManager,
      final ChangeListManager changeListManager) {

    myProject = project;
    myContextManager = contextManager;
    myChangeListManager = changeListManager;

    myChangeListListener =
        new ChangeListAdapter() {
          @Override
          public void changeListRemoved(ChangeList list) {
            LocalTask task = getAssociatedTask((LocalChangeList) list);
            if (task != null) {
              for (ChangeListInfo info : task.getChangeLists()) {
                if (Comparing.equal(info.id, ((LocalChangeList) list).getId())) {
                  info.id = "";
                }
              }
            }
          }

          @Override
          public void defaultListChanged(ChangeList oldDefaultList, ChangeList newDefaultList) {
            final LocalTask associatedTask = getAssociatedTask((LocalChangeList) newDefaultList);
            if (associatedTask != null && !getActiveTask().equals(associatedTask)) {
              ApplicationManager.getApplication()
                  .invokeLater(
                      new Runnable() {
                        public void run() {
                          activateTask(associatedTask, true);
                        }
                      },
                      myProject.getDisposed());
            }
          }
        };
  }

  @Override
  public TaskRepository[] getAllRepositories() {
    return myRepositories.toArray(new TaskRepository[myRepositories.size()]);
  }

  public <T extends TaskRepository> void setRepositories(List<T> repositories) {

    Set<TaskRepository> set = new HashSet<TaskRepository>(myRepositories);
    set.removeAll(repositories);
    myBadRepositories.removeAll(set); // remove all changed reps
    myIssueCache.clear();

    myRepositories.clear();
    myRepositories.addAll(repositories);

    reps:
    for (T repository : repositories) {
      if (repository.isShared() && repository.getUrl() != null) {
        List<TaskProjectConfiguration.SharedServer> servers = getProjectConfiguration().servers;
        TaskRepositoryType type = repository.getRepositoryType();
        for (TaskProjectConfiguration.SharedServer server : servers) {
          if (repository.getUrl().equals(server.url) && type.getName().equals(server.type)) {
            continue reps;
          }
        }
        TaskProjectConfiguration.SharedServer server = new TaskProjectConfiguration.SharedServer();
        server.type = type.getName();
        server.url = repository.getUrl();
        servers.add(server);
      }
    }
  }

  @Override
  public void removeTask(LocalTask task) {
    if (task.isDefault()) return;
    if (myActiveTask.equals(task)) {
      activateTask(myTasks.get(LocalTaskImpl.DEFAULT_TASK_ID), true);
    }
    myTasks.remove(task.getId());
    myDispatcher.getMulticaster().taskRemoved(task);
    myContextManager.removeContext(task);
  }

  @Override
  public void addTaskListener(TaskListener listener) {
    myDispatcher.addListener(listener);
  }

  @Override
  public void removeTaskListener(TaskListener listener) {
    myDispatcher.removeListener(listener);
  }

  @NotNull
  @Override
  public LocalTask getActiveTask() {
    return myActiveTask;
  }

  @Nullable
  @Override
  public LocalTask findTask(String id) {
    return myTasks.get(id);
  }

  @NotNull
  @Override
  public List<Task> getIssues(@Nullable final String query) {
    return getIssues(query, true);
  }

  @Override
  public List<Task> getIssues(@Nullable final String query, final boolean forceRequest) {
    return getIssues(query, 50, 0, forceRequest, true, new EmptyProgressIndicator());
  }

  @Override
  public List<Task> getIssues(
      @Nullable String query,
      int max,
      long since,
      boolean forceRequest,
      final boolean withClosed,
      @NotNull final ProgressIndicator cancelled) {
    List<Task> tasks = getIssuesFromRepositories(query, max, since, forceRequest, cancelled);
    if (tasks == null) return getCachedIssues(withClosed);
    myIssueCache.putAll(ContainerUtil.newMapFromValues(tasks.iterator(), KEY_CONVERTOR));
    return ContainerUtil.filter(
        tasks,
        new Condition<Task>() {
          @Override
          public boolean value(final Task task) {
            return withClosed || !task.isClosed();
          }
        });
  }

  @Override
  public List<Task> getCachedIssues() {
    return getCachedIssues(true);
  }

  @Override
  public List<Task> getCachedIssues(final boolean withClosed) {
    return ContainerUtil.filter(
        myIssueCache.values(),
        new Condition<Task>() {
          @Override
          public boolean value(final Task task) {
            return withClosed || !task.isClosed();
          }
        });
  }

  @Nullable
  @Override
  public Task updateIssue(@NotNull String id) {
    for (TaskRepository repository : getAllRepositories()) {
      if (repository.extractId(id) == null) {
        continue;
      }
      try {
        Task issue = repository.findTask(id);
        if (issue != null) {
          LocalTask localTask = findTask(id);
          if (localTask != null) {
            localTask.updateFromIssue(issue);
            return localTask;
          }
          return issue;
        }
      } catch (Exception e) {
        LOG.info(e);
      }
    }
    return null;
  }

  @Override
  public List<LocalTask> getLocalTasks() {
    return getLocalTasks(true);
  }

  @Override
  public List<LocalTask> getLocalTasks(final boolean withClosed) {
    synchronized (myTasks) {
      return ContainerUtil.filter(
          myTasks.values(),
          new Condition<LocalTask>() {
            @Override
            public boolean value(final LocalTask task) {
              return withClosed || !isLocallyClosed(task);
            }
          });
    }
  }

  @Override
  public LocalTask addTask(Task issue) {
    LocalTaskImpl task =
        issue instanceof LocalTaskImpl ? (LocalTaskImpl) issue : new LocalTaskImpl(issue);
    addTask(task);
    return task;
  }

  @Override
  public LocalTaskImpl createLocalTask(@NotNull String summary) {
    return createTask(LOCAL_TASK_ID_FORMAT.format(myConfig.localTasksCounter++), summary);
  }

  private static LocalTaskImpl createTask(@NotNull String id, @NotNull String summary) {
    LocalTaskImpl task = new LocalTaskImpl(id, summary);
    Date date = new Date();
    task.setCreated(date);
    task.setUpdated(date);
    return task;
  }

  @Override
  public LocalTask activateTask(@NotNull final Task origin, boolean clearContext) {
    LocalTask activeTask = getActiveTask();
    if (origin.equals(activeTask)) return activeTask;

    saveActiveTask();

    if (clearContext) {
      myContextManager.clearContext();
    }
    myContextManager.restoreContext(origin);

    final LocalTask task = doActivate(origin, true);

    return restoreVcsContext(task);
  }

  private LocalTask restoreVcsContext(LocalTask task) {
    if (!isVcsEnabled()) return task;

    List<ChangeListInfo> changeLists = task.getChangeLists();
    if (!changeLists.isEmpty()) {
      ChangeListInfo info = changeLists.get(0);
      LocalChangeList changeList = myChangeListManager.getChangeList(info.id);
      if (changeList == null) {
        changeList = myChangeListManager.addChangeList(info.name, info.comment);
        info.id = changeList.getId();
      }
      myChangeListManager.setDefaultChangeList(changeList);
    }

    List<BranchInfo> branches = task.getBranches(false);
    VcsTaskHandler.TaskInfo info = fromBranches(branches);

    VcsTaskHandler[] handlers = VcsTaskHandler.getAllHandlers(myProject);
    for (VcsTaskHandler handler : handlers) {
      handler.switchToTask(info);
    }
    return task;
  }

  private static VcsTaskHandler.TaskInfo fromBranches(List<BranchInfo> branches) {
    MultiMap<String, String> map = new MultiMap<String, String>();
    for (BranchInfo branch : branches) {
      map.putValue(branch.name, branch.repository);
    }
    return new VcsTaskHandler.TaskInfo(map);
  }

  public void createBranch(LocalTask task, LocalTask previousActive, String name) {
    VcsTaskHandler[] handlers = VcsTaskHandler.getAllHandlers(myProject);
    for (VcsTaskHandler handler : handlers) {
      VcsTaskHandler.TaskInfo info = handler.getActiveTask();
      if (previousActive != null) {
        addBranches(previousActive, info, false);
      }
      addBranches(task, info, true);
      addBranches(task, handler.startNewTask(name), false);
    }
  }

  public void mergeBranch(LocalTask task) {
    VcsTaskHandler.TaskInfo original = fromBranches(task.getBranches(true));
    VcsTaskHandler.TaskInfo feature = fromBranches(task.getBranches(false));

    VcsTaskHandler[] handlers = VcsTaskHandler.getAllHandlers(myProject);
    for (VcsTaskHandler handler : handlers) {
      handler.closeTask(feature, original);
    }
  }

  private static void addBranches(LocalTask task, VcsTaskHandler.TaskInfo info, boolean original) {
    List<BranchInfo> branchInfos = BranchInfo.fromTaskInfo(info, original);
    for (BranchInfo branchInfo : branchInfos) {
      task.addBranch(branchInfo);
    }
  }

  private void saveActiveTask() {
    myContextManager.saveContext(myActiveTask);
    myActiveTask.setUpdated(new Date());
  }

  private LocalTask doActivate(Task origin, boolean explicitly) {
    final LocalTaskImpl task =
        origin instanceof LocalTaskImpl ? (LocalTaskImpl) origin : new LocalTaskImpl(origin);
    if (explicitly) {
      task.setUpdated(new Date());
    }
    myActiveTask.setActive(false);
    task.setActive(true);
    addTask(task);
    if (task.isIssue()) {
      StartupManager.getInstance(myProject)
          .runWhenProjectIsInitialized(
              new Runnable() {
                public void run() {
                  ProgressManager.getInstance()
                      .run(
                          new com.intellij.openapi.progress.Task.Backgroundable(
                              myProject, "Updating " + task.getId()) {

                            public void run(@NotNull ProgressIndicator indicator) {
                              updateIssue(task.getId());
                            }
                          });
                }
              });
    }
    LocalTask oldActiveTask = myActiveTask;
    boolean isChanged = !task.equals(oldActiveTask);
    myActiveTask = task;
    if (isChanged) {
      myDispatcher.getMulticaster().taskDeactivated(oldActiveTask);
      myDispatcher.getMulticaster().taskActivated(task);
    }
    return task;
  }

  private void addTask(LocalTaskImpl task) {
    myTasks.put(task.getId(), task);
    myDispatcher.getMulticaster().taskAdded(task);
  }

  @Override
  public boolean testConnection(final TaskRepository repository) {

    TestConnectionTask task =
        new TestConnectionTask("Test connection") {
          public void run(@NotNull ProgressIndicator indicator) {
            indicator.setText("Connecting to " + repository.getUrl() + "...");
            indicator.setFraction(0);
            indicator.setIndeterminate(true);
            try {
              myConnection = repository.createCancellableConnection();
              if (myConnection != null) {
                Future<Exception> future =
                    ApplicationManager.getApplication().executeOnPooledThread(myConnection);
                while (true) {
                  try {
                    myException = future.get(100, TimeUnit.MILLISECONDS);
                    return;
                  } catch (TimeoutException ignore) {
                    try {
                      indicator.checkCanceled();
                    } catch (ProcessCanceledException e) {
                      myException = e;
                      myConnection.cancel();
                      return;
                    }
                  } catch (Exception e) {
                    myException = e;
                    return;
                  }
                }
              } else {
                try {
                  repository.testConnection();
                } catch (Exception e) {
                  LOG.info(e);
                  myException = e;
                }
              }
            } catch (Exception e) {
              myException = e;
            }
          }
        };
    ProgressManager.getInstance().run(task);
    Exception e = task.myException;
    if (e == null) {
      myBadRepositories.remove(repository);
      Messages.showMessageDialog(
          myProject, "Connection is successful", "Connection", Messages.getInformationIcon());
    } else if (!(e instanceof ProcessCanceledException)) {
      String message = e.getMessage();
      if (e instanceof UnknownHostException) {
        message = "Unknown host: " + message;
      }
      if (message == null) {
        LOG.error(e);
        message = "Unknown error";
      }
      Messages.showErrorDialog(myProject, StringUtil.capitalize(message), "Error");
    }
    return e == null;
  }

  @SuppressWarnings({"unchecked"})
  @NotNull
  public Config getState() {
    myConfig.tasks =
        ContainerUtil.map(
            myTasks.values(),
            new Function<Task, LocalTaskImpl>() {
              public LocalTaskImpl fun(Task task) {
                return new LocalTaskImpl(task);
              }
            });
    myConfig.servers = XmlSerializer.serialize(getAllRepositories());
    return myConfig;
  }

  @SuppressWarnings({"unchecked"})
  public void loadState(Config config) {
    XmlSerializerUtil.copyBean(config, myConfig);
    myTasks.clear();
    for (LocalTaskImpl task : config.tasks) {
      addTask(task);
    }

    myRepositories.clear();
    Element element = config.servers;
    List<TaskRepository> repositories = loadRepositories(element);
    myRepositories.addAll(repositories);
  }

  public static ArrayList<TaskRepository> loadRepositories(Element element) {
    ArrayList<TaskRepository> repositories = new ArrayList<TaskRepository>();
    for (TaskRepositoryType repositoryType : TaskRepositoryType.getRepositoryTypes()) {
      for (Object o : element.getChildren()) {
        if (((Element) o).getName().equals(repositoryType.getName())) {
          try {
            @SuppressWarnings({"unchecked"})
            TaskRepository repository =
                (TaskRepository)
                    XmlSerializer.deserialize((Element) o, repositoryType.getRepositoryClass());
            if (repository != null) {
              repository.setRepositoryType(repositoryType);
              repositories.add(repository);
            }
          } catch (XmlSerializationException e) {
            // ignore
            LOG.error(e.getMessage());
          }
        }
      }
    }
    return repositories;
  }

  public void projectOpened() {

    TaskProjectConfiguration projectConfiguration = getProjectConfiguration();

    servers:
    for (TaskProjectConfiguration.SharedServer server : projectConfiguration.servers) {
      if (server.type == null || server.url == null) {
        continue;
      }
      for (TaskRepositoryType<?> repositoryType : TaskRepositoryType.getRepositoryTypes()) {
        if (repositoryType.getName().equals(server.type)) {
          for (TaskRepository repository : myRepositories) {
            if (!repositoryType.equals(repository.getRepositoryType())) {
              continue;
            }
            if (server.url.equals(repository.getUrl())) {
              continue servers;
            }
          }
          TaskRepository repository = repositoryType.createRepository();
          repository.setUrl(server.url);
          myRepositories.add(repository);
        }
      }
    }

    myContextManager.pack(200, 50);

    // make sure the task is associated with default changelist
    LocalTask defaultTask = findTask(LocalTaskImpl.DEFAULT_TASK_ID);
    LocalChangeList defaultList = myChangeListManager.findChangeList(LocalChangeList.DEFAULT_NAME);
    if (defaultList != null && defaultTask != null) {
      ChangeListInfo listInfo = new ChangeListInfo(defaultList);
      if (!defaultTask.getChangeLists().contains(listInfo)) {
        defaultTask.addChangelist(listInfo);
      }
    }

    // remove already not existing changelists from tasks changelists
    for (LocalTask localTask : getLocalTasks()) {
      for (Iterator<ChangeListInfo> iterator = localTask.getChangeLists().iterator();
          iterator.hasNext(); ) {
        final ChangeListInfo changeListInfo = iterator.next();
        if (myChangeListManager.getChangeList(changeListInfo.id) == null) {
          iterator.remove();
        }
      }
    }

    myChangeListManager.addChangeListListener(myChangeListListener);
  }

  private TaskProjectConfiguration getProjectConfiguration() {
    return ServiceManager.getService(myProject, TaskProjectConfiguration.class);
  }

  public void projectClosed() {}

  @NotNull
  public String getComponentName() {
    return "Task Manager";
  }

  public void initComponent() {
    if (!ApplicationManager.getApplication().isUnitTestMode()) {
      myCacheRefreshTimer =
          UIUtil.createNamedTimer(
              "TaskManager refresh",
              myConfig.updateInterval * 60 * 1000,
              new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                  if (myConfig.updateEnabled && !myUpdating) {
                    updateIssues(null);
                  }
                }
              });
      myCacheRefreshTimer.setInitialDelay(0);
      StartupManager.getInstance(myProject)
          .registerPostStartupActivity(
              new Runnable() {
                public void run() {
                  myCacheRefreshTimer.start();
                }
              });
    }

    // make sure that the default task is exist
    LocalTask defaultTask = findTask(LocalTaskImpl.DEFAULT_TASK_ID);
    if (defaultTask == null) {
      defaultTask = createDefaultTask();
      addTask(defaultTask);
    }

    // search for active task
    LocalTask activeTask = null;
    final List<LocalTask> tasks = getLocalTasks();
    Collections.sort(tasks, TASK_UPDATE_COMPARATOR);
    for (LocalTask task : tasks) {
      if (activeTask == null) {
        if (task.isActive()) {
          activeTask = task;
        }
      } else {
        task.setActive(false);
      }
    }
    if (activeTask == null) {
      activeTask = defaultTask;
    }

    myActiveTask = activeTask;
    doActivate(myActiveTask, false);
    myDispatcher.getMulticaster().taskActivated(myActiveTask);
  }

  private static LocalTaskImpl createDefaultTask() {
    return new LocalTaskImpl(LocalTaskImpl.DEFAULT_TASK_ID, "Default task");
  }

  public void disposeComponent() {
    if (myCacheRefreshTimer != null) {
      myCacheRefreshTimer.stop();
    }
    myChangeListManager.removeChangeListListener(myChangeListListener);
  }

  public void updateIssues(final @Nullable Runnable onComplete) {
    TaskRepository first =
        ContainerUtil.find(
            getAllRepositories(),
            new Condition<TaskRepository>() {
              public boolean value(TaskRepository repository) {
                return repository.isConfigured();
              }
            });
    if (first == null) {
      myIssueCache.clear();
      if (onComplete != null) {
        onComplete.run();
      }
      return;
    }
    myUpdating = true;
    if (ApplicationManager.getApplication().isUnitTestMode()) {
      doUpdate(onComplete);
    } else {
      ApplicationManager.getApplication()
          .executeOnPooledThread(
              new Runnable() {
                public void run() {
                  doUpdate(onComplete);
                }
              });
    }
  }

  private void doUpdate(@Nullable Runnable onComplete) {
    try {
      List<Task> issues =
          getIssuesFromRepositories(
              null, myConfig.updateIssuesCount, 0, false, new EmptyProgressIndicator());
      if (issues == null) return;

      synchronized (myIssueCache) {
        myIssueCache.clear();
        for (Task issue : issues) {
          myIssueCache.put(issue.getId(), issue);
        }
      }
      // update local tasks
      synchronized (myTasks) {
        for (Map.Entry<String, LocalTask> entry : myTasks.entrySet()) {
          Task issue = myIssueCache.get(entry.getKey());
          if (issue != null) {
            entry.getValue().updateFromIssue(issue);
          }
        }
      }
    } finally {
      if (onComplete != null) {
        onComplete.run();
      }
      myUpdating = false;
    }
  }

  @Nullable
  private List<Task> getIssuesFromRepositories(
      @Nullable String request,
      int max,
      long since,
      boolean forceRequest,
      @NotNull final ProgressIndicator cancelled) {
    List<Task> issues = null;
    for (final TaskRepository repository : getAllRepositories()) {
      if (!repository.isConfigured() || (!forceRequest && myBadRepositories.contains(repository))) {
        continue;
      }
      try {
        Task[] tasks = repository.getIssues(request, max, since, cancelled);
        myBadRepositories.remove(repository);
        if (issues == null) issues = new ArrayList<Task>(tasks.length);
        if (!repository.isSupported(TaskRepository.NATIVE_SEARCH) && request != null) {
          List<Task> filteredTasks =
              TaskSearchSupport.filterTasks(request, ContainerUtil.list(tasks));
          ContainerUtil.addAll(issues, filteredTasks);
        } else {
          ContainerUtil.addAll(issues, tasks);
        }
      } catch (ProcessCanceledException ignored) {
        // OK
      } catch (Exception e) {
        String reason = "";
        // Fix to IDEA-111810
        if (e.getClass() == Exception.class) {
          // probably contains some message meaningful to end-user
          reason = e.getMessage();
        }
        //noinspection InstanceofCatchParameter
        if (e instanceof SocketTimeoutException) {
          LOG.warn("Socket timeout from " + repository);
        } else {
          LOG.warn("Cannot connect to " + repository, e);
        }
        myBadRepositories.add(repository);
        if (forceRequest) {
          notifyAboutConnectionFailure(repository, reason);
        }
      }
    }
    return issues;
  }

  private void notifyAboutConnectionFailure(final TaskRepository repository, String details) {
    Notifications.Bus.register(TASKS_NOTIFICATION_GROUP, NotificationDisplayType.BALLOON);
    String content = "<p><a href=\"\">Configure server...</a></p>";
    if (!StringUtil.isEmpty(details)) {
      content = "<p>" + details + "</p>" + content;
    }
    Notifications.Bus.notify(
        new Notification(
            TASKS_NOTIFICATION_GROUP,
            "Cannot connect to " + repository.getUrl(),
            content,
            NotificationType.WARNING,
            new NotificationListener() {
              public void hyperlinkUpdate(
                  @NotNull Notification notification, @NotNull HyperlinkEvent event) {
                TaskRepositoriesConfigurable configurable =
                    new TaskRepositoriesConfigurable(myProject);
                ShowSettingsUtil.getInstance().editConfigurable(myProject, configurable);
                if (!ArrayUtil.contains(repository, getAllRepositories())) {
                  notification.expire();
                }
              }
            }),
        myProject);
  }

  @Override
  public boolean isVcsEnabled() {
    return ProjectLevelVcsManager.getInstance(myProject).getAllActiveVcss().length > 0;
  }

  @Override
  public AbstractVcs getActiveVcs() {
    AbstractVcs[] vcss = ProjectLevelVcsManager.getInstance(myProject).getAllActiveVcss();
    if (vcss.length == 0) return null;
    for (AbstractVcs vcs : vcss) {
      if (vcs.getType() == VcsType.distributed) {
        return vcs;
      }
    }
    return vcss[0];
  }

  @Override
  public boolean isLocallyClosed(final LocalTask localTask) {
    if (isVcsEnabled()) {
      List<ChangeListInfo> lists = localTask.getChangeLists();
      if (lists.isEmpty()) return true;
      for (ChangeListInfo list : lists) {
        if (StringUtil.isEmpty(list.id)) {
          return true;
        }
      }
    }
    return false;
  }

  @Nullable
  @Override
  public LocalTask getAssociatedTask(LocalChangeList list) {
    for (LocalTask task : getLocalTasks()) {
      for (ChangeListInfo changeListInfo : task.getChangeLists()) {
        if (changeListInfo.id.equals(list.getId())) {
          return task;
        }
      }
    }
    return null;
  }

  @Override
  public void trackContext(LocalChangeList changeList) {
    ChangeListInfo changeListInfo = new ChangeListInfo(changeList);
    String changeListName = changeList.getName();
    LocalTaskImpl task = createLocalTask(changeListName);
    task.addChangelist(changeListInfo);
    addTask(task);
    if (changeList.isDefault()) {
      activateTask(task, false);
    }
  }

  @Override
  public void disassociateFromTask(LocalChangeList changeList) {
    ChangeListInfo changeListInfo = new ChangeListInfo(changeList);
    for (LocalTask localTask : getLocalTasks()) {
      if (localTask.getChangeLists().contains(changeListInfo)) {
        localTask.removeChangelist(changeListInfo);
      }
    }
  }

  public void decorateChangeList(
      LocalChangeList changeList,
      ColoredTreeCellRenderer cellRenderer,
      boolean selected,
      boolean expanded,
      boolean hasFocus) {
    LocalTask task = getAssociatedTask(changeList);
    if (task != null && task.isIssue()) {
      cellRenderer.setIcon(task.getIcon());
    }
  }

  public void createChangeList(LocalTask task, String name) {
    String comment = TaskUtil.getChangeListComment(task);
    createChangeList(task, name, comment);
  }

  private void createChangeList(LocalTask task, String name, @Nullable String comment) {
    LocalChangeList changeList = myChangeListManager.findChangeList(name);
    if (changeList == null) {
      changeList = myChangeListManager.addChangeList(name, comment);
    } else {
      final LocalTask associatedTask = getAssociatedTask(changeList);
      if (associatedTask != null) {
        associatedTask.removeChangelist(new ChangeListInfo(changeList));
      }
      changeList.setComment(comment);
    }
    task.addChangelist(new ChangeListInfo(changeList));
    myChangeListManager.setDefaultChangeList(changeList);
  }

  public String getChangelistName(Task task) {
    if (task.isIssue() && myConfig.changelistNameFormat != null) {
      return TaskUtil.formatTask(task, myConfig.changelistNameFormat);
    }
    return task.getSummary();
  }

  public String suggestBranchName(Task task) {
    if (task.isIssue() && StringUtil.isNotEmpty(task.getNumber())) {
      return task.getId().replace(' ', '-');
    } else {
      String summary = task.getSummary();
      List<String> words = StringUtil.getWordsIn(summary);
      String[] strings = ArrayUtil.toStringArray(words);
      return StringUtil.join(strings, 0, Math.min(2, strings.length), "-");
    }
  }

  @TestOnly
  public ChangeListAdapter getChangeListListener() {
    return myChangeListListener;
  }

  public static class Config {

    @Property(surroundWithTag = false)
    @AbstractCollection(surroundWithTag = false, elementTag = "task")
    public List<LocalTaskImpl> tasks = new ArrayList<LocalTaskImpl>();

    public int localTasksCounter = 1;

    public int taskHistoryLength = 50;

    public boolean updateEnabled = true;
    public int updateInterval = 20;
    public int updateIssuesCount = 100;

    // create task options
    public boolean clearContext = true;
    public boolean createChangelist = true;
    public boolean createBranch = true;

    // close task options
    public boolean closeIssue = true;
    public boolean commitChanges = true;
    public boolean mergeBranch = true;

    public boolean saveContextOnCommit = true;
    public boolean trackContextForNewChangelist = false;
    public boolean markAsInProgress = false;

    public String changelistNameFormat = "{id} {summary}";

    public boolean searchClosedTasks = false;

    @Tag("servers")
    public Element servers = new Element("servers");
  }

  private abstract class TestConnectionTask extends com.intellij.openapi.progress.Task.Modal {

    protected Exception myException;

    @Nullable protected TaskRepository.CancellableConnection myConnection;

    public TestConnectionTask(String title) {
      super(TaskManagerImpl.this.myProject, title, true);
    }

    @Override
    public void onCancel() {
      if (myConnection != null) {
        myConnection.cancel();
      }
    }
  }
}
/** @author peter */
public final class DomManagerImpl extends DomManager {
  private static final Key<Object> MOCK = Key.create("MockElement");

  static final Key<WeakReference<DomFileElementImpl>> CACHED_FILE_ELEMENT =
      Key.create("CACHED_FILE_ELEMENT");
  static final Key<DomFileDescription> MOCK_DESCRIPTION = Key.create("MockDescription");
  static final SemKey<FileDescriptionCachedValueProvider> FILE_DESCRIPTION_KEY =
      SemKey.createKey("FILE_DESCRIPTION_KEY");
  static final SemKey<DomInvocationHandler> DOM_HANDLER_KEY = SemKey.createKey("DOM_HANDLER_KEY");
  static final SemKey<IndexedElementInvocationHandler> DOM_INDEXED_HANDLER_KEY =
      DOM_HANDLER_KEY.subKey("DOM_INDEXED_HANDLER_KEY");
  static final SemKey<CollectionElementInvocationHandler> DOM_COLLECTION_HANDLER_KEY =
      DOM_HANDLER_KEY.subKey("DOM_COLLECTION_HANDLER_KEY");
  static final SemKey<CollectionElementInvocationHandler> DOM_CUSTOM_HANDLER_KEY =
      DOM_HANDLER_KEY.subKey("DOM_CUSTOM_HANDLER_KEY");
  static final SemKey<AttributeChildInvocationHandler> DOM_ATTRIBUTE_HANDLER_KEY =
      DOM_HANDLER_KEY.subKey("DOM_ATTRIBUTE_HANDLER_KEY");

  private final EventDispatcher<DomEventListener> myListeners =
      EventDispatcher.create(DomEventListener.class);
  private final GenericValueReferenceProvider myGenericValueReferenceProvider =
      new GenericValueReferenceProvider();

  private final Project myProject;
  private final SemService mySemService;
  private final ConverterManager myConverterManager;
  private final DomApplicationComponent myApplicationComponent;

  private long myModificationCount;
  private boolean myChanging;

  public DomManagerImpl(
      Project project,
      SemService semService,
      ConverterManager converterManager,
      DomApplicationComponent appComponent) {
    myProject = project;
    mySemService = semService;
    myConverterManager = converterManager;
    myApplicationComponent = appComponent;

    final PomModel pomModel = PomManager.getModel(project);
    pomModel.addModelListener(
        new PomModelListener() {
          public void modelChanged(PomModelEvent event) {
            if (myChanging) return;

            final XmlChangeSet changeSet =
                (XmlChangeSet) event.getChangeSet(pomModel.getModelAspect(XmlAspect.class));
            if (changeSet != null) {
              for (XmlFile file : changeSet.getChangedFiles()) {
                DomFileElementImpl<DomElement> element = getCachedFileElement(file);
                if (element != null) {
                  fireEvent(new DomEvent(element, false));
                }
              }
            }
          }

          public boolean isAspectChangeInteresting(PomModelAspect aspect) {
            return aspect instanceof XmlAspect;
          }
        },
        project);

    Runnable setupVfsListeners =
        new Runnable() {
          public void run() {
            final VirtualFileAdapter listener =
                new VirtualFileAdapter() {
                  private final List<DomEvent> myDeletionEvents = new SmartList<DomEvent>();

                  public void contentsChanged(VirtualFileEvent event) {
                    if (event.isFromSave()) return;

                    processVfsChange(event.getFile());
                  }

                  public void fileCreated(VirtualFileEvent event) {
                    processVfsChange(event.getFile());
                  }

                  public void beforeFileDeletion(final VirtualFileEvent event) {
                    if (!myProject.isDisposed()) {
                      beforeFileDeletion(event.getFile());
                    }
                  }

                  private void beforeFileDeletion(final VirtualFile file) {
                    if (file.isDirectory() && file instanceof NewVirtualFile) {
                      for (final VirtualFile child : ((NewVirtualFile) file).getCachedChildren()) {
                        beforeFileDeletion(child);
                      }
                      return;
                    }

                    if (file.isValid() && StdFileTypes.XML.equals(file.getFileType())) {
                      final PsiFile psiFile = getCachedPsiFile(file);
                      if (psiFile instanceof XmlFile) {
                        Collections.addAll(
                            myDeletionEvents, recomputeFileElement((XmlFile) psiFile));
                      }
                    }
                  }

                  public void fileDeleted(VirtualFileEvent event) {
                    if (!myDeletionEvents.isEmpty()) {
                      if (!myProject.isDisposed()) {
                        for (DomEvent domEvent : myDeletionEvents) {
                          fireEvent(domEvent);
                        }
                      }
                      myDeletionEvents.clear();
                    }
                  }

                  public void propertyChanged(VirtualFilePropertyEvent event) {
                    final VirtualFile file = event.getFile();
                    if (!file.isDirectory()
                        && VirtualFile.PROP_NAME.equals(event.getPropertyName())) {
                      processVfsChange(file);
                    }
                  }
                };
            VirtualFileManager.getInstance().addVirtualFileListener(listener, myProject);
          }
        };

    StartupManager startupManager = StartupManager.getInstance(project);
    if (!((StartupManagerEx) startupManager).startupActivityPassed()) {
      startupManager.registerStartupActivity(setupVfsListeners);
    } else {
      setupVfsListeners.run();
    }
  }

  private void processVfsChange(final VirtualFile file) {
    processFileOrDirectoryChange(file);
  }

  public long getPsiModificationCount() {
    return PsiManager.getInstance(getProject()).getModificationTracker().getModificationCount();
  }

  public <T extends DomInvocationHandler> void cacheHandler(
      SemKey<T> key, XmlElement element, T handler) {
    mySemService.setCachedSemElement(key, element, handler);
  }

  private void processFileChange(final VirtualFile file) {
    if (StdFileTypes.XML != file.getFileType()) return;
    processFileChange(getCachedPsiFile(file));
  }

  private PsiFile getCachedPsiFile(VirtualFile file) {
    return ((PsiManagerEx) PsiManager.getInstance(myProject))
        .getFileManager()
        .getCachedPsiFile(file);
  }

  private void processFileChange(final PsiFile file) {
    if (file != null && StdFileTypes.XML.equals(file.getFileType()) && file instanceof XmlFile) {
      for (final DomEvent event : recomputeFileElement((XmlFile) file)) {
        fireEvent(event);
      }
    }
  }

  static DomEvent[] recomputeFileElement(final XmlFile file) {
    final DomFileElementImpl oldElement = getCachedFileElement(file);
    return oldElement == null
        ? DomEvent.EMPTY_ARRAY
        : new DomEvent[] {new DomEvent(oldElement, false)};
  }

  private void processDirectoryChange(final VirtualFile directory) {
    for (final VirtualFile file : directory.getChildren()) {
      processFileOrDirectoryChange(file);
    }
  }

  private void processFileOrDirectoryChange(final VirtualFile file) {
    if (!ProjectFileIndex.SERVICE.getInstance(myProject).isInContent(file)) return;
    if (!file.isDirectory()) {
      processFileChange(file);
    } else {
      processDirectoryChange(file);
    }
  }

  @SuppressWarnings({"MethodOverridesStaticMethodOfSuperclass"})
  public static DomManagerImpl getDomManager(Project project) {
    return (DomManagerImpl) DomManager.getDomManager(project);
  }

  public void addDomEventListener(DomEventListener listener, Disposable parentDisposable) {
    myListeners.addListener(listener, parentDisposable);
  }

  public final ConverterManager getConverterManager() {
    return myConverterManager;
  }

  public final void addPsiReferenceFactoryForClass(
      Class clazz, PsiReferenceFactory psiReferenceFactory) {
    myGenericValueReferenceProvider.addReferenceProviderForClass(clazz, psiReferenceFactory);
  }

  public final ModelMerger createModelMerger() {
    return new ModelMergerImpl();
  }

  final void fireEvent(DomEvent event) {
    if (mySemService.isInsideAtomicChange()) return;
    myModificationCount++;
    myListeners.getMulticaster().eventOccured(event);
  }

  public final DomGenericInfo getGenericInfo(final Type type) {
    return myApplicationComponent.getStaticGenericInfo(type);
  }

  @Nullable
  public static DomInvocationHandler getDomInvocationHandler(DomElement proxy) {
    if (proxy instanceof DomFileElement) {
      return null;
    }
    if (proxy instanceof DomInvocationHandler) {
      return (DomInvocationHandler) proxy;
    }
    final InvocationHandler handler = AdvancedProxy.getInvocationHandler(proxy);
    if (handler instanceof StableInvocationHandler) {
      final DomElement element =
          ((StableInvocationHandler<DomElement>) handler).getWrappedElement();
      return element == null ? null : getDomInvocationHandler(element);
    }
    if (handler instanceof DomInvocationHandler) {
      return (DomInvocationHandler) handler;
    }
    return null;
  }

  @NotNull
  public static DomInvocationHandler getNotNullHandler(DomElement proxy) {
    DomInvocationHandler handler = getDomInvocationHandler(proxy);
    if (handler == null) {
      throw new AssertionError("null handler for " + proxy);
    }
    return handler;
  }

  public static StableInvocationHandler getStableInvocationHandler(Object proxy) {
    return (StableInvocationHandler) AdvancedProxy.getInvocationHandler(proxy);
  }

  public DomApplicationComponent getApplicationComponent() {
    return myApplicationComponent;
  }

  public final Project getProject() {
    return myProject;
  }

  @NotNull
  public final <T extends DomElement> DomFileElementImpl<T> getFileElement(
      final XmlFile file, final Class<T> aClass, String rootTagName) {
    //noinspection unchecked
    if (file.getUserData(MOCK_DESCRIPTION) == null) {
      file.putUserData(MOCK_DESCRIPTION, new MockDomFileDescription<T>(aClass, rootTagName, file));
      mySemService.clearCache();
    }
    final DomFileElementImpl<T> fileElement = getFileElement(file);
    assert fileElement != null;
    return fileElement;
  }

  @SuppressWarnings({"unchecked"})
  @NotNull
  final <T extends DomElement> FileDescriptionCachedValueProvider<T> getOrCreateCachedValueProvider(
      final XmlFile xmlFile) {
    return mySemService.getSemElement(FILE_DESCRIPTION_KEY, xmlFile);
  }

  public final Set<DomFileDescription> getFileDescriptions(String rootTagName) {
    return myApplicationComponent.getFileDescriptions(rootTagName);
  }

  public final Set<DomFileDescription> getAcceptingOtherRootTagNameDescriptions() {
    return myApplicationComponent.getAcceptingOtherRootTagNameDescriptions();
  }

  @NotNull
  @NonNls
  public final String getComponentName() {
    return getClass().getName();
  }

  final void runChange(Runnable change) {
    final boolean b = setChanging(true);
    try {
      change.run();
    } finally {
      setChanging(b);
    }
  }

  final boolean setChanging(final boolean changing) {
    boolean oldChanging = myChanging;
    if (changing) {
      assert !oldChanging;
    }
    myChanging = changing;
    return oldChanging;
  }

  @Nullable
  public final <T extends DomElement> DomFileElementImpl<T> getFileElement(XmlFile file) {
    if (file == null) return null;
    if (!(file.getFileType() instanceof DomSupportEnabled)) return null;
    final VirtualFile virtualFile = file.getVirtualFile();
    if (virtualFile != null && virtualFile.isDirectory()) return null;
    return this.<T>getOrCreateCachedValueProvider(file).getFileElement();
  }

  @Nullable
  static <T extends DomElement> DomFileElementImpl<T> getCachedFileElement(@NotNull XmlFile file) {
    WeakReference<DomFileElementImpl> ref = file.getUserData(CACHED_FILE_ELEMENT);
    return ref == null ? null : ref.get();
  }

  @Nullable
  public final <T extends DomElement> DomFileElementImpl<T> getFileElement(
      XmlFile file, Class<T> domClass) {
    final DomFileDescription description = getDomFileDescription(file);
    if (description != null
        && myApplicationComponent.assignabilityCache.isAssignable(
            domClass, description.getRootElementClass())) {
      return getFileElement(file);
    }
    return null;
  }

  @Nullable
  public final DomElement getDomElement(final XmlTag element) {
    if (myChanging) return null;

    final DomInvocationHandler handler = getDomHandler(element);
    return handler != null ? handler.getProxy() : null;
  }

  @Nullable
  public GenericAttributeValue getDomElement(final XmlAttribute attribute) {
    if (myChanging) return null;

    final AttributeChildInvocationHandler handler =
        mySemService.getSemElement(DOM_ATTRIBUTE_HANDLER_KEY, attribute);
    return handler == null ? null : (GenericAttributeValue) handler.getProxy();
  }

  @Nullable
  public DomInvocationHandler getDomHandler(final XmlElement tag) {
    if (tag == null) return null;

    List<DomInvocationHandler> cached = mySemService.getCachedSemElements(DOM_HANDLER_KEY, tag);
    if (cached != null && !cached.isEmpty()) {
      return cached.get(0);
    }

    return mySemService.getSemElement(DOM_HANDLER_KEY, tag);
  }

  @Nullable
  public AbstractDomChildrenDescription findChildrenDescription(
      @NotNull final XmlTag tag, @NotNull final DomElement parent) {
    return findChildrenDescription(tag, getDomInvocationHandler(parent));
  }

  static AbstractDomChildrenDescription findChildrenDescription(
      final XmlTag tag, final DomInvocationHandler parent) {
    final DomGenericInfoEx info = parent.getGenericInfo();
    return info.findChildrenDescription(
        parent, tag.getLocalName(), tag.getNamespace(), false, tag.getName());
  }

  public final boolean isDomFile(@Nullable PsiFile file) {
    return file instanceof XmlFile && getFileElement((XmlFile) file) != null;
  }

  @Nullable
  public final DomFileDescription<?> getDomFileDescription(PsiElement element) {
    if (element instanceof XmlElement) {
      final PsiFile psiFile = element.getContainingFile();
      if (psiFile instanceof XmlFile) {
        return getDomFileDescription((XmlFile) psiFile);
      }
    }
    return null;
  }

  public final <T extends DomElement> T createMockElement(
      final Class<T> aClass, final Module module, final boolean physical) {
    final XmlFile file =
        (XmlFile)
            PsiFileFactory.getInstance(myProject)
                .createFileFromText("a.xml", StdFileTypes.XML, "", (long) 0, physical);
    file.putUserData(MOCK_ELEMENT_MODULE, module);
    file.putUserData(MOCK, new Object());
    return getFileElement(
            file, aClass, "I_sincerely_hope_that_nobody_will_have_such_a_root_tag_name")
        .getRootElement();
  }

  public final boolean isMockElement(DomElement element) {
    return DomUtil.getFile(element).getUserData(MOCK) != null;
  }

  public final <T extends DomElement> T createStableValue(final Factory<T> provider) {
    return createStableValue(
        provider,
        new Condition<T>() {
          public boolean value(T t) {
            return t.isValid();
          }
        });
  }

  public final <T> T createStableValue(final Factory<T> provider, final Condition<T> validator) {
    final T initial = provider.create();
    assert initial != null;
    final StableInvocationHandler handler =
        new StableInvocationHandler<T>(initial, provider, validator);

    final Set<Class> intf = new HashSet<Class>();
    ContainerUtil.addAll(intf, initial.getClass().getInterfaces());
    intf.add(StableElement.class);
    //noinspection unchecked

    return (T)
        AdvancedProxy.createProxy(
            initial.getClass().getSuperclass(), intf.toArray(new Class[intf.size()]), handler);
  }

  public final <T extends DomElement> void registerFileDescription(
      final DomFileDescription<T> description, Disposable parentDisposable) {
    registerFileDescription(description);
    Disposer.register(
        parentDisposable,
        new Disposable() {
          public void dispose() {
            getFileDescriptions(description.getRootTagName()).remove(description);
            getAcceptingOtherRootTagNameDescriptions().remove(description);
          }
        });
  }

  public final void registerFileDescription(final DomFileDescription description) {
    mySemService.clearCache();

    myApplicationComponent.registerFileDescription(description);
  }

  @NotNull
  public final DomElement getResolvingScope(GenericDomValue element) {
    final DomFileDescription description = DomUtil.getFileElement(element).getFileDescription();
    return description.getResolveScope(element);
  }

  @Nullable
  public final DomElement getIdentityScope(DomElement element) {
    final DomFileDescription description = DomUtil.getFileElement(element).getFileDescription();
    return description.getIdentityScope(element);
  }

  public TypeChooserManager getTypeChooserManager() {
    return myApplicationComponent.getTypeChooserManager();
  }

  public long getModificationCount() {
    return myModificationCount
        + PsiManager.getInstance(myProject)
            .getModificationTracker()
            .getOutOfCodeBlockModificationCount();
  }

  public void performAtomicChange(@NotNull Runnable change) {
    mySemService.performAtomicChange(change);
    if (!mySemService.isInsideAtomicChange()) {
      myModificationCount++;
    }
  }

  public SemService getSemService() {
    return mySemService;
  }
}
/**
 * @author Anton Katilin
 * @author Vladimir Kondratyev
 */
public final class WindowManagerImpl extends WindowManagerEx
    implements ApplicationComponent, NamedJDOMExternalizable {
  private static final Logger LOG =
      Logger.getInstance("#com.intellij.openapi.wm.impl.WindowManagerImpl");

  private static boolean ourAlphaModeLibraryLoaded;
  @NonNls private static final String FOCUSED_WINDOW_PROPERTY_NAME = "focusedWindow";
  @NonNls private static final String X_ATTR = "x";
  @NonNls private static final String FRAME_ELEMENT = "frame";
  @NonNls private static final String Y_ATTR = "y";
  @NonNls private static final String WIDTH_ATTR = "width";
  @NonNls private static final String HEIGHT_ATTR = "height";
  @NonNls private static final String EXTENDED_STATE_ATTR = "extended-state";
  private Boolean myAlphaModeSupported = null;

  private final EventDispatcher<WindowManagerListener> myEventDispatcher =
      EventDispatcher.create(WindowManagerListener.class);

  static {
    initialize();
  }

  @SuppressWarnings({"HardCodedStringLiteral"})
  private static void initialize() {
    try {
      System.loadLibrary("jawt");
      ourAlphaModeLibraryLoaded = true;
    } catch (Throwable exc) {
      ourAlphaModeLibraryLoaded = false;
    }
  }

  /** Union of bounds of all available default screen devices. */
  private final Rectangle myScreenBounds;

  private final CommandProcessor myCommandProcessor;
  private final WindowWatcher myWindowWatcher;
  /** That is the default layout. */
  private final DesktopLayout myLayout;

  private final HashMap<Project, IdeFrameImpl> myProject2Frame;

  private final HashMap<Project, Set<JDialog>> myDialogsToDispose;

  /**
   * This members is needed to read frame's bounds from XML. <code>myFrameBounds</code> can be
   * <code>null</code>.
   */
  private Rectangle myFrameBounds;

  private int myFrameExtendedState;
  private final WindowAdapter myActivationListener;
  private final ApplicationInfoEx myApplicationInfoEx;
  private final DataManager myDataManager;
  private final ActionManagerEx myActionManager;
  private final UISettings myUiSettings;

  /**
   * invoked by reflection
   *
   * @param dataManager
   * @param applicationInfoEx
   * @param actionManager
   * @param uiSettings
   */
  public WindowManagerImpl(
      DataManager dataManager,
      ApplicationInfoEx applicationInfoEx,
      ActionManagerEx actionManager,
      UISettings uiSettings,
      MessageBus bus) {
    myApplicationInfoEx = applicationInfoEx;
    myDataManager = dataManager;
    myActionManager = actionManager;
    myUiSettings = uiSettings;
    if (myDataManager instanceof DataManagerImpl) {
      ((DataManagerImpl) myDataManager).setWindowManager(this);
    }

    final Application application = ApplicationManager.getApplication();
    if (!application.isUnitTestMode()) {
      Disposer.register(
          application,
          new Disposable() {
            @Override
            public void dispose() {
              disposeRootFrame();
            }
          });
    }

    myCommandProcessor = new CommandProcessor();
    myWindowWatcher = new WindowWatcher();
    final KeyboardFocusManager keyboardFocusManager =
        KeyboardFocusManager.getCurrentKeyboardFocusManager();
    keyboardFocusManager.addPropertyChangeListener(FOCUSED_WINDOW_PROPERTY_NAME, myWindowWatcher);
    if (Patches.SUN_BUG_ID_4218084) {
      keyboardFocusManager.addPropertyChangeListener(
          FOCUSED_WINDOW_PROPERTY_NAME, new SUN_BUG_ID_4218084_Patch());
    }
    myLayout = new DesktopLayout();
    myProject2Frame = new HashMap<Project, IdeFrameImpl>();
    myDialogsToDispose = new HashMap<Project, Set<JDialog>>();
    myFrameExtendedState = Frame.NORMAL;

    // Calculate screen bounds.

    Rectangle screenBounds = new Rectangle();
    if (!application.isHeadlessEnvironment()) {
      final GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
      final GraphicsDevice[] devices = env.getScreenDevices();
      for (final GraphicsDevice device : devices) {
        screenBounds = screenBounds.union(device.getDefaultConfiguration().getBounds());
      }
    }
    myScreenBounds = screenBounds;

    myActivationListener =
        new WindowAdapter() {
          public void windowActivated(WindowEvent e) {
            Window activeWindow = e.getWindow();
            if (activeWindow instanceof IdeFrameImpl) { // must be
              proceedDialogDisposalQueue(((IdeFrameImpl) activeWindow).getProject());
            }
          }
        };

    bus.connect()
        .subscribe(
            AppLifecycleListener.TOPIC,
            new AppLifecycleListener.Adapter() {
              @Override
              public void appClosing() {
                // save fullscreen window states
                if (isFullScreenSupportedInCurrentOS()
                    && GeneralSettings.getInstance().isReopenLastProject()) {
                  Project[] openProjects = ProjectManager.getInstance().getOpenProjects();

                  if (openProjects.length > 0) {
                    WindowManagerEx wm = WindowManagerEx.getInstanceEx();
                    for (Project project : openProjects) {
                      IdeFrameImpl frame = wm.getFrame(project);
                      if (frame != null) {
                        frame.storeFullScreenStateIfNeeded();
                      }
                    }
                  }
                }
              }
            });

    if (UIUtil.hasLeakingAppleListeners()) {
      UIUtil.addAwtListener(
          new AWTEventListener() {
            @Override
            public void eventDispatched(AWTEvent event) {
              if (event.getID() == ContainerEvent.COMPONENT_ADDED) {
                if (((ContainerEvent) event).getChild() instanceof JViewport) {
                  UIUtil.removeLeakingAppleListeners();
                }
              }
            }
          },
          AWTEvent.CONTAINER_EVENT_MASK,
          application);
    }
  }

  @NotNull
  public IdeFrameImpl[] getAllProjectFrames() {
    final Collection<IdeFrameImpl> ideFrames = myProject2Frame.values();
    return ideFrames.toArray(new IdeFrameImpl[ideFrames.size()]);
  }

  @Override
  public JFrame findVisibleFrame() {
    IdeFrameImpl[] frames = getAllProjectFrames();

    if (frames.length > 0) return frames[0];
    return (JFrame) WelcomeFrame.getInstance();
  }

  @Override
  public void addListener(final WindowManagerListener listener) {
    myEventDispatcher.addListener(listener);
  }

  public void removeListener(final WindowManagerListener listener) {
    myEventDispatcher.removeListener(listener);
  }

  public final Rectangle getScreenBounds() {
    return myScreenBounds;
  }

  @Override
  public boolean isFullScreen(@NotNull Frame frame) {
    return frame instanceof IdeFrameImpl && ((IdeFrameImpl) frame).isInFullScreen();
  }

  @Override
  public Rectangle getScreenBounds(@NotNull Project project) {
    final GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
    final Point onScreen = getFrame(project).getLocationOnScreen();
    final GraphicsDevice[] devices = environment.getScreenDevices();
    for (final GraphicsDevice device : devices) {
      final Rectangle bounds = device.getDefaultConfiguration().getBounds();
      if (bounds.contains(onScreen)) {
        return bounds;
      }
    }

    return null;
  }

  public final boolean isInsideScreenBounds(final int x, final int y, final int width) {
    return x >= myScreenBounds.x + 50 - width
        && y >= myScreenBounds.y - 50
        && x <= myScreenBounds.x + myScreenBounds.width - 50
        && y <= myScreenBounds.y + myScreenBounds.height - 50;
  }

  public final boolean isInsideScreenBounds(final int x, final int y) {
    return myScreenBounds.contains(x, y);
  }

  public final boolean isAlphaModeSupported() {
    if (myAlphaModeSupported == null) {
      myAlphaModeSupported = calcAlphaModelSupported();
    }
    return myAlphaModeSupported.booleanValue();
  }

  private static boolean calcAlphaModelSupported() {
    if (AWTUtilitiesWrapper.isTranslucencyAPISupported()) {
      return AWTUtilitiesWrapper.isTranslucencySupported(AWTUtilitiesWrapper.TRANSLUCENT);
    }
    try {
      return WindowUtils.isWindowAlphaSupported();
    } catch (Throwable e) {
      return false;
    }
  }

  public final void setAlphaModeRatio(final Window window, final float ratio) {
    if (!window.isDisplayable() || !window.isShowing()) {
      throw new IllegalArgumentException(
          "window must be displayable and showing. window=" + window);
    }
    if (ratio < 0.0f || ratio > 1.0f) {
      throw new IllegalArgumentException("ratio must be in [0..1] range. ratio=" + ratio);
    }
    if (!isAlphaModeSupported() || !isAlphaModeEnabled(window)) {
      return;
    }

    setAlphaMode(window, ratio);
  }

  private static void setAlphaMode(Window window, float ratio) {
    try {
      if (SystemInfo.isMacOSLeopard) {
        if (window instanceof JWindow) {
          ((JWindow) window).getRootPane().putClientProperty("Window.alpha", 1.0f - ratio);
        } else if (window instanceof JDialog) {
          ((JDialog) window).getRootPane().putClientProperty("Window.alpha", 1.0f - ratio);
        } else if (window instanceof JFrame) {
          ((JFrame) window).getRootPane().putClientProperty("Window.alpha", 1.0f - ratio);
        }
      } else if (AWTUtilitiesWrapper.isTranslucencySupported(AWTUtilitiesWrapper.TRANSLUCENT)) {
        AWTUtilitiesWrapper.setWindowOpacity(window, 1.0f - ratio);
      } else {
        WindowUtils.setWindowAlpha(window, 1.0f - ratio);
      }
    } catch (Throwable e) {
      LOG.debug(e);
    }
  }

  public void setWindowMask(final Window window, @Nullable final Shape mask) {
    try {
      if (AWTUtilitiesWrapper.isTranslucencySupported(AWTUtilitiesWrapper.PERPIXEL_TRANSPARENT)) {
        AWTUtilitiesWrapper.setWindowShape(window, mask);
      } else {
        WindowUtils.setWindowMask(window, mask);
      }
    } catch (Throwable e) {
      LOG.debug(e);
    }
  }

  @Override
  public void setWindowShadow(Window window, WindowShadowMode mode) {
    if (window instanceof JWindow) {
      JRootPane root = ((JWindow) window).getRootPane();
      root.putClientProperty(
          "Window.shadow", mode == WindowShadowMode.DISABLED ? Boolean.FALSE : Boolean.TRUE);
      root.putClientProperty("Window.style", mode == WindowShadowMode.SMALL ? "small" : null);
    }
  }

  public void resetWindow(final Window window) {
    try {
      if (!isAlphaModeSupported()) return;

      setWindowMask(window, null);
      setAlphaMode(window, 0f);
      setWindowShadow(window, WindowShadowMode.NORMAL);
    } catch (Throwable e) {
      LOG.debug(e);
    }
  }

  public final boolean isAlphaModeEnabled(final Window window) {
    if (!window.isDisplayable() || !window.isShowing()) {
      throw new IllegalArgumentException(
          "window must be displayable and showing. window=" + window);
    }
    return isAlphaModeSupported();
  }

  public final void setAlphaModeEnabled(final Window window, final boolean state) {
    if (!window.isDisplayable() || !window.isShowing()) {
      throw new IllegalArgumentException(
          "window must be displayable and showing. window=" + window);
    }
  }

  public void hideDialog(JDialog dialog, Project project) {
    if (project == null) {
      dialog.dispose();
    } else {
      IdeFrameImpl frame = getFrame(project);
      if (frame.isActive()) {
        dialog.dispose();
      } else {
        queueForDisposal(dialog, project);
        dialog.setVisible(false);
      }
    }
  }

  @Override
  public void adjustContainerWindow(Component c, Dimension oldSize, Dimension newSize) {
    if (c == null) return;

    Window wnd = SwingUtilities.getWindowAncestor(c);

    if (wnd instanceof JWindow) {
      JBPopup popup = (JBPopup) ((JWindow) wnd).getRootPane().getClientProperty(JBPopup.KEY);
      if (popup != null) {
        if (oldSize.height < newSize.height) {
          Dimension size = popup.getSize();
          size.height += (newSize.height - oldSize.height);
          popup.setSize(size);
          popup.moveToFitScreen();
        }
      }
    }
  }

  public final void disposeComponent() {}

  public final void initComponent() {}

  public final void doNotSuggestAsParent(final Window window) {
    myWindowWatcher.doNotSuggestAsParent(window);
  }

  public final void dispatchComponentEvent(final ComponentEvent e) {
    myWindowWatcher.dispatchComponentEvent(e);
  }

  @Nullable
  public final Window suggestParentWindow(@Nullable final Project project) {
    return myWindowWatcher.suggestParentWindow(project);
  }

  @Nullable
  public final StatusBar getStatusBar(final Project project) {
    if (!myProject2Frame.containsKey(project)) {
      return null;
    }
    final IdeFrameImpl frame = getFrame(project);
    LOG.assertTrue(frame != null);
    return frame.getStatusBar();
  }

  @Override
  public StatusBar getStatusBar(@NotNull Component c) {
    return getStatusBar(c, null);
  }

  @Override
  public StatusBar getStatusBar(@NotNull Component c, @Nullable Project project) {
    Component parent = UIUtil.findUltimateParent(c);
    if (parent instanceof IdeFrame) {
      return ((IdeFrame) parent).getStatusBar().findChild(c);
    }

    IdeFrame frame = findFrameFor(project);
    if (frame != null) {
      return frame.getStatusBar().findChild(c);
    }

    assert false : "Cannot find status bar for " + c;

    return null;
  }

  public IdeFrame findFrameFor(@Nullable final Project project) {
    IdeFrame frame = null;
    if (project != null) {
      frame = project.isDefault() ? WelcomeFrame.getInstance() : getFrame(project);
    } else {
      Container eachParent = getMostRecentFocusedWindow();
      while (eachParent != null) {
        if (eachParent instanceof IdeFrame) {

          frame = (IdeFrame) eachParent;
          break;
        }
        eachParent = eachParent.getParent();
      }

      if (frame == null) {
        frame = tryToFindTheOnlyFrame();
      }
    }

    return frame;
  }

  private static IdeFrame tryToFindTheOnlyFrame() {
    IdeFrame candidate = null;
    final Frame[] all = Frame.getFrames();
    for (Frame each : all) {
      if (each instanceof IdeFrame) {
        if (candidate == null) {
          candidate = (IdeFrame) each;
        } else {
          candidate = null;
          break;
        }
      }
    }
    return candidate;
  }

  public final IdeFrameImpl getFrame(@Nullable final Project project) {
    // no assert! otherwise WindowWatcher.suggestParentWindow fails for default project
    // LOG.assertTrue(myProject2Frame.containsKey(project));
    return myProject2Frame.get(project);
  }

  public IdeFrame getIdeFrame(@Nullable final Project project) {
    if (project != null) {
      return getFrame(project);
    }
    final Window window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
    final Component parent = UIUtil.findUltimateParent(window);
    if (parent instanceof IdeFrame) return (IdeFrame) parent;

    final Frame[] frames = Frame.getFrames();
    for (Frame each : frames) {
      if (each instanceof IdeFrame) {
        return (IdeFrame) each;
      }
    }

    return null;
  }

  public void showFrame() {
    final IdeFrameImpl frame =
        new IdeFrameImpl(
            myApplicationInfoEx,
            myActionManager,
            myUiSettings,
            myDataManager,
            ApplicationManager.getApplication());
    myProject2Frame.put(null, frame);

    if (myFrameBounds == null
        || !ScreenUtil.isVisible(
            myFrameBounds)) { // avoid situations when IdeFrame is out of all screens
      Rectangle rect = ScreenUtil.getScreenRectangle(0, 0);
      int yParts = rect.height / 6;
      int xParts = rect.width / 5;
      myFrameBounds = new Rectangle(xParts, yParts, xParts * 3, yParts * 4);
    }

    frame.setBounds(myFrameBounds);
    frame.setVisible(true);
    frame.setExtendedState(myFrameExtendedState);
  }

  public final IdeFrameImpl allocateFrame(final Project project) {
    LOG.assertTrue(!myProject2Frame.containsKey(project));

    final IdeFrameImpl frame;
    if (myProject2Frame.containsKey(null)) {
      frame = myProject2Frame.get(null);
      myProject2Frame.remove(null);
      myProject2Frame.put(project, frame);
      frame.setProject(project);
    } else {
      frame =
          new IdeFrameImpl(
              (ApplicationInfoEx) ApplicationInfo.getInstance(),
              ActionManagerEx.getInstanceEx(),
              UISettings.getInstance(),
              DataManager.getInstance(),
              ApplicationManager.getApplication());
      final Rectangle bounds = ProjectFrameBounds.getInstance(project).getBounds();
      if (bounds != null) {
        frame.setBounds(bounds);
      } else if (myFrameBounds != null) {
        frame.setBounds(myFrameBounds);
      }
      frame.setExtendedState(myFrameExtendedState);
      frame.setProject(project);
      myProject2Frame.put(project, frame);
      frame.setVisible(true);
    }

    frame.addWindowListener(myActivationListener);

    myEventDispatcher.getMulticaster().frameCreated(frame);

    return frame;
  }

  private void proceedDialogDisposalQueue(Project project) {
    Set<JDialog> dialogs = myDialogsToDispose.get(project);
    if (dialogs == null) return;
    for (JDialog dialog : dialogs) {
      dialog.dispose();
    }
    myDialogsToDispose.put(project, null);
  }

  private void queueForDisposal(JDialog dialog, Project project) {
    Set<JDialog> dialogs = myDialogsToDispose.get(project);
    if (dialogs == null) {
      dialogs = new HashSet<JDialog>();
      myDialogsToDispose.put(project, dialogs);
    }
    dialogs.add(dialog);
  }

  public final void releaseFrame(final IdeFrameImpl frame) {

    myEventDispatcher.getMulticaster().beforeFrameReleased(frame);

    final Project project = frame.getProject();
    LOG.assertTrue(project != null);

    frame.removeWindowListener(myActivationListener);
    proceedDialogDisposalQueue(project);

    frame.setProject(null);
    frame.setTitle(null);
    frame.setFileTitle(null, null);

    myProject2Frame.remove(project);
    Disposer.dispose(frame.getStatusBar());
    frame.dispose();
  }

  public final void disposeRootFrame() {
    if (myProject2Frame.size() == 1) {
      final IdeFrameImpl rootFrame = myProject2Frame.remove(null);
      if (rootFrame != null) {
        // disposing last frame if quitting
        rootFrame.dispose();
      }
    }
  }

  public final Window getMostRecentFocusedWindow() {
    return myWindowWatcher.getFocusedWindow();
  }

  public final Component getFocusedComponent(@NotNull final Window window) {
    return myWindowWatcher.getFocusedComponent(window);
  }

  @Nullable
  public final Component getFocusedComponent(@Nullable final Project project) {
    return myWindowWatcher.getFocusedComponent(project);
  }

  /** Private part */
  public final CommandProcessor getCommandProcessor() {
    return myCommandProcessor;
  }

  public final String getExternalFileName() {
    return "window.manager";
  }

  public final void readExternal(final Element element) {
    final Element frameElement = element.getChild(FRAME_ELEMENT);
    if (frameElement != null) {
      myFrameBounds = loadFrameBounds(frameElement);
      try {
        myFrameExtendedState =
            Integer.parseInt(frameElement.getAttributeValue(EXTENDED_STATE_ATTR));
        if ((myFrameExtendedState & Frame.ICONIFIED) > 0) {
          myFrameExtendedState = Frame.NORMAL;
        }
      } catch (NumberFormatException ignored) {
        myFrameExtendedState = Frame.NORMAL;
      }
    }

    final Element desktopElement = element.getChild(DesktopLayout.TAG);
    if (desktopElement != null) {
      myLayout.readExternal(desktopElement);
    }
  }

  private static Rectangle loadFrameBounds(final Element frameElement) {
    Rectangle bounds = new Rectangle();
    try {
      bounds.x = Integer.parseInt(frameElement.getAttributeValue(X_ATTR));
    } catch (NumberFormatException ignored) {
      return null;
    }
    try {
      bounds.y = Integer.parseInt(frameElement.getAttributeValue(Y_ATTR));
    } catch (NumberFormatException ignored) {
      return null;
    }
    try {
      bounds.width = Integer.parseInt(frameElement.getAttributeValue(WIDTH_ATTR));
    } catch (NumberFormatException ignored) {
      return null;
    }
    try {
      bounds.height = Integer.parseInt(frameElement.getAttributeValue(HEIGHT_ATTR));
    } catch (NumberFormatException ignored) {
      return null;
    }
    return bounds;
  }

  public final void writeExternal(final Element element) {
    // Save frame bounds
    final Element frameElement = new Element(FRAME_ELEMENT);
    element.addContent(frameElement);
    final Project[] projects = ProjectManager.getInstance().getOpenProjects();
    final Project project;
    if (projects.length > 0) {
      project = projects[projects.length - 1];
    } else {
      project = null;
    }

    final IdeFrameImpl frame = getFrame(project);
    if (frame != null) {
      int extendedState = frame.getExtendedState();
      if (SystemInfo.isMacOSLion && frame.getPeer() instanceof FramePeer) {
        // frame.state is not updated by jdk so get it directly from peer
        extendedState = ((FramePeer) frame.getPeer()).getState();
      }
      boolean usePreviousBounds =
          extendedState == Frame.MAXIMIZED_BOTH
              || isFullScreenSupportedInCurrentOS()
                  && WindowManagerEx.getInstanceEx().isFullScreen(frame);
      Rectangle rectangle = usePreviousBounds ? myFrameBounds : frame.getBounds();
      if (rectangle == null) { // frame is out of the screen?
        rectangle = ScreenUtil.getScreenRectangle(0, 0);
      }
      frameElement.setAttribute(X_ATTR, Integer.toString(rectangle.x));
      frameElement.setAttribute(Y_ATTR, Integer.toString(rectangle.y));
      frameElement.setAttribute(WIDTH_ATTR, Integer.toString(rectangle.width));
      frameElement.setAttribute(HEIGHT_ATTR, Integer.toString(rectangle.height));
      frameElement.setAttribute(EXTENDED_STATE_ATTR, Integer.toString(extendedState));

      // Save default layout
      final Element layoutElement = new Element(DesktopLayout.TAG);
      element.addContent(layoutElement);
      myLayout.writeExternal(layoutElement);
    }
  }

  public final DesktopLayout getLayout() {
    return myLayout;
  }

  public final void setLayout(final DesktopLayout layout) {
    myLayout.copyFrom(layout);
  }

  @NotNull
  public final String getComponentName() {
    return "WindowManager";
  }

  /**
   * We cannot clear selected menu path just by changing of focused window. Under Windows LAF
   * focused window changes sporadically when user clickes on menu item or submenu. The problem is
   * that all popups under Windows LAF always has native window ancestor. This window isn't
   * focusable but by mouse click focused window changes in this manner: InitialFocusedWindow->null
   * null->InitialFocusedWindow To fix this problem we use alarm to accumulate such focus events.
   */
  private static final class SUN_BUG_ID_4218084_Patch implements PropertyChangeListener {
    private final Alarm myAlarm;
    private Window myInitialFocusedWindow;
    private Window myLastFocusedWindow;
    private final Runnable myClearSelectedPathRunnable;

    public SUN_BUG_ID_4218084_Patch() {
      myAlarm = new Alarm();
      myClearSelectedPathRunnable =
          new Runnable() {
            public void run() {
              if (myInitialFocusedWindow != myLastFocusedWindow) {
                MenuSelectionManager.defaultManager().clearSelectedPath();
              }
            }
          };
    }

    public void propertyChange(final PropertyChangeEvent e) {
      if (myAlarm.getActiveRequestCount() == 0) {
        myInitialFocusedWindow = (Window) e.getOldValue();
        final MenuElement[] selectedPath = MenuSelectionManager.defaultManager().getSelectedPath();
        if (selectedPath.length == 0) { // there is no visible popup
          return;
        }
        Component firstComponent = null;
        for (final MenuElement menuElement : selectedPath) {
          final Component component = menuElement.getComponent();
          if (component instanceof JMenuBar) {
            firstComponent = component;
            break;
          } else if (component instanceof JPopupMenu) {
            firstComponent = ((JPopupMenu) component).getInvoker();
            break;
          }
        }
        if (firstComponent == null) {
          return;
        }
        final Window window = SwingUtilities.getWindowAncestor(firstComponent);
        if (window != myInitialFocusedWindow) { // focused window doesn't have popup
          return;
        }
      }
      myLastFocusedWindow = (Window) e.getNewValue();
      myAlarm.cancelAllRequests();
      myAlarm.addRequest(myClearSelectedPathRunnable, 150);
    }
  }

  public WindowWatcher getWindowWatcher() {
    return myWindowWatcher;
  }

  public void setFullScreen(IdeFrameImpl frame, boolean fullScreen) {
    if (!isFullScreenSupportedInCurrentOS() || frame.isInFullScreen() == fullScreen) return;

    try {
      if (SystemInfo.isMacOSLion) {
        frame.getFrameDecorator().toggleFullScreen(fullScreen);
        return;
      }

      if (SystemInfo.isWindows) {
        GraphicsDevice device = ScreenUtil.getScreenDevice(frame.getBounds());
        if (device == null) return;
        try {
          frame.getRootPane().putClientProperty(ScreenUtil.DISPOSE_TEMPORARY, Boolean.TRUE);
          if (fullScreen) {
            frame.getRootPane().putClientProperty("oldBounds", frame.getBounds());
          }
          frame.dispose();
          frame.setUndecorated(fullScreen);
        } finally {
          if (fullScreen) {
            frame.setBounds(device.getDefaultConfiguration().getBounds());
          } else {
            Object o = frame.getRootPane().getClientProperty("oldBounds");
            if (o instanceof Rectangle) {
              frame.setBounds((Rectangle) o);
            }
          }
          frame.setVisible(true);
          frame.getRootPane().putClientProperty(ScreenUtil.DISPOSE_TEMPORARY, null);
        }
      }
    } finally {
      frame.storeFullScreenStateIfNeeded(fullScreen);
    }
  }
}
@SuppressWarnings({"SSBasedInspection"})
public class LaterInvocator {
  private static final Logger LOG =
      Logger.getInstance("#com.intellij.openapi.application.impl.LaterInvocator");
  private static final boolean DEBUG = LOG.isDebugEnabled();

  private static final Object LOCK = new Object();
  private static final IdeEventQueue ourEventQueue = IdeEventQueue.getInstance();
  private static final FrequentEventDetector ourFrequentEventDetector =
      new FrequentEventDetector(1009, 100);

  private LaterInvocator() {}

  private static class RunnableInfo {
    @NotNull private final Runnable runnable;
    @NotNull private final ModalityState modalityState;
    @NotNull private final Condition<?> expired;
    @NotNull private final ActionCallback callback;

    public RunnableInfo(
        @NotNull Runnable runnable,
        @NotNull ModalityState modalityState,
        @NotNull Condition<?> expired,
        @NotNull ActionCallback callback) {
      this.runnable = runnable;
      this.modalityState = modalityState;
      this.expired = expired;
      this.callback = callback;
    }

    @Override
    @NonNls
    public String toString() {
      return "[runnable: "
          + runnable
          + "; state="
          + modalityState
          + (expired.value(null) ? "; expired" : "")
          + "] ";
    }
  }

  private static final List<Object> ourModalEntities =
      ContainerUtil.createLockFreeCopyOnWriteList();
  private static final List<RunnableInfo> ourQueue =
      new ArrayList<RunnableInfo>(); // protected by LOCK
  private static volatile int ourQueueSkipCount = 0; // optimization
  private static final FlushQueue ourFlushQueueRunnable = new FlushQueue();

  private static final Stack<AWTEvent> ourEventStack = new Stack<AWTEvent>(); // guarded by RUN_LOCK

  private static final EventDispatcher<ModalityStateListener> ourModalityStateMulticaster =
      EventDispatcher.create(ModalityStateListener.class);

  private static final List<RunnableInfo> ourForcedFlushQueue = new ArrayList<RunnableInfo>();

  public static void addModalityStateListener(
      @NotNull ModalityStateListener listener, @NotNull Disposable parentDisposable) {
    if (!ourModalityStateMulticaster.getListeners().contains(listener)) {
      ourModalityStateMulticaster.addListener(listener, parentDisposable);
    }
  }

  @NotNull
  static ModalityStateEx modalityStateForWindow(@NotNull Window window) {
    int index = ourModalEntities.indexOf(window);
    if (index < 0) {
      Window owner = window.getOwner();
      if (owner == null) {
        return (ModalityStateEx) ApplicationManager.getApplication().getNoneModalityState();
      }
      ModalityStateEx ownerState = modalityStateForWindow(owner);
      if (window instanceof Dialog && ((Dialog) window).isModal()) {
        return ownerState.appendEntity(window);
      }
      return ownerState;
    }

    List<Object> result = new ArrayList<Object>();
    for (Object entity : ourModalEntities) {
      if (entity instanceof Window
          || entity instanceof ProgressIndicator && ((ProgressIndicator) entity).isModal()) {
        result.add(entity);
      }
    }
    return new ModalityStateEx(result.toArray());
  }

  @NotNull
  static ActionCallback invokeLater(@NotNull Runnable runnable, @NotNull Condition<?> expired) {
    ModalityState modalityState = ModalityState.defaultModalityState();
    return invokeLater(runnable, modalityState, expired);
  }

  @NotNull
  static ActionCallback invokeLater(
      @NotNull Runnable runnable, @NotNull ModalityState modalityState) {
    return invokeLater(runnable, modalityState, Conditions.FALSE);
  }

  @NotNull
  static ActionCallback invokeLater(
      @NotNull Runnable runnable,
      @NotNull ModalityState modalityState,
      @NotNull Condition<?> expired) {
    ourFrequentEventDetector.eventHappened();

    final ActionCallback callback = new ActionCallback();
    RunnableInfo runnableInfo = new RunnableInfo(runnable, modalityState, expired, callback);
    synchronized (LOCK) {
      ourQueue.add(runnableInfo);
    }
    requestFlush();
    return callback;
  }

  static void invokeAndWait(
      @NotNull final Runnable runnable, @NotNull ModalityState modalityState) {
    LOG.assertTrue(!isDispatchThread());

    final Semaphore semaphore = new Semaphore();
    semaphore.down();
    final Ref<Throwable> exception = Ref.create();
    Runnable runnable1 =
        new Runnable() {
          @Override
          public void run() {
            try {
              runnable.run();
            } catch (Throwable e) {
              exception.set(e);
            } finally {
              semaphore.up();
            }
          }

          @Override
          @NonNls
          public String toString() {
            return "InvokeAndWait[" + runnable + "]";
          }
        };
    invokeLater(runnable1, modalityState);
    semaphore.waitFor();
    if (!exception.isNull()) {
      throw new RuntimeException(exception.get());
    }
  }

  public static void enterModal(@NotNull Object modalEntity) {
    LOG.assertTrue(isDispatchThread(), "enterModal() should be invoked in event-dispatch thread");

    if (LOG.isDebugEnabled()) {
      LOG.debug("enterModal:" + modalEntity);
    }

    ourModalityStateMulticaster.getMulticaster().beforeModalityStateChanged(true);

    ourModalEntities.add(modalEntity);
  }

  public static void leaveModal(@NotNull Object modalEntity) {
    LOG.assertTrue(isDispatchThread(), "leaveModal() should be invoked in event-dispatch thread");

    if (LOG.isDebugEnabled()) {
      LOG.debug("leaveModal:" + modalEntity);
    }

    //noinspection StatementWithEmptyBody
    while (ourFlushQueueRunnable.runNextEvent()) ;

    ourModalityStateMulticaster.getMulticaster().beforeModalityStateChanged(false);

    boolean removed = ourModalEntities.remove(modalEntity);
    LOG.assertTrue(removed, modalEntity);
    cleanupQueueForModal(modalEntity);
    ourQueueSkipCount = 0;
    requestFlush();
  }

  private static void cleanupQueueForModal(@NotNull final Object modalEntity) {
    synchronized (LOCK) {
      for (Iterator<RunnableInfo> iterator = ourQueue.iterator(); iterator.hasNext(); ) {
        RunnableInfo runnableInfo = iterator.next();
        if (runnableInfo.modalityState instanceof ModalityStateEx) {
          ModalityStateEx stateEx = (ModalityStateEx) runnableInfo.modalityState;
          if (stateEx.contains(modalEntity)) {
            ourForcedFlushQueue.add(runnableInfo);
            iterator.remove();
          }
        }
      }
    }
  }

  @TestOnly
  static void leaveAllModals() {
    ourModalEntities.clear();
    ourQueueSkipCount = 0;
    requestFlush();
  }

  @NotNull
  public static Object[] getCurrentModalEntities() {
    ApplicationManager.getApplication().assertIsDispatchThread();
    // TODO!
    // LOG.assertTrue(IdeEventQueue.getInstance().isInInputEvent() || isInMyRunnable());

    return ArrayUtil.toObjectArray(ourModalEntities);
  }

  public static boolean isInModalContext() {
    LOG.assertTrue(isDispatchThread());
    return !ourModalEntities.isEmpty();
  }

  private static boolean isDispatchThread() {
    return ApplicationManager.getApplication().isDispatchThread();
  }

  private static void requestFlush() {
    if (FLUSHER_SCHEDULED.compareAndSet(false, true)) {
      SwingUtilities.invokeLater(ourFlushQueueRunnable);
    }
  }

  /**
   * There might be some requests in the queue, but ourFlushQueueRunnable might not be scheduled
   * yet. In these circumstances {@link EventQueue#peekEvent()} default implementation would return
   * null, and {@link UIUtil#dispatchAllInvocationEvents()} would stop processing events too early
   * and lead to spurious test failures.
   *
   * @see IdeEventQueue#peekEvent()
   */
  public static boolean ensureFlushRequested() {
    if (getNextEvent(false) != null) {
      SwingUtilities.invokeLater(ourFlushQueueRunnable);
      return true;
    }
    return false;
  }

  @Nullable
  private static RunnableInfo getNextEvent(boolean remove) {
    synchronized (LOCK) {
      if (!ourForcedFlushQueue.isEmpty()) {
        final RunnableInfo toRun =
            remove ? ourForcedFlushQueue.remove(0) : ourForcedFlushQueue.get(0);
        if (!toRun.expired.value(null)) {
          return toRun;
        } else {
          toRun.callback.setDone();
        }
      }

      ModalityState currentModality;
      if (ourModalEntities.isEmpty()) {
        Application application = ApplicationManager.getApplication();
        currentModality =
            application == null ? ModalityState.NON_MODAL : application.getNoneModalityState();
      } else {
        currentModality = new ModalityStateEx(ourModalEntities.toArray());
      }

      while (ourQueueSkipCount < ourQueue.size()) {
        RunnableInfo info = ourQueue.get(ourQueueSkipCount);

        if (info.expired.value(null)) {
          ourQueue.remove(ourQueueSkipCount);
          info.callback.setDone();
          continue;
        }

        if (!currentModality.dominates(info.modalityState)) {
          if (remove) {
            ourQueue.remove(ourQueueSkipCount);
          }
          return info;
        }
        ourQueueSkipCount++;
      }

      return null;
    }
  }

  private static final AtomicBoolean FLUSHER_SCHEDULED = new AtomicBoolean(false);
  private static final Object RUN_LOCK = new Object();

  private static class FlushQueue implements Runnable {
    @SuppressWarnings("FieldAccessedSynchronizedAndUnsynchronized")
    private RunnableInfo myLastInfo;

    @Override
    public void run() {
      FLUSHER_SCHEDULED.set(false);
      if (runNextEvent()) {
        requestFlush();
      }
    }

    private boolean runNextEvent() {
      final RunnableInfo lastInfo = getNextEvent(true);
      myLastInfo = lastInfo;

      if (lastInfo != null) {
        synchronized (RUN_LOCK) { // necessary only because of switching to our own event queue
          AWTEvent event = ourEventQueue.getTrueCurrentEvent();
          ourEventStack.push(event);
          int stackSize = ourEventStack.size();

          try {
            lastInfo.runnable.run();
            lastInfo.callback.setDone();
          } catch (ProcessCanceledException ignored) {
          } catch (Throwable t) {
            LOG.error(t);
          } finally {
            LOG.assertTrue(ourEventStack.size() == stackSize);
            ourEventStack.pop();

            if (!DEBUG) myLastInfo = null;
          }
        }
      }
      return lastInfo != null;
    }

    @Override
    public String toString() {
      return "LaterInvocator.FlushQueue" + (myLastInfo == null ? "" : " lastInfo=" + myLastInfo);
    }
  }

  @TestOnly
  public static List<RunnableInfo> getLaterInvocatorQueue() {
    synchronized (LOCK) {
      return ContainerUtil.newArrayList(ourQueue);
    }
  }
}
Beispiel #14
0
public class SModelRootEntry implements ModelRootEntry {

  private SModelRoot myModelRoot;
  private EventDispatcher<ModelRootEntryListener> myEventDispatcher =
      EventDispatcher.create(ModelRootEntryListener.class);

  @Override
  public ModelRoot getModelRoot() {
    return myModelRoot;
  }

  @Override
  public void setModelRoot(ModelRoot modelRoot) {
    if (!(modelRoot instanceof SModelRoot))
      throw new ClassCastException(
          "Can't convert "
              + modelRoot.getClass().getCanonicalName()
              + " to "
              + SModelRoot.class.getCanonicalName());
    myModelRoot = (SModelRoot) modelRoot;
  }

  @Override
  public String getDetailsText() {
    final StringBuilder messageText = new StringBuilder();
    messageText.append("<html>");
    messageText.append("Type : ").append(myModelRoot.getType()).append("<br>");
    messageText
        .append("Manager : ")
        .append(
            myModelRoot.getModelRoot().getManager() != null
                ? myModelRoot.getModelRoot().getManager().getClassName()
                : "Default")
        .append("<br>");
    messageText.append("Path : ").append(myModelRoot.getPath()).append("<br>");
    return messageText.toString();
  }

  @Override
  public boolean isValid() {
    String path = myModelRoot.getPath();
    return (new java.io.File(path != null ? path : "")).exists();
  }

  @Override
  public ModelRootEntryEditor getEditor() {
    return new SModelRootEntryEditor(myModelRoot);
  }

  @Override
  public void addModelRootEntryListener(ModelRootEntryListener listener) {
    myEventDispatcher.addListener(listener);
  }

  private class SModelRootEntryEditor implements ModelRootEntryEditor {
    private JPanel myTreePanel;
    private ComboBox myComboBox;
    private SModelRoot myModelRoot;

    public SModelRootEntryEditor(SModelRoot modelRoot) {
      myModelRoot = modelRoot;
    }

    @Override
    public JComponent createComponent() {
      JPanel panel = new JPanel(new GridLayoutManager(2, 1));

      List<ModelRootManager> managers = ManagerTableCellEditor.getManagers();
      managers.remove(null);
      final ModelRootManager empty = new ModelRootManager("", "Default");
      managers.add(empty);
      myComboBox = new ComboBox(managers.toArray(), 10);
      myComboBox.setRenderer(
          new DefaultListCellRenderer() {
            @Override
            public Component getListCellRendererComponent(
                JList list, Object value, int index, boolean selected, boolean focus) {
              ModelRootManager manager = ((ModelRootManager) value);
              String managerName = NameUtil.shortNameFromLongName(manager.getClassName());
              return super.getListCellRendererComponent(list, managerName, index, selected, focus);
            }
          });
      panel.add(
          myComboBox,
          new GridConstraints(
              0,
              0,
              1,
              1,
              GridConstraints.ANCHOR_NORTHWEST,
              GridConstraints.FILL_HORIZONTAL,
              GridConstraints.SIZEPOLICY_CAN_GROW | GridConstraints.SIZEPOLICY_CAN_SHRINK,
              GridConstraints.SIZEPOLICY_FIXED,
              null,
              null,
              null));
      myComboBox.setSelectedItem(
          myModelRoot.getModelRoot().getManager() == null
              ? empty
              : myModelRoot.getModelRoot().getManager());
      myComboBox.addItemListener(
          new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
              if (e.getStateChange() == ItemEvent.SELECTED) {
                ModelRootManager manager = (ModelRootManager) e.getItem();
                myModelRoot.getModelRoot().setManager(manager.equals(empty) ? null : manager);
                myEventDispatcher.getMulticaster().fireDataChanged();
              }
            }
          });

      myTreePanel = new JPanel(new BorderLayout());
      updateTree();
      final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myTreePanel);
      panel.add(
          scrollPane,
          new GridConstraints(
              1,
              0,
              1,
              1,
              GridConstraints.ANCHOR_CENTER,
              GridConstraints.FILL_BOTH,
              GridConstraints.SIZEPOLICY_CAN_GROW | GridConstraints.SIZEPOLICY_CAN_SHRINK,
              GridConstraints.SIZEPOLICY_CAN_GROW | GridConstraints.SIZEPOLICY_CAN_SHRINK,
              null,
              null,
              null));

      return panel;
    }

    private void updateTree() {
      FileSystemTreeImpl fileSystemTree =
          new FileSystemTreeImpl(
              null, new FileChooserDescriptor(true, true, true, true, true, false));
      AbstractTreeUi ui = fileSystemTree.getTreeBuilder().getUi();

      String path = myModelRoot.getPath() == null ? "" : myModelRoot.getPath();
      VirtualFile virtualFile =
          VirtualFileManager.getInstance()
              .findFileByUrl(VirtualFileManager.constructUrl("file", path));
      if (myModelRoot.getModule() != null && (virtualFile == null || path.isEmpty())) {
        if (myModelRoot.getModule() instanceof AbstractModule) {
          virtualFile =
              VirtualFileManager.getInstance()
                  .findFileByUrl(
                      VirtualFileManager.constructUrl(
                          "file",
                          ((AbstractModule) myModelRoot.getModule())
                              .getModuleSourceDir()
                              .getPath()));
        }
      }

      if (virtualFile != null) fileSystemTree.select(virtualFile, null);

      fileSystemTree.addListener(
          new Listener() {
            @Override
            public void selectionChanged(List<VirtualFile> selection) {
              if (selection.size() > 0) {
                myModelRoot.setPath(FileUtil.getCanonicalPath(selection.get(0).getPath()));
                myEventDispatcher.getMulticaster().fireDataChanged();
              }
            }
          },
          new Disposable() {
            @Override
            public void dispose() {}
          });

      myTreePanel.removeAll();
      myTreePanel.add(ui.getTree(), BorderLayout.CENTER);
      ui.scrollSelectionToVisible(null, true);
    }
  }
}
/** The handler that is based on per-line processing of the text. */
public class LineHandler extends TextHandler {
  /** the partial line from stdout stream */
  private final StringBuilder myStdoutLine = new StringBuilder();
  /** the partial line from stderr stream */
  private final StringBuilder myStderrLine = new StringBuilder();
  /** Line listeners */
  private final EventDispatcher<LineHandlerListener> myLineListeners =
      EventDispatcher.create(LineHandlerListener.class);

  /**
   * A constructor
   *
   * @param project a project
   * @param directory a process directory
   * @param command a command to execute
   */
  @SuppressWarnings({"WeakerAccess"})
  public LineHandler(@NotNull Project project, @NotNull File directory, @NotNull Command command) {
    super(project, directory, command);
  }

  /**
   * A constructor
   *
   * @param project a project
   * @param vcsRoot a process directory
   * @param command a command to execute
   */
  public LineHandler(
      @NotNull final Project project,
      @NotNull final VirtualFile vcsRoot,
      @NotNull final Command command) {
    super(project, vcsRoot, command);
  }

  /** {@inheritDoc} */
  protected void processTerminated(final int exitCode) {
    // force newline
    if (myStdoutLine.length() != 0) {
      onTextAvailable("\n\r", ProcessOutputTypes.STDOUT);
    } else if (!isStderrSuppressed() && myStderrLine.length() != 0) {
      onTextAvailable("\n\r", ProcessOutputTypes.STDERR);
    }
  }

  /**
   * Add listener
   *
   * @param listener a listener to add
   */
  public void addLineListener(LineHandlerListener listener) {
    super.addListener(listener);
    myLineListeners.addListener(listener);
  }

  /** {@inheritDoc} */
  protected void onTextAvailable(final String text, final Key outputType) {
    Iterator<String> lines = splitText(text).iterator();
    if (ProcessOutputTypes.STDOUT == outputType) {
      notifyLines(outputType, lines, myStdoutLine);
    } else if (ProcessOutputTypes.STDERR == outputType) {
      notifyLines(outputType, lines, myStderrLine);
    }
  }

  /**
   * Notify listeners for each complete line. Note that in the case of stderr, the last line is
   * saved.
   *
   * @param outputType output type
   * @param lines line iterator
   * @param lineBuilder a line builder
   */
  private void notifyLines(
      final Key outputType, final Iterator<String> lines, final StringBuilder lineBuilder) {
    /*
        while(lines.hasNext()) {
          String line = lines.next();
          //lineBuilder.append(line);
          notifyLine(line, outputType);
        }
    */
    if (!lines.hasNext()) return;
    if (lineBuilder.length() > 0) {
      lineBuilder.append(lines.next());
      if (lines.hasNext()) {
        // line is complete
        final String line = lineBuilder.toString();
        notifyLine(line, outputType);
        lineBuilder.setLength(0);
      }
    }
    while (true) {
      String line = lines.next();
      if (lines.hasNext()) {
        notifyLine(line, outputType);
      } else {
        if (line.length() > 0) {
          lineBuilder.append(line);
        }
        break;
      }
    }
  }

  /**
   * Notify single line
   *
   * @param line a line to notify
   * @param outputType output type
   */
  private void notifyLine(final String line, final Key outputType) {
    String trimmed = trimLineSeparator(line);
    // if line ends with return, then it is a progress line, ignore it
    if (myVcs != null && !"\r".equals(line.substring(trimmed.length()))) {
      if (outputType == ProcessOutputTypes.STDOUT && !isStdoutSuppressed()) {
        myVcs.showMessages(trimmed);
      } else if (outputType == ProcessOutputTypes.STDERR && !isStderrSuppressed()) {
        myVcs.showErrorMessages(trimmed);
      }
    }
    myLineListeners.getMulticaster().onLineAvailable(trimmed, outputType);
  }

  /**
   * Trim line separator from new line if it presents
   *
   * @param line a line to process
   * @return a trimmed line
   */
  private static String trimLineSeparator(String line) {
    int n = line.length();
    if (n == 0) {
      return line;
    }
    char ch = line.charAt(n - 1);
    if (ch == '\n' || ch == '\r') {
      n--;
    } else {
      return line;
    }
    if (n > 0) {
      char ch2 = line.charAt(n - 1);
      if ((ch2 == '\n' || ch2 == '\r') && ch2 != ch) {
        n--;
      }
    }
    return line.substring(0, n);
  }

  /**
   * Split text into lines. New line characters are treated as separators. So if the text starts
   * with newline, empty string will be the first element, if the text ends with new line, the empty
   * string will be the last element. The returned lines will be substrings of the text argument.
   * The new line characters are included into the line text.
   *
   * @param text a text to split
   * @return a list of elements (note that there are always at least one element)
   */
  private static List<String> splitText(String text) {
    int startLine = 0;
    int i = 0;
    int n = text.length();
    ArrayList<String> rc = new ArrayList<String>();
    while (i < n) {
      switch (text.charAt(i)) {
        case '\n':
          i++;
          if (i < n && text.charAt(i) == '\r') {
            i++;
          }
          rc.add(text.substring(startLine, i));
          startLine = i;
          break;
        case '\r':
          i++;
          if (i < n && text.charAt(i) == '\n') {
            i++;
          }
          rc.add(text.substring(startLine, i));
          startLine = i;
          break;
        default:
          i++;
      }
    }
    if (startLine == text.length()) {
      // still add empty line or previous line wouldn't be treated as completed
      rc.add("");
    } else {
      rc.add(text.substring(startLine, i));
    }
    return rc;
  }
}
@SuppressWarnings({"AssignmentToStaticFieldFromInstanceMethod"})
public class ApplicationImpl extends ComponentManagerImpl implements ApplicationEx {
  private static final Logger LOG =
      Logger.getInstance("#com.intellij.application.impl.ApplicationImpl");
  private final ModalityState MODALITY_STATE_NONE = ModalityState.NON_MODAL;
  private final ModalityInvokator myInvokator = new ModalityInvokatorImpl();

  private final EventDispatcher<ApplicationListener> myDispatcher =
      EventDispatcher.create(ApplicationListener.class);

  private boolean myTestModeFlag;
  private final boolean myHeadlessMode;
  private final boolean myCommandLineMode;

  private final boolean myIsInternal;
  private final String myName;

  private final ReentrantWriterPreferenceReadWriteLock myActionsLock =
      new ReentrantWriterPreferenceReadWriteLock();
  private final Stack<Runnable> myWriteActionsStack =
      new Stack<Runnable>(); // accessed from EDT only, no need to sync

  private volatile Runnable myExceptionalThreadWithReadAccessRunnable;

  private int myInEditorPaintCounter = 0;
  private long myStartTime = 0;
  private boolean myDoNotSave = false;
  private volatile boolean myDisposeInProgress = false;

  private int myRestartCode = 0;
  private volatile int myExitCode = 0;

  private final AtomicBoolean mySaveSettingsIsInProgress = new AtomicBoolean(false);

  @SuppressWarnings({"UseOfArchaicSystemPropertyAccessors"})
  private static final int ourDumpThreadsOnLongWriteActionWaiting =
      Integer.getInteger(System.getProperty("dump.threads.on.long.write.action.waiting"), 0);

  private final ExecutorService ourThreadExecutorsService =
      new ThreadPoolExecutor(
          3,
          Integer.MAX_VALUE,
          5 * 60L,
          TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(),
          new ThreadFactory() {
            int i;

            public Thread newThread(Runnable r) {
              final Thread thread =
                  new Thread(r, "ApplicationImpl pooled thread " + i++) {
                    public void interrupt() {
                      if (LOG.isDebugEnabled()) {
                        LOG.debug("Interrupted worker, will remove from pool");
                      }
                      super.interrupt();
                    }

                    public void run() {
                      try {
                        super.run();
                      } catch (Throwable t) {
                        if (LOG.isDebugEnabled()) {
                          LOG.debug("Worker exits due to exception", t);
                        }
                      }
                    }
                  };
              thread.setPriority(Thread.NORM_PRIORITY - 1);
              return thread;
            }
          });
  private boolean myIsFiringLoadingEvent = false;
  @NonNls private static final String WAS_EVER_SHOWN = "was.ever.shown";

  private Boolean myActive;

  private static final ThreadLocal<Integer> ourEdtSafe = new ThreadLocal<Integer>();

  protected void boostrapPicoContainer() {
    super.boostrapPicoContainer();
    getPicoContainer()
        .registerComponentImplementation(
            IComponentStore.class, StoresFactory.getApplicationStoreClass());
    getPicoContainer().registerComponentImplementation(ApplicationPathMacroManager.class);
  }

  @Override
  @NotNull
  public synchronized IApplicationStore getStateStore() {
    return (IApplicationStore) super.getStateStore();
  }

  public ApplicationImpl(
      boolean isInternal,
      boolean isUnitTestMode,
      boolean isHeadless,
      boolean isCommandLine,
      @NotNull String appName) {
    super(null);

    getPicoContainer().registerComponentInstance(Application.class, this);

    CommonBundle.assertKeyIsFound = isUnitTestMode;

    if ((isInternal || isUnitTestMode)
        && !Comparing.equal("off", System.getProperty("idea.disposer.debug"))) {
      Disposer.setDebugMode(true);
    }
    myStartTime = System.currentTimeMillis();
    myName = appName;
    ApplicationManagerEx.setApplication(this);

    PluginsFacade.INSTANCE =
        new PluginsFacade() {
          public IdeaPluginDescriptor getPlugin(PluginId id) {
            return PluginManager.getPlugin(id);
          }

          public IdeaPluginDescriptor[] getPlugins() {
            return PluginManager.getPlugins();
          }
        };

    if (!isUnitTestMode && !isHeadless) {
      Toolkit.getDefaultToolkit().getSystemEventQueue().push(IdeEventQueue.getInstance());
      if (Patches.SUN_BUG_ID_6209673) {
        RepaintManager.setCurrentManager(new IdeRepaintManager());
      }
      IconLoader.activate();
    }

    myIsInternal = isInternal;
    myTestModeFlag = isUnitTestMode;
    myHeadlessMode = isHeadless;
    myCommandLineMode = isCommandLine;

    loadApplicationComponents();

    if (myTestModeFlag) {
      registerShutdownHook();
    }

    if (!isUnitTestMode && !isHeadless) {
      Disposer.register(this, Disposer.newDisposable(), "ui");

      StartupUtil.addExternalInstanceListener(
          new Consumer<List<String>>() {
            @Override
            public void consume(final List<String> args) {
              invokeLater(
                  new Runnable() {
                    @Override
                    public void run() {
                      final Project project = CommandLineProcessor.processExternalCommandLine(args);
                      final IdeFrame frame;
                      if (project != null) {
                        frame = WindowManager.getInstance().getIdeFrame(project);
                      } else {
                        frame = WindowManager.getInstance().getAllFrames()[0];
                      }
                      ((IdeFrameImpl) frame).requestFocus();
                    }
                  });
            }
          });
    }

    final String s = System.getProperty("jb.restart.code");
    if (s != null) {
      try {
        myRestartCode = Integer.parseInt(s);
      } catch (NumberFormatException ignore) {
      }
    }
  }

  private void registerShutdownHook() {
    ShutDownTracker
        .getInstance(); // Necessary to avoid creating an instance while already shutting down.

    ShutDownTracker.getInstance()
        .registerShutdownTask(
            new Runnable() {
              public void run() {
                if (isDisposed() || isDisposeInProgress()) {
                  return;
                }
                try {
                  SwingUtilities.invokeAndWait(
                      new Runnable() {
                        public void run() {
                          ApplicationManagerEx.setApplication(ApplicationImpl.this);
                          try {
                            saveAll();
                          } finally {
                            disposeSelf();
                          }
                        }
                      });
                } catch (InterruptedException e) {
                  LOG.error(e);
                } catch (InvocationTargetException e) {
                  LOG.error(e);
                }
              }
            });
  }

  private boolean disposeSelf() {
    myDisposeInProgress = true;
    final CommandProcessor commandProcessor = CommandProcessor.getInstance();
    final Ref<Boolean> canClose = new Ref<Boolean>(Boolean.TRUE);
    for (final Project project : ProjectManagerEx.getInstanceEx().getOpenProjects()) {
      try {
        commandProcessor.executeCommand(
            project,
            new Runnable() {
              public void run() {
                canClose.set(ProjectUtil.closeAndDispose(project));
              }
            },
            ApplicationBundle.message("command.exit"),
            null);
      } catch (Throwable e) {
        LOG.error(e);
      }
      if (!canClose.get()) {
        myDisposeInProgress = false;
        return false;
      }
    }
    Disposer.dispose(this);

    Disposer.assertIsEmpty();
    return true;
  }

  @NotNull
  public String getName() {
    return myName;
  }

  public boolean holdsReadLock() {
    return myActionsLock.isReadLockAcquired();
  }

  @Override
  protected void handleInitComponentError(
      final Throwable ex, final boolean fatal, final String componentClassName) {
    if (PluginManager.isPluginClass(componentClassName)) {
      LOG.error(ex);
      PluginId pluginId = PluginManager.getPluginByClassName(componentClassName);
      @NonNls
      final String errorMessage =
          "Plugin "
              + pluginId.getIdString()
              + " failed to initialize and will be disabled:\n"
              + ex.getMessage()
              + "\nPlease restart "
              + ApplicationNamesInfo.getInstance().getFullProductName()
              + ".";
      PluginManager.disablePlugin(pluginId.getIdString());
      if (!myHeadlessMode) {
        JOptionPane.showMessageDialog(null, errorMessage);
      } else {
        //noinspection UseOfSystemOutOrSystemErr
        System.out.println(errorMessage);
      }
      System.exit(1);
    } else if (fatal) {
      LOG.error(ex);
      @NonNls
      final String errorMessage =
          "Fatal error initializing class "
              + componentClassName
              + ":\n"
              + ex.toString()
              + "\nComplete error stacktrace was written to idea.log";
      if (!myHeadlessMode) {
        JOptionPane.showMessageDialog(null, errorMessage);
      } else {
        //noinspection UseOfSystemOutOrSystemErr
        System.out.println(errorMessage);
      }
    }
    super.handleInitComponentError(ex, fatal, componentClassName);
  }

  private void loadApplicationComponents() {
    final IdeaPluginDescriptor[] plugins = PluginManager.getPlugins();
    for (IdeaPluginDescriptor plugin : plugins) {
      if (PluginManager.shouldSkipPlugin(plugin)) continue;
      loadComponentsConfiguration(plugin.getAppComponents(), plugin, false);
    }
  }

  protected MutablePicoContainer createPicoContainer() {
    return Extensions.getRootArea().getPicoContainer();
  }

  public boolean isInternal() {
    return myIsInternal;
  }

  public boolean isUnitTestMode() {
    return myTestModeFlag;
  }

  public void setUnitTestMode(boolean testModeFlag) {
    myTestModeFlag = testModeFlag;
  }

  public boolean isHeadlessEnvironment() {
    return myHeadlessMode;
  }

  public boolean isCommandLine() {
    return myCommandLineMode;
  }

  public IdeaPluginDescriptor getPlugin(PluginId id) {
    return PluginsFacade.INSTANCE.getPlugin(id);
  }

  public IdeaPluginDescriptor[] getPlugins() {
    return PluginsFacade.INSTANCE.getPlugins();
  }

  public Future<?> executeOnPooledThread(@NotNull final Runnable action) {
    return ourThreadExecutorsService.submit(
        new Runnable() {
          public void run() {
            try {
              action.run();
            } catch (ProcessCanceledException e) {
              // ignore
            } catch (Throwable t) {
              LOG.error(t);
            } finally {
              Thread.interrupted(); // reset interrupted status
            }
          }
        });
  }

  @Override
  public <T> Future<T> executeOnPooledThread(@NotNull final Callable<T> action) {
    return ourThreadExecutorsService.submit(
        new Callable<T>() {
          public T call() {
            try {
              return action.call();
            } catch (ProcessCanceledException e) {
              // ignore
            } catch (Throwable t) {
              LOG.error(t);
            } finally {
              Thread.interrupted(); // reset interrupted status
            }
            return null;
          }
        });
  }

  private static Thread ourDispatchThread = null;

  public boolean isDispatchThread() {
    return EventQueue.isDispatchThread();
  }

  @NotNull
  public ModalityInvokator getInvokator() {
    return myInvokator;
  }

  public void invokeLater(@NotNull final Runnable runnable) {
    myInvokator.invokeLater(runnable);
  }

  public void invokeLater(@NotNull final Runnable runnable, @NotNull final Condition expired) {
    myInvokator.invokeLater(runnable, expired);
  }

  public void invokeLater(@NotNull final Runnable runnable, @NotNull final ModalityState state) {
    myInvokator.invokeLater(runnable, state);
  }

  public void invokeLater(
      @NotNull final Runnable runnable,
      @NotNull final ModalityState state,
      @NotNull final Condition expired) {
    myInvokator.invokeLater(runnable, state, expired);
  }

  public void load(String path) throws IOException, InvalidDataException {
    getStateStore().setOptionsPath(path);
    getStateStore().setConfigPath(PathManager.getConfigPath());
    myIsFiringLoadingEvent = true;
    try {
      fireBeforeApplicationLoaded();
    } finally {
      myIsFiringLoadingEvent = false;
    }

    loadComponentRoamingTypes();

    try {
      getStateStore().load();
    } catch (StateStorage.StateStorageException e) {
      throw new IOException(e.getMessage());
    }
  }

  @Override
  protected <T> T getComponentFromContainer(final Class<T> interfaceClass) {
    if (myIsFiringLoadingEvent) {
      return null;
    }
    return super.getComponentFromContainer(interfaceClass);
  }

  private static void loadComponentRoamingTypes() {
    ExtensionPoint<RoamingTypeExtensionPointBean> point =
        Extensions.getRootArea().getExtensionPoint("com.intellij.ComponentRoamingType");
    final RoamingTypeExtensionPointBean[] componentRoamingTypes = point.getExtensions();

    for (RoamingTypeExtensionPointBean object : componentRoamingTypes) {

      assert object.componentName != null;
      assert object.roamingType != null;

      final RoamingType type = RoamingType.valueOf(object.roamingType);

      assert type != null;

      ComponentRoamingManager.getInstance().setRoamingType(object.componentName, type);
    }
  }

  private void fireBeforeApplicationLoaded() {
    ExtensionPoint<ApplicationLoadListener> point =
        Extensions.getRootArea().getExtensionPoint("com.intellij.ApplicationLoadListener");
    final ApplicationLoadListener[] objects = point.getExtensions();
    for (ApplicationLoadListener object : objects) {
      object.beforeApplicationLoaded(this);
    }
  }

  public void dispose() {
    fireApplicationExiting();
    disposeComponents();

    ourThreadExecutorsService.shutdownNow();
    super.dispose();
  }

  private final Object lock = new Object();

  private void makeChangesVisibleToEDT() {
    synchronized (lock) {
      lock.hashCode();
    }
  }

  public boolean runProcessWithProgressSynchronously(
      @NotNull final Runnable process,
      @NotNull String progressTitle,
      boolean canBeCanceled,
      Project project) {
    return runProcessWithProgressSynchronously(
        process, progressTitle, canBeCanceled, project, null);
  }

  public boolean runProcessWithProgressSynchronously(
      @NotNull final Runnable process,
      @NotNull final String progressTitle,
      final boolean canBeCanceled,
      @Nullable final Project project,
      final JComponent parentComponent) {
    return runProcessWithProgressSynchronously(
        process, progressTitle, canBeCanceled, project, parentComponent, null);
  }

  public boolean runProcessWithProgressSynchronously(
      @NotNull final Runnable process,
      @NotNull final String progressTitle,
      final boolean canBeCanceled,
      @Nullable final Project project,
      final JComponent parentComponent,
      final String cancelText) {
    assertIsDispatchThread();

    if (myExceptionalThreadWithReadAccessRunnable != null
        || ApplicationManager.getApplication().isUnitTestMode()
        || ApplicationManager.getApplication().isHeadlessEnvironment()) {
      try {
        ProgressManager.getInstance().runProcess(process, new EmptyProgressIndicator());
      } catch (ProcessCanceledException e) {
        // ok to ignore.
        return false;
      }
      return true;
    }

    final ProgressWindow progress =
        new ProgressWindow(canBeCanceled, false, project, parentComponent, cancelText);
    progress.setTitle(progressTitle);

    try {
      myExceptionalThreadWithReadAccessRunnable = process;
      final boolean[] threadStarted = {false};
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              if (myExceptionalThreadWithReadAccessRunnable != process) {
                LOG.error(
                    "myExceptionalThreadWithReadAccessRunnable != process, process = "
                        + myExceptionalThreadWithReadAccessRunnable);
              }

              executeOnPooledThread(
                  new Runnable() {
                    public void run() {
                      if (myExceptionalThreadWithReadAccessRunnable != process) {
                        LOG.error(
                            "myExceptionalThreadWithReadAccessRunnable != process, process = "
                                + myExceptionalThreadWithReadAccessRunnable);
                      }

                      final boolean old = setExceptionalThreadWithReadAccessFlag(true);
                      LOG.assertTrue(isReadAccessAllowed());
                      try {
                        ProgressManager.getInstance().runProcess(process, progress);
                      } catch (ProcessCanceledException e) {
                        progress.cancel();
                        // ok to ignore.
                      } catch (RuntimeException e) {
                        progress.cancel();
                        throw e;
                      } finally {
                        setExceptionalThreadWithReadAccessFlag(old);
                        makeChangesVisibleToEDT();
                      }
                    }
                  });
              threadStarted[0] = true;
            }
          });

      progress.startBlocking();

      LOG.assertTrue(threadStarted[0]);
      LOG.assertTrue(!progress.isRunning());
    } finally {
      myExceptionalThreadWithReadAccessRunnable = null;
      makeChangesVisibleToEDT();
    }

    return !progress.isCanceled();
  }

  public boolean isInModalProgressThread() {
    if (myExceptionalThreadWithReadAccessRunnable == null || !isExceptionalThreadWithReadAccess()) {
      return false;
    }
    ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator();
    return progressIndicator.isModal()
        && ((ProgressIndicatorEx) progressIndicator).isModalityEntered();
  }

  public void invokeAndWait(@NotNull Runnable runnable, @NotNull ModalityState modalityState) {
    if (isDispatchThread()) {
      LOG.error("invokeAndWait must not be called from event queue thread");
      runnable.run();
      return;
    }

    if (isExceptionalThreadWithReadAccess()) { // OK if we're in exceptional thread.
      LaterInvocator.invokeAndWait(runnable, modalityState);
      return;
    }

    if (myActionsLock.isReadLockAcquired()) {
      LOG.error("Calling invokeAndWait from read-action leads to possible deadlock.");
    }

    LaterInvocator.invokeAndWait(runnable, modalityState);
  }

  @NotNull
  public ModalityState getCurrentModalityState() {
    Object[] entities = LaterInvocator.getCurrentModalEntities();
    return entities.length > 0 ? new ModalityStateEx(entities) : getNoneModalityState();
  }

  @NotNull
  public ModalityState getModalityStateForComponent(@NotNull Component c) {
    Window window = c instanceof Window ? (Window) c : SwingUtilities.windowForComponent(c);
    if (window == null) return getNoneModalityState(); // ?
    return LaterInvocator.modalityStateForWindow(window);
  }

  @NotNull
  public ModalityState getDefaultModalityState() {
    if (EventQueue.isDispatchThread()) {
      return getCurrentModalityState();
    } else {
      ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator();
      return progress == null ? getNoneModalityState() : progress.getModalityState();
    }
  }

  @NotNull
  public ModalityState getNoneModalityState() {
    return MODALITY_STATE_NONE;
  }

  public long getStartTime() {
    return myStartTime;
  }

  public long getIdleTime() {
    return IdeEventQueue.getInstance().getIdleTime();
  }

  public void exit() {
    exit(false);
  }

  public void exit(final boolean force) {
    if (!force && getDefaultModalityState() != ModalityState.NON_MODAL) {
      return;
    }

    Runnable runnable =
        new Runnable() {
          public void run() {
            if (!force) {
              if (!showConfirmation()) {
                saveAll();
                myExitCode = 0;
                return;
              }
            }

            getMessageBus().syncPublisher(AppLifecycleListener.TOPIC).appClosing();

            FileDocumentManager.getInstance().saveAllDocuments();

            saveSettings();

            if (!canExit()) {
              myExitCode = 0;
              return;
            }

            final boolean success = disposeSelf();
            if (!success || isUnitTestMode()) {
              myExitCode = 0;
              return;
            }

            System.exit(myExitCode);
          }
        };

    if (!isDispatchThread()) {
      invokeLater(runnable, ModalityState.NON_MODAL);
    } else {
      runnable.run();
    }
  }

  private static boolean showConfirmation() {
    final boolean hasUnsafeBgTasks = ProgressManager.getInstance().hasUnsafeProgressIndicator();
    final ConfirmExitDialog confirmExitDialog = new ConfirmExitDialog(hasUnsafeBgTasks);
    if (confirmExitDialog.isToBeShown()) {
      confirmExitDialog.show();
      if (!confirmExitDialog.isOK()) {
        return false;
      }
    } else {
      confirmExitDialog.close(DialogWrapper.OK_EXIT_CODE);
    }
    return true;
  }

  private boolean canExit() {
    for (ApplicationListener applicationListener : myDispatcher.getListeners()) {
      if (!applicationListener.canExitApplication()) {
        return false;
      }
    }

    ProjectManagerEx projectManager = (ProjectManagerEx) ProjectManager.getInstance();
    Project[] projects = projectManager.getOpenProjects();
    for (Project project : projects) {
      if (!projectManager.canClose(project)) {
        return false;
      }
    }

    return true;
  }

  public void runReadAction(@NotNull final Runnable action) {
    /**
     * if we are inside read action, do not try to acquire read lock again since it will deadlock if
     * there is a pending writeAction see {@link
     * com.intellij.util.concurrency.ReentrantWriterPreferenceReadWriteLock#allowReader()}
     */
    if (isReadAccessAllowed()) {
      action.run();
      return;
    }

    LOG.assertTrue(
        !Thread.holdsLock(PsiLock.LOCK),
        "Thread must not hold PsiLock while performing readAction");
    try {
      myActionsLock.readLock().acquire();
    } catch (InterruptedException e) {
      throw new RuntimeInterruptedException(e);
    }

    try {
      action.run();
    } finally {
      myActionsLock.readLock().release();
    }
  }

  private static final ThreadLocal<Boolean> exceptionalThreadWithReadAccessFlag =
      new ThreadLocal<Boolean>();

  private static boolean isExceptionalThreadWithReadAccess() {
    Boolean flag = exceptionalThreadWithReadAccessFlag.get();
    return flag == Boolean.TRUE;
  }

  public static boolean setExceptionalThreadWithReadAccessFlag(boolean flag) {
    boolean old = isExceptionalThreadWithReadAccess();
    if (flag) {
      exceptionalThreadWithReadAccessFlag.set(Boolean.TRUE);
    } else {
      exceptionalThreadWithReadAccessFlag.remove();
    }
    return old;
  }

  public <T> T runReadAction(@NotNull final Computable<T> computation) {
    final Ref<T> ref = Ref.create(null);
    runReadAction(
        new Runnable() {
          public void run() {
            ref.set(computation.compute());
          }
        });
    return ref.get();
  }

  public void runWriteAction(@NotNull final Runnable action) {
    assertCanRunWriteAction();

    ActivityTracker.getInstance().inc();
    fireBeforeWriteActionStart(action);
    final AtomicBoolean stopped = new AtomicBoolean(false);

    if (ourDumpThreadsOnLongWriteActionWaiting > 0) {
      executeOnPooledThread(
          new Runnable() {
            @Override
            public void run() {
              while (!stopped.get()) {
                try {
                  Thread.sleep(ourDumpThreadsOnLongWriteActionWaiting);
                  if (!stopped.get()) {
                    PerformanceWatcher.getInstance().dumpThreads(true);
                  }
                } catch (InterruptedException ignored) {
                }
              }
            }
          });
    }

    LOG.assertTrue(
        myActionsLock.isWriteLockAcquired(Thread.currentThread())
            || !Thread.holdsLock(PsiLock.LOCK),
        "Thread must not hold PsiLock while performing writeAction");
    try {
      myActionsLock.writeLock().acquire();
    } catch (InterruptedException e) {
      throw new RuntimeInterruptedException(e);
    }
    stopped.set(true);

    try {
      myWriteActionsStack.push(action);

      fireWriteActionStarted(action);

      action.run();
    } finally {
      try {
        fireWriteActionFinished(action);

        myWriteActionsStack.pop();
      } finally {
        myActionsLock.writeLock().release();
      }
    }
  }

  public <T> T runWriteAction(@NotNull final Computable<T> computation) {
    final Ref<T> ref = Ref.create(null);
    runWriteAction(
        new Runnable() {
          public void run() {
            ref.set(computation.compute());
          }
        });
    return ref.get();
  }

  public <T> T getCurrentWriteAction(@Nullable Class<T> actionClass) {
    assertCanRunWriteAction();

    for (int i = myWriteActionsStack.size() - 1; i >= 0; i--) {
      Runnable action = myWriteActionsStack.get(i);
      if (actionClass == null || ReflectionCache.isAssignable(actionClass, action.getClass()))
        return (T) action;
    }
    return null;
  }

  public void assertReadAccessAllowed() {
    if (myHeadlessMode) return;
    if (!isReadAccessAllowed()) {
      LOG.error(
          "Read access is allowed from event dispatch thread or inside read-action only (see com.intellij.openapi.application.Application.runReadAction())",
          "Current thread: " + describe(Thread.currentThread()),
          "Our dispatch thread:" + describe(ourDispatchThread),
          "SystemEventQueueThread: " + describe(getEventQueueThread()));
    }
  }

  @NonNls
  private static String describe(Thread o) {
    if (o == null) return "null";
    return o.toString() + " " + System.identityHashCode(o);
  }

  @Nullable
  private static Thread getEventQueueThread() {
    EventQueue eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
    try {
      Method method = EventQueue.class.getDeclaredMethod("getDispatchThread");
      method.setAccessible(true);
      return (Thread) method.invoke(eventQueue);
    } catch (Exception e1) {
      // ok
    }
    return null;
  }

  public boolean isReadAccessAllowed() {
    Thread currentThread = Thread.currentThread();
    return ourDispatchThread == currentThread
        || isExceptionalThreadWithReadAccess()
        || myActionsLock.isReadLockAcquired()
        || myActionsLock.isWriteLockAcquired()
        || isDispatchThread();
  }

  public void assertReadAccessToDocumentsAllowed() {
    /* TODO
    Thread currentThread = Thread.currentThread();
    if (ourDispatchThread != currentThread) {
      if (myExceptionalThreadWithReadAccess == currentThread) return;
      if (myActionsLock.isReadLockAcquired(currentThread)) return;
      if (myActionsLock.isWriteLockAcquired(currentThread)) return;
      if (isDispatchThread(currentThread)) return;
      LOG.error(
        "Read access is allowed from event dispatch thread or inside read-action only (see com.intellij.openapi.application.Application.runReadAction())");
    }
    */
  }

  private static void assertCanRunWriteAction() {
    assertIsDispatchThread("Write access is allowed from event dispatch thread only");
  }

  public void assertIsDispatchThread() {
    assertIsDispatchThread("Access is allowed from event dispatch thread only.");
  }

  private static void assertIsDispatchThread(String message) {
    if (ShutDownTracker.isShutdownHookRunning()) return;
    final Thread currentThread = Thread.currentThread();
    if (ourDispatchThread == currentThread) return;

    if (EventQueue.isDispatchThread()) {
      ourDispatchThread = currentThread;
    }
    if (ourDispatchThread == currentThread) return;

    Integer safeCounter = ourEdtSafe.get();
    if (safeCounter != null && safeCounter > 0) return;

    LOG.error(
        message,
        "Current thread: " + describe(Thread.currentThread()),
        "Our dispatch thread:" + describe(ourDispatchThread),
        "SystemEventQueueThread: " + describe(getEventQueueThread()));
  }

  public void runEdtSafeAction(@NotNull Runnable runnable) {
    Integer value = ourEdtSafe.get();
    if (value == null) {
      value = Integer.valueOf(0);
    }

    ourEdtSafe.set(value + 1);

    try {
      runnable.run();
    } finally {
      int newValue = ourEdtSafe.get() - 1;
      ourEdtSafe.set(newValue >= 1 ? newValue : null);
    }
  }

  public void assertIsDispatchThread(@Nullable final JComponent component) {
    if (component == null) return;

    Thread curThread = Thread.currentThread();
    if (ourDispatchThread == curThread) {
      return;
    }

    if (Boolean.TRUE.equals(component.getClientProperty(WAS_EVER_SHOWN))) {
      assertIsDispatchThread();
    } else {
      final JRootPane root = component.getRootPane();
      if (root != null) {
        component.putClientProperty(WAS_EVER_SHOWN, Boolean.TRUE);
        assertIsDispatchThread();
      }
    }
  }

  public void assertTimeConsuming() {
    if (myTestModeFlag || myHeadlessMode || ShutDownTracker.isShutdownHookRunning()) return;
    LOG.assertTrue(
        !isDispatchThread(), "This operation is time consuming and must not be called on EDT");
  }

  public boolean tryRunReadAction(@NotNull Runnable action) {
    /**
     * if we are inside read action, do not try to acquire read lock again since it will deadlock if
     * there is a pending writeAction see {@link
     * com.intellij.util.concurrency.ReentrantWriterPreferenceReadWriteLock#allowReader()}
     */
    boolean mustAcquire = !isReadAccessAllowed();

    if (mustAcquire) {
      LOG.assertTrue(
          myTestModeFlag || !Thread.holdsLock(PsiLock.LOCK),
          "Thread must not hold PsiLock while performing readAction");
      try {
        if (!myActionsLock.readLock().attempt(0)) return false;
      } catch (InterruptedException e) {
        throw new RuntimeInterruptedException(e);
      }
    }

    try {
      action.run();
    } finally {
      if (mustAcquire) {
        myActionsLock.readLock().release();
      }
    }
    return true;
  }

  public boolean tryToApplyActivationState(boolean active, Window window) {
    final Component frame = UIUtil.findUltimateParent(window);

    if (frame instanceof IdeFrame) {
      final IdeFrame ideFrame = (IdeFrame) frame;
      if (isActive() != active) {
        myActive = Boolean.valueOf(active);
        System.setProperty("idea.active", Boolean.valueOf(myActive).toString());
        if (active) {
          myDispatcher.getMulticaster().applicationActivated(ideFrame);
        } else {
          myDispatcher.getMulticaster().applicationDeactivated(ideFrame);
        }
        return true;
      }
    }

    return false;
  }

  public boolean isActive() {
    if (isUnitTestMode()) return true;

    if (myActive == null) {
      Window active = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
      return active != null;
    }

    return myActive;
  }

  public void assertWriteAccessAllowed() {
    LOG.assertTrue(
        isWriteAccessAllowed(),
        "Write access is allowed inside write-action only (see com.intellij.openapi.application.Application.runWriteAction())");
  }

  public boolean isWriteAccessAllowed() {
    return myActionsLock.isWriteLockAcquired(Thread.currentThread());
  }

  public void editorPaintStart() {
    myInEditorPaintCounter++;
  }

  public void editorPaintFinish() {
    myInEditorPaintCounter--;
    LOG.assertTrue(myInEditorPaintCounter >= 0);
  }

  public void addApplicationListener(@NotNull ApplicationListener l) {
    myDispatcher.addListener(l);
  }

  public void addApplicationListener(@NotNull ApplicationListener l, @NotNull Disposable parent) {
    myDispatcher.addListener(l, parent);
  }

  public void removeApplicationListener(@NotNull ApplicationListener l) {
    myDispatcher.removeListener(l);
  }

  private void fireApplicationExiting() {
    myDispatcher.getMulticaster().applicationExiting();
  }

  private void fireBeforeWriteActionStart(Runnable action) {
    myDispatcher.getMulticaster().beforeWriteActionStart(action);
  }

  private void fireWriteActionStarted(Runnable action) {
    myDispatcher.getMulticaster().writeActionStarted(action);
  }

  private void fireWriteActionFinished(Runnable action) {
    myDispatcher.getMulticaster().writeActionFinished(action);
  }

  public void _saveSettings() { // public for testing purposes
    if (mySaveSettingsIsInProgress.compareAndSet(false, true)) {
      try {
        doSave();
      } catch (final Throwable ex) {
        if (isUnitTestMode()) {
          System.out.println("Saving application settings failed");
          ex.printStackTrace();
        } else {
          LOG.info("Saving application settings failed", ex);
          invokeLater(
              new Runnable() {
                public void run() {
                  if (ex instanceof PluginException) {
                    final PluginException pluginException = (PluginException) ex;
                    PluginManager.disablePlugin(pluginException.getPluginId().getIdString());
                    Messages.showMessageDialog(
                        "The plugin "
                            + pluginException.getPluginId()
                            + " failed to save settings and has been disabled. Please restart "
                            + ApplicationNamesInfo.getInstance().getFullProductName(),
                        CommonBundle.getErrorTitle(),
                        Messages.getErrorIcon());
                  } else {
                    Messages.showMessageDialog(
                        ApplicationBundle.message(
                            "application.save.settings.error", ex.getLocalizedMessage()),
                        CommonBundle.getErrorTitle(),
                        Messages.getErrorIcon());
                  }
                }
              });
        }
      } finally {
        mySaveSettingsIsInProgress.set(false);
      }
    }
  }

  public void saveSettings() {
    if (!myDoNotSave && !isUnitTestMode() && !isHeadlessEnvironment()) {
      _saveSettings();
    }
  }

  public void saveAll() {
    if (myDoNotSave || isUnitTestMode() || isHeadlessEnvironment()) return;

    FileDocumentManager.getInstance().saveAllDocuments();

    Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
    for (Project openProject : openProjects) {
      ProjectEx project = (ProjectEx) openProject;
      project.save();
    }

    saveSettings();
  }

  public void doNotSave() {
    myDoNotSave = true;
  }

  public boolean isDoNotSave() {
    return myDoNotSave;
  }

  public <T> T[] getExtensions(final ExtensionPointName<T> extensionPointName) {
    return Extensions.getRootArea().getExtensionPoint(extensionPointName).getExtensions();
  }

  public boolean isDisposeInProgress() {
    return myDisposeInProgress;
  }

  public boolean isRestartCapable() {
    return SystemInfo.isWindows || SystemInfo.isMacOSSnowLeopard || myRestartCode > 0;
  }

  public void restart() {
    if (SystemInfo.isWindows) {
      Win32Restarter.restart();
    } else if (SystemInfo.isMacOSSnowLeopard) {
      MacRestarter.restart();
    } else if (myRestartCode > 0) {
      myExitCode = myRestartCode;
      exit(true);
    } else {
      exit(true);
    }
  }

  public boolean isSaving() {
    if (getStateStore().isSaving()) return true;
    Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
    for (Project openProject : openProjects) {
      ProjectEx project = (ProjectEx) openProject;
      if (project.getStateStore().isSaving()) return true;
    }

    return false;
  }

  @Override
  public String toString() {
    return "Application";
  }
}
  protected class MyContentEntryEditor extends ContentEntryEditor {
    private final EventDispatcher<ChangeListener> myEventDispatcher =
        EventDispatcher.create(ChangeListener.class);

    public MyContentEntryEditor(
        String contentEntryUrl, List<ModuleSourceRootEditHandler<?>> handlers) {
      super(contentEntryUrl, handlers);
    }

    @Override
    protected ModifiableRootModel getModel() {
      return PyContentEntriesEditor.this.getModel();
    }

    public void addListener(ChangeListener changeListener) {
      myEventDispatcher.addListener(changeListener);
    }

    public void removeListener(ChangeListener changeListener) {
      myEventDispatcher.removeListener(changeListener);
    }

    @Override
    protected ContentRootPanel createContentRootPane() {
      return new MyContentRootPanel();
    }

    @Override
    public void deleteContentFolder(ContentEntry contentEntry, ContentFolder folder) {
      for (PyRootTypeProvider provider : myRootTypeProviders) {
        if (provider.isMine(folder)) {
          removeRoot(contentEntry, folder.getUrl(), provider);
          return;
        }
      }
      super.deleteContentFolder(contentEntry, folder);
    }

    public void removeRoot(
        @Nullable ContentEntry contentEntry, String folder, PyRootTypeProvider provider) {
      if (contentEntry == null) {
        contentEntry = getContentEntry();
      }
      VirtualFilePointer root = getRoot(provider, folder);
      if (root != null) {
        provider.removeRoot(contentEntry, root, getModel());
        fireUpdate();
      }
    }

    public void fireUpdate() {
      myEventDispatcher.getMulticaster().stateChanged(new ChangeEvent(this));
      update();
    }

    public VirtualFilePointer getRoot(PyRootTypeProvider provider, @NotNull final String url) {
      for (VirtualFilePointer filePointer : provider.getRoots().get(getContentEntry())) {
        if (Comparing.equal(filePointer.getUrl(), url)) {
          return filePointer;
        }
      }
      return null;
    }

    public void addRoot(PyRootTypeProvider provider, @NotNull final VirtualFilePointer root) {
      provider.getRoots().putValue(getContentEntry(), root);
      fireUpdate();
    }

    protected class MyContentRootPanel extends ContentRootPanel {
      public MyContentRootPanel() {
        super(MyContentEntryEditor.this, getEditHandlers());
      }

      @Override
      @NotNull
      protected ContentEntryImpl getContentEntry() {
        //noinspection ConstantConditions
        return (ContentEntryImpl) MyContentEntryEditor.this.getContentEntry();
      }

      @Override
      protected void addFolderGroupComponents() {
        super.addFolderGroupComponents();
        for (PyRootTypeProvider provider : myRootTypeProviders) {
          MultiMap<ContentEntry, VirtualFilePointer> roots = provider.getRoots();
          if (!roots.get(getContentEntry()).isEmpty()) {
            final JComponent sourcesComponent =
                createFolderGroupComponent(
                    provider.getName() + " Folders",
                    provider.createFolders(getContentEntry()),
                    provider.getColor(),
                    null);
            this.add(
                sourcesComponent,
                new GridBagConstraints(
                    0,
                    GridBagConstraints.RELATIVE,
                    1,
                    1,
                    1.0,
                    0.0,
                    GridBagConstraints.NORTH,
                    GridBagConstraints.HORIZONTAL,
                    new Insets(0, 0, 10, 0),
                    0,
                    0));
          }
        }
      }
    }
  }
/** @author Eugene Zhuravlev Date: Oct 4, 2003 Time: 6:29:56 PM */
@SuppressWarnings({"AssignmentToStaticFieldFromInstanceMethod"})
public abstract class ModuleEditor implements Place.Navigator, Disposable {
  private static final Logger LOG = Logger.getInstance(ModuleEditor.class);
  private static final ExtensionPointName<ModuleConfigurableEP> MODULE_CONFIGURABLES =
      ExtensionPointName.create("com.intellij.moduleConfigurable");
  public static final String SELECTED_EDITOR_NAME = "selectedEditor";

  private final Project myProject;
  private JPanel myGenericSettingsPanel;
  private ModifiableRootModel
      myModifiableRootModel; // important: in order to correctly update OrderEntries UI use
                             // corresponding proxy for the model

  private final ModulesProvider myModulesProvider;
  private String myName;
  private final Module myModule;

  protected final List<ModuleConfigurationEditor> myEditors = new ArrayList<>();
  private ModifiableRootModel myModifiableRootModelProxy;

  private final EventDispatcher<ChangeListener> myEventDispatcher =
      EventDispatcher.create(ChangeListener.class);
  @NonNls private static final String METHOD_COMMIT = "commit";
  private boolean myEditorsInitialized;

  protected History myHistory;

  public ModuleEditor(Project project, ModulesProvider modulesProvider, @NotNull Module module) {
    myProject = project;
    myModulesProvider = modulesProvider;
    myModule = module;
    myName = module.getName();
  }

  public void init(History history) {
    myHistory = history;

    for (ModuleConfigurationEditor each : myEditors) {
      if (each instanceof ModuleElementsEditor) {
        ((ModuleElementsEditor) each).setHistory(myHistory);
      }
    }

    restoreSelectedEditor();
  }

  public abstract ProjectFacetsConfigurator getFacetsConfigurator();

  protected abstract JComponent createCenterPanel();

  @Nullable
  public abstract ModuleConfigurationEditor getSelectedEditor();

  public abstract void selectEditor(String displayName);

  protected abstract void restoreSelectedEditor();

  @Nullable
  public abstract ModuleConfigurationEditor getEditor(@NotNull String displayName);

  protected abstract void disposeCenterPanel();

  public interface ChangeListener extends EventListener {
    void moduleStateChanged(ModifiableRootModel moduleRootModel);
  }

  public void addChangeListener(ChangeListener listener) {
    myEventDispatcher.addListener(listener);
  }

  public void removeChangeListener(ChangeListener listener) {
    myEventDispatcher.removeListener(listener);
  }

  @Nullable
  public Module getModule() {
    final Module[] all = myModulesProvider.getModules();
    for (Module each : all) {
      if (each == myModule) return myModule;
    }

    return myModulesProvider.getModule(myName);
  }

  public ModifiableRootModel getModifiableRootModel() {
    if (myModifiableRootModel == null) {
      final Module module = getModule();
      if (module != null) {
        myModifiableRootModel =
            ((ModuleRootManagerImpl) ModuleRootManager.getInstance(module))
                .getModifiableModel(new UIRootConfigurationAccessor(myProject));
      }
    }
    return myModifiableRootModel;
  }

  public OrderEntry[] getOrderEntries() {
    if (myModifiableRootModel == null) { // do not clone all model if not necessary
      return ModuleRootManager.getInstance(getModule()).getOrderEntries();
    } else {
      return myModifiableRootModel.getOrderEntries();
    }
  }

  public ModifiableRootModel getModifiableRootModelProxy() {
    if (myModifiableRootModelProxy == null) {
      final ModifiableRootModel rootModel = getModifiableRootModel();
      if (rootModel != null) {
        myModifiableRootModelProxy =
            (ModifiableRootModel)
                Proxy.newProxyInstance(
                    getClass().getClassLoader(),
                    new Class[] {ModifiableRootModel.class},
                    new ModifiableRootModelInvocationHandler(rootModel));
      }
    }
    return myModifiableRootModelProxy;
  }

  public ModuleRootModel getRootModel() {
    if (myModifiableRootModel != null) {
      return getModifiableRootModelProxy();
    }
    return ModuleRootManager.getInstance(myModule);
  }

  public boolean isModified() {
    for (ModuleConfigurationEditor moduleElementsEditor : myEditors) {
      if (moduleElementsEditor.isModified()) {
        return true;
      }
    }
    return false;
  }

  private void createEditors(@Nullable Module module) {
    if (module == null) return;

    ModuleConfigurationState state = createModuleConfigurationState();
    for (ModuleConfigurationEditorProvider provider : collectProviders(module)) {
      ModuleConfigurationEditor[] editors = provider.createEditors(state);
      if (editors.length > 0
          && provider instanceof ModuleConfigurationEditorProviderEx
          && ((ModuleConfigurationEditorProviderEx) provider).isCompleteEditorSet()) {
        myEditors.clear();
        ContainerUtil.addAll(myEditors, editors);
        break;
      } else {
        ContainerUtil.addAll(myEditors, editors);
      }
    }

    for (Configurable moduleConfigurable : ServiceKt.getComponents(module, Configurable.class)) {
      reportDeprecatedModuleEditor(moduleConfigurable.getClass());
      myEditors.add(new ModuleConfigurableWrapper(moduleConfigurable));
    }
    for (ModuleConfigurableEP extension : module.getExtensions(MODULE_CONFIGURABLES)) {
      if (extension.canCreateConfigurable()) {
        Configurable configurable = extension.createConfigurable();
        if (configurable != null) {
          reportDeprecatedModuleEditor(configurable.getClass());
          myEditors.add(new ModuleConfigurableWrapper(configurable));
        }
      }
    }
  }

  private static Set<Class<?>> ourReportedDeprecatedClasses = new HashSet<>();

  private static void reportDeprecatedModuleEditor(Class<?> aClass) {
    if (ourReportedDeprecatedClasses.add(aClass)) {
      LOG.warn(
          aClass.getName()
              + " uses deprecated way to register itself as a module editor. "
              + ModuleConfigurationEditorProvider.class.getName()
              + " extension point should be used instead");
    }
  }

  private static ModuleConfigurationEditorProvider[] collectProviders(@NotNull Module module) {
    List<ModuleConfigurationEditorProvider> result = new ArrayList<>();
    result.addAll(ServiceKt.getComponents(module, ModuleConfigurationEditorProvider.class));
    for (ModuleConfigurationEditorProvider component : result) {
      reportDeprecatedModuleEditor(component.getClass());
    }
    ContainerUtil.addAll(
        result, Extensions.getExtensions(ModuleConfigurationEditorProvider.EP_NAME, module));
    return result.toArray(new ModuleConfigurationEditorProvider[result.size()]);
  }

  public ModuleConfigurationState createModuleConfigurationState() {
    return new ModuleConfigurationStateImpl(myProject, myModulesProvider) {
      @Override
      public ModifiableRootModel getRootModel() {
        return getModifiableRootModelProxy();
      }

      @Override
      public FacetsProvider getFacetsProvider() {
        return getFacetsConfigurator();
      }
    };
  }

  private JPanel createPanel() {
    getModifiableRootModel(); // initialize model if needed
    getModifiableRootModelProxy();

    myGenericSettingsPanel = new ModuleEditorPanel();

    createEditors(getModule());

    final JComponent component = createCenterPanel();
    myGenericSettingsPanel.add(component, BorderLayout.CENTER);
    myEditorsInitialized = true;
    return myGenericSettingsPanel;
  }

  public JPanel getPanel() {
    if (myGenericSettingsPanel == null) {
      myGenericSettingsPanel = createPanel();
    }

    return myGenericSettingsPanel;
  }

  public void moduleCountChanged() {
    updateOrderEntriesInEditors(false);
  }

  private void updateOrderEntriesInEditors(boolean forceInitEditors) {
    if (getModule() != null) { // module with attached module libraries was deleted
      if (myEditorsInitialized || forceInitEditors) {
        getPanel(); // init editor if needed
        for (final ModuleConfigurationEditor myEditor : myEditors) {
          myEditor.moduleStateChanged();
        }
      }
      myEventDispatcher.getMulticaster().moduleStateChanged(getModifiableRootModelProxy());
    }
  }

  public void updateCompilerOutputPathChanged(String baseUrl, String moduleName) {
    if (myGenericSettingsPanel == null) return; // wasn't initialized yet
    for (final ModuleConfigurationEditor myEditor : myEditors) {
      if (myEditor instanceof ModuleElementsEditor) {
        ((ModuleElementsEditor) myEditor).moduleCompileOutputChanged(baseUrl, moduleName);
      }
    }
  }

  @Override
  public void dispose() {
    try {
      for (final ModuleConfigurationEditor myEditor : myEditors) {
        myEditor.disposeUIResources();
      }

      myEditors.clear();

      disposeCenterPanel();

      if (myModifiableRootModel != null) {
        myModifiableRootModel.dispose();
      }

      myGenericSettingsPanel = null;
    } finally {
      myModifiableRootModel = null;
      myModifiableRootModelProxy = null;
    }
  }

  public ModifiableRootModel apply() throws ConfigurationException {
    try {
      for (ModuleConfigurationEditor editor : myEditors) {
        editor.saveData();
        editor.apply();
      }

      return myModifiableRootModel;
    } finally {
      myModifiableRootModel = null;
      myModifiableRootModelProxy = null;
    }
  }

  public void canApply() throws ConfigurationException {
    for (ModuleConfigurationEditor editor : myEditors) {
      if (editor instanceof ModuleElementsEditor) {
        ((ModuleElementsEditor) editor).canApply();
      }
    }
  }

  public String getName() {
    return myName;
  }

  private class ModifiableRootModelInvocationHandler
      implements InvocationHandler, ProxyDelegateAccessor {
    private final ModifiableRootModel myDelegateModel;

    @NonNls
    private final Set<String> myCheckedNames =
        new HashSet<>(
            Arrays.asList(
                "addOrderEntry",
                "addLibraryEntry",
                "addInvalidLibrary",
                "addModuleOrderEntry",
                "addInvalidModuleEntry",
                "removeOrderEntry",
                "setSdk",
                "inheritSdk",
                "inheritCompilerOutputPath",
                "setExcludeOutput",
                "replaceEntryOfType",
                "rearrangeOrderEntries"));

    ModifiableRootModelInvocationHandler(ModifiableRootModel model) {
      myDelegateModel = model;
    }

    @Override
    public Object invoke(Object object, Method method, Object[] params) throws Throwable {
      final boolean needUpdate = myCheckedNames.contains(method.getName());
      try {
        final Object result = method.invoke(myDelegateModel, unwrapParams(params));
        if (result instanceof LibraryTable) {
          return Proxy.newProxyInstance(
              getClass().getClassLoader(),
              new Class[] {LibraryTable.class},
              new LibraryTableInvocationHandler((LibraryTable) result));
        }
        return result;
      } catch (InvocationTargetException e) {
        throw e.getCause();
      } finally {
        if (needUpdate) {
          updateOrderEntriesInEditors(true);
        }
      }
    }

    @Override
    public Object getDelegate() {
      return myDelegateModel;
    }
  }

  private class LibraryTableInvocationHandler implements InvocationHandler, ProxyDelegateAccessor {
    private final LibraryTable myDelegateTable;

    @NonNls
    private final Set<String> myCheckedNames =
        new HashSet<>(Arrays.asList("removeLibrary" /*,"createLibrary"*/));

    LibraryTableInvocationHandler(LibraryTable table) {
      myDelegateTable = table;
    }

    @Override
    public Object invoke(Object object, Method method, Object[] params) throws Throwable {
      final boolean needUpdate = myCheckedNames.contains(method.getName());
      try {
        final Object result = method.invoke(myDelegateTable, unwrapParams(params));
        if (result instanceof Library) {
          return Proxy.newProxyInstance(
              getClass().getClassLoader(),
              new Class[] {result instanceof LibraryEx ? LibraryEx.class : Library.class},
              new LibraryInvocationHandler((Library) result));
        } else if (result instanceof LibraryTable.ModifiableModel) {
          return Proxy.newProxyInstance(
              getClass().getClassLoader(),
              new Class[] {LibraryTableBase.ModifiableModel.class},
              new LibraryTableModelInvocationHandler((LibraryTable.ModifiableModel) result));
        }
        if (result instanceof Library[]) {
          Library[] libraries = (Library[]) result;
          for (int idx = 0; idx < libraries.length; idx++) {
            Library library = libraries[idx];
            libraries[idx] =
                (Library)
                    Proxy.newProxyInstance(
                        getClass().getClassLoader(),
                        new Class[] {
                          library instanceof LibraryEx ? LibraryEx.class : Library.class
                        },
                        new LibraryInvocationHandler(library));
          }
        }
        return result;
      } catch (InvocationTargetException e) {
        throw e.getCause();
      } finally {
        if (needUpdate) {
          updateOrderEntriesInEditors(true);
        }
      }
    }

    @Override
    public Object getDelegate() {
      return myDelegateTable;
    }
  }

  private class LibraryInvocationHandler implements InvocationHandler, ProxyDelegateAccessor {
    private final Library myDelegateLibrary;

    LibraryInvocationHandler(Library delegateLibrary) {
      myDelegateLibrary = delegateLibrary;
    }

    @Override
    public Object invoke(Object object, Method method, Object[] params) throws Throwable {
      try {
        final Object result = method.invoke(myDelegateLibrary, unwrapParams(params));
        if (result instanceof LibraryEx.ModifiableModelEx) {
          return Proxy.newProxyInstance(
              getClass().getClassLoader(),
              new Class[] {LibraryEx.ModifiableModelEx.class},
              new LibraryModifiableModelInvocationHandler((LibraryEx.ModifiableModelEx) result));
        }
        return result;
      } catch (InvocationTargetException e) {
        throw e.getCause();
      }
    }

    @Override
    public Object getDelegate() {
      return myDelegateLibrary;
    }
  }

  private class LibraryModifiableModelInvocationHandler
      implements InvocationHandler, ProxyDelegateAccessor {
    private final Library.ModifiableModel myDelegateModel;

    LibraryModifiableModelInvocationHandler(Library.ModifiableModel delegateModel) {
      myDelegateModel = delegateModel;
    }

    @Override
    public Object invoke(Object object, Method method, Object[] params) throws Throwable {
      final boolean needUpdate = METHOD_COMMIT.equals(method.getName());
      try {
        return method.invoke(myDelegateModel, unwrapParams(params));
      } catch (InvocationTargetException e) {
        throw e.getCause();
      } finally {
        if (needUpdate) {
          updateOrderEntriesInEditors(true);
        }
      }
    }

    @Override
    public Object getDelegate() {
      return myDelegateModel;
    }
  }

  private class LibraryTableModelInvocationHandler
      implements InvocationHandler, ProxyDelegateAccessor {
    private final LibraryTable.ModifiableModel myDelegateModel;

    LibraryTableModelInvocationHandler(LibraryTable.ModifiableModel delegateModel) {
      myDelegateModel = delegateModel;
    }

    @Override
    public Object invoke(Object object, Method method, Object[] params) throws Throwable {
      final boolean needUpdate = METHOD_COMMIT.equals(method.getName());
      try {
        Object result = method.invoke(myDelegateModel, unwrapParams(params));
        if (result instanceof Library[]) {
          Library[] libraries = (Library[]) result;
          for (int idx = 0; idx < libraries.length; idx++) {
            Library library = libraries[idx];
            libraries[idx] =
                (Library)
                    Proxy.newProxyInstance(
                        getClass().getClassLoader(),
                        new Class[] {LibraryEx.class},
                        new LibraryInvocationHandler(library));
          }
        }
        if (result instanceof Library) {
          result =
              Proxy.newProxyInstance(
                  getClass().getClassLoader(),
                  new Class[] {LibraryEx.class},
                  new LibraryInvocationHandler((Library) result));
        }
        return result;
      } catch (InvocationTargetException e) {
        throw e.getCause();
      } finally {
        if (needUpdate) {
          updateOrderEntriesInEditors(true);
        }
      }
    }

    @Override
    public Object getDelegate() {
      return myDelegateModel;
    }
  }

  public interface ProxyDelegateAccessor {
    Object getDelegate();
  }

  private static Object[] unwrapParams(Object[] params) {
    if (params == null || params.length == 0) {
      return params;
    }
    final Object[] unwrappedParams = new Object[params.length];
    for (int idx = 0; idx < params.length; idx++) {
      Object param = params[idx];
      if (param != null && Proxy.isProxyClass(param.getClass())) {
        final InvocationHandler invocationHandler = Proxy.getInvocationHandler(param);
        if (invocationHandler instanceof ProxyDelegateAccessor) {
          param = ((ProxyDelegateAccessor) invocationHandler).getDelegate();
        }
      }
      unwrappedParams[idx] = param;
    }
    return unwrappedParams;
  }

  @Nullable
  public String getHelpTopic() {
    if (myEditors.isEmpty()) {
      return null;
    }
    final ModuleConfigurationEditor selectedEditor = getSelectedEditor();
    return selectedEditor != null ? selectedEditor.getHelpTopic() : null;
  }

  public void setModuleName(final String name) {
    myName = name;
  }

  private class ModuleEditorPanel extends JPanel implements DataProvider {
    public ModuleEditorPanel() {
      super(new BorderLayout());
    }

    @Override
    public Object getData(String dataId) {
      if (LangDataKeys.MODULE_CONTEXT.is(dataId)) {
        return getModule();
      }
      return null;
    }
  }

  @Override
  public void setHistory(final History history) {}
}
/**
 * @author Eugene Belyaev
 * @author Vladimir Kondratyev
 */
public final class InternalDecorator extends JPanel implements Queryable, DataProvider {

  private Project myProject;
  private WindowInfoImpl myInfo;
  private final ToolWindowImpl myToolWindow;
  private final MyDivider myDivider;
  private final EventDispatcher<InternalDecoratorListener> myDispatcher =
      EventDispatcher.create(InternalDecoratorListener.class);
  /*
   * Actions
   */
  private final TogglePinnedModeAction myToggleAutoHideModeAction;
  private final ToggleDockModeAction myToggleDockModeAction;
  private final ToggleFloatingModeAction myToggleFloatingModeAction;
  private final ToggleWindowedModeAction myToggleWindowedModeAction;
  private final ToggleSideModeAction myToggleSideModeAction;
  private final ToggleContentUiTypeAction myToggleContentUiTypeAction;

  private ActionGroup myAdditionalGearActions;
  /** Catches all event from tool window and modifies decorator's appearance. */
  @NonNls private static final String HIDE_ACTIVE_WINDOW_ACTION_ID = "HideActiveWindow";

  @NonNls public static final String TOGGLE_PINNED_MODE_ACTION_ID = "TogglePinnedMode";
  @NonNls public static final String TOGGLE_DOCK_MODE_ACTION_ID = "ToggleDockMode";
  @NonNls public static final String TOGGLE_FLOATING_MODE_ACTION_ID = "ToggleFloatingMode";
  @NonNls public static final String TOGGLE_WINDOWED_MODE_ACTION_ID = "ToggleWindowedMode";
  @NonNls public static final String TOGGLE_SIDE_MODE_ACTION_ID = "ToggleSideMode";
  @NonNls private static final String TOGGLE_CONTENT_UI_TYPE_ACTION_ID = "ToggleContentUiTypeMode";

  private ToolWindowHeader myHeader;
  private ActionGroup myToggleToolbarGroup;

  InternalDecorator(
      final Project project, @NotNull WindowInfoImpl info, final ToolWindowImpl toolWindow) {
    super(new BorderLayout());
    myProject = project;
    myToolWindow = toolWindow;
    myToolWindow.setDecorator(this);
    myDivider = new MyDivider();

    myToggleFloatingModeAction = new ToggleFloatingModeAction();
    myToggleWindowedModeAction = new ToggleWindowedModeAction();
    myToggleSideModeAction = new ToggleSideModeAction();
    myToggleDockModeAction = new ToggleDockModeAction();
    myToggleAutoHideModeAction = new TogglePinnedModeAction();
    myToggleContentUiTypeAction = new ToggleContentUiTypeAction();
    myToggleToolbarGroup = ToggleToolbarAction.createToggleToolbarGroup(myProject, myToolWindow);

    myHeader =
        new ToolWindowHeader(
            toolWindow,
            info,
            new Producer<ActionGroup>() {
              @Override
              public ActionGroup produce() {
                return /*createGearPopupGroup()*/ createPopupGroup(true);
              }
            }) {
          @Override
          protected boolean isActive() {
            return isFocused();
          }

          @Override
          protected void hideToolWindow() {
            fireHidden();
          }

          @Override
          protected void toolWindowTypeChanged(ToolWindowType type) {
            fireTypeChanged(type);
          }

          @Override
          protected void sideHidden() {
            fireHiddenSide();
          }
        };

    init();

    apply(info);
  }

  public boolean isFocused() {
    IdeFocusManager fm = IdeFocusManager.getInstance(myProject);
    Component component = fm.getFocusedDescendantFor(myToolWindow.getComponent());
    if (component != null) return true;

    Component owner = fm.getLastFocusedFor(WindowManager.getInstance().getIdeFrame(myProject));

    return owner != null && SwingUtilities.isDescendingFrom(owner, myToolWindow.getComponent());
  }

  /** Applies specified decoration. */
  public final void apply(@NotNull WindowInfoImpl info) {
    if (Comparing.equal(myInfo, info) || myProject == null || myProject.isDisposed()) {
      return;
    }
    myInfo = info;

    // Anchor
    final ToolWindowAnchor anchor = myInfo.getAnchor();
    if (info.isSliding()) {
      myDivider.invalidate();
      if (ToolWindowAnchor.TOP == anchor) {
        add(myDivider, BorderLayout.SOUTH);
      } else if (ToolWindowAnchor.LEFT == anchor) {
        add(myDivider, BorderLayout.EAST);
      } else if (ToolWindowAnchor.BOTTOM == anchor) {
        add(myDivider, BorderLayout.NORTH);
      } else if (ToolWindowAnchor.RIGHT == anchor) {
        add(myDivider, BorderLayout.WEST);
      }
      myDivider.setPreferredSize(new Dimension(0, 0));
    } else { // docked and floating windows don't have divider
      remove(myDivider);
    }

    validate();
    repaint();

    // Push "apply" request forward

    if (myInfo.isFloating() && myInfo.isVisible()) {
      final FloatingDecorator floatingDecorator =
          (FloatingDecorator) SwingUtilities.getAncestorOfClass(FloatingDecorator.class, this);
      if (floatingDecorator != null) {
        floatingDecorator.apply(myInfo);
      }
    }

    myToolWindow.getContentUI().setType(myInfo.getContentUiType());
    setBorder(new InnerPanelBorder(myToolWindow));
  }

  @Nullable
  @Override
  public Object getData(@NonNls String dataId) {
    if (PlatformDataKeys.TOOL_WINDOW.is(dataId)) {
      return myToolWindow;
    }
    return null;
  }

  final void addInternalDecoratorListener(InternalDecoratorListener l) {
    myDispatcher.addListener(l);
  }

  final void removeInternalDecoratorListener(InternalDecoratorListener l) {
    myDispatcher.removeListener(l);
  }

  final void dispose() {
    removeAll();

    Disposer.dispose(myHeader);
    myHeader = null;
    myProject = null;
  }

  private void fireAnchorChanged(ToolWindowAnchor anchor) {
    myDispatcher.getMulticaster().anchorChanged(this, anchor);
  }

  private void fireAutoHideChanged(boolean autoHide) {
    myDispatcher.getMulticaster().autoHideChanged(this, autoHide);
  }

  /** Fires event that "hide" button has been pressed. */
  final void fireHidden() {
    myDispatcher.getMulticaster().hidden(this);
  }

  /** Fires event that "hide" button has been pressed. */
  final void fireHiddenSide() {
    myDispatcher.getMulticaster().hiddenSide(this);
  }

  /** Fires event that user performed click into the title bar area. */
  final void fireActivated() {
    myDispatcher.getMulticaster().activated(this);
  }

  private void fireTypeChanged(ToolWindowType type) {
    myDispatcher.getMulticaster().typeChanged(this, type);
  }

  final void fireResized() {
    myDispatcher.getMulticaster().resized(this);
  }

  private void fireSideStatusChanged(boolean isSide) {
    myDispatcher.getMulticaster().sideStatusChanged(this, isSide);
  }

  private void fireContentUiTypeChanges(ToolWindowContentUiType type) {
    myDispatcher.getMulticaster().contentUiTypeChanges(this, type);
  }

  private void init() {
    enableEvents(AWTEvent.COMPONENT_EVENT_MASK);

    final JPanel contentPane = new JPanel(new BorderLayout());
    contentPane.add(myHeader, BorderLayout.NORTH);

    JPanel innerPanel = new JPanel(new BorderLayout());
    JComponent toolWindowComponent = myToolWindow.getComponent();
    innerPanel.add(toolWindowComponent, BorderLayout.CENTER);

    final NonOpaquePanel inner = new NonOpaquePanel(innerPanel);
    inner.setBorder(new EmptyBorder(-1, 0, 0, 0));

    contentPane.add(inner, BorderLayout.CENTER);
    add(contentPane, BorderLayout.CENTER);
    if (SystemInfo.isMac) {
      setBackground(new JBColor(Gray._200, Gray._90));
    }

    // Add listeners
    registerKeyboardAction(
        new ActionListener() {
          @Override
          public void actionPerformed(final ActionEvent e) {
            ToolWindowManager.getInstance(myProject).activateEditorComponent();
          }
        },
        KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
        JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
  }

  public void setTitleActions(AnAction[] actions) {
    myHeader.setAdditionalTitleActions(actions);
  }

  private class InnerPanelBorder implements Border {

    private final ToolWindow myWindow;

    private InnerPanelBorder(ToolWindow window) {
      myWindow = window;
    }

    @Override
    public void paintBorder(
        final Component c,
        final Graphics g,
        final int x,
        final int y,
        final int width,
        final int height) {
      if (UIUtil.isUnderDarcula()) {
        g.setColor(Gray._40);
        doPaintBorder(c, g, x, y, width, height);
      } else {
        g.setColor(UIUtil.getPanelBackground());
        doPaintBorder(c, g, x, y, width, height);
        g.setColor(Gray._155);
        doPaintBorder(c, g, x, y, width, height);
      }
    }

    private void doPaintBorder(Component c, Graphics g, int x, int y, int width, int height) {
      Insets insets = getBorderInsets(c);

      if (insets.top > 0) {
        UIUtil.drawLine(g, x, y + insets.top - 1, x + width - 1, y + insets.top - 1);
        UIUtil.drawLine(g, x, y + insets.top, x + width - 1, y + insets.top);
      }

      if (insets.left > 0) {
        UIUtil.drawLine(g, x, y, x, y + height);
        UIUtil.drawLine(g, x + 1, y, x + 1, y + height);
      }

      if (insets.right > 0) {
        UIUtil.drawLine(g, x + width - 1, y + insets.top, x + width - 1, y + height);
        UIUtil.drawLine(g, x + width, y + insets.top, x + width, y + height);
      }

      if (insets.bottom > 0) {
        UIUtil.drawLine(g, x, y + height - 1, x + width, y + height - 1);
        UIUtil.drawLine(g, x, y + height, x + width, y + height);
      }
    }

    @Override
    public Insets getBorderInsets(final Component c) {
      if (myProject == null) return new Insets(0, 0, 0, 0);
      ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(myProject);
      if (!(toolWindowManager instanceof ToolWindowManagerImpl)
          || !((ToolWindowManagerImpl) toolWindowManager).isToolWindowRegistered(myInfo.getId())
          || myWindow.getType() == ToolWindowType.FLOATING) {
        return new Insets(0, 0, 0, 0);
      }
      ToolWindowAnchor anchor = myWindow.getAnchor();
      Component component = myWindow.getComponent();
      Container parent = component.getParent();
      while (parent != null) {
        if (parent instanceof Splitter) {
          Splitter splitter = (Splitter) parent;
          boolean isFirst = splitter.getFirstComponent() == component;
          boolean isVertical = splitter.isVertical();
          return new Insets(
              0,
              anchor == ToolWindowAnchor.RIGHT || (!isVertical && !isFirst) ? 1 : 0,
              (isVertical && isFirst) ? 1 : 0,
              anchor == ToolWindowAnchor.LEFT || (!isVertical && isFirst) ? 1 : 0);
        }
        component = parent;
        parent = component.getParent();
      }
      return new Insets(
          0,
          anchor == ToolWindowAnchor.RIGHT ? 1 : 0,
          anchor == ToolWindowAnchor.TOP ? 1 : 0,
          anchor == ToolWindowAnchor.LEFT ? 1 : 0);
    }

    @Override
    public boolean isBorderOpaque() {
      return false;
    }
  }

  public final ActionGroup createPopupGroup() {
    return createPopupGroup(false);
  }

  public final ActionGroup createPopupGroup(boolean skipHideAction) {
    final DefaultActionGroup group = createGearPopupGroup();
    if (!ToolWindowId.PREVIEW.equals(myInfo.getId())) {
      group.add(myToggleContentUiTypeAction);
    }

    final DefaultActionGroup moveGroup =
        new DefaultActionGroup(UIBundle.message("tool.window.move.to.action.group.name"), true);
    final ToolWindowAnchor anchor = myInfo.getAnchor();
    if (anchor != ToolWindowAnchor.TOP) {
      final AnAction topAction =
          new ChangeAnchorAction(
              UIBundle.message("tool.window.move.to.top.action.name"), ToolWindowAnchor.TOP);
      moveGroup.add(topAction);
    }
    if (anchor != ToolWindowAnchor.LEFT) {
      final AnAction leftAction =
          new ChangeAnchorAction(
              UIBundle.message("tool.window.move.to.left.action.name"), ToolWindowAnchor.LEFT);
      moveGroup.add(leftAction);
    }
    if (anchor != ToolWindowAnchor.BOTTOM) {
      final AnAction bottomAction =
          new ChangeAnchorAction(
              UIBundle.message("tool.window.move.to.bottom.action.name"), ToolWindowAnchor.BOTTOM);
      moveGroup.add(bottomAction);
    }
    if (anchor != ToolWindowAnchor.RIGHT) {
      final AnAction rightAction =
          new ChangeAnchorAction(
              UIBundle.message("tool.window.move.to.right.action.name"), ToolWindowAnchor.RIGHT);
      moveGroup.add(rightAction);
    }
    group.add(moveGroup);

    DefaultActionGroup resize =
        new DefaultActionGroup(ActionsBundle.groupText("ResizeToolWindowGroup"), true);
    resize.add(new ResizeToolWindowAction.Left(myToolWindow, this));
    resize.add(new ResizeToolWindowAction.Right(myToolWindow, this));
    resize.add(new ResizeToolWindowAction.Up(myToolWindow, this));
    resize.add(new ResizeToolWindowAction.Down(myToolWindow, this));
    resize.add(ActionManager.getInstance().getAction("MaximizeToolWindow"));

    group.add(resize);
    if (!skipHideAction) {
      group.addSeparator();
      group.add(new HideAction());
    }
    return group;
  }

  private DefaultActionGroup createGearPopupGroup() {
    final DefaultActionGroup group = new DefaultActionGroup();

    if (myAdditionalGearActions != null) {
      addSorted(group, myAdditionalGearActions);
      group.addSeparator();
    }
    group.addAction(myToggleToolbarGroup).setAsSecondary(true);
    if (myInfo.isDocked()) {
      group.add(myToggleAutoHideModeAction);
      group.add(myToggleDockModeAction);
      group.add(myToggleFloatingModeAction);
      group.add(myToggleWindowedModeAction);
      group.add(myToggleSideModeAction);
    } else if (myInfo.isFloating()) {
      group.add(myToggleAutoHideModeAction);
      group.add(myToggleFloatingModeAction);
      group.add(myToggleWindowedModeAction);
    } else if (myInfo.isWindowed()) {
      group.add(myToggleFloatingModeAction);
      group.add(myToggleWindowedModeAction);
    } else if (myInfo.isSliding()) {
      if (!ToolWindowId.PREVIEW.equals(myInfo.getId())) {
        group.add(myToggleDockModeAction);
      }
      group.add(myToggleFloatingModeAction);
      group.add(myToggleWindowedModeAction);
      group.add(myToggleSideModeAction);
    }
    return group;
  }

  private static void addSorted(DefaultActionGroup main, ActionGroup group) {
    final AnAction[] children = group.getChildren(null);
    boolean hadSecondary = false;
    for (AnAction action : children) {
      if (group.isPrimary(action)) {
        main.add(action);
      } else {
        hadSecondary = true;
      }
    }
    if (hadSecondary) {
      main.addSeparator();
      for (AnAction action : children) {
        if (!group.isPrimary(action)) {
          main.addAction(action).setAsSecondary(true);
        }
      }
    }
    String separatorText = group.getTemplatePresentation().getText();
    if (children.length > 0 && !StringUtil.isEmpty(separatorText)) {
      main.addAction(new Separator(separatorText), Constraints.FIRST);
    }
  }

  /** @return tool window associated with the decorator. */
  final ToolWindowImpl getToolWindow() {
    return myToolWindow;
  }

  /** @return last window info applied to the decorator. */
  @NotNull
  final WindowInfoImpl getWindowInfo() {
    return myInfo;
  }

  public int getHeaderHeight() {
    return myHeader.getPreferredSize().height;
  }

  @Override
  protected final void processComponentEvent(final ComponentEvent e) {
    super.processComponentEvent(e);
    if (ComponentEvent.COMPONENT_RESIZED == e.getID()) {
      fireResized();
    }
  }

  private final class ChangeAnchorAction extends AnAction implements DumbAware {
    private final ToolWindowAnchor myAnchor;

    public ChangeAnchorAction(final String title, final ToolWindowAnchor anchor) {
      super(title);
      myAnchor = anchor;
    }

    @Override
    public final void actionPerformed(@NotNull final AnActionEvent e) {
      fireAnchorChanged(myAnchor);
    }
  }

  private final class TogglePinnedModeAction extends ToggleAction implements DumbAware {
    public TogglePinnedModeAction() {
      copyFrom(ActionManager.getInstance().getAction(TOGGLE_PINNED_MODE_ACTION_ID));
    }

    @Override
    public final boolean isSelected(final AnActionEvent event) {
      return !myInfo.isAutoHide();
    }

    @Override
    public final void setSelected(final AnActionEvent event, final boolean flag) {
      fireAutoHideChanged(!myInfo.isAutoHide());
    }
  }

  private final class ToggleDockModeAction extends ToggleAction implements DumbAware {
    public ToggleDockModeAction() {
      copyFrom(ActionManager.getInstance().getAction(TOGGLE_DOCK_MODE_ACTION_ID));
    }

    @Override
    public final boolean isSelected(final AnActionEvent event) {
      return myInfo.isDocked();
    }

    @Override
    public final void setSelected(final AnActionEvent event, final boolean flag) {
      if (myInfo.isDocked()) {
        fireTypeChanged(ToolWindowType.SLIDING);
      } else if (myInfo.isSliding()) {
        fireTypeChanged(ToolWindowType.DOCKED);
      }
    }
  }

  private final class ToggleFloatingModeAction extends ToggleAction implements DumbAware {
    public ToggleFloatingModeAction() {
      copyFrom(ActionManager.getInstance().getAction(TOGGLE_FLOATING_MODE_ACTION_ID));
    }

    @Override
    public final boolean isSelected(final AnActionEvent event) {
      return myInfo.isFloating();
    }

    @Override
    public final void setSelected(final AnActionEvent event, final boolean flag) {
      if (myInfo.isFloating()) {
        fireTypeChanged(myInfo.getInternalType());
      } else {
        fireTypeChanged(ToolWindowType.FLOATING);
      }
    }
  }

  private final class ToggleWindowedModeAction extends ToggleAction implements DumbAware {
    public ToggleWindowedModeAction() {
      copyFrom(ActionManager.getInstance().getAction(TOGGLE_WINDOWED_MODE_ACTION_ID));
    }

    @Override
    public final boolean isSelected(final AnActionEvent event) {
      return myInfo.isWindowed();
    }

    @Override
    public final void setSelected(final AnActionEvent event, final boolean flag) {
      if (myInfo.isWindowed()) {
        fireTypeChanged(myInfo.getInternalType());
      } else {
        fireTypeChanged(ToolWindowType.WINDOWED);
      }
    }

    @Override
    public void update(@NotNull AnActionEvent e) {
      super.update(e);
      if (SystemInfo.isMac) {
        e.getPresentation().setEnabledAndVisible(false);
      }
    }
  }

  private final class ToggleSideModeAction extends ToggleAction implements DumbAware {
    public ToggleSideModeAction() {
      copyFrom(ActionManager.getInstance().getAction(TOGGLE_SIDE_MODE_ACTION_ID));
    }

    @Override
    public final boolean isSelected(final AnActionEvent event) {
      return myInfo.isSplit();
    }

    @Override
    public final void setSelected(final AnActionEvent event, final boolean flag) {
      fireSideStatusChanged(flag);
    }

    @Override
    public void update(@NotNull final AnActionEvent e) {
      super.update(e);
    }
  }

  private final class HideAction extends AnAction implements DumbAware {
    @NonNls
    public static final String HIDE_ACTIVE_WINDOW_ACTION_ID =
        InternalDecorator.HIDE_ACTIVE_WINDOW_ACTION_ID;

    public HideAction() {
      copyFrom(ActionManager.getInstance().getAction(HIDE_ACTIVE_WINDOW_ACTION_ID));
      getTemplatePresentation().setText(UIBundle.message("tool.window.hide.action.name"));
    }

    @Override
    public final void actionPerformed(@NotNull final AnActionEvent e) {
      fireHidden();
    }

    @Override
    public final void update(@NotNull final AnActionEvent event) {
      final Presentation presentation = event.getPresentation();
      presentation.setEnabled(myInfo.isVisible());
    }
  }

  private final class ToggleContentUiTypeAction extends ToggleAction implements DumbAware {
    private boolean myHadSeveralContents;

    private ToggleContentUiTypeAction() {
      copyFrom(ActionManager.getInstance().getAction(TOGGLE_CONTENT_UI_TYPE_ACTION_ID));
    }

    @Override
    public void update(@NotNull AnActionEvent e) {
      myHadSeveralContents =
          myHadSeveralContents || myToolWindow.getContentManager().getContentCount() > 1;
      super.update(e);
      e.getPresentation().setVisible(myHadSeveralContents);
    }

    @Override
    public boolean isSelected(AnActionEvent e) {
      return myInfo.getContentUiType() == ToolWindowContentUiType.COMBO;
    }

    @Override
    public void setSelected(AnActionEvent e, boolean state) {
      fireContentUiTypeChanges(
          state ? ToolWindowContentUiType.COMBO : ToolWindowContentUiType.TABBED);
    }
  }

  private final class MyDivider extends JPanel {
    private boolean myDragging;
    private Point myLastPoint;
    private Disposable myDisposable;
    private IdeGlassPane myGlassPane;

    private final MouseAdapter myListener = new MyMouseAdapter();

    @Override
    public void addNotify() {
      super.addNotify();
      myGlassPane = IdeGlassPaneUtil.find(this);
      myDisposable = Disposer.newDisposable();
      myGlassPane.addMouseMotionPreprocessor(myListener, myDisposable);
      myGlassPane.addMousePreprocessor(myListener, myDisposable);
    }

    @Override
    public void removeNotify() {
      super.removeNotify();
      if (myDisposable != null && !Disposer.isDisposed(myDisposable)) {
        Disposer.dispose(myDisposable);
      }
    }

    boolean isInDragZone(MouseEvent e) {
      final Point p = SwingUtilities.convertMouseEvent(e.getComponent(), e, this).getPoint();
      return Math.abs(myInfo.getAnchor().isHorizontal() ? p.y : p.x) < 6;
    }

    private class MyMouseAdapter extends MouseAdapter {

      private void updateCursor(MouseEvent e) {
        if (isInDragZone(e)) {
          myGlassPane.setCursor(MyDivider.this.getCursor(), MyDivider.this);
          e.consume();
        }
      }

      @Override
      public void mousePressed(MouseEvent e) {
        myDragging = isInDragZone(e);
        updateCursor(e);
      }

      @Override
      public void mouseClicked(MouseEvent e) {
        updateCursor(e);
      }

      @Override
      public void mouseReleased(MouseEvent e) {
        updateCursor(e);
        myDragging = false;
      }

      @Override
      public void mouseMoved(MouseEvent e) {
        updateCursor(e);
      }

      @Override
      public void mouseDragged(MouseEvent e) {
        if (!myDragging) return;
        MouseEvent event = SwingUtilities.convertMouseEvent(e.getComponent(), e, MyDivider.this);
        final ToolWindowAnchor anchor = myInfo.getAnchor();
        final Point point = event.getPoint();
        final Container windowPane = InternalDecorator.this.getParent();
        myLastPoint = SwingUtilities.convertPoint(MyDivider.this, point, windowPane);
        myLastPoint.x = Math.min(Math.max(myLastPoint.x, 0), windowPane.getWidth());
        myLastPoint.y = Math.min(Math.max(myLastPoint.y, 0), windowPane.getHeight());

        final Rectangle bounds = InternalDecorator.this.getBounds();
        if (anchor == ToolWindowAnchor.TOP) {
          InternalDecorator.this.setBounds(0, 0, bounds.width, myLastPoint.y);
        } else if (anchor == ToolWindowAnchor.LEFT) {
          InternalDecorator.this.setBounds(0, 0, myLastPoint.x, bounds.height);
        } else if (anchor == ToolWindowAnchor.BOTTOM) {
          InternalDecorator.this.setBounds(
              0, myLastPoint.y, bounds.width, windowPane.getHeight() - myLastPoint.y);
        } else if (anchor == ToolWindowAnchor.RIGHT) {
          InternalDecorator.this.setBounds(
              myLastPoint.x, 0, windowPane.getWidth() - myLastPoint.x, bounds.height);
        }
        InternalDecorator.this.validate();
        e.consume();
      }
    }

    @NotNull
    @Override
    public Cursor getCursor() {
      final boolean isVerticalCursor =
          myInfo.isDocked()
              ? myInfo.getAnchor().isSplitVertically()
              : myInfo.getAnchor().isHorizontal();
      return isVerticalCursor
          ? Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR)
          : Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR);
    }
  }

  @Override
  public void putInfo(@NotNull Map<String, String> info) {
    info.put("toolWindowTitle", myToolWindow.getTitle());

    final Content selection = myToolWindow.getContentManager().getSelectedContent();
    if (selection != null) {
      info.put("toolWindowTab", selection.getTabName());
    }
  }

  public void setAdditionalGearActions(@Nullable ActionGroup additionalGearActions) {
    myAdditionalGearActions = additionalGearActions;
  }
}
/** @author Robert F. Beeger ([email protected]) */
@State(
    name = "Osmorc",
    storages = {@Storage(file = "$PROJECT_FILE$")})
public class ProjectSettings implements PersistentStateComponent<ProjectSettings> {

  private EventDispatcher<ProjectSettingsListener> dispatcher =
      EventDispatcher.create(ProjectSettingsListener.class);

  @Nullable
  public String getBundlesOutputPath() {
    return _bundlesOutputPath;
  }

  public void setBundlesOutputPath(@Nullable String _bundlesOutputPath) {
    this._bundlesOutputPath = _bundlesOutputPath;
  }

  @NotNull
  public static String getDefaultBundlesOutputPath(Project project) {
    CompilerProjectExtension instance = CompilerProjectExtension.getInstance(project);
    if (instance != null) {
      final VirtualFilePointer compilerOutput = instance.getCompilerOutputPointer();
      if (compilerOutput != null) {
        return VfsUtil.urlToPath(compilerOutput.getUrl()) + "/bundles";
      }
    }
    // this actually should never happen (only in tests)
    return FileUtil.getTempDirectory();
  }

  public static ProjectSettings getInstance(Project project) {
    return ServiceManager.getService(project, ProjectSettings.class);
  }

  @Nullable
  public String getFrameworkInstanceName() {
    return _frameworkInstanceName;
  }

  public void setFrameworkInstanceName(@Nullable String frameworkInstanceName) {
    _frameworkInstanceName = frameworkInstanceName;
    dispatcher.getMulticaster().projectSettingsChanged();
  }

  @NotNull
  public ProjectSettings getState() {
    return this;
  }

  public void loadState(@NotNull ProjectSettings state) {
    XmlSerializerUtil.copyBean(state, this);
  }

  public void setCreateFrameworkInstanceModule(boolean selected) {
    _createFrameworkInstanceModule = selected;
    dispatcher.getMulticaster().projectSettingsChanged();
  }

  public boolean isCreateFrameworkInstanceModule() {
    return _createFrameworkInstanceModule;
  }

  public void setDefaultManifestFileLocation(@NotNull String defaultManifestFileLocation) {
    _defaultManifestFileLocation = defaultManifestFileLocation;
    if (_defaultManifestFileLocation.equals("META-INF")) {
      // we specify full names, so to work with older projects, we have to convert this
      _defaultManifestFileLocation = "META-INF/MANIFEST.MF";
    }
    dispatcher.getMulticaster().projectSettingsChanged();
  }

  public void addProjectSettingsListener(ProjectSettingsListener listener) {
    dispatcher.addListener(listener);
  }

  public void removeProjectSettingsListener(ProjectSettingsListener listener) {
    dispatcher.removeListener(listener);
  }

  @NotNull
  public String getDefaultManifestFileLocation() {
    return _defaultManifestFileLocation;
  }

  private @Nullable String _frameworkInstanceName;
  private boolean _createFrameworkInstanceModule;
  private @NotNull String _defaultManifestFileLocation = "META-INF/MANIFEST.MF";
  private @Nullable String _bundlesOutputPath;

  public interface ProjectSettingsListener extends EventListener {
    void projectSettingsChanged();
  }
}
@State(
    name = "UISettings",
    storages = {@Storage(file = StoragePathMacros.APP_CONFIG + "/ui.lnf.xml")})
public class UISettings extends SimpleModificationTracker
    implements PersistentStateComponent<UISettings>, ExportableApplicationComponent {
  /** Not tabbed pane. */
  public static final int TABS_NONE = 0;

  public static UISettings getInstance() {
    return ApplicationManager.getApplication().getComponent(UISettings.class);
  }

  /**
   * Use this method if you are not sure whether the application is initialized.
   *
   * @return persisted UISettings instance or default values.
   */
  public static UISettings getShadowInstance() {
    Application application = ApplicationManager.getApplication();
    return application != null ? getInstance() : new UISettings();
  }

  @Property(filter = FontFilter.class)
  public String FONT_FACE;

  @Property(filter = FontFilter.class)
  public int FONT_SIZE;

  public int RECENT_FILES_LIMIT = 50;
  public int CONSOLE_COMMAND_HISTORY_LIMIT = 300;
  public int EDITOR_TAB_LIMIT = 10;
  public int EDITOR_TAB_TITLE_LIMIT = 100;
  public boolean ANIMATE_WINDOWS = true;
  public int ANIMATION_SPEED = 2000; // Pixels per second
  public boolean SHOW_TOOL_WINDOW_NUMBERS = true;
  public boolean HIDE_TOOL_STRIPES = true;
  public boolean WIDESCREEN_SUPPORT = false;
  public boolean LEFT_HORIZONTAL_SPLIT = false;
  public boolean RIGHT_HORIZONTAL_SPLIT = false;
  public boolean SHOW_EDITOR_TOOLTIP = true;
  public boolean SHOW_MEMORY_INDICATOR = false;
  public boolean ALLOW_MERGE_BUTTONS = true;
  public boolean SHOW_MAIN_TOOLBAR = false;
  public boolean SHOW_STATUS_BAR = true;
  public boolean SHOW_NAVIGATION_BAR = true;
  public boolean ALWAYS_SHOW_WINDOW_BUTTONS = false;
  public boolean CYCLE_SCROLLING = true;
  public boolean SCROLL_TAB_LAYOUT_IN_EDITOR = false;
  public boolean SHOW_CLOSE_BUTTON = true;
  public int EDITOR_TAB_PLACEMENT = 1;
  public boolean HIDE_KNOWN_EXTENSION_IN_TABS = false;
  public boolean SHOW_ICONS_IN_QUICK_NAVIGATION = true;
  public boolean CLOSE_NON_MODIFIED_FILES_FIRST = false;
  public boolean ACTIVATE_MRU_EDITOR_ON_CLOSE = false;
  public boolean ACTIVATE_RIGHT_EDITOR_ON_CLOSE = false;
  public boolean ANTIALIASING_IN_EDITOR = true;
  public boolean MOVE_MOUSE_ON_DEFAULT_BUTTON = false;
  public boolean ENABLE_ALPHA_MODE = false;
  public int ALPHA_MODE_DELAY = 1500;
  public float ALPHA_MODE_RATIO = 0.5f;
  public int MAX_CLIPBOARD_CONTENTS = 5;
  public boolean OVERRIDE_NONIDEA_LAF_FONTS = false;
  public boolean SHOW_ICONS_IN_MENUS = true;
  public boolean DISABLE_MNEMONICS =
      SystemInfo.isMac; // IDEADEV-33409, should be disabled by default on MacOS
  public boolean DISABLE_MNEMONICS_IN_CONTROLS = false;
  public boolean USE_SMALL_LABELS_ON_TABS = SystemInfo.isMac;
  public boolean SORT_LOOKUP_ELEMENTS_LEXICOGRAPHICALLY = false;
  public int MAX_LOOKUP_WIDTH2 = 500;
  public int MAX_LOOKUP_LIST_HEIGHT = 11;
  public boolean HIDE_NAVIGATION_ON_FOCUS_LOSS = true;
  public boolean DND_WITH_PRESSED_ALT_ONLY = false;
  public boolean FILE_COLORS_IN_PROJECT_VIEW = false;
  public boolean DEFAULT_AUTOSCROLL_TO_SOURCE = false;
  @Transient public boolean PRESENTATION_MODE = false;
  public int PRESENTATION_MODE_FONT_SIZE = 24;
  public boolean MARK_MODIFIED_TABS_WITH_ASTERISK = false;
  public boolean SHOW_TABS_TOOLTIPS = true;
  public boolean SHOW_DIRECTORY_FOR_NON_UNIQUE_FILENAMES = true;

  private final EventDispatcher<UISettingsListener> myDispatcher =
      EventDispatcher.create(UISettingsListener.class);

  public UISettings() {
    tweakPlatformDefaults();
    setSystemFontFaceAndSize();
  }

  private void tweakPlatformDefaults() {
    // TODO[anton] consider making all IDEs use the same settings
    if (PlatformUtilsCore.isAppCode()) {
      SCROLL_TAB_LAYOUT_IN_EDITOR = true;
      ACTIVATE_RIGHT_EDITOR_ON_CLOSE = true;
      SHOW_ICONS_IN_MENUS = false;
    }
  }

  /**
   * @deprecated use {@link UISettings#addUISettingsListener(com.intellij.ide.ui.UISettingsListener,
   *     Disposable disposable)} instead.
   */
  public void addUISettingsListener(UISettingsListener listener) {
    myDispatcher.addListener(listener);
  }

  public void addUISettingsListener(
      @NotNull final UISettingsListener listener, @NotNull Disposable parentDisposable) {
    myDispatcher.addListener(listener, parentDisposable);
  }

  /** Notifies all registered listeners that UI settings has been changed. */
  public void fireUISettingsChanged() {
    incModificationCount();
    myDispatcher.getMulticaster().uiSettingsChanged(this);
    ApplicationManager.getApplication()
        .getMessageBus()
        .syncPublisher(UISettingsListener.TOPIC)
        .uiSettingsChanged(this);
  }

  public void removeUISettingsListener(UISettingsListener listener) {
    myDispatcher.removeListener(listener);
  }

  private void setSystemFontFaceAndSize() {
    if (FONT_FACE == null || FONT_SIZE <= 0) {
      final Pair<String, Integer> fontData = getSystemFontFaceAndSize();
      FONT_FACE = fontData.first;
      FONT_SIZE = fontData.second;
    }
  }

  private static Pair<String, Integer> getSystemFontFaceAndSize() {
    final Pair<String, Integer> fontData = UIUtil.getSystemFontData();
    if (fontData != null) {
      return fontData;
    }

    return Pair.create("Dialog", 12);
  }

  public static class FontFilter implements SerializationFilter {
    public boolean accepts(Accessor accessor, Object bean) {
      UISettings settings = (UISettings) bean;
      return !hasDefaultFontSetting(settings);
    }
  }

  private static boolean hasDefaultFontSetting(final UISettings settings) {
    final Pair<String, Integer> fontData = getSystemFontFaceAndSize();
    return fontData.first.equals(settings.FONT_FACE) && fontData.second.equals(settings.FONT_SIZE);
  }

  public UISettings getState() {
    return this;
  }

  public void loadState(UISettings object) {
    XmlSerializerUtil.copyBean(object, this);

    // Check tab placement in editor
    if (EDITOR_TAB_PLACEMENT != TABS_NONE
        && EDITOR_TAB_PLACEMENT != SwingConstants.TOP
        && EDITOR_TAB_PLACEMENT != SwingConstants.LEFT
        && EDITOR_TAB_PLACEMENT != SwingConstants.BOTTOM
        && EDITOR_TAB_PLACEMENT != SwingConstants.RIGHT) {
      EDITOR_TAB_PLACEMENT = SwingConstants.TOP;
    }

    // Check that alpha delay and ratio are valid
    if (ALPHA_MODE_DELAY < 0) {
      ALPHA_MODE_DELAY = 1500;
    }
    if (ALPHA_MODE_RATIO < 0.0f || ALPHA_MODE_RATIO > 1.0f) {
      ALPHA_MODE_RATIO = 0.5f;
    }

    setSystemFontFaceAndSize();
    // 1. Sometimes system font cannot display standard ASCII symbols. If so we have
    // find any other suitable font withing "preferred" fonts first.
    boolean fontIsValid = isValidFont(new Font(FONT_FACE, Font.PLAIN, FONT_SIZE));
    if (!fontIsValid) {
      @NonNls final String[] preferredFonts = {"dialog", "Arial", "Tahoma"};
      for (String preferredFont : preferredFonts) {
        if (isValidFont(new Font(preferredFont, Font.PLAIN, FONT_SIZE))) {
          FONT_FACE = preferredFont;
          fontIsValid = true;
          break;
        }
      }

      // 2. If all preferred fonts are not valid in current environment
      // we have to find first valid font (if any)
      if (!fontIsValid) {
        String[] fontNames = UIUtil.getValidFontNames(false);
        if (fontNames.length > 0) {
          FONT_FACE = fontNames[0];
        }
      }
    }

    if (MAX_CLIPBOARD_CONTENTS <= 0) {
      MAX_CLIPBOARD_CONTENTS = 5;
    }

    fireUISettingsChanged();
  }

  private static final boolean DEFAULT_ALIASING =
      SystemProperties.getBooleanProperty("idea.use.default.antialiasing.in.editor", false);
  private static final boolean FORCE_USE_FRACTIONAL_METRICS =
      SystemProperties.getBooleanProperty("idea.force.use.fractional.metrics", false);

  public static void setupAntialiasing(final Graphics g) {
    if (DEFAULT_ALIASING) return;

    Graphics2D g2d = (Graphics2D) g;
    UISettings uiSettings = getInstance();

    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
    if (!isRemoteDesktopConnected() && UIUtil.isRetina()) {
      g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    } else {
      if (uiSettings == null || uiSettings.ANTIALIASING_IN_EDITOR) {
        Toolkit tk = Toolkit.getDefaultToolkit();
        //noinspection HardCodedStringLiteral
        Map map = (Map) tk.getDesktopProperty("awt.font.desktophints");
        if (map != null) {
          if (isRemoteDesktopConnected()) {
            g2d.setRenderingHint(
                RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT);
          } else {
            g2d.addRenderingHints(map);
          }
        } else {
          g2d.setRenderingHint(
              RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        }
        if (FORCE_USE_FRACTIONAL_METRICS) {
          g2d.setRenderingHint(
              RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        }
      } else {
        g2d.setRenderingHint(
            RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
      }
    }
  }

  /** @return true when Remote Desktop (i.e. Windows RDP) is connected */
  // TODO[neuro]: move to UIUtil
  public static boolean isRemoteDesktopConnected() {
    if (System.getProperty("os.name").contains("Windows")) {
      final Map map = (Map) Toolkit.getDefaultToolkit().getDesktopProperty("awt.font.desktophints");
      return map != null
          && RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT.equals(
              map.get(RenderingHints.KEY_TEXT_ANTIALIASING));
    }
    return false;
  }

  @NotNull
  @Override
  public File[] getExportFiles() {
    return new File[] {PathManager.getOptionsFile("ui.lnf")};
  }

  @NotNull
  @Override
  public String getPresentableName() {
    return IdeBundle.message("ui.settings");
  }

  @NonNls
  @NotNull
  @Override
  public String getComponentName() {
    return "UISettings";
  }

  @Override
  public void initComponent() {}

  @Override
  public void disposeComponent() {}
}
public class ClasspathPanelImpl extends JPanel implements ClasspathPanel {
  private static final Logger LOG =
      Logger.getInstance(
          "#com.intellij.openapi.roots.ui.configuration.classpath.ClasspathPanelImpl");
  private final JBTable myEntryTable;
  private final ClasspathTableModel myModel;
  private final EventDispatcher<OrderPanelListener> myListeners =
      EventDispatcher.create(OrderPanelListener.class);
  private List<AddItemPopupAction<?>> myPopupActions = null;
  private AnActionButton myEditButton;
  private final ModuleConfigurationState myState;
  private AnActionButton myRemoveButton;

  public ClasspathPanelImpl(ModuleConfigurationState state) {
    super(new BorderLayout());

    myState = state;
    myModel = new ClasspathTableModel(state, getStructureConfigurableContext());
    myEntryTable =
        new JBTable(myModel) {
          @Override
          protected TableRowSorter<TableModel> createRowSorter(TableModel model) {
            return new DefaultColumnInfoBasedRowSorter(model) {
              @Override
              public void toggleSortOrder(int column) {
                if (isSortable(column)) {
                  SortKey oldKey = ContainerUtil.getFirstItem(getSortKeys());
                  SortOrder oldOrder;
                  if (oldKey == null || oldKey.getColumn() != column) {
                    oldOrder = SortOrder.UNSORTED;
                  } else {
                    oldOrder = oldKey.getSortOrder();
                  }
                  setSortKeys(
                      Collections.singletonList(new SortKey(column, getNextSortOrder(oldOrder))));
                }
              }
            };
          }
        };
    myEntryTable.setShowGrid(false);
    myEntryTable.setDragEnabled(false);
    myEntryTable.setIntercellSpacing(new Dimension(0, 0));

    myEntryTable.setDefaultRenderer(
        ClasspathTableItem.class, new TableItemRenderer(getStructureConfigurableContext()));
    myEntryTable.setDefaultRenderer(
        Boolean.class, new ExportFlagRenderer(myEntryTable.getDefaultRenderer(Boolean.class)));

    JComboBox scopeEditor =
        new ComboBox(new EnumComboBoxModel<DependencyScope>(DependencyScope.class));
    myEntryTable.setDefaultEditor(DependencyScope.class, new DefaultCellEditor(scopeEditor));
    myEntryTable.setDefaultRenderer(
        DependencyScope.class,
        new ComboBoxTableRenderer<DependencyScope>(DependencyScope.values()) {
          @Override
          protected String getTextFor(@NotNull final DependencyScope value) {
            return value.getDisplayName();
          }
        });

    myEntryTable.setTransferHandler(
        new TransferHandler() {
          @Nullable
          @Override
          protected Transferable createTransferable(JComponent c) {
            OrderEntry entry = getSelectedEntry();
            if (entry == null) return null;
            String text = entry.getPresentableName();
            return new TextTransferable(text);
          }

          @Override
          public int getSourceActions(JComponent c) {
            return COPY;
          }
        });

    myEntryTable
        .getSelectionModel()
        .setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);

    new SpeedSearchBase<JBTable>(myEntryTable) {
      @Override
      public int getSelectedIndex() {
        return myEntryTable.getSelectedRow();
      }

      @Override
      protected int convertIndexToModel(int viewIndex) {
        return myEntryTable.convertRowIndexToModel(viewIndex);
      }

      @Override
      public Object[] getAllElements() {
        final int count = myModel.getRowCount();
        Object[] elements = new Object[count];
        for (int idx = 0; idx < count; idx++) {
          elements[idx] = myModel.getItem(idx);
        }
        return elements;
      }

      @Override
      public String getElementText(Object element) {
        return getCellAppearance(
                (ClasspathTableItem<?>) element, getStructureConfigurableContext(), false)
            .getText();
      }

      @Override
      public void selectElement(Object element, String selectedText) {
        final int count = myModel.getRowCount();
        for (int row = 0; row < count; row++) {
          if (element.equals(myModel.getItem(row))) {
            final int viewRow = myEntryTable.convertRowIndexToView(row);
            myEntryTable.getSelectionModel().setSelectionInterval(viewRow, viewRow);
            TableUtil.scrollSelectionToVisible(myEntryTable);
            break;
          }
        }
      }
    };
    setFixedColumnWidth(ClasspathTableModel.EXPORT_COLUMN);
    setFixedColumnWidth(ClasspathTableModel.SCOPE_COLUMN); // leave space for combobox border

    myEntryTable.registerKeyboardAction(
        new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent e) {
            final int[] selectedRows = myEntryTable.getSelectedRows();
            boolean currentlyMarked = true;
            for (final int selectedRow : selectedRows) {
              final ClasspathTableItem<?> item = getItemAt(selectedRow);
              if (selectedRow < 0 || !item.isExportable()) {
                return;
              }
              currentlyMarked &= item.isExported();
            }
            for (final int selectedRow : selectedRows) {
              getItemAt(selectedRow).setExported(!currentlyMarked);
            }
            myModel.fireTableDataChanged();
            TableUtil.selectRows(myEntryTable, selectedRows);
          }
        },
        KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0),
        WHEN_FOCUSED);

    myEditButton =
        new AnActionButton(
            ProjectBundle.message("module.classpath.button.edit"), null, IconUtil.getEditIcon()) {
          @Override
          public void actionPerformed(@NotNull AnActionEvent e) {
            doEdit();
          }

          @Override
          public boolean isEnabled() {
            ClasspathTableItem<?> selectedItem = getSelectedItem();
            return selectedItem != null && selectedItem.isEditable();
          }

          @Override
          public boolean isDumbAware() {
            return true;
          }
        };
    add(createTableWithButtons(), BorderLayout.CENTER);

    if (myEntryTable.getRowCount() > 0) {
      myEntryTable.getSelectionModel().setSelectionInterval(0, 0);
    }

    new DoubleClickListener() {
      @Override
      protected boolean onDoubleClick(MouseEvent e) {
        navigate(true);
        return true;
      }
    }.installOn(myEntryTable);

    DefaultActionGroup actionGroup = new DefaultActionGroup();
    final AnAction navigateAction =
        new AnAction(ProjectBundle.message("classpath.panel.navigate.action.text")) {
          @Override
          public void actionPerformed(@NotNull AnActionEvent e) {
            navigate(false);
          }

          @Override
          public void update(@NotNull AnActionEvent e) {
            final Presentation presentation = e.getPresentation();
            presentation.setEnabled(false);
            final OrderEntry entry = getSelectedEntry();
            if (entry != null && entry.isValid()) {
              if (!(entry instanceof ModuleSourceOrderEntry)) {
                presentation.setEnabled(true);
              }
            }
          }
        };
    navigateAction.registerCustomShortcutSet(
        ActionManager.getInstance().getAction(IdeActions.ACTION_EDIT_SOURCE).getShortcutSet(),
        myEntryTable);
    actionGroup.add(myEditButton);
    actionGroup.add(myRemoveButton);
    actionGroup.add(navigateAction);
    actionGroup.add(new InlineModuleDependencyAction(this));
    actionGroup.add(new MyFindUsagesAction());
    actionGroup.add(new AnalyzeDependencyAction());
    addChangeLibraryLevelAction(actionGroup, LibraryTablesRegistrar.PROJECT_LEVEL);
    addChangeLibraryLevelAction(actionGroup, LibraryTablesRegistrar.APPLICATION_LEVEL);
    addChangeLibraryLevelAction(actionGroup, LibraryTableImplUtil.MODULE_LEVEL);
    PopupHandler.installPopupHandler(
        myEntryTable, actionGroup, ActionPlaces.UNKNOWN, ActionManager.getInstance());
  }

  @NotNull
  private static SortOrder getNextSortOrder(@NotNull SortOrder order) {
    switch (order) {
      case ASCENDING:
        return SortOrder.DESCENDING;
      case DESCENDING:
        return SortOrder.UNSORTED;
      case UNSORTED:
      default:
        return SortOrder.ASCENDING;
    }
  }

  private ClasspathTableItem<?> getItemAt(int selectedRow) {
    return myModel.getItem(myEntryTable.convertRowIndexToModel(selectedRow));
  }

  private void addChangeLibraryLevelAction(DefaultActionGroup actionGroup, String tableLevel) {
    final LibraryTablePresentation presentation =
        LibraryEditingUtil.getLibraryTablePresentation(getProject(), tableLevel);
    actionGroup.add(
        new ChangeLibraryLevelInClasspathAction(
            this, presentation.getDisplayName(true), tableLevel));
  }

  @Override
  @Nullable
  public OrderEntry getSelectedEntry() {
    ClasspathTableItem<?> item = getSelectedItem();
    return item != null ? item.getEntry() : null;
  }

  @Nullable
  private ClasspathTableItem<?> getSelectedItem() {
    if (myEntryTable.getSelectedRowCount() != 1) return null;
    return getItemAt(myEntryTable.getSelectedRow());
  }

  private void setFixedColumnWidth(final int columnIndex) {
    final TableColumn column =
        myEntryTable.getTableHeader().getColumnModel().getColumn(columnIndex);
    column.setResizable(false);
    column.setMaxWidth(column.getPreferredWidth());
  }

  @Override
  public void navigate(boolean openLibraryEditor) {
    final OrderEntry entry = getSelectedEntry();
    final ProjectStructureConfigurable rootConfigurable =
        ProjectStructureConfigurable.getInstance(myState.getProject());
    if (entry instanceof ModuleOrderEntry) {
      Module module = ((ModuleOrderEntry) entry).getModule();
      if (module != null) {
        rootConfigurable.select(module.getName(), null, true);
      }
    } else if (entry instanceof LibraryOrderEntry) {
      if (!openLibraryEditor
          && !((LibraryOrderEntry) entry)
              .getLibraryLevel()
              .equals(LibraryTableImplUtil.MODULE_LEVEL)) {
        rootConfigurable.select((LibraryOrderEntry) entry, true);
      } else {
        doEdit();
      }
    } else if (entry instanceof JdkOrderEntry) {
      Sdk jdk = ((JdkOrderEntry) entry).getJdk();
      if (jdk != null) {
        rootConfigurable.select(jdk, true);
      }
    }
  }

  private JComponent createTableWithButtons() {
    final boolean isAnalyzeShown = false;

    final ClasspathPanelAction removeAction =
        new ClasspathPanelAction(this) {
          @Override
          public void run() {
            removeSelectedItems(TableUtil.removeSelectedItems(myEntryTable));
          }
        };

    final AnActionButton analyzeButton =
        new AnActionButton(
            ProjectBundle.message("classpath.panel.analyze"), null, IconUtil.getAnalyzeIcon()) {
          @Override
          public void actionPerformed(@NotNull AnActionEvent e) {
            AnalyzeDependenciesDialog.show(getRootModel().getModule());
          }
        };

    // addButton.setShortcut(CustomShortcutSet.fromString("alt A", "INSERT"));
    // removeButton.setShortcut(CustomShortcutSet.fromString("alt DELETE"));
    // upButton.setShortcut(CustomShortcutSet.fromString("alt UP"));
    // downButton.setShortcut(CustomShortcutSet.fromString("alt DOWN"));

    final ToolbarDecorator decorator = ToolbarDecorator.createDecorator(myEntryTable);
    AnActionButtonUpdater moveUpDownUpdater =
        new AnActionButtonUpdater() {
          @Override
          public boolean isEnabled(AnActionEvent e) {
            for (RowSorter.SortKey key : myEntryTable.getRowSorter().getSortKeys()) {
              if (key.getSortOrder() != SortOrder.UNSORTED) {
                return false;
              }
            }
            return true;
          }
        };
    decorator
        .setAddAction(
            new AnActionButtonRunnable() {
              @Override
              public void run(AnActionButton button) {
                initPopupActions();
                final JBPopup popup =
                    JBPopupFactory.getInstance()
                        .createListPopup(
                            new BaseListPopupStep<AddItemPopupAction<?>>(null, myPopupActions) {
                              @Override
                              public Icon getIconFor(AddItemPopupAction<?> aValue) {
                                return aValue.getIcon();
                              }

                              @Override
                              public boolean hasSubstep(AddItemPopupAction<?> selectedValue) {
                                return selectedValue.hasSubStep();
                              }

                              @Override
                              public boolean isMnemonicsNavigationEnabled() {
                                return true;
                              }

                              @Override
                              public PopupStep onChosen(
                                  final AddItemPopupAction<?> selectedValue,
                                  final boolean finalChoice) {
                                if (selectedValue.hasSubStep()) {
                                  return selectedValue.createSubStep();
                                }
                                return doFinalStep(
                                    new Runnable() {
                                      @Override
                                      public void run() {
                                        selectedValue.execute();
                                      }
                                    });
                              }

                              @Override
                              @NotNull
                              public String getTextFor(AddItemPopupAction<?> value) {
                                return "&" + value.getIndex() + "  " + value.getTitle();
                              }
                            });
                popup.show(button.getPreferredPopupPoint());
              }
            })
        .setRemoveAction(
            new AnActionButtonRunnable() {
              @Override
              public void run(AnActionButton button) {
                removeAction.actionPerformed(null);
              }
            })
        .setRemoveActionUpdater(
            new AnActionButtonUpdater() {
              @Override
              public boolean isEnabled(AnActionEvent e) {
                final int[] selectedRows = myEntryTable.getSelectedRows();
                for (final int selectedRow : selectedRows) {
                  if (!getItemAt(selectedRow).isRemovable()) {
                    return false;
                  }
                }
                return selectedRows.length > 0;
              }
            })
        .setMoveUpAction(
            new AnActionButtonRunnable() {
              @Override
              public void run(AnActionButton button) {
                moveSelectedRows(-1);
              }
            })
        .setMoveUpActionUpdater(moveUpDownUpdater)
        .setMoveUpActionName("Move Up (disabled if items are shown in sorted order)")
        .setMoveDownAction(
            new AnActionButtonRunnable() {
              @Override
              public void run(AnActionButton button) {
                moveSelectedRows(+1);
              }
            })
        .setMoveDownActionUpdater(moveUpDownUpdater)
        .setMoveDownActionName("Move Down (disabled if items are shown in sorted order)")
        .addExtraAction(myEditButton);
    if (isAnalyzeShown) {
      decorator.addExtraAction(analyzeButton);
    }

    final JPanel panel = decorator.createPanel();
    myRemoveButton = ToolbarDecorator.findRemoveButton(panel);
    return panel;
  }

  private void doEdit() {
    final OrderEntry entry = getSelectedEntry();
    if (!(entry instanceof LibraryOrderEntry)) return;

    final Library library = ((LibraryOrderEntry) entry).getLibrary();
    if (library == null) {
      return;
    }
    final LibraryTable table = library.getTable();
    final String tableLevel =
        table != null ? table.getTableLevel() : LibraryTableImplUtil.MODULE_LEVEL;
    final LibraryTablePresentation presentation =
        LibraryEditingUtil.getLibraryTablePresentation(getProject(), tableLevel);
    final LibraryTableModifiableModelProvider provider = getModifiableModelProvider(tableLevel);
    EditExistingLibraryDialog dialog =
        EditExistingLibraryDialog.createDialog(
            this,
            provider,
            library,
            myState.getProject(),
            presentation,
            getStructureConfigurableContext());
    dialog.setContextModule(getRootModel().getModule());
    dialog.show();
    myEntryTable.repaint();
    ModuleStructureConfigurable.getInstance(myState.getProject()).getTree().repaint();
  }

  private void removeSelectedItems(final List removedRows) {
    if (removedRows.isEmpty()) {
      return;
    }
    for (final Object removedRow : removedRows) {
      final ClasspathTableItem<?> item =
          (ClasspathTableItem<?>) ((Object[]) removedRow)[ClasspathTableModel.ITEM_COLUMN];
      final OrderEntry orderEntry = item.getEntry();
      if (orderEntry == null) {
        continue;
      }

      getRootModel().removeOrderEntry(orderEntry);
    }
    final int[] selectedRows = myEntryTable.getSelectedRows();
    myModel.fireTableDataChanged();
    TableUtil.selectRows(myEntryTable, selectedRows);
    final StructureConfigurableContext context =
        ModuleStructureConfigurable.getInstance(myState.getProject()).getContext();
    context
        .getDaemonAnalyzer()
        .queueUpdate(new ModuleProjectStructureElement(context, getRootModel().getModule()));
  }

  @Override
  @NotNull
  public LibraryTableModifiableModelProvider getModifiableModelProvider(
      @NotNull String tableLevel) {
    if (LibraryTableImplUtil.MODULE_LEVEL.equals(tableLevel)) {
      final LibraryTable moduleLibraryTable = getRootModel().getModuleLibraryTable();
      return new LibraryTableModifiableModelProvider() {
        @Override
        public LibraryTable.ModifiableModel getModifiableModel() {
          return moduleLibraryTable.getModifiableModel();
        }
      };
    } else {
      return getStructureConfigurableContext().createModifiableModelProvider(tableLevel);
    }
  }

  @Override
  public void runClasspathPanelAction(Runnable action) {
    try {
      disableModelUpdate();
      action.run();
    } finally {
      enableModelUpdate();
      myEntryTable.requestFocus();
    }
  }

  @Override
  public void addItems(List<ClasspathTableItem<?>> toAdd) {
    for (ClasspathTableItem<?> item : toAdd) {
      myModel.addRow(item);
    }
    TIntArrayList toSelect = new TIntArrayList();
    for (int i = myModel.getRowCount() - toAdd.size(); i < myModel.getRowCount(); i++) {
      toSelect.add(myEntryTable.convertRowIndexToView(i));
    }
    TableUtil.selectRows(myEntryTable, toSelect.toNativeArray());
    TableUtil.scrollSelectionToVisible(myEntryTable);

    final StructureConfigurableContext context =
        ModuleStructureConfigurable.getInstance(myState.getProject()).getContext();
    context
        .getDaemonAnalyzer()
        .queueUpdate(new ModuleProjectStructureElement(context, getRootModel().getModule()));
  }

  @Override
  public ModifiableRootModel getRootModel() {
    return myState.getRootModel();
  }

  @Override
  public Project getProject() {
    return myState.getProject();
  }

  @Override
  public ModuleConfigurationState getModuleConfigurationState() {
    return myState;
  }

  @Override
  public JComponent getComponent() {
    return this;
  }

  public void rootsChanged() {
    forceInitFromModel();
  }

  private void initPopupActions() {
    if (myPopupActions == null) {
      int actionIndex = 1;
      final List<AddItemPopupAction<?>> actions = new ArrayList<AddItemPopupAction<?>>();
      final StructureConfigurableContext context = getStructureConfigurableContext();
      actions.add(new AddNewModuleLibraryAction(this, actionIndex++, context));
      actions.add(
          new AddLibraryDependencyAction(
              this, actionIndex++, ProjectBundle.message("classpath.add.library.action"), context));
      actions.add(new AddModuleDependencyAction(this, actionIndex, context));

      myPopupActions = actions;
    }
  }

  private StructureConfigurableContext getStructureConfigurableContext() {
    return ProjectStructureConfigurable.getInstance(myState.getProject()).getContext();
  }

  private void enableModelUpdate() {
    myInsideChange--;
  }

  private void disableModelUpdate() {
    myInsideChange++;
  }

  public void addListener(OrderPanelListener listener) {
    myListeners.addListener(listener);
  }

  public void removeListener(OrderPanelListener listener) {
    myListeners.removeListener(listener);
  }

  private void moveSelectedRows(int increment) {
    LOG.assertTrue(increment == -1 || increment == 1);
    if (myEntryTable.isEditing()) {
      myEntryTable.getCellEditor().stopCellEditing();
    }
    final ListSelectionModel selectionModel = myEntryTable.getSelectionModel();
    for (int row = increment < 0 ? 0 : myModel.getRowCount() - 1;
        increment < 0 ? row < myModel.getRowCount() : row >= 0;
        row += increment < 0 ? +1 : -1) {
      if (selectionModel.isSelectedIndex(row)) {
        final int newRow = moveRow(row, increment);
        selectionModel.removeSelectionInterval(row, row);
        selectionModel.addSelectionInterval(newRow, newRow);
      }
    }
    Rectangle cellRect = myEntryTable.getCellRect(selectionModel.getMinSelectionIndex(), 0, true);
    myEntryTable.scrollRectToVisible(cellRect);
    myEntryTable.repaint();
  }

  public void selectOrderEntry(@NotNull OrderEntry entry) {
    for (int row = 0; row < myModel.getRowCount(); row++) {
      final OrderEntry orderEntry = getItemAt(row).getEntry();
      if (orderEntry != null
          && entry.getPresentableName().equals(orderEntry.getPresentableName())) {
        myEntryTable.getSelectionModel().setSelectionInterval(row, row);
        TableUtil.scrollSelectionToVisible(myEntryTable);
      }
    }
    IdeFocusManager.getInstance(myState.getProject()).requestFocus(myEntryTable, true);
  }

  private int moveRow(final int row, final int increment) {
    int newIndex = Math.abs(row + increment) % myModel.getRowCount();
    myModel.exchangeRows(row, newIndex);
    return newIndex;
  }

  public void stopEditing() {
    TableUtil.stopEditing(myEntryTable);
  }

  private int myInsideChange = 0;

  public void initFromModel() {
    if (myInsideChange == 0) {
      forceInitFromModel();
    }
  }

  public void forceInitFromModel() {
    Set<ClasspathTableItem<?>> oldSelection = new HashSet<ClasspathTableItem<?>>();
    for (int i : myEntryTable.getSelectedRows()) {
      ContainerUtil.addIfNotNull(getItemAt(i), oldSelection);
    }
    myModel.clear();
    myModel.init();
    myModel.fireTableDataChanged();
    TIntArrayList newSelection = new TIntArrayList();
    for (int i = 0; i < myModel.getRowCount(); i++) {
      if (oldSelection.contains(getItemAt(i))) {
        newSelection.add(i);
      }
    }
    TableUtil.selectRows(myEntryTable, newSelection.toNativeArray());
  }

  static CellAppearanceEx getCellAppearance(
      final ClasspathTableItem<?> item,
      final StructureConfigurableContext context,
      final boolean selected) {
    final OrderEntryAppearanceService service = OrderEntryAppearanceService.getInstance();
    if (item instanceof InvalidJdkItem) {
      return service.forJdk(null, false, selected, true);
    } else {
      final OrderEntry entry = item.getEntry();
      assert entry != null : item;
      return service.forOrderEntry(context.getProject(), entry, selected);
    }
  }

  private static class TableItemRenderer extends ColoredTableCellRenderer {
    private final Border NO_FOCUS_BORDER = BorderFactory.createEmptyBorder(1, 1, 1, 1);
    private StructureConfigurableContext myContext;

    public TableItemRenderer(StructureConfigurableContext context) {
      myContext = context;
    }

    @Override
    protected void customizeCellRenderer(
        JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) {
      setPaintFocusBorder(false);
      setFocusBorderAroundIcon(true);
      setBorder(NO_FOCUS_BORDER);
      if (value instanceof ClasspathTableItem<?>) {
        final ClasspathTableItem<?> tableItem = (ClasspathTableItem<?>) value;
        getCellAppearance(tableItem, myContext, selected).customize(this);
        setToolTipText(tableItem.getTooltipText());
      }
    }
  }

  private static class ExportFlagRenderer implements TableCellRenderer {
    private final TableCellRenderer myDelegate;
    private final JPanel myBlankPanel;

    public ExportFlagRenderer(TableCellRenderer delegate) {
      myDelegate = delegate;
      myBlankPanel = new JPanel();
    }

    @Override
    public Component getTableCellRendererComponent(
        JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
      if (!table.isCellEditable(row, column)) {
        myBlankPanel.setBackground(
            isSelected ? table.getSelectionBackground() : table.getBackground());
        return myBlankPanel;
      }
      return myDelegate.getTableCellRendererComponent(
          table, value, isSelected, hasFocus, row, column);
    }
  }

  private class MyFindUsagesAction extends FindUsagesInProjectStructureActionBase {
    private MyFindUsagesAction() {
      super(myEntryTable, myState.getProject());
    }

    @Override
    protected boolean isEnabled() {
      return getSelectedElement() != null;
    }

    @Override
    protected ProjectStructureElement getSelectedElement() {
      final OrderEntry entry = getSelectedEntry();
      if (entry instanceof LibraryOrderEntry) {
        final Library library = ((LibraryOrderEntry) entry).getLibrary();
        if (library != null) {
          return new LibraryProjectStructureElement(getContext(), library);
        }
      } else if (entry instanceof ModuleOrderEntry) {
        final Module module = ((ModuleOrderEntry) entry).getModule();
        if (module != null) {
          return new ModuleProjectStructureElement(getContext(), module);
        }
      } else if (entry instanceof JdkOrderEntry) {
        final Sdk jdk = ((JdkOrderEntry) entry).getJdk();
        if (jdk != null) {
          return new SdkProjectStructureElement(getContext(), jdk);
        }
      }
      return null;
    }

    @Override
    protected RelativePoint getPointToShowResults() {
      Rectangle rect = myEntryTable.getCellRect(myEntryTable.getSelectedRow(), 1, false);
      Point location = rect.getLocation();
      location.y += rect.height;
      return new RelativePoint(myEntryTable, location);
    }
  }

  private class AnalyzeDependencyAction extends AnAction {
    private AnalyzeDependencyAction() {
      super("Analyze This Dependency");
    }

    @Override
    public void actionPerformed(@NotNull AnActionEvent e) {
      final OrderEntry selectedEntry = getSelectedEntry();
      GlobalSearchScope targetScope;
      if (selectedEntry instanceof ModuleOrderEntry) {
        final Module module = ((ModuleOrderEntry) selectedEntry).getModule();
        LOG.assertTrue(module != null);
        targetScope = GlobalSearchScope.moduleScope(module);
      } else {
        Library library = ((LibraryOrderEntry) selectedEntry).getLibrary();
        LOG.assertTrue(library != null);
        targetScope = new LibraryScope(getProject(), library);
      }
      new AnalyzeDependenciesOnSpecifiedTargetHandler(
          getProject(), new AnalysisScope(myState.getRootModel().getModule()), targetScope) {
        @Override
        protected boolean shouldShowDependenciesPanel(List<DependenciesBuilder> builders) {
          for (DependenciesBuilder builder : builders) {
            for (Set<PsiFile> files : builder.getDependencies().values()) {
              if (!files.isEmpty()) {
                Messages.showInfoMessage(
                    myProject,
                    "Dependencies were successfully collected in \""
                        + ToolWindowId.DEPENDENCIES
                        + "\" toolwindow",
                    FindBundle.message("find.pointcut.applications.not.found.title"));
                return true;
              }
            }
          }
          if (Messages.showOkCancelDialog(
                  myProject,
                  "No code dependencies were found. Would you like to remove the dependency?",
                  CommonBundle.getWarningTitle(),
                  Messages.getWarningIcon())
              == Messages.OK) {
            removeSelectedItems(TableUtil.removeSelectedItems(myEntryTable));
          }
          return false;
        }
      }.analyze();
    }

    @Override
    public void update(@NotNull AnActionEvent e) {
      final OrderEntry entry = getSelectedEntry();
      e.getPresentation()
          .setVisible(
              entry instanceof ModuleOrderEntry && ((ModuleOrderEntry) entry).getModule() != null
                  || entry instanceof LibraryOrderEntry
                      && ((LibraryOrderEntry) entry).getLibrary() != null);
    }
  }
}
/** Created with IntelliJ IDEA. User: Irina.Chernushina Date: 1/25/12 Time: 12:58 PM */
public abstract class SvnCommand {
  static final Logger LOG = Logger.getInstance(SvnCommand.class.getName());
  private final File myConfigDir;

  private boolean myIsDestroyed;
  private int myExitCode;
  protected final GeneralCommandLine myCommandLine;
  private final File myWorkingDirectory;
  private Process myProcess;
  private OSProcessHandler myHandler;
  // TODO: Try to implement commands in a way that they manually indicate if they need full output -
  // to prevent situations
  // TODO: when large amount of data needs to be stored instead of just sequential processing.
  private CapturingProcessAdapter outputAdapter;
  private final Object myLock;

  private final EventDispatcher<ProcessEventListener> myListeners =
      EventDispatcher.create(ProcessEventListener.class);
  private final SvnCommandName myCommandName;

  public SvnCommand(
      File workingDirectory, @NotNull SvnCommandName commandName, @NotNull @NonNls String exePath) {
    this(workingDirectory, commandName, exePath, null);
  }

  public SvnCommand(
      File workingDirectory,
      @NotNull SvnCommandName commandName,
      @NotNull @NonNls String exePath,
      @Nullable File configDir) {
    myCommandName = commandName;
    myLock = new Object();
    myCommandLine = new GeneralCommandLine();
    myWorkingDirectory = workingDirectory;
    myCommandLine.setExePath(exePath);
    myCommandLine.setWorkDirectory(workingDirectory);
    myConfigDir = configDir;
    if (configDir != null) {
      myCommandLine.addParameters("--config-dir", configDir.getPath());
    }
    myCommandLine.addParameter(commandName.getName());
  }

  public String[] getParameters() {
    synchronized (myLock) {
      return myCommandLine.getParametersList().getArray();
    }
  }

  /**
   * Indicates if process was destroyed "manually" by command execution logic.
   *
   * @return
   */
  public boolean isManuallyDestroyed() {
    return myIsDestroyed;
  }

  public void start() {
    synchronized (myLock) {
      checkNotStarted();

      try {
        myProcess = myCommandLine.createProcess();
        if (LOG.isDebugEnabled()) {
          LOG.debug(myCommandLine.toString());
        }
        myHandler = new OSProcessHandler(myProcess, myCommandLine.getCommandLineString());
        startHandlingStreams();
      } catch (Throwable t) {
        myListeners.getMulticaster().startFailed(t);
      }
    }
  }

  private void startHandlingStreams() {
    final ProcessListener processListener =
        new ProcessListener() {
          public void startNotified(final ProcessEvent event) {
            // do nothing
          }

          public void processTerminated(final ProcessEvent event) {
            final int exitCode = event.getExitCode();
            try {
              setExitCode(exitCode);
              SvnCommand.this.processTerminated(exitCode);
            } finally {
              listeners().processTerminated(exitCode);
            }
          }

          public void processWillTerminate(
              final ProcessEvent event, final boolean willBeDestroyed) {
            // do nothing
          }

          public void onTextAvailable(final ProcessEvent event, final Key outputType) {
            SvnCommand.this.onTextAvailable(event.getText(), outputType);
          }
        };

    outputAdapter = new CapturingProcessAdapter();
    myHandler.addProcessListener(outputAdapter);
    myHandler.addProcessListener(processListener);
    myHandler.startNotify();
  }

  public String getOutput() {
    return outputAdapter.getOutput().getStdout();
  }

  public String getErrorOutput() {
    return outputAdapter.getOutput().getStderr();
  }

  /**
   * Wait for process termination
   *
   * @param timeout
   */
  public boolean waitFor(int timeout) {
    checkStarted();
    final OSProcessHandler handler;
    synchronized (myLock) {
      // TODO: This line seems to cause situation when exitCode is not set before
      // SvnLineCommand.runCommand() is finished.
      // TODO: Carefully analyze behavior (on all operating systems) and fix.
      if (myIsDestroyed) return true;
      handler = myHandler;
    }
    if (timeout == -1) {
      return handler.waitFor();
    } else {
      return handler.waitFor(timeout);
    }
  }

  protected abstract void processTerminated(int exitCode);

  protected abstract void onTextAvailable(final String text, final Key outputType);

  public void cancel() {
    synchronized (myLock) {
      checkStarted();
      destroyProcess();
    }
  }

  protected void setExitCode(final int code) {
    synchronized (myLock) {
      myExitCode = code;
    }
  }

  public void addListener(final ProcessEventListener listener) {
    synchronized (myLock) {
      myListeners.addListener(listener);
    }
  }

  protected ProcessEventListener listeners() {
    synchronized (myLock) {
      return myListeners.getMulticaster();
    }
  }

  public void addParameters(@NonNls @NotNull String... parameters) {
    synchronized (myLock) {
      checkNotStarted();
      myCommandLine.addParameters(parameters);
    }
  }

  public void addParameters(List<String> parameters) {
    synchronized (myLock) {
      checkNotStarted();
      myCommandLine.addParameters(parameters);
    }
  }

  public void destroyProcess() {
    synchronized (myLock) {
      if (!myIsDestroyed) {
        myIsDestroyed = true;
        myHandler.destroyProcess();
      }
    }
  }

  public String getCommandText() {
    synchronized (myLock) {
      return myCommandLine.getCommandLineString();
    }
  }

  public String getExePath() {
    synchronized (myLock) {
      return myCommandLine.getExePath();
    }
  }

  /**
   * check that process is not started yet
   *
   * @throws IllegalStateException if process has been already started
   */
  private void checkNotStarted() {
    if (isStarted()) {
      throw new IllegalStateException("The process has been already started");
    }
  }

  /**
   * check that process is started
   *
   * @throws IllegalStateException if process has not been started
   */
  protected void checkStarted() {
    if (!isStarted()) {
      throw new IllegalStateException("The process is not started yet");
    }
  }

  /** @return true if process is started */
  public boolean isStarted() {
    synchronized (myLock) {
      return myProcess != null;
    }
  }

  protected int getExitCode() {
    synchronized (myLock) {
      return myExitCode;
    }
  }

  protected File getWorkingDirectory() {
    return myWorkingDirectory;
  }

  public SvnCommandName getCommandName() {
    return myCommandName;
  }
}