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);
 }
 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;
 }
  /**
   * @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()]);
  }
 /** @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;
 }
/** Author: msk */
public class EditorsSplitters extends IdePanePanel implements UISettingsListener, Disposable {
  private static final Logger LOG =
      Logger.getInstance("#com.intellij.openapi.fileEditor.impl.EditorsSplitters");
  private static final String PINNED = "pinned";
  private static final String CURRENT_IN_TAB = "current-in-tab";

  private static final Key<Object> DUMMY_KEY = Key.create("EditorsSplitters.dummy.key");

  private EditorWindow myCurrentWindow;
  private final Set<EditorWindow> myWindows = new CopyOnWriteArraySet<EditorWindow>();

  private final FileEditorManagerImpl myManager;
  private Element mySplittersElement; // temporarily used during initialization
  int myInsideChange;
  private final MyFocusWatcher myFocusWatcher;
  private final Alarm myIconUpdaterAlarm = new Alarm();
  private final UIBuilder myUIBuilder = new UIBuilder();

  EditorsSplitters(
      final FileEditorManagerImpl manager,
      DockManager dockManager,
      boolean createOwnDockableContainer) {
    super(new BorderLayout());
    myManager = manager;
    myFocusWatcher = new MyFocusWatcher();
    setFocusTraversalPolicy(new MyFocusTraversalPolicy());
    clear();

    if (createOwnDockableContainer) {
      DockableEditorTabbedContainer dockable =
          new DockableEditorTabbedContainer(myManager.getProject(), this, false);
      Disposer.register(manager.getProject(), dockable);
      dockManager.register(dockable);
    }
    KeymapManagerListener keymapListener =
        new KeymapManagerListener() {
          @Override
          public void activeKeymapChanged(Keymap keymap) {
            invalidate();
            repaint();
          }
        };
    KeymapManager.getInstance().addKeymapManagerListener(keymapListener, this);
  }

  public FileEditorManagerImpl getManager() {
    return myManager;
  }

  public void clear() {
    for (EditorWindow window : myWindows) {
      window.dispose();
    }
    removeAll();
    myWindows.clear();
    setCurrentWindow(null);
    repaint(); // revalidate doesn't repaint correctly after "Close All"
  }

  void startListeningFocus() {
    myFocusWatcher.install(this);
  }

  private void stopListeningFocus() {
    myFocusWatcher.deinstall(this);
  }

  @Override
  public void dispose() {
    myIconUpdaterAlarm.cancelAllRequests();
    stopListeningFocus();
  }

  @Nullable
  public VirtualFile getCurrentFile() {
    if (myCurrentWindow != null) {
      return myCurrentWindow.getSelectedFile();
    }
    return null;
  }

  private boolean showEmptyText() {
    return myCurrentWindow == null || myCurrentWindow.getFiles().length == 0;
  }

  @Override
  protected void paintComponent(Graphics g) {
    if (showEmptyText()) {
      Graphics2D gg = IdeBackgroundUtil.withFrameBackground(g, this);
      super.paintComponent(gg);
      g.setColor(UIUtil.isUnderDarcula() ? UIUtil.getBorderColor() : new Color(0, 0, 0, 50));
      g.drawLine(0, 0, getWidth(), 0);
    }
  }

  public void writeExternal(final Element element) {
    if (getComponentCount() != 0) {
      final Component comp = getComponent(0);
      LOG.assertTrue(comp instanceof JPanel);
      final JPanel panel = (JPanel) comp;
      if (panel.getComponentCount() != 0) {
        element.addContent(writePanel(panel));
      }
    }
  }

  @SuppressWarnings("HardCodedStringLiteral")
  private Element writePanel(final JPanel panel) {
    final Component comp = panel.getComponent(0);
    if (comp instanceof Splitter) {
      final Splitter splitter = (Splitter) comp;
      final Element res = new Element("splitter");
      res.setAttribute("split-orientation", splitter.getOrientation() ? "vertical" : "horizontal");
      res.setAttribute("split-proportion", Float.toString(splitter.getProportion()));
      final Element first = new Element("split-first");
      first.addContent(writePanel((JPanel) splitter.getFirstComponent()));
      final Element second = new Element("split-second");
      second.addContent(writePanel((JPanel) splitter.getSecondComponent()));
      res.addContent(first);
      res.addContent(second);
      return res;
    } else if (comp instanceof JBTabs) {
      final Element res = new Element("leaf");
      Integer limit =
          UIUtil.getClientProperty(
              ((JBTabs) comp).getComponent(), JBTabsImpl.SIDE_TABS_SIZE_LIMIT_KEY);
      if (limit != null) {
        res.setAttribute(JBTabsImpl.SIDE_TABS_SIZE_LIMIT_KEY.toString(), String.valueOf(limit));
      }

      writeWindow(res, findWindowWith(comp));
      return res;
    } else if (comp instanceof EditorWindow.TCompForTablessMode) {
      EditorWithProviderComposite composite = ((EditorWindow.TCompForTablessMode) comp).myEditor;
      Element res = new Element("leaf");
      res.addContent(writeComposite(composite.getFile(), composite, false, composite));
      return res;
    } else {
      LOG.error(comp != null ? comp.getClass().getName() : null);
      return null;
    }
  }

  private void writeWindow(@NotNull Element res, @Nullable EditorWindow window) {
    if (window != null) {
      EditorWithProviderComposite[] composites = window.getEditors();
      for (int i = 0; i < composites.length; i++) {
        VirtualFile file = window.getFileAt(i);
        res.addContent(
            writeComposite(
                file, composites[i], window.isFilePinned(file), window.getSelectedEditor()));
      }
    }
  }

  @NotNull
  private Element writeComposite(
      VirtualFile file,
      EditorWithProviderComposite composite,
      boolean pinned,
      EditorWithProviderComposite selectedEditor) {
    Element fileElement = new Element("file");
    fileElement.setAttribute("leaf-file-name", file.getName()); // TODO: all files
    composite.currentStateAsHistoryEntry().writeExternal(fileElement, getManager().getProject());
    fileElement.setAttribute(PINNED, Boolean.toString(pinned));
    fileElement.setAttribute(CURRENT_IN_TAB, Boolean.toString(composite.equals(selectedEditor)));
    return fileElement;
  }

  public void openFiles() {
    if (mySplittersElement != null) {
      initializeProgress();
      final JPanel comp = myUIBuilder.process(mySplittersElement, getTopPanel());
      UIUtil.invokeAndWaitIfNeeded(
          new Runnable() {
            @Override
            public void run() {
              if (comp != null) {
                removeAll();
                add(comp, BorderLayout.CENTER);
                mySplittersElement = null;
              }
              // clear empty splitters
              for (EditorWindow window : getWindows()) {
                if (window.getEditors().length == 0) {
                  for (EditorWindow sibling : window.findSiblings()) {
                    sibling.unsplit(false);
                  }
                }
              }
            }
          });
    }
  }

  private static void initializeProgress() {
    ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
    if (indicator != null) {
      indicator.setText(IdeBundle.message("loading.editors"));
    }
  }

  public int getEditorsCount() {
    return mySplittersElement == null ? 0 : countFiles(mySplittersElement);
  }

  private double myProgressStep;

  public void setProgressStep(double step) {
    myProgressStep = step;
  }

  private void updateProgress() {
    ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
    if (indicator != null) {
      indicator.setFraction(indicator.getFraction() + myProgressStep);
    }
  }

  private static int countFiles(Element element) {
    Integer value =
        new ConfigTreeReader<Integer>() {
          @Override
          protected Integer processFiles(
              @NotNull List<Element> fileElements, @Nullable Integer context) {
            return fileElements.size();
          }

          @Override
          protected Integer processSplitter(
              @NotNull Element element,
              @Nullable Element firstChild,
              @Nullable Element secondChild,
              @Nullable Integer context) {
            Integer first = process(firstChild, null);
            Integer second = process(secondChild, null);
            return (first == null ? 0 : first) + (second == null ? 0 : second);
          }
        }.process(element, null);
    return value == null ? 0 : value;
  }

  public void readExternal(final Element element) {
    mySplittersElement = element;
  }

  @NotNull
  public VirtualFile[] getOpenFiles() {
    final Set<VirtualFile> files = new ArrayListSet<VirtualFile>();
    for (final EditorWindow myWindow : myWindows) {
      final EditorWithProviderComposite[] editors = myWindow.getEditors();
      for (final EditorWithProviderComposite editor : editors) {
        VirtualFile file = editor.getFile();
        // background thread may call this method when invalid file is being removed
        // do not return it here as it will quietly drop out soon
        if (file.isValid()) {
          files.add(file);
        }
      }
    }
    return VfsUtilCore.toVirtualFileArray(files);
  }

  @NotNull
  public VirtualFile[] getSelectedFiles() {
    final Set<VirtualFile> files = new ArrayListSet<VirtualFile>();
    for (final EditorWindow window : myWindows) {
      final VirtualFile file = window.getSelectedFile();
      if (file != null) {
        files.add(file);
      }
    }
    final VirtualFile[] virtualFiles = VfsUtilCore.toVirtualFileArray(files);
    final VirtualFile currentFile = getCurrentFile();
    if (currentFile != null) {
      for (int i = 0; i != virtualFiles.length; ++i) {
        if (Comparing.equal(virtualFiles[i], currentFile)) {
          virtualFiles[i] = virtualFiles[0];
          virtualFiles[0] = currentFile;
          break;
        }
      }
    }
    return virtualFiles;
  }

  @NotNull
  public FileEditor[] getSelectedEditors() {
    List<FileEditor> editors = new ArrayList<FileEditor>();
    Set<EditorWindow> windows = new THashSet<EditorWindow>(myWindows);
    final EditorWindow currentWindow = getCurrentWindow();
    if (currentWindow != null) {
      windows.add(currentWindow);
    }
    for (final EditorWindow window : windows) {
      final EditorWithProviderComposite composite = window.getSelectedEditor();
      if (composite != null) {
        editors.add(composite.getSelectedEditor());
      }
    }
    return editors.toArray(new FileEditor[editors.size()]);
  }

  public void updateFileIcon(@NotNull final VirtualFile file) {
    updateFileIconLater(file);
  }

  private void updateFileIconImmediately(final VirtualFile file) {
    final Collection<EditorWindow> windows = findWindows(file);
    for (EditorWindow window : windows) {
      window.updateFileIcon(file);
    }
  }

  private final Set<VirtualFile> myFilesToUpdateIconsFor = new HashSet<VirtualFile>();

  private void updateFileIconLater(VirtualFile file) {
    myFilesToUpdateIconsFor.add(file);
    myIconUpdaterAlarm.cancelAllRequests();
    myIconUpdaterAlarm.addRequest(
        () -> {
          if (myManager.getProject().isDisposed()) return;
          for (VirtualFile file1 : myFilesToUpdateIconsFor) {
            updateFileIconImmediately(file1);
          }
          myFilesToUpdateIconsFor.clear();
        },
        200,
        ModalityState.stateForComponent(this));
  }

  void updateFileColor(@NotNull final VirtualFile file) {
    final Collection<EditorWindow> windows = findWindows(file);
    for (final EditorWindow window : windows) {
      final int index = window.findEditorIndex(window.findFileComposite(file));
      LOG.assertTrue(index != -1);
      window.setForegroundAt(index, getManager().getFileColor(file));
      window.setWaveColor(index, getManager().isProblem(file) ? JBColor.red : null);
    }
  }

  public void trimToSize(final int editor_tab_limit) {
    for (final EditorWindow window : myWindows) {
      window.trimToSize(editor_tab_limit, null, true);
    }
  }

  public void setTabsPlacement(final int tabPlacement) {
    final EditorWindow[] windows = getWindows();
    for (int i = 0; i != windows.length; ++i) {
      windows[i].setTabsPlacement(tabPlacement);
    }
  }

  void setTabLayoutPolicy(int scrollTabLayout) {
    final EditorWindow[] windows = getWindows();
    for (int i = 0; i != windows.length; ++i) {
      windows[i].setTabLayoutPolicy(scrollTabLayout);
    }
  }

  void updateFileName(@Nullable final VirtualFile updatedFile) {
    final EditorWindow[] windows = getWindows();
    for (int i = 0; i != windows.length; ++i) {
      for (VirtualFile file : windows[i].getFiles()) {
        if (updatedFile == null || file.getName().equals(updatedFile.getName())) {
          windows[i].updateFileName(file);
        }
      }
    }

    Project project = myManager.getProject();

    final IdeFrame frame = getFrame(project);
    if (frame != null) {
      VirtualFile file = getCurrentFile();

      File ioFile = file == null ? null : new File(file.getPresentableUrl());
      String fileTitle = null;
      if (file != null) {
        fileTitle =
            DumbService.isDumb(project)
                ? file.getName()
                : FrameTitleBuilder.getInstance().getFileTitle(project, file);
      }

      frame.setFileTitle(fileTitle, ioFile);
    }
  }

  protected IdeFrame getFrame(Project project) {
    final WindowManagerEx windowManagerEx = WindowManagerEx.getInstanceEx();
    final IdeFrame frame = windowManagerEx.getFrame(project);
    LOG.assertTrue(ApplicationManager.getApplication().isUnitTestMode() || frame != null);
    return frame;
  }

  boolean isInsideChange() {
    return myInsideChange > 0;
  }

  private void setCurrentWindow(@Nullable final EditorWindow currentWindow) {
    if (currentWindow != null && !myWindows.contains(currentWindow)) {
      throw new IllegalArgumentException(currentWindow + " is not a member of this container");
    }
    myCurrentWindow = currentWindow;
  }

  void updateFileBackgroundColor(@NotNull VirtualFile file) {
    final EditorWindow[] windows = getWindows();
    for (int i = 0; i != windows.length; ++i) {
      windows[i].updateFileBackgroundColor(file);
    }
  }

  int getSplitCount() {
    if (getComponentCount() > 0) {
      JPanel panel = (JPanel) getComponent(0);
      return getSplitCount(panel);
    }
    return 0;
  }

  private static int getSplitCount(JComponent component) {
    if (component.getComponentCount() > 0) {
      final JComponent firstChild = (JComponent) component.getComponent(0);
      if (firstChild instanceof Splitter) {
        final Splitter splitter = (Splitter) firstChild;
        return getSplitCount(splitter.getFirstComponent())
            + getSplitCount(splitter.getSecondComponent());
      }
      return 1;
    }
    return 0;
  }

  protected void afterFileClosed(VirtualFile file) {}

  protected void afterFileOpen(VirtualFile file) {}

  @Nullable
  JBTabs getTabsAt(RelativePoint point) {
    Point thisPoint = point.getPoint(this);
    Component c = SwingUtilities.getDeepestComponentAt(this, thisPoint.x, thisPoint.y);
    while (c != null) {
      if (c instanceof JBTabs) {
        return (JBTabs) c;
      }
      c = c.getParent();
    }

    return null;
  }

  boolean isEmptyVisible() {
    EditorWindow[] windows = getWindows();
    for (EditorWindow each : windows) {
      if (!each.isEmptyVisible()) {
        return false;
      }
    }
    return true;
  }

  @Nullable
  private VirtualFile findNextFile(final VirtualFile file) {
    final EditorWindow[] windows = getWindows(); // TODO: use current file as base
    for (int i = 0; i != windows.length; ++i) {
      final VirtualFile[] files = windows[i].getFiles();
      for (final VirtualFile fileAt : files) {
        if (!Comparing.equal(fileAt, file)) {
          return fileAt;
        }
      }
    }
    return null;
  }

  void closeFile(VirtualFile file, boolean moveFocus) {
    final List<EditorWindow> windows = findWindows(file);
    if (!windows.isEmpty()) {
      final VirtualFile nextFile = findNextFile(file);
      for (final EditorWindow window : windows) {
        LOG.assertTrue(window.getSelectedEditor() != null);
        window.closeFile(file, false, moveFocus);
        if (window.getTabCount() == 0 && nextFile != null && myManager.getProject().isOpen()) {
          EditorWithProviderComposite newComposite = myManager.newEditorComposite(nextFile);
          window.setEditor(newComposite, moveFocus); // newComposite can be null
        }
      }
      // cleanup windows with no tabs
      for (final EditorWindow window : windows) {
        if (window.isDisposed()) {
          // call to window.unsplit() which might make its sibling disposed
          continue;
        }
        if (window.getTabCount() == 0) {
          window.unsplit(false);
        }
      }
    }
  }

  @Override
  public void uiSettingsChanged(UISettings source) {
    if (!myManager.getProject().isOpen()) return;
    for (VirtualFile file : getOpenFiles()) {
      updateFileBackgroundColor(file);
      updateFileIcon(file);
      updateFileColor(file);
    }
  }

  private final class MyFocusTraversalPolicy extends IdeFocusTraversalPolicy {
    @Override
    public final Component getDefaultComponentImpl(final Container focusCycleRoot) {
      if (myCurrentWindow != null) {
        final EditorWithProviderComposite selectedEditor = myCurrentWindow.getSelectedEditor();
        if (selectedEditor != null) {
          return IdeFocusTraversalPolicy.getPreferredFocusedComponent(
              selectedEditor.getComponent(), this);
        }
      }
      return IdeFocusTraversalPolicy.getPreferredFocusedComponent(EditorsSplitters.this, this);
    }
  }

  @Nullable
  public JPanel getTopPanel() {
    return getComponentCount() > 0 ? (JPanel) getComponent(0) : null;
  }

  public EditorWindow getCurrentWindow() {
    return myCurrentWindow;
  }

  public EditorWindow getOrCreateCurrentWindow(final VirtualFile file) {
    final List<EditorWindow> windows = findWindows(file);
    if (getCurrentWindow() == null) {
      final Iterator<EditorWindow> iterator = myWindows.iterator();
      if (!windows.isEmpty()) {
        setCurrentWindow(windows.get(0), false);
      } else if (iterator.hasNext()) {
        setCurrentWindow(iterator.next(), false);
      } else {
        createCurrentWindow();
      }
    } else if (!windows.isEmpty()) {
      if (!windows.contains(getCurrentWindow())) {
        setCurrentWindow(windows.get(0), false);
      }
    }
    return getCurrentWindow();
  }

  void createCurrentWindow() {
    LOG.assertTrue(myCurrentWindow == null);
    setCurrentWindow(createEditorWindow());
    add(myCurrentWindow.myPanel, BorderLayout.CENTER);
  }

  protected EditorWindow createEditorWindow() {
    return new EditorWindow(this);
  }

  /**
   * sets the window passed as a current ('focused') window among all splitters. All file openings
   * will be done inside this current window
   *
   * @param window a window to be set as current
   * @param requestFocus whether to request focus to the editor currently selected in this window
   */
  void setCurrentWindow(@Nullable final EditorWindow window, final boolean requestFocus) {
    final EditorWithProviderComposite newEditor =
        window == null ? null : window.getSelectedEditor();

    Runnable fireRunnable = () -> getManager().fireSelectionChanged(newEditor);

    setCurrentWindow(window);

    getManager().updateFileName(window == null ? null : window.getSelectedFile());

    if (window != null) {
      final EditorWithProviderComposite selectedEditor = window.getSelectedEditor();
      if (selectedEditor != null) {
        fireRunnable.run();
      }

      if (requestFocus) {
        window.requestFocus(true);
      }
    } else {
      fireRunnable.run();
    }
  }

  void addWindow(EditorWindow window) {
    myWindows.add(window);
  }

  void removeWindow(EditorWindow window) {
    myWindows.remove(window);
    if (myCurrentWindow == window) {
      myCurrentWindow = null;
    }
  }

  boolean containsWindow(EditorWindow window) {
    return myWindows.contains(window);
  }

  // ---------------------------------------------------------

  public EditorWithProviderComposite[] getEditorsComposites() {
    List<EditorWithProviderComposite> res = new ArrayList<EditorWithProviderComposite>();

    for (final EditorWindow myWindow : myWindows) {
      final EditorWithProviderComposite[] editors = myWindow.getEditors();
      ContainerUtil.addAll(res, editors);
    }
    return res.toArray(new EditorWithProviderComposite[res.size()]);
  }

  // ---------------------------------------------------------

  @NotNull
  public List<EditorWithProviderComposite> findEditorComposites(@NotNull VirtualFile file) {
    List<EditorWithProviderComposite> res = new ArrayList<EditorWithProviderComposite>();
    for (final EditorWindow window : myWindows) {
      final EditorWithProviderComposite fileComposite = window.findFileComposite(file);
      if (fileComposite != null) {
        res.add(fileComposite);
      }
    }
    return res;
  }

  @NotNull
  private List<EditorWindow> findWindows(final VirtualFile file) {
    List<EditorWindow> res = new ArrayList<EditorWindow>();
    for (final EditorWindow window : myWindows) {
      if (window.findFileComposite(file) != null) {
        res.add(window);
      }
    }
    return res;
  }

  @NotNull
  public EditorWindow[] getWindows() {
    return myWindows.toArray(new EditorWindow[myWindows.size()]);
  }

  @NotNull
  EditorWindow[] getOrderedWindows() {
    final List<EditorWindow> res = new ArrayList<EditorWindow>();

    // Collector for windows in tree ordering:
    class Inner {
      private void collect(final JPanel panel) {
        final Component comp = panel.getComponent(0);
        if (comp instanceof Splitter) {
          final Splitter splitter = (Splitter) comp;
          collect((JPanel) splitter.getFirstComponent());
          collect((JPanel) splitter.getSecondComponent());
        } else if (comp instanceof JPanel || comp instanceof JBTabs) {
          final EditorWindow window = findWindowWith(comp);
          if (window != null) {
            res.add(window);
          }
        }
      }
    }

    // get root component and traverse splitters tree:
    if (getComponentCount() != 0) {
      final Component comp = getComponent(0);
      LOG.assertTrue(comp instanceof JPanel);
      final JPanel panel = (JPanel) comp;
      if (panel.getComponentCount() != 0) {
        new Inner().collect(panel);
      }
    }

    LOG.assertTrue(res.size() == myWindows.size());
    return res.toArray(new EditorWindow[res.size()]);
  }

  @Nullable
  private EditorWindow findWindowWith(final Component component) {
    if (component != null) {
      for (final EditorWindow window : myWindows) {
        if (SwingUtilities.isDescendingFrom(component, window.myPanel)) {
          return window;
        }
      }
    }
    return null;
  }

  public boolean isFloating() {
    return false;
  }

  public boolean isPreview() {
    return false;
  }

  private final class MyFocusWatcher extends FocusWatcher {
    @Override
    protected void focusedComponentChanged(final Component component, final AWTEvent cause) {
      EditorWindow newWindow = null;

      if (component != null) {
        newWindow = findWindowWith(component);
      } else if (cause instanceof ContainerEvent
          && cause.getID() == ContainerEvent.COMPONENT_REMOVED) {
        // do not change current window in case of child removal as in JTable.removeEditor
        // otherwise Escape in a toolwindow will not focus editor with JTable content
        return;
      }

      setCurrentWindow(newWindow);
      setCurrentWindow(newWindow, false);
    }
  }

  private abstract static class ConfigTreeReader<T> {
    @Nullable
    public T process(@Nullable Element element, @Nullable T context) {
      if (element == null) {
        return null;
      }
      final Element splitterElement = element.getChild("splitter");
      if (splitterElement != null) {
        final Element first = splitterElement.getChild("split-first");
        final Element second = splitterElement.getChild("split-second");
        return processSplitter(splitterElement, first, second, context);
      }

      final Element leaf = element.getChild("leaf");
      if (leaf == null) {
        return null;
      }

      List<Element> fileElements = leaf.getChildren("file");
      final List<Element> children = new ArrayList<Element>(fileElements.size());

      // trim to EDITOR_TAB_LIMIT, ignoring CLOSE_NON_MODIFIED_FILES_FIRST policy
      int toRemove = fileElements.size() - UISettings.getInstance().EDITOR_TAB_LIMIT;
      for (Element fileElement : fileElements) {
        if (toRemove <= 0
            || Boolean.valueOf(fileElement.getAttributeValue(PINNED)).booleanValue()) {
          children.add(fileElement);
        } else {
          toRemove--;
        }
      }

      return processFiles(children, context);
    }

    @Nullable
    protected abstract T processFiles(@NotNull List<Element> fileElements, @Nullable T context);

    @Nullable
    protected abstract T processSplitter(
        @NotNull Element element,
        @Nullable Element firstChild,
        @Nullable Element secondChild,
        @Nullable T context);
  }

  private class UIBuilder extends ConfigTreeReader<JPanel> {

    @Override
    protected JPanel processFiles(@NotNull List<Element> fileElements, final JPanel context) {
      final Ref<EditorWindow> windowRef = new Ref<EditorWindow>();
      UIUtil.invokeAndWaitIfNeeded(
          new Runnable() {
            @Override
            public void run() {
              windowRef.set(context == null ? createEditorWindow() : findWindowWith(context));
            }
          });
      final EditorWindow window = windowRef.get();
      LOG.assertTrue(window != null);
      VirtualFile focusedFile = null;

      for (int i = 0; i < fileElements.size(); i++) {
        final Element file = fileElements.get(i);
        if (i == 0) {
          EditorTabbedContainer tabbedPane = window.getTabbedPane();
          if (tabbedPane != null) {
            try {
              int limit =
                  Integer.parseInt(
                      file.getParentElement()
                          .getAttributeValue(
                              JBTabsImpl.SIDE_TABS_SIZE_LIMIT_KEY.toString(),
                              String.valueOf(JBTabsImpl.DEFAULT_MAX_TAB_WIDTH)));
              UIUtil.putClientProperty(
                  tabbedPane.getComponent(), JBTabsImpl.SIDE_TABS_SIZE_LIMIT_KEY, limit);
            } catch (NumberFormatException e) {
              // ignore
            }
          }
        }
        try {
          final FileEditorManagerImpl fileEditorManager = getManager();
          Element historyElement = file.getChild(HistoryEntry.TAG);
          final HistoryEntry entry =
              HistoryEntry.createLight(fileEditorManager.getProject(), historyElement);
          final VirtualFile virtualFile = entry.getFile();
          if (virtualFile == null)
            throw new InvalidDataException("No file exists: " + entry.getFilePointer().getUrl());
          Document document =
              ApplicationManager.getApplication()
                  .runReadAction(
                      new Computable<Document>() {
                        @Override
                        public Document compute() {
                          return virtualFile.isValid()
                              ? FileDocumentManager.getInstance().getDocument(virtualFile)
                              : null;
                        }
                      });
          final boolean isCurrentInTab =
              Boolean.valueOf(file.getAttributeValue(CURRENT_IN_TAB)).booleanValue();
          Boolean pin = Boolean.valueOf(file.getAttributeValue(PINNED));
          fileEditorManager.openFileImpl4(
              window, virtualFile, entry, isCurrentInTab, isCurrentInTab, pin, i);
          if (isCurrentInTab) {
            focusedFile = virtualFile;
          }
          if (document != null) {
            // This is just to make sure document reference is kept on stack till this point
            // so that document is available for folding state deserialization in HistoryEntry
            // constructor
            // and that document will be created only once during file opening
            document.putUserData(DUMMY_KEY, null);
          }
          updateProgress();
        } catch (InvalidDataException e) {
          if (ApplicationManager.getApplication().isUnitTestMode()) {
            LOG.error(e);
          }
        }
      }
      if (focusedFile != null) {
        getManager().addSelectionRecord(focusedFile, window);
      }
      return window.myPanel;
    }

    @Override
    protected JPanel processSplitter(
        @NotNull Element splitterElement,
        Element firstChild,
        Element secondChild,
        final JPanel context) {
      if (context == null) {
        final boolean orientation =
            "vertical".equals(splitterElement.getAttributeValue("split-orientation"));
        final float proportion =
            Float.valueOf(splitterElement.getAttributeValue("split-proportion")).floatValue();
        final JPanel firstComponent = process(firstChild, null);
        final JPanel secondComponent = process(secondChild, null);
        final Ref<JPanel> panelRef = new Ref<JPanel>();
        UIUtil.invokeAndWaitIfNeeded(
            new Runnable() {
              @Override
              public void run() {
                JPanel panel = new JPanel(new BorderLayout());
                panel.setOpaque(false);
                Splitter splitter = new OnePixelSplitter(orientation, proportion, 0.1f, 0.9f);
                panel.add(splitter, BorderLayout.CENTER);
                splitter.setFirstComponent(firstComponent);
                splitter.setSecondComponent(secondComponent);
                panelRef.set(panel);
              }
            });
        return panelRef.get();
      }
      final Ref<JPanel> firstComponent = new Ref<JPanel>();
      final Ref<JPanel> secondComponent = new Ref<JPanel>();
      UIUtil.invokeAndWaitIfNeeded(
          new Runnable() {
            @Override
            public void run() {
              if (context.getComponent(0) instanceof Splitter) {
                Splitter splitter = (Splitter) context.getComponent(0);
                firstComponent.set((JPanel) splitter.getFirstComponent());
                secondComponent.set((JPanel) splitter.getSecondComponent());
              } else {
                firstComponent.set(context);
                secondComponent.set(context);
              }
            }
          });
      process(firstChild, firstComponent.get());
      process(secondChild, secondComponent.get());
      return context;
    }
  }
}
public abstract class PsiDocumentManagerBase extends PsiDocumentManager
    implements DocumentListener {
  static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.PsiDocumentManagerImpl");
  private static final Key<Document> HARD_REF_TO_DOCUMENT =
      Key.create("HARD_REFERENCE_TO_DOCUMENT");
  private static final Key<PsiFile> HARD_REF_TO_PSI = Key.create("HARD_REFERENCE_TO_PSI");
  private static final Key<List<Runnable>> ACTION_AFTER_COMMIT = Key.create("ACTION_AFTER_COMMIT");

  protected final Project myProject;
  private final PsiManager myPsiManager;
  private final DocumentCommitProcessor myDocumentCommitProcessor;
  protected final Set<Document> myUncommittedDocuments = ContainerUtil.newConcurrentSet();
  private final Map<Document, Pair<CharSequence, Long>> myLastCommittedTexts =
      ContainerUtil.newConcurrentMap();
  protected boolean myStopTrackingDocuments;
  protected boolean myPerformBackgroundCommit = true;

  private volatile boolean myIsCommitInProgress;
  private final PsiToDocumentSynchronizer mySynchronizer;

  private final List<Listener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();

  protected PsiDocumentManagerBase(
      @NotNull final Project project,
      @NotNull PsiManager psiManager,
      @NotNull MessageBus bus,
      @NonNls @NotNull final DocumentCommitProcessor documentCommitProcessor) {
    myProject = project;
    myPsiManager = psiManager;
    myDocumentCommitProcessor = documentCommitProcessor;
    mySynchronizer = new PsiToDocumentSynchronizer(this, bus);
    myPsiManager.addPsiTreeChangeListener(mySynchronizer);
    bus.connect()
        .subscribe(
            PsiDocumentTransactionListener.TOPIC,
            new PsiDocumentTransactionListener() {
              @Override
              public void transactionStarted(@NotNull Document document, @NotNull PsiFile file) {
                myUncommittedDocuments.remove(document);
              }

              @Override
              public void transactionCompleted(@NotNull Document document, @NotNull PsiFile file) {}
            });
  }

  @Override
  @Nullable
  public PsiFile getPsiFile(@NotNull Document document) {
    final PsiFile userData = document.getUserData(HARD_REF_TO_PSI);
    if (userData != null) return userData;

    PsiFile psiFile = getCachedPsiFile(document);
    if (psiFile != null) return psiFile;

    final VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document);
    if (virtualFile == null || !virtualFile.isValid()) return null;

    psiFile = getPsiFile(virtualFile);
    if (psiFile == null) return null;

    fireFileCreated(document, psiFile);

    return psiFile;
  }

  public static void cachePsi(@NotNull Document document, @Nullable PsiFile file) {
    document.putUserData(HARD_REF_TO_PSI, file);
  }

  @Override
  public PsiFile getCachedPsiFile(@NotNull Document document) {
    final PsiFile userData = document.getUserData(HARD_REF_TO_PSI);
    if (userData != null) return userData;

    final VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document);
    if (virtualFile == null || !virtualFile.isValid()) return null;
    return getCachedPsiFile(virtualFile);
  }

  @Nullable
  FileViewProvider getCachedViewProvider(@NotNull Document document) {
    final VirtualFile virtualFile = getVirtualFile(document);
    if (virtualFile == null) return null;
    return getCachedViewProvider(virtualFile);
  }

  private FileViewProvider getCachedViewProvider(@NotNull VirtualFile virtualFile) {
    return ((PsiManagerEx) myPsiManager).getFileManager().findCachedViewProvider(virtualFile);
  }

  private static VirtualFile getVirtualFile(@NotNull Document document) {
    final VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document);
    if (virtualFile == null || !virtualFile.isValid()) return null;
    return virtualFile;
  }

  @Nullable
  PsiFile getCachedPsiFile(@NotNull VirtualFile virtualFile) {
    return ((PsiManagerEx) myPsiManager).getFileManager().getCachedPsiFile(virtualFile);
  }

  @Nullable
  private PsiFile getPsiFile(@NotNull VirtualFile virtualFile) {
    return ((PsiManagerEx) myPsiManager).getFileManager().findFile(virtualFile);
  }

  @Nullable
  @Override
  public Document getDocument(@NotNull PsiFile file) {
    if (file instanceof PsiBinaryFile) return null;

    Document document = getCachedDocument(file);
    if (document != null) {
      if (!file.getViewProvider().isPhysical() && document.getUserData(HARD_REF_TO_PSI) == null) {
        PsiUtilCore.ensureValid(file);
        cachePsi(document, file);
      }
      return document;
    }

    FileViewProvider viewProvider = file.getViewProvider();
    if (!viewProvider.isEventSystemEnabled()) return null;

    document = FileDocumentManager.getInstance().getDocument(viewProvider.getVirtualFile());
    if (document != null) {
      if (document.getTextLength() != file.getTextLength()) {
        String message =
            "Document/PSI mismatch: "
                + file
                + " ("
                + file.getClass()
                + "); physical="
                + viewProvider.isPhysical();
        if (document.getTextLength() + file.getTextLength() < 8096) {
          message +=
              "\n=== document ===\n" + document.getText() + "\n=== PSI ===\n" + file.getText();
        }
        throw new AssertionError(message);
      }

      if (!viewProvider.isPhysical()) {
        PsiUtilCore.ensureValid(file);
        cachePsi(document, file);
        file.putUserData(HARD_REF_TO_DOCUMENT, document);
      }
    }

    return document;
  }

  @Override
  public Document getCachedDocument(@NotNull PsiFile file) {
    if (!file.isPhysical()) return null;
    VirtualFile vFile = file.getViewProvider().getVirtualFile();
    return FileDocumentManager.getInstance().getCachedDocument(vFile);
  }

  @Override
  public void commitAllDocuments() {
    ApplicationManager.getApplication().assertIsDispatchThread();
    if (myUncommittedDocuments.isEmpty()) return;

    final Document[] documents = getUncommittedDocuments();
    for (Document document : documents) {
      commitDocument(document);
    }

    LOG.assertTrue(!hasUncommitedDocuments(), myUncommittedDocuments);
  }

  @Override
  public void performForCommittedDocument(
      @NotNull final Document doc, @NotNull final Runnable action) {
    final Document document =
        doc instanceof DocumentWindow ? ((DocumentWindow) doc).getDelegate() : doc;
    if (isCommitted(document)) {
      action.run();
    } else {
      addRunOnCommit(document, action);
    }
  }

  private final Map<Object, Runnable> actionsWhenAllDocumentsAreCommitted =
      new LinkedHashMap<Object, Runnable>(); // accessed from EDT only
  private static final Object PERFORM_ALWAYS_KEY =
      new Object() {
        @Override
        @NonNls
        public String toString() {
          return "PERFORM_ALWAYS";
        }
      };

  /**
   * Cancel previously registered action and schedules (new) action to be executed when all
   * documents are committed.
   *
   * @param key the (unique) id of the action.
   * @param action The action to be executed after automatic commit. This action will overwrite any
   *     action which was registered under this key earlier. The action will be executed in EDT.
   * @return true if action has been run immediately, or false if action was scheduled for execution
   *     later.
   */
  public boolean cancelAndRunWhenAllCommitted(
      @NonNls @NotNull Object key, @NotNull final Runnable action) {
    ApplicationManager.getApplication().assertIsDispatchThread();
    if (myProject.isDisposed()) {
      action.run();
      return true;
    }
    if (myUncommittedDocuments.isEmpty()) {
      action.run();
      if (!hasUncommitedDocuments()) {
        assert actionsWhenAllDocumentsAreCommitted.isEmpty() : actionsWhenAllDocumentsAreCommitted;
      }
      return true;
    }
    actionsWhenAllDocumentsAreCommitted.put(key, action);
    return false;
  }

  public static void addRunOnCommit(@NotNull Document document, @NotNull Runnable action) {
    synchronized (ACTION_AFTER_COMMIT) {
      List<Runnable> list = document.getUserData(ACTION_AFTER_COMMIT);
      if (list == null) {
        document.putUserData(ACTION_AFTER_COMMIT, list = new SmartList<Runnable>());
      }
      list.add(action);
    }
  }

  @Override
  public void commitDocument(@NotNull final Document doc) {
    final Document document =
        doc instanceof DocumentWindow ? ((DocumentWindow) doc).getDelegate() : doc;
    if (!isCommitted(document)) {
      doCommit(document);
    }
  }

  // public for Upsource
  public boolean finishCommit(
      @NotNull final Document document,
      @NotNull final List<Processor<Document>> finishProcessors,
      final boolean synchronously,
      @NotNull final Object reason) {
    assert !myProject.isDisposed() : "Already disposed";
    final boolean[] ok = {true};
    ApplicationManager.getApplication()
        .runWriteAction(
            new CommitToPsiFileAction(document, myProject) {
              @Override
              public void run() {
                ok[0] = finishCommitInWriteAction(document, finishProcessors, synchronously);
              }
            });

    if (ok[0]) {
      // otherwise changes maybe not synced to the document yet, and injectors will crash
      if (!mySynchronizer.isDocumentAffectedByTransactions(document)) {
        InjectedLanguageManager.getInstance(myProject).startRunInjectors(document, synchronously);
      }
      // run after commit actions outside write action
      runAfterCommitActions(document);
      if (DebugUtil.DO_EXPENSIVE_CHECKS && !ApplicationInfoImpl.isInPerformanceTest()) {
        checkAllElementsValid(document, reason);
      }
    }
    return ok[0];
  }

  protected boolean finishCommitInWriteAction(
      @NotNull final Document document,
      @NotNull final List<Processor<Document>> finishProcessors,
      final boolean synchronously) {
    if (myProject.isDisposed()) return false;
    assert !(document instanceof DocumentWindow);
    myIsCommitInProgress = true;
    boolean success = true;
    try {
      final FileViewProvider viewProvider = getCachedViewProvider(document);
      if (viewProvider != null) {
        for (Processor<Document> finishRunnable : finishProcessors) {
          success = finishRunnable.process(document);
          if (synchronously) {
            assert success : finishRunnable + " in " + finishProcessors;
          }
          if (!success) {
            break;
          }
        }
        if (success) {
          myLastCommittedTexts.remove(document);
          viewProvider.contentsSynchronized();
        }
      } else {
        handleCommitWithoutPsi(document);
      }
    } finally {
      myDocumentCommitProcessor.log(
          "in PDI.finishDoc: ", null, synchronously, success, myUncommittedDocuments);
      if (success) {
        myUncommittedDocuments.remove(document);
        myDocumentCommitProcessor.log(
            "in PDI.finishDoc: removed doc", null, synchronously, success, myUncommittedDocuments);
      }
      myIsCommitInProgress = false;
      myDocumentCommitProcessor.log(
          "in PDI.finishDoc: exit", null, synchronously, success, myUncommittedDocuments);
    }

    return success;
  }

  private void checkAllElementsValid(@NotNull Document document, @NotNull final Object reason) {
    final PsiFile psiFile = getCachedPsiFile(document);
    if (psiFile != null) {
      psiFile.accept(
          new PsiRecursiveElementWalkingVisitor() {
            @Override
            public void visitElement(PsiElement element) {
              if (!element.isValid()) {
                throw new AssertionError(
                    "Commit to '"
                        + psiFile.getVirtualFile()
                        + "' has led to invalid element: "
                        + element
                        + "; Reason: '"
                        + reason
                        + "'");
              }
            }
          });
    }
  }

  private void doCommit(@NotNull final Document document) {
    assert !myIsCommitInProgress : "Do not call commitDocument() from inside PSI change listener";
    ApplicationManager.getApplication()
        .runWriteAction(
            new Runnable() {
              @Override
              public void run() {
                // otherwise there are many clients calling commitAllDocs() on PSI childrenChanged()
                if (getSynchronizer().isDocumentAffectedByTransactions(document)) return;

                myIsCommitInProgress = true;
                try {
                  myDocumentCommitProcessor.commitSynchronously(document, myProject);
                } finally {
                  myIsCommitInProgress = false;
                }
                assert !isInUncommittedSet(document) : "Document :" + document;
              }
            });
  }

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

  @Override
  public void reparseFiles(@NotNull Collection<VirtualFile> files, boolean includeOpenFiles) {
    FileContentUtilCore.reparseFiles(files);
  }

  @Override
  public void commitAndRunReadAction(@NotNull final Runnable runnable) {
    final Application application = ApplicationManager.getApplication();
    if (SwingUtilities.isEventDispatchThread()) {
      commitAllDocuments();
      runnable.run();
    } else {
      if (ApplicationManager.getApplication().isReadAccessAllowed()) {
        LOG.error(
            "Don't call commitAndRunReadAction inside ReadAction, it will cause a deadlock otherwise. "
                + Thread.currentThread());
      }

      final Semaphore s1 = new Semaphore();
      final Semaphore s2 = new Semaphore();
      final boolean[] committed = {false};

      application.runReadAction(
          new Runnable() {
            @Override
            public void run() {
              if (myUncommittedDocuments.isEmpty()) {
                runnable.run();
                committed[0] = true;
              } else {
                s1.down();
                s2.down();
                final Runnable commitRunnable =
                    new Runnable() {
                      @Override
                      public void run() {
                        commitAllDocuments();
                        s1.up();
                        s2.waitFor();
                      }
                    };
                final ProgressIndicator progressIndicator =
                    ProgressManager.getInstance().getProgressIndicator();
                if (progressIndicator == null) {
                  ApplicationManager.getApplication().invokeLater(commitRunnable);
                } else {
                  ApplicationManager.getApplication()
                      .invokeLater(commitRunnable, progressIndicator.getModalityState());
                }
              }
            }
          });

      if (!committed[0]) {
        s1.waitFor();
        application.runReadAction(
            new Runnable() {
              @Override
              public void run() {
                s2.up();
                runnable.run();
              }
            });
      }
    }
  }

  /**
   * Schedules action to be executed when all documents are committed.
   *
   * @return true if action has been run immediately, or false if action was scheduled for execution
   *     later.
   */
  @Override
  public boolean performWhenAllCommitted(@NotNull final Runnable action) {
    ApplicationManager.getApplication().assertIsDispatchThread();
    assert !myProject.isDisposed() : "Already disposed: " + myProject;
    if (myUncommittedDocuments.isEmpty()) {
      action.run();
      return true;
    }
    CompositeRunnable actions =
        (CompositeRunnable) actionsWhenAllDocumentsAreCommitted.get(PERFORM_ALWAYS_KEY);
    if (actions == null) {
      actions = new CompositeRunnable();
      actionsWhenAllDocumentsAreCommitted.put(PERFORM_ALWAYS_KEY, actions);
    }
    actions.add(action);
    myDocumentCommitProcessor.log(
        "PDI: added performWhenAllCommitted", null, false, action, myUncommittedDocuments);
    return false;
  }

  private static class CompositeRunnable extends ArrayList<Runnable> implements Runnable {
    @Override
    public void run() {
      for (Runnable runnable : this) {
        runnable.run();
      }
    }
  }

  private void runAfterCommitActions(@NotNull Document document) {
    ApplicationManager.getApplication().assertIsDispatchThread();
    List<Runnable> list;
    synchronized (ACTION_AFTER_COMMIT) {
      list = document.getUserData(ACTION_AFTER_COMMIT);
      if (list != null) {
        list = new ArrayList<Runnable>(list);
        document.putUserData(ACTION_AFTER_COMMIT, null);
      }
    }
    if (list != null) {
      for (final Runnable runnable : list) {
        runnable.run();
      }
    }

    if (!hasUncommitedDocuments() && !actionsWhenAllDocumentsAreCommitted.isEmpty()) {
      List<Object> keys = new ArrayList<Object>(actionsWhenAllDocumentsAreCommitted.keySet());
      for (Object key : keys) {
        try {
          Runnable action = actionsWhenAllDocumentsAreCommitted.remove(key);
          myDocumentCommitProcessor.log(
              "Running after commit runnable: ", null, false, key, action);
          action.run();
        } catch (Throwable e) {
          LOG.error(e);
        }
      }
    }
  }

  @Override
  public void addListener(@NotNull Listener listener) {
    myListeners.add(listener);
  }

  @Override
  public void removeListener(@NotNull Listener listener) {
    myListeners.remove(listener);
  }

  @Override
  public boolean isDocumentBlockedByPsi(@NotNull Document doc) {
    return false;
  }

  @Override
  public void doPostponedOperationsAndUnblockDocument(@NotNull Document doc) {}

  void fireDocumentCreated(@NotNull Document document, PsiFile file) {
    for (Listener listener : myListeners) {
      listener.documentCreated(document, file);
    }
  }

  private void fireFileCreated(@NotNull Document document, @NotNull PsiFile file) {
    for (Listener listener : myListeners) {
      listener.fileCreated(file, document);
    }
  }

  @Override
  @NotNull
  public CharSequence getLastCommittedText(@NotNull Document document) {
    Pair<CharSequence, Long> pair = myLastCommittedTexts.get(document);
    return pair != null ? pair.first : document.getImmutableCharSequence();
  }

  @Override
  public long getLastCommittedStamp(@NotNull Document document) {
    Pair<CharSequence, Long> pair = myLastCommittedTexts.get(document);
    return pair != null ? pair.second : document.getModificationStamp();
  }

  @Override
  @NotNull
  public Document[] getUncommittedDocuments() {
    ApplicationManager.getApplication().assertIsDispatchThread();
    Document[] documents =
        myUncommittedDocuments.toArray(new Document[myUncommittedDocuments.size()]);
    return ArrayUtil.stripTrailingNulls(documents);
  }

  boolean isInUncommittedSet(@NotNull Document document) {
    if (document instanceof DocumentWindow)
      return isInUncommittedSet(((DocumentWindow) document).getDelegate());
    return myUncommittedDocuments.contains(document);
  }

  @Override
  public boolean isUncommited(@NotNull Document document) {
    return !isCommitted(document);
  }

  @Override
  public boolean isCommitted(@NotNull Document document) {
    if (document instanceof DocumentWindow)
      return isCommitted(((DocumentWindow) document).getDelegate());
    if (getSynchronizer().isInSynchronization(document)) return true;
    return !((DocumentEx) document).isInEventsHandling() && !isInUncommittedSet(document);
  }

  @Override
  public boolean hasUncommitedDocuments() {
    return !myIsCommitInProgress && !myUncommittedDocuments.isEmpty();
  }

  @Override
  public void beforeDocumentChange(@NotNull DocumentEvent event) {
    if (myStopTrackingDocuments) return;

    final Document document = event.getDocument();
    if (!(document instanceof DocumentWindow) && !myLastCommittedTexts.containsKey(document)) {
      myLastCommittedTexts.put(
          document,
          Pair.create(document.getImmutableCharSequence(), document.getModificationStamp()));
    }

    VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document);
    boolean isRelevant = virtualFile != null && isRelevant(virtualFile);

    final FileViewProvider viewProvider = getCachedViewProvider(document);
    boolean inMyProject = viewProvider != null && viewProvider.getManager() == myPsiManager;
    if (!isRelevant || !inMyProject) {
      return;
    }

    final List<PsiFile> files = viewProvider.getAllFiles();
    PsiFile psiCause = null;
    for (PsiFile file : files) {
      if (file == null) {
        throw new AssertionError(
            "View provider "
                + viewProvider
                + " ("
                + viewProvider.getClass()
                + ") returned null in its files array: "
                + files
                + " for file "
                + viewProvider.getVirtualFile());
      }

      if (mySynchronizer.isInsideAtomicChange(file)) {
        psiCause = file;
      }
    }

    if (psiCause == null) {
      beforeDocumentChangeOnUnlockedDocument(viewProvider);
    }

    ((SingleRootFileViewProvider) viewProvider).beforeDocumentChanged(psiCause);
  }

  protected void beforeDocumentChangeOnUnlockedDocument(
      @NotNull final FileViewProvider viewProvider) {}

  @Override
  public void documentChanged(DocumentEvent event) {
    if (myStopTrackingDocuments) return;

    final Document document = event.getDocument();
    VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document);
    boolean isRelevant = virtualFile != null && isRelevant(virtualFile);

    final FileViewProvider viewProvider = getCachedViewProvider(document);
    if (viewProvider == null) {
      handleCommitWithoutPsi(document);
      return;
    }
    boolean inMyProject = viewProvider.getManager() == myPsiManager;
    if (!isRelevant || !inMyProject) {
      myLastCommittedTexts.remove(document);
      return;
    }

    ApplicationManager.getApplication().assertWriteAccessAllowed();
    final List<PsiFile> files = viewProvider.getAllFiles();
    boolean commitNecessary = true;
    for (PsiFile file : files) {

      if (mySynchronizer.isInsideAtomicChange(file)) {
        commitNecessary = false;
        continue;
      }

      assert file instanceof PsiFileImpl
              || "mock.file".equals(file.getName())
                  && ApplicationManager.getApplication().isUnitTestMode()
          : event + "; file=" + file + "; allFiles=" + files + "; viewProvider=" + viewProvider;
    }

    boolean forceCommit =
        ApplicationManager.getApplication().hasWriteAction(ExternalChangeAction.class)
            && (SystemProperties.getBooleanProperty("idea.force.commit.on.external.change", false)
                || ApplicationManager.getApplication().isHeadlessEnvironment()
                    && !ApplicationManager.getApplication().isUnitTestMode());

    // Consider that it's worth to perform complete re-parse instead of merge if the whole document
    // text is replaced and
    // current document lines number is roughly above 5000. This makes sense in situations when
    // external change is performed
    // for the huge file (that causes the whole document to be reloaded and 'merge' way takes a
    // while to complete).
    if (event.isWholeTextReplaced() && document.getTextLength() > 100000) {
      document.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, Boolean.TRUE);
    }

    if (commitNecessary) {
      assert !(document instanceof DocumentWindow);
      myUncommittedDocuments.add(document);
      myDocumentCommitProcessor.log(
          "added uncommitted doc",
          null,
          false,
          myProject,
          document,
          ((DocumentEx) document).isInBulkUpdate());
      if (forceCommit) {
        commitDocument(document);
      } else if (!((DocumentEx) document).isInBulkUpdate() && myPerformBackgroundCommit) {
        myDocumentCommitProcessor.commitAsynchronously(myProject, document, event);
      }
    } else {
      myLastCommittedTexts.remove(document);
    }
  }

  void handleCommitWithoutPsi(@NotNull Document document) {
    final Pair<CharSequence, Long> prevPair = myLastCommittedTexts.remove(document);
    if (prevPair == null) {
      return;
    }

    if (!myProject.isInitialized() || myProject.isDisposed()) {
      return;
    }

    myUncommittedDocuments.remove(document);

    VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document);
    if (virtualFile == null || !FileIndexFacade.getInstance(myProject).isInContent(virtualFile)) {
      return;
    }

    final PsiFile psiFile = getPsiFile(document);
    if (psiFile == null) {
      return;
    }

    // we can end up outside write action here if the document has forUseInNonAWTThread=true
    ApplicationManager.getApplication()
        .runWriteAction(
            new ExternalChangeAction() {
              @Override
              public void run() {
                psiFile.getViewProvider().beforeContentsSynchronized();
                synchronized (PsiLock.LOCK) {
                  final int oldLength = prevPair.first.length();
                  PsiManagerImpl manager = (PsiManagerImpl) psiFile.getManager();
                  BlockSupportImpl.sendBeforeChildrenChangeEvent(manager, psiFile, true);
                  BlockSupportImpl.sendBeforeChildrenChangeEvent(manager, psiFile, false);
                  if (psiFile instanceof PsiFileImpl) {
                    ((PsiFileImpl) psiFile).onContentReload();
                  }
                  BlockSupportImpl.sendAfterChildrenChangedEvent(
                      manager, psiFile, oldLength, false);
                  BlockSupportImpl.sendAfterChildrenChangedEvent(manager, psiFile, oldLength, true);
                }
                psiFile.getViewProvider().contentsSynchronized();
              }
            });
  }

  private boolean isRelevant(@NotNull VirtualFile virtualFile) {
    return !virtualFile.getFileType().isBinary() && !myProject.isDisposed();
  }

  public static boolean checkConsistency(@NotNull PsiFile psiFile, @NotNull Document document) {
    // todo hack
    if (psiFile.getVirtualFile() == null) return true;

    CharSequence editorText = document.getCharsSequence();
    int documentLength = document.getTextLength();
    if (psiFile.textMatches(editorText)) {
      LOG.assertTrue(psiFile.getTextLength() == documentLength);
      return true;
    }

    char[] fileText = psiFile.textToCharArray();
    @SuppressWarnings("NonConstantStringShouldBeStringBuffer")
    @NonNls
    String error =
        "File '"
            + psiFile.getName()
            + "' text mismatch after reparse. "
            + "File length="
            + fileText.length
            + "; Doc length="
            + documentLength
            + "\n";
    int i = 0;
    for (; i < documentLength; i++) {
      if (i >= fileText.length) {
        error += "editorText.length > psiText.length i=" + i + "\n";
        break;
      }
      if (i >= editorText.length()) {
        error += "editorText.length > psiText.length i=" + i + "\n";
        break;
      }
      if (editorText.charAt(i) != fileText[i]) {
        error += "first unequal char i=" + i + "\n";
        break;
      }
    }
    // error += "*********************************************" + "\n";
    // if (i <= 500){
    //  error += "Equal part:" + editorText.subSequence(0, i) + "\n";
    // }
    // else{
    //  error += "Equal part start:\n" + editorText.subSequence(0, 200) + "\n";
    //  error += "................................................" + "\n";
    //  error += "................................................" + "\n";
    //  error += "................................................" + "\n";
    //  error += "Equal part end:\n" + editorText.subSequence(i - 200, i) + "\n";
    // }
    error += "*********************************************" + "\n";
    error +=
        "Editor Text tail:("
            + (documentLength - i)
            + ")\n"; // + editorText.subSequence(i, Math.min(i + 300, documentLength)) + "\n";
    error += "*********************************************" + "\n";
    error += "Psi Text tail:(" + (fileText.length - i) + ")\n";
    error += "*********************************************" + "\n";

    if (document instanceof DocumentWindow) {
      error += "doc: '" + document.getText() + "'\n";
      error += "psi: '" + psiFile.getText() + "'\n";
      error += "ast: '" + psiFile.getNode().getText() + "'\n";
      error += psiFile.getLanguage() + "\n";
      PsiElement context =
          InjectedLanguageManager.getInstance(psiFile.getProject()).getInjectionHost(psiFile);
      if (context != null) {
        error += "context: " + context + "; text: '" + context.getText() + "'\n";
        error += "context file: " + context.getContainingFile() + "\n";
      }
      error +=
          "document window ranges: "
              + Arrays.asList(((DocumentWindow) document).getHostRanges())
              + "\n";
    }
    LOG.error(error);
    // document.replaceString(0, documentLength, psiFile.getText());
    return false;
  }

  @TestOnly
  public void clearUncommittedDocuments() {
    myLastCommittedTexts.clear();
    myUncommittedDocuments.clear();
    mySynchronizer.cleanupForNextTest();
  }

  @TestOnly
  public void disableBackgroundCommit(@NotNull Disposable parentDisposable) {
    assert myPerformBackgroundCommit;
    myPerformBackgroundCommit = false;
    Disposer.register(
        parentDisposable,
        new Disposable() {
          @Override
          public void dispose() {
            myPerformBackgroundCommit = true;
          }
        });
  }

  @NotNull
  public PsiToDocumentSynchronizer getSynchronizer() {
    return mySynchronizer;
  }
}
public class FindUsagesManager implements JDOMExternalizable {
  private static final Logger LOG =
      Logger.getInstance("#com.intellij.find.findParameterUsages.FindUsagesManager");

  private enum FileSearchScope {
    FROM_START,
    FROM_END,
    AFTER_CARET,
    BEFORE_CARET
  }

  private static final Key<String> KEY_START_USAGE_AGAIN = Key.create("KEY_START_USAGE_AGAIN");
  @NonNls private static final String VALUE_START_USAGE_AGAIN = "START_AGAIN";
  private final Project myProject;
  private final com.intellij.usages.UsageViewManager myAnotherManager;
  private boolean myToOpenInNewTab = false;

  public static class SearchData {
    public SmartPsiElementPointer[] myElements = null;
    public FindUsagesOptions myOptions = null;

    public boolean equals(final Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;

      final SearchData that = (SearchData) o;

      return Arrays.equals(myElements, that.myElements)
          && (myOptions != null ? myOptions.equals(that.myOptions) : that.myOptions == null);
    }

    public int hashCode() {
      return myElements != null ? Arrays.hashCode(myElements) : 0;
    }
  }

  private SearchData myLastSearchInFileData = new SearchData();
  private final List<SearchData> myFindUsagesHistory =
      ContainerUtil.createLockFreeCopyOnWriteList();

  public FindUsagesManager(
      @NotNull Project project, @NotNull com.intellij.usages.UsageViewManager anotherManager) {
    myProject = project;
    myAnotherManager = anotherManager;
  }

  public boolean canFindUsages(@NotNull final PsiElement element) {
    for (FindUsagesHandlerFactory factory :
        Extensions.getExtensions(FindUsagesHandlerFactory.EP_NAME, myProject)) {
      try {
        if (factory.canFindUsages(element)) {
          return true;
        }
      } catch (IndexNotReadyException e) {
        throw e;
      } catch (Exception e) {
        LOG.error(e);
      }
    }
    return false;
  }

  public void clearFindingNextUsageInFile() {
    myLastSearchInFileData.myOptions = null;
    myLastSearchInFileData.myElements = null;
  }

  public boolean findNextUsageInFile(FileEditor editor) {
    return findUsageInFile(editor, FileSearchScope.AFTER_CARET);
  }

  public boolean findPreviousUsageInFile(FileEditor editor) {
    return findUsageInFile(editor, FileSearchScope.BEFORE_CARET);
  }

  @Override
  public void readExternal(Element element) throws InvalidDataException {
    myToOpenInNewTab = JDOMExternalizer.readBoolean(element, "OPEN_NEW_TAB");
  }

  @Override
  public void writeExternal(Element element) throws WriteExternalException {
    JDOMExternalizer.write(element, "OPEN_NEW_TAB", myToOpenInNewTab);
  }

  private boolean findUsageInFile(@NotNull FileEditor editor, @NotNull FileSearchScope direction) {
    PsiElement[] elements = restorePsiElements(myLastSearchInFileData, true);
    if (elements == null) return false;
    if (elements.length == 0) return true; // all elements have invalidated

    UsageInfoToUsageConverter.TargetElementsDescriptor descriptor =
        new UsageInfoToUsageConverter.TargetElementsDescriptor(elements);

    // todo
    TextEditor textEditor = (TextEditor) editor;
    Document document = textEditor.getEditor().getDocument();
    PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(document);
    if (psiFile == null) return false;

    final FindUsagesHandler handler = getFindUsagesHandler(elements[0], false);
    if (handler == null) return false;
    findUsagesInEditor(
        descriptor, handler, psiFile, direction, myLastSearchInFileData.myOptions, textEditor);
    return true;
  }

  // returns null if cannot find, empty Pair if all elements have been changed
  @Nullable
  private PsiElement[] restorePsiElements(SearchData searchData, final boolean showErrorMessage) {
    if (searchData == null) return null;
    SmartPsiElementPointer[] lastSearchElements = searchData.myElements;
    if (lastSearchElements == null) return null;
    List<PsiElement> elements = new ArrayList<PsiElement>();
    for (SmartPsiElementPointer pointer : lastSearchElements) {
      PsiElement element = pointer.getElement();
      if (element != null) elements.add(element);
    }
    if (elements.isEmpty() && showErrorMessage) {
      Messages.showMessageDialog(
          myProject,
          FindBundle.message("find.searched.elements.have.been.changed.error"),
          FindBundle.message("cannot.search.for.usages.title"),
          Messages.getInformationIcon());
      // SCR #10022
      // clearFindingNextUsageInFile();
      return PsiElement.EMPTY_ARRAY;
    }

    return PsiUtilCore.toPsiElementArray(elements);
  }

  private void initLastSearchElement(
      final FindUsagesOptions findUsagesOptions,
      UsageInfoToUsageConverter.TargetElementsDescriptor descriptor) {
    myLastSearchInFileData = createSearchData(descriptor.getAllElements(), findUsagesOptions);
  }

  private SearchData createSearchData(
      @NotNull List<? extends PsiElement> psiElements, final FindUsagesOptions findUsagesOptions) {
    SearchData data = new SearchData();

    data.myElements = new SmartPsiElementPointer[psiElements.size()];
    int idx = 0;
    for (PsiElement psiElement : psiElements) {
      data.myElements[idx++] =
          SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(psiElement);
    }
    data.myOptions = findUsagesOptions;
    return data;
  }

  @Nullable
  public FindUsagesHandler getFindUsagesHandler(
      PsiElement element, final boolean forHighlightUsages) {
    for (FindUsagesHandlerFactory factory :
        Extensions.getExtensions(FindUsagesHandlerFactory.EP_NAME, myProject)) {
      if (factory.canFindUsages(element)) {
        final FindUsagesHandler handler =
            factory.createFindUsagesHandler(element, forHighlightUsages);
        if (handler == FindUsagesHandler.NULL_HANDLER) return null;
        if (handler != null) {
          return handler;
        }
      }
    }
    return null;
  }

  @Nullable
  public FindUsagesHandler getNewFindUsagesHandler(
      @NotNull PsiElement element, final boolean forHighlightUsages) {
    for (FindUsagesHandlerFactory factory :
        Extensions.getExtensions(FindUsagesHandlerFactory.EP_NAME, myProject)) {
      if (factory.canFindUsages(element)) {
        Class<? extends FindUsagesHandlerFactory> aClass = factory.getClass();
        FindUsagesHandlerFactory copy =
            (FindUsagesHandlerFactory)
                new ConstructorInjectionComponentAdapter(aClass.getName(), aClass)
                    .getComponentInstance(myProject.getPicoContainer());
        final FindUsagesHandler handler = copy.createFindUsagesHandler(element, forHighlightUsages);
        if (handler == FindUsagesHandler.NULL_HANDLER) return null;
        if (handler != null) {
          return handler;
        }
      }
    }
    return null;
  }

  public void findUsages(
      @NotNull PsiElement psiElement,
      final PsiFile scopeFile,
      final FileEditor editor,
      boolean showDialog) {
    doShowDialogAndStartFind(psiElement, scopeFile, editor, showDialog, true);
  }

  private void doShowDialogAndStartFind(
      @NotNull PsiElement psiElement,
      PsiFile scopeFile,
      FileEditor editor,
      boolean showDialog,
      boolean useMaximalScope) {
    FindUsagesHandler handler = getNewFindUsagesHandler(psiElement, false);
    if (handler == null) return;

    boolean singleFile = scopeFile != null;
    AbstractFindUsagesDialog dialog =
        handler.getFindUsagesDialog(singleFile, shouldOpenInNewTab(), mustOpenInNewTab());
    if (showDialog) {
      dialog.show();
      if (!dialog.isOK()) return;
    } else {
      dialog.close(DialogWrapper.OK_EXIT_CODE);
    }

    setOpenInNewTab(dialog.isShowInSeparateWindow());

    FindUsagesOptions findUsagesOptions = dialog.calcFindUsagesOptions();
    if (!showDialog && useMaximalScope) {
      findUsagesOptions.searchScope = getMaximalScope(handler);
    }

    clearFindingNextUsageInFile();
    LOG.assertTrue(handler.getPsiElement().isValid());
    PsiElement[] primaryElements = handler.getPrimaryElements();
    checkNotNull(primaryElements, handler, "getPrimaryElements()");
    PsiElement[] secondaryElements = handler.getSecondaryElements();
    checkNotNull(secondaryElements, handler, "getSecondaryElements()");
    UsageInfoToUsageConverter.TargetElementsDescriptor descriptor =
        new UsageInfoToUsageConverter.TargetElementsDescriptor(primaryElements, secondaryElements);
    if (singleFile) {
      findUsagesOptions = findUsagesOptions.clone();
      editor.putUserData(KEY_START_USAGE_AGAIN, null);
      findUsagesInEditor(
          descriptor, handler, scopeFile, FileSearchScope.FROM_START, findUsagesOptions, editor);
    } else {
      findUsages(
          descriptor,
          handler,
          dialog.isSkipResultsWhenOneUsage(),
          dialog.isShowInSeparateWindow(),
          findUsagesOptions);
    }
  }

  public void showSettingsAndFindUsages(@NotNull NavigationItem[] targets) {
    UsageTarget[] usageTargets = (UsageTarget[]) targets;
    PsiElement[] elements = getPsiElements(usageTargets);
    if (elements.length == 0) return;
    PsiElement psiElement = elements[0];
    doShowDialogAndStartFind(psiElement, null, null, true, false);
  }

  private static void checkNotNull(
      @NotNull PsiElement[] primaryElements,
      @NotNull FindUsagesHandler handler,
      @NonNls @NotNull String methodName) {
    for (PsiElement element : primaryElements) {
      if (element == null) {
        LOG.error(
            handler
                + "."
                + methodName
                + " has returned array with null elements: "
                + Arrays.asList(primaryElements));
      }
    }
  }

  public boolean isUsed(@NotNull PsiElement element, @NotNull FindUsagesOptions findUsagesOptions) {
    FindUsagesHandler handler = getFindUsagesHandler(element, true);
    if (handler == null) return false;
    UsageInfoToUsageConverter.TargetElementsDescriptor descriptor =
        new UsageInfoToUsageConverter.TargetElementsDescriptor(element);
    UsageSearcher usageSearcher = createUsageSearcher(descriptor, handler, findUsagesOptions, null);
    final AtomicBoolean used = new AtomicBoolean();
    usageSearcher.generate(
        new Processor<Usage>() {
          @Override
          public boolean process(final Usage usage) {
            used.set(true);
            return false;
          }
        });
    return used.get();
  }

  @NotNull
  public static ProgressIndicator startProcessUsages(
      @NotNull FindUsagesHandler handler,
      @NotNull UsageInfoToUsageConverter.TargetElementsDescriptor descriptor,
      @NotNull final Processor<Usage> processor,
      @NotNull FindUsagesOptions findUsagesOptions,
      @NotNull final Runnable onComplete) {
    final UsageSearcher usageSearcher =
        createUsageSearcher(descriptor, handler, findUsagesOptions, null);

    final ProgressIndicatorBase indicator = new ProgressIndicatorBase();
    ApplicationManager.getApplication()
        .executeOnPooledThread(
            new Runnable() {
              @Override
              public void run() {
                try {
                  ProgressManager.getInstance()
                      .runProcess(
                          new Runnable() {
                            @Override
                            public void run() {
                              usageSearcher.generate(processor);
                            }
                          },
                          indicator);
                } finally {
                  onComplete.run();
                }
              }
            });

    return indicator;
  }

  @NotNull
  public UsageViewPresentation createPresentation(
      @NotNull FindUsagesHandler handler, @NotNull FindUsagesOptions findUsagesOptions) {
    PsiElement element = handler.getPsiElement();
    LOG.assertTrue(element.isValid());
    return createPresentation(element, findUsagesOptions, myToOpenInNewTab);
  }

  private void setOpenInNewTab(final boolean toOpenInNewTab) {
    if (!mustOpenInNewTab()) {
      myToOpenInNewTab = toOpenInNewTab;
    }
  }

  private boolean shouldOpenInNewTab() {
    return mustOpenInNewTab() || myToOpenInNewTab;
  }

  private boolean mustOpenInNewTab() {
    Content selectedContent = UsageViewManager.getInstance(myProject).getSelectedContent(true);
    return selectedContent != null && selectedContent.isPinned();
  }

  private static UsageSearcher createUsageSearcher(
      @NotNull final UsageInfoToUsageConverter.TargetElementsDescriptor descriptor,
      @NotNull final FindUsagesHandler handler,
      @NotNull FindUsagesOptions _options,
      final PsiFile scopeFile) {
    final FindUsagesOptions options = _options.clone();
    return new UsageSearcher() {
      @Override
      public void generate(@NotNull final Processor<Usage> processor) {
        if (scopeFile != null) {
          options.searchScope = new LocalSearchScope(scopeFile);
        }
        final Processor<UsageInfo> usageInfoProcessor =
            new CommonProcessors.UniqueProcessor<UsageInfo>(
                new Processor<UsageInfo>() {
                  @Override
                  public boolean process(UsageInfo usageInfo) {
                    return processor.process(
                        UsageInfoToUsageConverter.convert(descriptor, usageInfo));
                  }
                });
        final List<? extends PsiElement> elements =
            ApplicationManager.getApplication()
                .runReadAction(
                    new Computable<List<? extends PsiElement>>() {
                      @Override
                      public List<? extends PsiElement> compute() {
                        return descriptor.getAllElements();
                      }
                    });

        options.fastTrack = new SearchRequestCollector(new SearchSession());

        try {
          for (final PsiElement element : elements) {
            ApplicationManager.getApplication()
                .runReadAction(
                    new Runnable() {
                      @Override
                      public void run() {
                        LOG.assertTrue(element.isValid());
                      }
                    });
            handler.processElementUsages(element, usageInfoProcessor, options);
            for (CustomUsageSearcher searcher :
                Extensions.getExtensions(CustomUsageSearcher.EP_NAME)) {
              try {
                searcher.processElementUsages(element, processor, options);
              } catch (IndexNotReadyException e) {
                DumbService.getInstance(element.getProject())
                    .showDumbModeNotification("Find usages is not available during indexing");
              } catch (Exception e) {
                LOG.error(e);
              }
            }
          }

          Project project =
              ApplicationManager.getApplication()
                  .runReadAction(
                      new Computable<Project>() {
                        @Override
                        public Project compute() {
                          return scopeFile != null
                              ? scopeFile.getProject()
                              : !elements.isEmpty()
                                  ? elements.get(0).getProject()
                                  : handler.getProject();
                        }
                      });
          PsiSearchHelper.SERVICE
              .getInstance(project)
              .processRequests(
                  options.fastTrack,
                  new ReadActionProcessor<PsiReference>() {
                    @Override
                    public boolean processInReadAction(final PsiReference ref) {
                      return !ref.getElement().isValid()
                          || usageInfoProcessor.process(new UsageInfo(ref));
                    }
                  });
        } finally {
          options.fastTrack = null;
        }
      }
    };
  }

  private static PsiElement2UsageTargetAdapter[] convertToUsageTargets(
      final List<? extends PsiElement> elementsToSearch) {
    final ArrayList<PsiElement2UsageTargetAdapter> targets =
        new ArrayList<PsiElement2UsageTargetAdapter>(elementsToSearch.size());
    for (PsiElement element : elementsToSearch) {
      convertToUsageTarget(targets, element);
    }
    return targets.toArray(new PsiElement2UsageTargetAdapter[targets.size()]);
  }

  private void findUsages(
      @NotNull final UsageInfoToUsageConverter.TargetElementsDescriptor descriptor,
      @NotNull final FindUsagesHandler handler,
      final boolean toSkipUsagePanelWhenOneUsage,
      final boolean toOpenInNewTab,
      @NotNull final FindUsagesOptions findUsagesOptions) {
    List<? extends PsiElement> elements = descriptor.getAllElements();
    if (elements.isEmpty()) {
      throw new AssertionError(handler + " " + findUsagesOptions);
    }
    final UsageTarget[] targets = convertToUsageTargets(elements);
    myAnotherManager.searchAndShowUsages(
        targets,
        new Factory<UsageSearcher>() {
          @Override
          public UsageSearcher create() {
            return createUsageSearcher(descriptor, handler, findUsagesOptions, null);
          }
        },
        !toSkipUsagePanelWhenOneUsage,
        true,
        createPresentation(elements.get(0), findUsagesOptions, toOpenInNewTab),
        null);
    addToHistory(elements, findUsagesOptions);
  }

  @NotNull
  private static UsageViewPresentation createPresentation(
      @NotNull PsiElement psiElement,
      @NotNull FindUsagesOptions findUsagesOptions,
      boolean toOpenInNewTab) {
    UsageViewPresentation presentation = new UsageViewPresentation();
    String scopeString =
        findUsagesOptions.searchScope == null
            ? null
            : findUsagesOptions.searchScope.getDisplayName();
    presentation.setScopeText(scopeString);
    String usagesString = generateUsagesString(findUsagesOptions);
    presentation.setUsagesString(usagesString);
    String title =
        scopeString == null
            ? FindBundle.message(
                "find.usages.of.element.panel.title",
                usagesString,
                UsageViewUtil.getLongName(psiElement))
            : FindBundle.message(
                "find.usages.of.element.in.scope.panel.title",
                usagesString,
                UsageViewUtil.getLongName(psiElement),
                scopeString);
    presentation.setTabText(title);
    presentation.setTabName(
        FindBundle.message(
            "find.usages.of.element.tab.name",
            usagesString,
            UsageViewUtil.getShortName(psiElement)));
    presentation.setTargetsNodeText(StringUtil.capitalize(UsageViewUtil.getType(psiElement)));
    presentation.setOpenInNewTab(toOpenInNewTab);
    return presentation;
  }

  private void findUsagesInEditor(
      @NotNull UsageInfoToUsageConverter.TargetElementsDescriptor descriptor,
      @NotNull FindUsagesHandler handler,
      @NotNull PsiFile scopeFile,
      @NotNull FileSearchScope direction,
      @NotNull final FindUsagesOptions findUsagesOptions,
      @NotNull FileEditor fileEditor) {
    initLastSearchElement(findUsagesOptions, descriptor);

    clearStatusBar();

    final FileEditorLocation currentLocation = fileEditor.getCurrentLocation();

    final UsageSearcher usageSearcher =
        createUsageSearcher(descriptor, handler, findUsagesOptions, scopeFile);
    AtomicBoolean usagesWereFound = new AtomicBoolean();

    Usage fUsage =
        findSiblingUsage(usageSearcher, direction, currentLocation, usagesWereFound, fileEditor);

    if (fUsage != null) {
      fUsage.navigate(true);
      fUsage.selectInEditor();
    } else if (!usagesWereFound.get()) {
      String message =
          getNoUsagesFoundMessage(descriptor.getPrimaryElements()[0])
              + " in "
              + scopeFile.getName();
      showHintOrStatusBarMessage(message, fileEditor);
    } else {
      fileEditor.putUserData(KEY_START_USAGE_AGAIN, VALUE_START_USAGE_AGAIN);
      showHintOrStatusBarMessage(
          getSearchAgainMessage(descriptor.getPrimaryElements()[0], direction), fileEditor);
    }
  }

  private static String getNoUsagesFoundMessage(PsiElement psiElement) {
    String elementType = UsageViewUtil.getType(psiElement);
    String elementName = UsageViewUtil.getShortName(psiElement);
    return FindBundle.message(
        "find.usages.of.element_type.element_name.not.found.message", elementType, elementName);
  }

  private void clearStatusBar() {
    StatusBar.Info.set("", myProject);
  }

  private static String getSearchAgainMessage(PsiElement element, final FileSearchScope direction) {
    String message = getNoUsagesFoundMessage(element);
    if (direction == FileSearchScope.AFTER_CARET) {
      AnAction action = ActionManager.getInstance().getAction(IdeActions.ACTION_FIND_NEXT);
      String shortcutsText = KeymapUtil.getFirstKeyboardShortcutText(action);
      if (shortcutsText.isEmpty()) {
        message = FindBundle.message("find.search.again.from.top.action.message", message);
      } else {
        message =
            FindBundle.message("find.search.again.from.top.hotkey.message", message, shortcutsText);
      }
    } else {
      String shortcutsText =
          KeymapUtil.getFirstKeyboardShortcutText(
              ActionManager.getInstance().getAction(IdeActions.ACTION_FIND_PREVIOUS));
      if (shortcutsText.isEmpty()) {
        message = FindBundle.message("find.search.again.from.bottom.action.message", message);
      } else {
        message =
            FindBundle.message(
                "find.search.again.from.bottom.hotkey.message", message, shortcutsText);
      }
    }
    return message;
  }

  private void showHintOrStatusBarMessage(String message, FileEditor fileEditor) {
    if (fileEditor instanceof TextEditor) {
      TextEditor textEditor = (TextEditor) fileEditor;
      showEditorHint(message, textEditor.getEditor());
    } else {
      StatusBar.Info.set(message, myProject);
    }
  }

  private static Usage findSiblingUsage(
      @NotNull final UsageSearcher usageSearcher,
      @NotNull FileSearchScope dir,
      final FileEditorLocation currentLocation,
      @NotNull final AtomicBoolean usagesWereFound,
      @NotNull FileEditor fileEditor) {
    if (fileEditor.getUserData(KEY_START_USAGE_AGAIN) != null) {
      dir =
          dir == FileSearchScope.AFTER_CARET
              ? FileSearchScope.FROM_START
              : FileSearchScope.FROM_END;
    }

    final FileSearchScope direction = dir;

    final AtomicReference<Usage> foundUsage = new AtomicReference<Usage>();
    usageSearcher.generate(
        new Processor<Usage>() {
          @Override
          public boolean process(Usage usage) {
            usagesWereFound.set(true);
            if (direction == FileSearchScope.FROM_START) {
              foundUsage.compareAndSet(null, usage);
              return false;
            }
            if (direction == FileSearchScope.FROM_END) {
              foundUsage.set(usage);
            } else if (direction == FileSearchScope.AFTER_CARET) {
              if (Comparing.compare(usage.getLocation(), currentLocation) > 0) {
                foundUsage.set(usage);
                return false;
              }
            } else if (direction == FileSearchScope.BEFORE_CARET) {
              if (Comparing.compare(usage.getLocation(), currentLocation) >= 0) {
                return false;
              }
              while (true) {
                Usage found = foundUsage.get();
                if (found == null) {
                  if (foundUsage.compareAndSet(null, usage)) break;
                } else {
                  if (Comparing.compare(found.getLocation(), usage.getLocation()) < 0
                      && foundUsage.compareAndSet(found, usage)) break;
                }
              }
            }

            return true;
          }
        });

    fileEditor.putUserData(KEY_START_USAGE_AGAIN, null);

    return foundUsage.get();
  }

  private static void convertToUsageTarget(
      @NotNull List<PsiElement2UsageTargetAdapter> targets, @NotNull PsiElement elementToSearch) {
    if (elementToSearch instanceof NavigationItem) {
      targets.add(new PsiElement2UsageTargetAdapter(elementToSearch));
    } else {
      throw new IllegalArgumentException(
          "Wrong usage target:" + elementToSearch + "; " + elementToSearch.getClass());
    }
  }

  private static String generateUsagesString(final FindUsagesOptions selectedOptions) {
    return selectedOptions.generateUsagesString();
  }

  private static void showEditorHint(String message, final Editor editor) {
    JComponent component = HintUtil.createInformationLabel(message);
    final LightweightHint hint = new LightweightHint(component);
    HintManagerImpl.getInstanceImpl()
        .showEditorHint(
            hint,
            editor,
            HintManager.UNDER,
            HintManager.HIDE_BY_ANY_KEY
                | HintManager.HIDE_BY_TEXT_CHANGE
                | HintManager.HIDE_BY_SCROLLING,
            0,
            false);
  }

  public static String getHelpID(PsiElement element) {
    return LanguageFindUsages.INSTANCE.forLanguage(element.getLanguage()).getHelpId(element);
  }

  private void addToHistory(
      final List<? extends PsiElement> elements, final FindUsagesOptions findUsagesOptions) {
    SearchData data = createSearchData(elements, findUsagesOptions);
    myFindUsagesHistory.remove(data);
    myFindUsagesHistory.add(data);

    // todo configure history depth limit
    if (myFindUsagesHistory.size() > 15) {
      myFindUsagesHistory.remove(0);
    }
  }

  public void rerunAndRecallFromHistory(@NotNull SearchData searchData) {
    myFindUsagesHistory.remove(searchData);
    PsiElement[] elements = restorePsiElements(searchData, true);
    if (elements == null || elements.length == 0) return;
    UsageInfoToUsageConverter.TargetElementsDescriptor descriptor =
        new UsageInfoToUsageConverter.TargetElementsDescriptor(elements);
    final FindUsagesHandler handler = getFindUsagesHandler(elements[0], false);
    if (handler == null) return;
    findUsages(descriptor, handler, false, false, searchData.myOptions);
  }

  // most recent entry is at the end of the list
  public List<SearchData> getFindUsageHistory() {
    removeInvalidElementsFromHistory();
    return Collections.unmodifiableList(myFindUsagesHistory);
  }

  private void removeInvalidElementsFromHistory() {
    for (SearchData data : myFindUsagesHistory) {
      PsiElement[] elements = restorePsiElements(data, false);
      if (elements == null || elements.length == 0) myFindUsagesHistory.remove(data);
    }
  }

  @NotNull
  private static PsiElement[] getPsiElements(@NotNull UsageTarget[] targets) {
    List<PsiElement> result = new ArrayList<PsiElement>();
    for (UsageTarget target : targets) {
      if (target instanceof PsiElementUsageTarget) {
        PsiElement element = ((PsiElementUsageTarget) target).getElement();
        if (element != null) {
          result.add(element);
        }
      }
    }
    return PsiUtilCore.toPsiElementArray(result);
  }

  @NotNull
  public static GlobalSearchScope getMaximalScope(@NotNull FindUsagesHandler handler) {
    PsiElement element = handler.getPsiElement();
    Project project = element.getProject();
    PsiFile file = element.getContainingFile();
    if (file != null
        && ProjectFileIndex.SERVICE
            .getInstance(project)
            .isInContent(file.getViewProvider().getVirtualFile())) {
      return GlobalSearchScope.projectScope(project);
    }
    return GlobalSearchScope.allScope(project);
  }
}
public class StructureViewComponent extends SimpleToolWindowPanel
    implements TreeActionsOwner, DataProvider, StructureView.Scrollable {
  private static final Logger LOG =
      Logger.getInstance("#com.intellij.ide.structureView.newStructureView.StructureViewComponent");
  @NonNls private static final String ourHelpID = "viewingStructure.fileStructureView";

  private StructureTreeBuilder myAbstractTreeBuilder;

  private FileEditor myFileEditor;
  private final TreeModelWrapper myTreeModelWrapper;

  private StructureViewState myStructureViewState;
  private boolean myAutoscrollFeedback;

  private final Alarm myAutoscrollAlarm = new Alarm();

  private final CopyPasteDelegator myCopyPasteDelegator;
  private final MyAutoScrollToSourceHandler myAutoScrollToSourceHandler;
  private final AutoScrollFromSourceHandler myAutoScrollFromSourceHandler;

  private static final Key<StructureViewState> STRUCTURE_VIEW_STATE_KEY =
      Key.create("STRUCTURE_VIEW_STATE");
  private final Project myProject;
  private final StructureViewModel myTreeModel;
  private static int ourSettingsModificationCount;
  private Tree myTree;

  public StructureViewComponent(
      FileEditor editor, StructureViewModel structureViewModel, Project project) {
    this(editor, structureViewModel, project, true);
  }

  public StructureViewComponent(
      final FileEditor editor,
      final StructureViewModel structureViewModel,
      final Project project,
      final boolean showRootNode) {
    super(true, true);

    myProject = project;
    myFileEditor = editor;
    myTreeModel = structureViewModel;
    myTreeModelWrapper = new TreeModelWrapper(myTreeModel, this);

    SmartTreeStructure treeStructure =
        new SmartTreeStructure(project, myTreeModelWrapper) {
          public void rebuildTree() {
            if (!isDisposed()) {
              super.rebuildTree();
            }
          }

          public boolean isToBuildChildrenInBackground(final Object element) {
            return getRootElement() == element;
          }

          protected TreeElementWrapper createTree() {
            return new StructureViewTreeElementWrapper(myProject, myModel.getRoot(), myModel);
          }

          @Override
          public String toString() {
            return "structure view tree structure(model=" + myTreeModel + ")";
          }
        };

    final DefaultTreeModel model =
        new DefaultTreeModel(new DefaultMutableTreeNode(treeStructure.getRootElement()));
    myTree = new Tree(model);
    myTree.setRootVisible(showRootNode);
    myTree.setShowsRootHandles(true);

    myAbstractTreeBuilder =
        new StructureTreeBuilder(
            project,
            myTree,
            (DefaultTreeModel) myTree.getModel(),
            treeStructure,
            myTreeModelWrapper) {
          @Override
          protected boolean validateNode(Object child) {
            return isValid(child);
          }
        };
    Disposer.register(this, myAbstractTreeBuilder);
    Disposer.register(
        myAbstractTreeBuilder,
        new Disposable() {
          public void dispose() {
            storeState();
          }
        });

    setContent(ScrollPaneFactory.createScrollPane(myAbstractTreeBuilder.getTree()));

    myAbstractTreeBuilder.getTree().setCellRenderer(new NodeRenderer());

    myAutoScrollToSourceHandler = new MyAutoScrollToSourceHandler();
    myAutoScrollFromSourceHandler = new MyAutoScrollFromSourceHandler(myProject, this);

    JComponent toolbarComponent =
        ActionManager.getInstance()
            .createActionToolbar(ActionPlaces.STRUCTURE_VIEW_TOOLBAR, createActionGroup(), true)
            .getComponent();
    setToolbar(toolbarComponent);

    installTree();

    myCopyPasteDelegator =
        new CopyPasteDelegator(myProject, getTree()) {
          @NotNull
          protected PsiElement[] getSelectedElements() {
            return getSelectedPsiElements();
          }
        };
  }

  private void installTree() {
    getTree().getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
    myAutoScrollToSourceHandler.install(getTree());
    myAutoScrollFromSourceHandler.install();

    TreeUtil.installActions(getTree());
    new TreeSpeedSearch(getTree());

    addTreeKeyListener();
    addTreeMouseListeners();
    restoreState();
  }

  private PsiElement[] getSelectedPsiElements() {
    return filterPsiElements(getSelectedElements());
  }

  @NotNull
  private static PsiElement[] filterPsiElements(Object[] selectedElements) {
    if (selectedElements == null) {
      return PsiElement.EMPTY_ARRAY;
    }
    ArrayList<PsiElement> psiElements = new ArrayList<PsiElement>();

    for (Object selectedElement : selectedElements) {
      if (selectedElement instanceof PsiElement) {
        psiElements.add((PsiElement) selectedElement);
      }
    }
    return PsiUtilBase.toPsiElementArray(psiElements);
  }

  private Object[] getSelectedElements() {
    final JTree tree = getTree();
    return tree != null
        ? convertPathsToValues(tree.getSelectionPaths())
        : ArrayUtil.EMPTY_OBJECT_ARRAY;
  }

  @Nullable
  private Object[] getSelectedTreeElements() {
    final JTree tree = getTree();
    return tree != null ? convertPathsToTreeElements(tree.getSelectionPaths()) : null;
  }

  private static Object[] convertPathsToValues(TreePath[] selectionPaths) {
    if (selectionPaths != null) {
      List<Object> result = new ArrayList<Object>();

      for (TreePath selectionPath : selectionPaths) {
        final Object userObject =
            ((DefaultMutableTreeNode) selectionPath.getLastPathComponent()).getUserObject();
        if (userObject instanceof AbstractTreeNode) {
          Object value = ((AbstractTreeNode) userObject).getValue();
          if (value instanceof StructureViewTreeElement) {
            value = ((StructureViewTreeElement) value).getValue();
          }
          result.add(value);
        }
      }
      return ArrayUtil.toObjectArray(result);
    } else {
      return null;
    }
  }

  @Nullable
  private static Object[] convertPathsToTreeElements(TreePath[] selectionPaths) {
    if (selectionPaths != null) {
      Object[] result = new Object[selectionPaths.length];

      for (int i = 0; i < selectionPaths.length; i++) {
        Object userObject =
            ((DefaultMutableTreeNode) selectionPaths[i].getLastPathComponent()).getUserObject();
        if (!(userObject instanceof AbstractTreeNode)) return null;
        result[i] = ((AbstractTreeNode) userObject).getValue();
      }
      return result;
    } else {
      return null;
    }
  }

  private void addTreeMouseListeners() {
    EditSourceOnDoubleClickHandler.install(getTree());
    CustomizationUtil.installPopupHandler(
        getTree(), IdeActions.GROUP_STRUCTURE_VIEW_POPUP, ActionPlaces.STRUCTURE_VIEW_POPUP);
  }

  private void addTreeKeyListener() {
    getTree()
        .addKeyListener(
            new KeyAdapter() {
              public void keyPressed(KeyEvent e) {
                if (KeyEvent.VK_ENTER == e.getKeyCode()) {
                  DataContext dataContext = DataManager.getInstance().getDataContext(getTree());
                  OpenSourceUtil.openSourcesFrom(dataContext, false);
                } else if (KeyEvent.VK_ESCAPE == e.getKeyCode()) {
                  if (e.isConsumed()) {
                    return;
                  }
                  PsiCopyPasteManager copyPasteManager = PsiCopyPasteManager.getInstance();
                  boolean[] isCopied = new boolean[1];
                  if (copyPasteManager.getElements(isCopied) != null && !isCopied[0]) {
                    copyPasteManager.clear();
                    e.consume();
                  }
                }
              }
            });
  }

  public void storeState() {
    if (!isDisposed()) {
      myStructureViewState = getState();
      myFileEditor.putUserData(STRUCTURE_VIEW_STATE_KEY, myStructureViewState);
    }
  }

  public StructureViewState getState() {
    StructureViewState structureViewState = new StructureViewState();
    if (getTree() != null) {
      structureViewState.setExpandedElements(getExpandedElements());
      structureViewState.setSelectedElements(getSelectedElements());
    }
    return structureViewState;
  }

  private Object[] getExpandedElements() {
    final JTree tree = getTree();
    if (tree == null) return ArrayUtil.EMPTY_OBJECT_ARRAY;
    final List<TreePath> expandedPaths = TreeUtil.collectExpandedPaths(tree);
    return convertPathsToValues(expandedPaths.toArray(new TreePath[expandedPaths.size()]));
  }

  public void restoreState() {
    myStructureViewState = myFileEditor.getUserData(STRUCTURE_VIEW_STATE_KEY);
    if (myStructureViewState == null) {
      TreeUtil.expand(getTree(), 2);
    } else {
      expandStoredElements();
      selectStoredElements();
      myFileEditor.putUserData(STRUCTURE_VIEW_STATE_KEY, null);
      myStructureViewState = null;
    }
  }

  private void selectStoredElements() {
    Object[] selectedPsiElements = null;

    if (myStructureViewState != null) {
      selectedPsiElements = myStructureViewState.getSelectedElements();
    }

    if (selectedPsiElements == null) {
      getTree().setSelectionPath(new TreePath(getRootNode().getPath()));
    } else {
      for (Object element : selectedPsiElements) {
        if (element instanceof PsiElement && !((PsiElement) element).isValid()) {
          continue;
        }
        addSelectionPathTo(element);
      }
    }
  }

  public void addSelectionPathTo(final Object element) {
    DefaultMutableTreeNode node = myAbstractTreeBuilder.getNodeForElement(element);
    if (node != null) {
      final JTree tree = getTree();
      final TreePath path = new TreePath(node.getPath());
      if (node == tree.getModel().getRoot() && !tree.isExpanded(path)) tree.expandPath(path);
      tree.addSelectionPath(path);
    }
  }

  private DefaultMutableTreeNode getRootNode() {
    return (DefaultMutableTreeNode) getTree().getModel().getRoot();
  }

  private void expandStoredElements() {
    Object[] expandedPsiElements = null;

    if (myStructureViewState != null) {
      expandedPsiElements = myStructureViewState.getExpandedElements();
    }

    if (expandedPsiElements == null) {
      getTree().expandPath(new TreePath(getRootNode().getPath()));
    } else {
      for (Object element : expandedPsiElements) {
        if (element instanceof PsiElement && !((PsiElement) element).isValid()) {
          continue;
        }
        expandPathToElement(element);
      }
    }
  }

  protected ActionGroup createActionGroup() {
    DefaultActionGroup result = new DefaultActionGroup();
    Sorter[] sorters = myTreeModel.getSorters();
    for (final Sorter sorter : sorters) {
      if (sorter.isVisible()) {
        result.add(new TreeActionWrapper(sorter, this));
      }
    }
    if (sorters.length > 0) {
      result.addSeparator();
    }

    Grouper[] groupers = myTreeModel.getGroupers();
    for (Grouper grouper : groupers) {
      result.add(new TreeActionWrapper(grouper, this));
    }
    Filter[] filters = myTreeModel.getFilters();
    for (Filter filter : filters) {
      result.add(new TreeActionWrapper(filter, this));
    }
    if (myTreeModel instanceof ProvidingTreeModel) {
      final Collection<NodeProvider> providers =
          ((ProvidingTreeModel) myTreeModel).getNodeProviders();
      for (NodeProvider provider : providers) {
        result.add(new TreeActionWrapper(provider, this));
      }
    }

    result.add(new ExpandAllAction(getTree()));
    result.add(new CollapseAllAction(getTree()));
    if (showScrollToFromSourceActions()) {
      result.addSeparator();

      result.add(myAutoScrollToSourceHandler.createToggleAction());
      result.add(myAutoScrollFromSourceHandler.createToggleAction());
    }
    return result;
  }

  protected boolean showScrollToFromSourceActions() {
    return true;
  }

  public FileEditor getFileEditor() {
    return myFileEditor;
  }

  public AsyncResult<AbstractTreeNode> expandPathToElement(Object element) {
    if (myAbstractTreeBuilder == null) return new AsyncResult.Rejected<AbstractTreeNode>();

    ArrayList<AbstractTreeNode> pathToElement = getPathToElement(element);
    if (pathToElement.isEmpty()) return new AsyncResult.Rejected<AbstractTreeNode>();

    final AsyncResult<AbstractTreeNode> result = new AsyncResult<AbstractTreeNode>();
    final AbstractTreeNode toExpand = pathToElement.get(pathToElement.size() - 1);
    myAbstractTreeBuilder.expand(
        toExpand,
        new Runnable() {
          public void run() {
            result.setDone(toExpand);
          }
        });

    return result;
  }

  public boolean select(final Object element, final boolean requestFocus) {
    myAbstractTreeBuilder
        .getReady(this)
        .doWhenDone(
            new Runnable() {
              public void run() {
                expandPathToElement(element)
                    .doWhenDone(
                        new AsyncResult.Handler<AbstractTreeNode>() {
                          public void run(AbstractTreeNode abstractTreeNode) {
                            myAbstractTreeBuilder.select(
                                abstractTreeNode,
                                new Runnable() {
                                  public void run() {
                                    if (requestFocus) {
                                      IdeFocusManager.getInstance(myProject)
                                          .requestFocus(myAbstractTreeBuilder.getTree(), false);
                                    }
                                  }
                                });
                          }
                        });
              }
            });
    return true;
  }

  private ArrayList<AbstractTreeNode> getPathToElement(Object element) {
    ArrayList<AbstractTreeNode> result = new ArrayList<AbstractTreeNode>();
    final AbstractTreeStructure treeStructure = myAbstractTreeBuilder.getTreeStructure();
    if (treeStructure != null) {
      addToPath(
          (AbstractTreeNode) treeStructure.getRootElement(),
          element,
          result,
          new THashSet<Object>());
    }
    return result;
  }

  private static boolean addToPath(
      AbstractTreeNode<?> rootElement,
      Object element,
      ArrayList<AbstractTreeNode> result,
      Collection<Object> processedElements) {
    Object value = rootElement.getValue();
    if (value instanceof StructureViewTreeElement) {
      value = ((StructureViewTreeElement) value).getValue();
    }
    if (!processedElements.add(value)) {
      return false;
    }

    if (Comparing.equal(value, element)) {
      result.add(0, rootElement);
      return true;
    }

    Collection<? extends AbstractTreeNode> children = rootElement.getChildren();
    for (AbstractTreeNode child : children) {
      if (addToPath(child, element, result, processedElements)) {
        result.add(0, rootElement);
        return true;
      }
    }

    return false;
  }

  private static DefaultMutableTreeNode findInChildren(
      DefaultMutableTreeNode currentTreeNode, AbstractTreeNode topPathElement) {
    for (int i = 0; i < currentTreeNode.getChildCount(); i++) {
      TreeNode child = currentTreeNode.getChildAt(i);
      if (((DefaultMutableTreeNode) child).getUserObject().equals(topPathElement)) {
        return (DefaultMutableTreeNode) child;
      }
    }
    return null;
  }

  private void scrollToSelectedElement() {
    if (myAutoscrollFeedback) {
      myAutoscrollFeedback = false;
      return;
    }

    StructureViewFactoryImpl structureViewFactory =
        (StructureViewFactoryImpl) StructureViewFactoryEx.getInstance(myProject);

    if (!structureViewFactory.getState().AUTOSCROLL_FROM_SOURCE) {
      return;
    }

    myAutoscrollAlarm.cancelAllRequests();
    myAutoscrollAlarm.addRequest(
        new Runnable() {
          public void run() {
            if (myAbstractTreeBuilder == null) {
              return;
            }
            try {
              selectViewableElement();
            } catch (IndexNotReadyException ignore) {
            }
          }
        },
        1000);
  }

  private void selectViewableElement() {
    PsiDocumentManager.getInstance(myProject).commitAllDocuments();
    final Object currentEditorElement = myTreeModel.getCurrentEditorElement();
    if (currentEditorElement != null) {
      select(currentEditorElement, false);
    }
  }

  public void dispose() {
    LOG.assertTrue(EventQueue.isDispatchThread(), Thread.currentThread().getName());
    myAbstractTreeBuilder = null;
    // this will also dispose wrapped TreeModel
    myTreeModelWrapper.dispose();
    myFileEditor = null;
  }

  public boolean isDisposed() {
    return myAbstractTreeBuilder == null;
  }

  public void centerSelectedRow() {
    TreePath path = getTree().getSelectionPath();
    if (path == null) {
      return;
    }
    myAutoScrollToSourceHandler.setShouldAutoScroll(false);
    TreeUtil.showRowCentered(getTree(), getTree().getRowForPath(path), false);
    myAutoScrollToSourceHandler.setShouldAutoScroll(true);
  }

  public void setActionActive(String name, boolean state) {
    StructureViewFactoryEx.getInstanceEx(myProject).setActiveAction(name, state);
    rebuild();
    TreeUtil.expand(getTree(), 2);
  }

  protected void rebuild() {
    storeState();
    ++ourSettingsModificationCount;
    ((SmartTreeStructure) myAbstractTreeBuilder.getTreeStructure()).rebuildTree();
    myAbstractTreeBuilder.updateFromRoot();
    restoreState();
  }

  public boolean isActionActive(String name) {
    return !myProject.isDisposed()
        && StructureViewFactoryEx.getInstanceEx(myProject).isActionActive(name);
  }

  public AbstractTreeStructure getTreeStructure() {
    return myAbstractTreeBuilder.getTreeStructure();
  }

  public JTree getTree() {
    return myTree;
  }

  private final class MyAutoScrollToSourceHandler extends AutoScrollToSourceHandler {
    private boolean myShouldAutoScroll = true;

    public void setShouldAutoScroll(boolean shouldAutoScroll) {
      myShouldAutoScroll = shouldAutoScroll;
    }

    protected boolean isAutoScrollMode() {
      return myShouldAutoScroll
          && !myProject.isDisposed()
          && ((StructureViewFactoryImpl) StructureViewFactory.getInstance(myProject))
              .getState()
              .AUTOSCROLL_MODE;
    }

    protected void setAutoScrollMode(boolean state) {
      ((StructureViewFactoryImpl) StructureViewFactory.getInstance(myProject))
              .getState()
              .AUTOSCROLL_MODE =
          state;
    }

    protected void scrollToSource(Component tree) {
      if (myAbstractTreeBuilder == null) return;
      myAutoscrollFeedback = true;

      Navigatable editSourceDescriptor =
          PlatformDataKeys.NAVIGATABLE.getData(DataManager.getInstance().getDataContext(getTree()));
      if (myFileEditor != null
          && editSourceDescriptor != null
          && editSourceDescriptor.canNavigateToSource()) {
        editSourceDescriptor.navigate(false);
      }
    }
  }

  private class MyAutoScrollFromSourceHandler extends AutoScrollFromSourceHandler {
    private FileEditorPositionListener myFileEditorPositionListener;

    private MyAutoScrollFromSourceHandler(Project project, Disposable parentDisposable) {
      super(project, parentDisposable);
    }

    public void install() {
      addEditorCaretListener();
    }

    public void dispose() {
      myTreeModel.removeEditorPositionListener(myFileEditorPositionListener);
    }

    private void addEditorCaretListener() {
      myFileEditorPositionListener =
          new FileEditorPositionListener() {
            public void onCurrentElementChanged() {
              scrollToSelectedElement();
            }
          };
      myTreeModel.addEditorPositionListener(myFileEditorPositionListener);
    }

    protected boolean isAutoScrollMode() {
      StructureViewFactoryImpl structureViewFactory =
          (StructureViewFactoryImpl) StructureViewFactory.getInstance(myProject);
      return structureViewFactory.getState().AUTOSCROLL_FROM_SOURCE;
    }

    protected void setAutoScrollMode(boolean state) {
      StructureViewFactoryImpl structureViewFactory =
          (StructureViewFactoryImpl) StructureViewFactory.getInstance(myProject);
      structureViewFactory.getState().AUTOSCROLL_FROM_SOURCE = state;
      final FileEditor[] selectedEditors =
          FileEditorManager.getInstance(myProject).getSelectedEditors();
      if (selectedEditors.length > 0 && state) {
        scrollToSelectedElement();
      }
    }
  }

  public Object getData(String dataId) {
    if (LangDataKeys.PSI_ELEMENT.is(dataId)) {
      TreePath path = getSelectedUniquePath();
      if (path == null) return null;
      DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
      Object userObject = node.getUserObject();
      if (!(userObject instanceof AbstractTreeNode)) return null;
      AbstractTreeNode descriptor = (AbstractTreeNode) userObject;
      Object element = descriptor.getValue();
      if (element instanceof StructureViewTreeElement) {
        element = ((StructureViewTreeElement) element).getValue();
      }
      if (!(element instanceof PsiElement)) return null;
      if (!((PsiElement) element).isValid()) return null;
      return element;
    }
    if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) {
      return convertToPsiElementsArray(getSelectedElements());
    }
    if (PlatformDataKeys.FILE_EDITOR.is(dataId)) {
      return myFileEditor;
    }
    if (PlatformDataKeys.CUT_PROVIDER.is(dataId)) {
      return myCopyPasteDelegator.getCutProvider();
    }
    if (PlatformDataKeys.COPY_PROVIDER.is(dataId)) {
      return myCopyPasteDelegator.getCopyProvider();
    }
    if (PlatformDataKeys.PASTE_PROVIDER.is(dataId)) {
      return myCopyPasteDelegator.getPasteProvider();
    }
    if (PlatformDataKeys.NAVIGATABLE.is(dataId)) {
      Object[] selectedElements = getSelectedTreeElements();
      if (selectedElements == null || selectedElements.length == 0) return null;
      if (selectedElements[0] instanceof Navigatable) {
        return selectedElements[0];
      }
    }
    if (PlatformDataKeys.HELP_ID.is(dataId)) {
      return getHelpID();
    }
    return super.getData(dataId);
  }

  @Nullable
  private static PsiElement[] convertToPsiElementsArray(final Object[] selectedElements) {
    if (selectedElements == null) return null;
    ArrayList<PsiElement> psiElements = new ArrayList<PsiElement>();
    for (Object selectedElement : selectedElements) {
      if (selectedElement instanceof PsiElement && ((PsiElement) selectedElement).isValid()) {
        psiElements.add((PsiElement) selectedElement);
      }
    }
    return PsiUtilBase.toPsiElementArray(psiElements);
  }

  @Nullable
  private TreePath getSelectedUniquePath() {
    JTree tree = getTree();
    if (tree == null) return null;
    TreePath[] paths = tree.getSelectionPaths();
    return paths == null || paths.length != 1 ? null : paths[0];
  }

  public StructureViewModel getTreeModel() {
    return myTreeModel;
  }

  public boolean navigateToSelectedElement(boolean requestFocus) {
    return select(myTreeModel.getCurrentEditorElement(), requestFocus);
  }

  public void doUpdate() {
    assert ApplicationManager.getApplication().isUnitTestMode();
    myAbstractTreeBuilder.addRootToUpdate();
  }

  // todo [kirillk] dirty hack for discovering invalid psi elements, to delegate it to a proper
  // place after 8.1
  public static boolean isValid(Object treeElement) {
    if (treeElement instanceof StructureViewTreeElementWrapper) {
      final StructureViewTreeElementWrapper wrapper = (StructureViewTreeElementWrapper) treeElement;
      if (wrapper.getValue() instanceof PsiTreeElementBase) {
        final PsiTreeElementBase psiNode = (PsiTreeElementBase) wrapper.getValue();
        return psiNode.isValid();
      }
    }
    return true;
  }

  public static class StructureViewTreeElementWrapper extends TreeElementWrapper
      implements NodeDescriptorProvidingKey {
    private long childrenStamp = -1;
    private long modificationCountForChildren = ourSettingsModificationCount;

    public StructureViewTreeElementWrapper(
        Project project, TreeElement value, TreeModel treeModel) {
      super(project, value, treeModel);
    }

    @NotNull
    public Object getKey() {
      StructureViewTreeElement element = (StructureViewTreeElement) getValue();
      if (element instanceof NodeDescriptorProvidingKey)
        return ((NodeDescriptorProvidingKey) element).getKey();
      Object value = element.getValue();
      return value == null ? this : value;
    }

    @NotNull
    public Collection<AbstractTreeNode> getChildren() {
      if (ourSettingsModificationCount != modificationCountForChildren) {
        resetChildren();
        modificationCountForChildren = ourSettingsModificationCount;
      }

      final Object o = unwrapValue(getValue());
      long currentStamp;
      if ((o instanceof PsiElement
              && ((PsiElement) o).getNode() instanceof CompositeElement
              && childrenStamp
                  != (currentStamp =
                      ((CompositeElement) ((PsiElement) o).getNode()).getModificationCount()))
          || (o instanceof ModificationTracker
              && childrenStamp
                  != (currentStamp = ((ModificationTracker) o).getModificationCount()))) {
        resetChildren();
        childrenStamp = currentStamp;
      }
      try {
        return super.getChildren();
      } catch (IndexNotReadyException ignore) {
        return Collections.emptyList();
      }
    }

    @Override
    public boolean isAlwaysShowPlus() {
      if (getElementInfoProvider() != null) {
        return getElementInfoProvider().isAlwaysShowsPlus((StructureViewTreeElement) getValue());
      }
      return true;
    }

    @Override
    public boolean isAlwaysLeaf() {
      if (getElementInfoProvider() != null) {
        return getElementInfoProvider().isAlwaysLeaf((StructureViewTreeElement) getValue());
      }

      return false;
    }

    @Nullable
    private StructureViewModel.ElementInfoProvider getElementInfoProvider() {
      if (myTreeModel instanceof StructureViewModel.ElementInfoProvider) {
        return ((StructureViewModel.ElementInfoProvider) myTreeModel);
      } else if (myTreeModel instanceof TreeModelWrapper) {
        StructureViewModel model = ((TreeModelWrapper) myTreeModel).getModel();
        if (model instanceof StructureViewModel.ElementInfoProvider) {
          return (StructureViewModel.ElementInfoProvider) model;
        }
      }

      return null;
    }

    protected TreeElementWrapper createChildNode(final TreeElement child) {
      return new StructureViewTreeElementWrapper(myProject, child, myTreeModel);
    }

    @Override
    protected GroupWrapper createGroupWrapper(
        final Project project, Group group, final TreeModel treeModel) {
      return new StructureViewGroup(project, group, treeModel);
    }

    public boolean equals(Object o) {
      if (o instanceof StructureViewTreeElementWrapper) {
        return Comparing.equal(
            unwrapValue(getValue()), unwrapValue(((StructureViewTreeElementWrapper) o).getValue()));
      } else if (o instanceof StructureViewTreeElement) {
        return Comparing.equal(unwrapValue(getValue()), ((StructureViewTreeElement) o).getValue());
      }
      return false;
    }

    private static Object unwrapValue(Object o) {
      if (o instanceof StructureViewTreeElement) {
        return ((StructureViewTreeElement) o).getValue();
      } else {
        return o;
      }
    }

    public int hashCode() {
      final Object o = unwrapValue(getValue());

      return o != null ? o.hashCode() : 0;
    }

    private class StructureViewGroup extends GroupWrapper {
      public StructureViewGroup(Project project, Group group, TreeModel treeModel) {
        super(project, group, treeModel);
      }

      @Override
      protected TreeElementWrapper createChildNode(TreeElement child) {
        return new StructureViewTreeElementWrapper(getProject(), child, myTreeModel);
      }

      @Override
      protected GroupWrapper createGroupWrapper(Project project, Group group, TreeModel treeModel) {
        return new StructureViewGroup(project, group, treeModel);
      }

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

  public String getHelpID() {
    return ourHelpID;
  }

  public Dimension getCurrentSize() {
    return myTree.getSize();
  }

  public void setReferenceSizeWhileInitializing(Dimension size) {
    _setRefSize(size);

    if (size != null) {
      myAbstractTreeBuilder
          .getReady(this)
          .doWhenDone(
              new Runnable() {
                public void run() {
                  _setRefSize(null);
                }
              });
    }
  }

  private void _setRefSize(Dimension size) {
    myTree.setPreferredSize(size);
    myTree.setMinimumSize(size);
    myTree.setMaximumSize(size);

    myTree.revalidate();
    myTree.repaint();
  }
}
/** @author Gregory Shrago */
public class QuickEditHandler extends DocumentAdapter implements Disposable {
  private final Project myProject;
  private final QuickEditAction myAction;

  private final Editor myEditor;
  private final Document myOrigDocument;

  private final Document myNewDocument;
  private final PsiFile myNewFile;
  private final LightVirtualFile myNewVirtualFile;

  private final long myOrigCreationStamp;
  private EditorWindow mySplittedWindow;
  private boolean myCommittingToOriginal;

  private final PsiFile myInjectedFile;
  private final List<Trinity<RangeMarker, RangeMarker, SmartPsiElementPointer>> myMarkers =
      ContainerUtil.newLinkedList();

  @Nullable private final RangeMarker myAltFullRange;
  private static final Key<String> REPLACEMENT_KEY = Key.create("REPLACEMENT_KEY");

  QuickEditHandler(
      Project project,
      @NotNull PsiFile injectedFile,
      final PsiFile origFile,
      Editor editor,
      QuickEditAction action) {
    myProject = project;
    myEditor = editor;
    myAction = action;
    myOrigDocument = editor.getDocument();
    Place shreds = InjectedLanguageUtil.getShreds(injectedFile);
    FileType fileType = injectedFile.getFileType();
    Language language = injectedFile.getLanguage();
    PsiLanguageInjectionHost.Shred firstShred = ContainerUtil.getFirstItem(shreds);

    PsiFileFactory factory = PsiFileFactory.getInstance(project);
    String text = InjectedLanguageManager.getInstance(project).getUnescapedText(injectedFile);
    String newFileName =
        StringUtil.notNullize(language.getDisplayName(), "Injected")
            + " Fragment "
            + "("
            + origFile.getName()
            + ":"
            + firstShred.getHost().getTextRange().getStartOffset()
            + ")"
            + "."
            + fileType.getDefaultExtension();

    // preserve \r\n as it is done in MultiHostRegistrarImpl
    myNewFile = factory.createFileFromText(newFileName, language, text, true, false);
    myNewVirtualFile = ObjectUtils.assertNotNull((LightVirtualFile) myNewFile.getVirtualFile());
    myNewVirtualFile.setOriginalFile(origFile.getVirtualFile());

    assert myNewFile != null : "PSI file is null";
    assert myNewFile.getTextLength() == myNewVirtualFile.getContent().length()
        : "PSI / Virtual file text mismatch";

    myNewVirtualFile.setOriginalFile(origFile.getVirtualFile());
    // suppress possible errors as in injected mode
    myNewFile.putUserData(
        InjectedLanguageUtil.FRANKENSTEIN_INJECTION,
        injectedFile.getUserData(InjectedLanguageUtil.FRANKENSTEIN_INJECTION));
    myNewFile.putUserData(FileContextUtil.INJECTED_IN_ELEMENT, shreds.getHostPointer());
    myNewDocument = PsiDocumentManager.getInstance(project).getDocument(myNewFile);
    assert myNewDocument != null;
    EditorActionManager.getInstance()
        .setReadonlyFragmentModificationHandler(myNewDocument, new MyQuietHandler());
    myOrigCreationStamp =
        myOrigDocument.getModificationStamp(); // store creation stamp for UNDO tracking
    myOrigDocument.addDocumentListener(this, this);
    myNewDocument.addDocumentListener(this, this);
    EditorFactory editorFactory = ObjectUtils.assertNotNull(EditorFactory.getInstance());
    // not FileEditorManager listener because of RegExp checker and alike
    editorFactory.addEditorFactoryListener(
        new EditorFactoryAdapter() {
          int useCount;

          @Override
          public void editorCreated(@NotNull EditorFactoryEvent event) {
            if (event.getEditor().getDocument() != myNewDocument) return;
            useCount++;
          }

          @Override
          public void editorReleased(@NotNull EditorFactoryEvent event) {
            if (event.getEditor().getDocument() != myNewDocument) return;
            if (--useCount > 0) return;
            if (Boolean.TRUE.equals(
                myNewVirtualFile.getUserData(FileEditorManagerImpl.CLOSING_TO_REOPEN))) return;

            Disposer.dispose(QuickEditHandler.this);
          }
        },
        this);

    if ("JAVA".equals(firstShred.getHost().getLanguage().getID())) {
      PsiLanguageInjectionHost.Shred lastShred = ContainerUtil.getLastItem(shreds);
      myAltFullRange =
          myOrigDocument.createRangeMarker(
              firstShred.getHostRangeMarker().getStartOffset(),
              lastShred.getHostRangeMarker().getEndOffset());
      myAltFullRange.setGreedyToLeft(true);
      myAltFullRange.setGreedyToRight(true);

      initGuardedBlocks(shreds);
      myInjectedFile = null;
    } else {
      initMarkers(shreds);
      myAltFullRange = null;
      myInjectedFile = injectedFile;
    }
  }

  public boolean isValid() {
    boolean valid =
        myNewVirtualFile.isValid()
            && (myAltFullRange == null && myInjectedFile.isValid()
                || myAltFullRange != null && myAltFullRange.isValid());
    if (valid) {
      for (Trinity<RangeMarker, RangeMarker, SmartPsiElementPointer> t : myMarkers) {
        if (!t.first.isValid() || !t.second.isValid() || t.third.getElement() == null) {
          valid = false;
          break;
        }
      }
    }
    return valid;
  }

  public void navigate(int injectedOffset) {
    if (myAction.isShowInBalloon()) {
      final JComponent component = myAction.createBalloonComponent(myNewFile);
      if (component != null) {
        final Balloon balloon =
            JBPopupFactory.getInstance()
                .createBalloonBuilder(component)
                .setShadow(true)
                .setAnimationCycle(0)
                .setHideOnClickOutside(true)
                .setHideOnKeyOutside(true)
                .setHideOnAction(false)
                .setFillColor(UIUtil.getControlColor())
                .createBalloon();
        new AnAction() {
          @Override
          public void actionPerformed(AnActionEvent e) {
            balloon.hide();
          }
        }.registerCustomShortcutSet(CommonShortcuts.ESCAPE, component);
        Disposer.register(myNewFile.getProject(), balloon);
        final Balloon.Position position = QuickEditAction.getBalloonPosition(myEditor);
        RelativePoint point = JBPopupFactory.getInstance().guessBestPopupLocation(myEditor);
        if (position == Balloon.Position.above) {
          final Point p = point.getPoint();
          point =
              new RelativePoint(
                  point.getComponent(), new Point(p.x, p.y - myEditor.getLineHeight()));
        }
        balloon.show(point, position);
      }
    } else {
      final FileEditorManagerEx fileEditorManager = FileEditorManagerEx.getInstanceEx(myProject);
      final FileEditor[] editors = fileEditorManager.getEditors(myNewVirtualFile);
      if (editors.length == 0) {
        final EditorWindow curWindow = fileEditorManager.getCurrentWindow();
        mySplittedWindow =
            curWindow.split(SwingConstants.HORIZONTAL, false, myNewVirtualFile, true);
      }
      Editor editor =
          fileEditorManager.openTextEditor(
              new OpenFileDescriptor(myProject, myNewVirtualFile, injectedOffset), true);
      // fold missing values
      if (editor != null) {
        editor.putUserData(QuickEditAction.QUICK_EDIT_HANDLER, this);
        final FoldingModel foldingModel = editor.getFoldingModel();
        foldingModel.runBatchFoldingOperation(
            () -> {
              for (RangeMarker o :
                  ContainerUtil.reverse(((DocumentEx) myNewDocument).getGuardedBlocks())) {
                String replacement = o.getUserData(REPLACEMENT_KEY);
                if (StringUtil.isEmpty(replacement)) continue;
                FoldRegion region =
                    foldingModel.addFoldRegion(o.getStartOffset(), o.getEndOffset(), replacement);
                if (region != null) region.setExpanded(false);
              }
            });
      }
      SwingUtilities.invokeLater(
          () -> myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE));
    }
  }

  @Override
  public void documentChanged(DocumentEvent e) {
    UndoManager undoManager = UndoManager.getInstance(myProject);
    boolean undoOrRedo = undoManager.isUndoInProgress() || undoManager.isRedoInProgress();
    if (undoOrRedo) {
      // allow undo/redo up until 'creation stamp' back in time
      // and check it after action is completed
      if (e.getDocument() == myOrigDocument) {
        //noinspection SSBasedInspection
        SwingUtilities.invokeLater(
            () -> {
              if (myOrigCreationStamp > myOrigDocument.getModificationStamp()) {
                closeEditor();
              }
            });
      }
    } else if (e.getDocument() == myNewDocument) {
      commitToOriginal(e);
      if (!isValid()) {
        ApplicationManager.getApplication()
            .invokeLater(() -> closeEditor(), myProject.getDisposed());
      }
    } else if (e.getDocument() == myOrigDocument) {
      if (myCommittingToOriginal || myAltFullRange != null && myAltFullRange.isValid()) return;
      ApplicationManager.getApplication().invokeLater(() -> closeEditor(), myProject.getDisposed());
    }
  }

  private void closeEditor() {
    boolean unsplit = false;
    if (mySplittedWindow != null && !mySplittedWindow.isDisposed()) {
      final EditorWithProviderComposite[] editors = mySplittedWindow.getEditors();
      if (editors.length == 1 && Comparing.equal(editors[0].getFile(), myNewVirtualFile)) {
        unsplit = true;
      }
    }
    FileEditorManager.getInstance(myProject).closeFile(myNewVirtualFile);
    if (unsplit) {
      for (EditorWindow editorWindow : mySplittedWindow.findSiblings()) {
        editorWindow.unsplit(true);
      }
    }
  }

  public void initMarkers(Place shreds) {
    SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(myProject);
    int curOffset = -1;
    for (PsiLanguageInjectionHost.Shred shred : shreds) {
      final RangeMarker rangeMarker =
          myNewDocument.createRangeMarker(
              shred.getRange().getStartOffset() + shred.getPrefix().length(),
              shred.getRange().getEndOffset() - shred.getSuffix().length());
      final TextRange rangeInsideHost = shred.getRangeInsideHost();
      PsiLanguageInjectionHost host = shred.getHost();
      RangeMarker origMarker =
          myOrigDocument.createRangeMarker(
              rangeInsideHost.shiftRight(host.getTextRange().getStartOffset()));
      SmartPsiElementPointer<PsiLanguageInjectionHost> elementPointer =
          smartPointerManager.createSmartPsiElementPointer(host);
      Trinity<RangeMarker, RangeMarker, SmartPsiElementPointer> markers =
          Trinity.<RangeMarker, RangeMarker, SmartPsiElementPointer>create(
              origMarker, rangeMarker, elementPointer);
      myMarkers.add(markers);

      origMarker.setGreedyToRight(true);
      rangeMarker.setGreedyToRight(true);
      if (origMarker.getStartOffset() > curOffset) {
        origMarker.setGreedyToLeft(true);
        rangeMarker.setGreedyToLeft(true);
      }
      curOffset = origMarker.getEndOffset();
    }
    initGuardedBlocks(shreds);
  }

  private void initGuardedBlocks(Place shreds) {
    int origOffset = -1;
    int curOffset = 0;
    for (PsiLanguageInjectionHost.Shred shred : shreds) {
      Segment hostRangeMarker = shred.getHostRangeMarker();
      int start = shred.getRange().getStartOffset() + shred.getPrefix().length();
      int end = shred.getRange().getEndOffset() - shred.getSuffix().length();
      if (curOffset < start) {
        RangeMarker guard = myNewDocument.createGuardedBlock(curOffset, start);
        if (curOffset == 0 && shred == shreds.get(0)) guard.setGreedyToLeft(true);
        String padding =
            origOffset < 0
                ? ""
                : myOrigDocument.getText().substring(origOffset, hostRangeMarker.getStartOffset());
        guard.putUserData(REPLACEMENT_KEY, fixQuotes(padding));
      }
      curOffset = end;
      origOffset = hostRangeMarker.getEndOffset();
    }
    if (curOffset < myNewDocument.getTextLength()) {
      RangeMarker guard =
          myNewDocument.createGuardedBlock(curOffset, myNewDocument.getTextLength());
      guard.setGreedyToRight(true);
      guard.putUserData(REPLACEMENT_KEY, "");
    }
  }

  private void commitToOriginal(final DocumentEvent e) {
    VirtualFile origVirtualFile = PsiUtilCore.getVirtualFile(myNewFile.getContext());
    myCommittingToOriginal = true;
    try {
      if (origVirtualFile == null
          || !ReadonlyStatusHandler.getInstance(myProject)
              .ensureFilesWritable(origVirtualFile)
              .hasReadonlyFiles()) {
        PostprocessReformattingAspect.getInstance(myProject)
            .disablePostprocessFormattingInside(
                () -> {
                  if (myAltFullRange != null) {
                    altCommitToOriginal(e);
                    return;
                  }
                  commitToOriginalInner();
                });
        PsiDocumentManager.getInstance(myProject)
            .doPostponedOperationsAndUnblockDocument(myOrigDocument);
      }
    } finally {
      myCommittingToOriginal = false;
    }
  }

  private void commitToOriginalInner() {
    final String text = myNewDocument.getText();
    final Map<
            PsiLanguageInjectionHost,
            Set<Trinity<RangeMarker, RangeMarker, SmartPsiElementPointer>>>
        map =
            ContainerUtil.classify(
                myMarkers.iterator(),
                new Convertor<
                    Trinity<RangeMarker, RangeMarker, SmartPsiElementPointer>,
                    PsiLanguageInjectionHost>() {
                  @Override
                  public PsiLanguageInjectionHost convert(
                      final Trinity<RangeMarker, RangeMarker, SmartPsiElementPointer> o) {
                    final PsiElement element = o.third.getElement();
                    return (PsiLanguageInjectionHost) element;
                  }
                });
    PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject);
    documentManager.commitDocument(myOrigDocument); // commit here and after each manipulator update
    int localInsideFileCursor = 0;
    for (PsiLanguageInjectionHost host : map.keySet()) {
      if (host == null) continue;
      String hostText = host.getText();
      ProperTextRange insideHost = null;
      StringBuilder sb = new StringBuilder();
      for (Trinity<RangeMarker, RangeMarker, SmartPsiElementPointer> entry : map.get(host)) {
        RangeMarker origMarker = entry.first; // check for validity?
        int hostOffset = host.getTextRange().getStartOffset();
        ProperTextRange localInsideHost =
            new ProperTextRange(
                origMarker.getStartOffset() - hostOffset, origMarker.getEndOffset() - hostOffset);
        RangeMarker rangeMarker = entry.second;
        ProperTextRange localInsideFile =
            new ProperTextRange(
                Math.max(localInsideFileCursor, rangeMarker.getStartOffset()),
                rangeMarker.getEndOffset());
        if (insideHost != null) {
          // append unchanged inter-markers fragment
          sb.append(
              hostText.substring(insideHost.getEndOffset(), localInsideHost.getStartOffset()));
        }
        sb.append(
            localInsideFile.getEndOffset() <= text.length() && !localInsideFile.isEmpty()
                ? localInsideFile.substring(text)
                : "");
        localInsideFileCursor = localInsideFile.getEndOffset();
        insideHost = insideHost == null ? localInsideHost : insideHost.union(localInsideHost);
      }
      assert insideHost != null;
      ElementManipulators.getManipulator(host).handleContentChange(host, insideHost, sb.toString());
      documentManager.commitDocument(myOrigDocument);
    }
  }

  private void altCommitToOriginal(@NotNull DocumentEvent e) {
    final PsiFile origPsiFile =
        PsiDocumentManager.getInstance(myProject).getPsiFile(myOrigDocument);
    String newText = myNewDocument.getText();
    // prepare guarded blocks
    LinkedHashMap<String, String> replacementMap = new LinkedHashMap<String, String>();
    int count = 0;
    for (RangeMarker o : ContainerUtil.reverse(((DocumentEx) myNewDocument).getGuardedBlocks())) {
      String replacement = o.getUserData(REPLACEMENT_KEY);
      String tempText = "REPLACE" + (count++) + Long.toHexString(StringHash.calc(replacement));
      newText =
          newText.substring(0, o.getStartOffset()) + tempText + newText.substring(o.getEndOffset());
      replacementMap.put(tempText, replacement);
    }
    // run preformat processors
    final int hostStartOffset = myAltFullRange.getStartOffset();
    myEditor.getCaretModel().moveToOffset(hostStartOffset);
    for (CopyPastePreProcessor preProcessor :
        Extensions.getExtensions(CopyPastePreProcessor.EP_NAME)) {
      newText = preProcessor.preprocessOnPaste(myProject, origPsiFile, myEditor, newText, null);
    }
    myOrigDocument.replaceString(hostStartOffset, myAltFullRange.getEndOffset(), newText);
    // replace temp strings for guarded blocks
    for (String tempText : replacementMap.keySet()) {
      int idx =
          CharArrayUtil.indexOf(
              myOrigDocument.getCharsSequence(),
              tempText,
              hostStartOffset,
              myAltFullRange.getEndOffset());
      myOrigDocument.replaceString(idx, idx + tempText.length(), replacementMap.get(tempText));
    }
    // JAVA: fix occasional char literal concatenation
    fixDocumentQuotes(myOrigDocument, hostStartOffset - 1);
    fixDocumentQuotes(myOrigDocument, myAltFullRange.getEndOffset());

    // reformat
    PsiDocumentManager.getInstance(myProject).commitDocument(myOrigDocument);
    Runnable task =
        () -> {
          try {
            CodeStyleManager.getInstance(myProject)
                .reformatRange(origPsiFile, hostStartOffset, myAltFullRange.getEndOffset(), true);
          } catch (IncorrectOperationException e1) {
            // LOG.error(e);
          }
        };
    DocumentUtil.executeInBulk(myOrigDocument, true, task);

    PsiElement newInjected =
        InjectedLanguageManager.getInstance(myProject)
            .findInjectedElementAt(origPsiFile, hostStartOffset);
    DocumentWindow documentWindow =
        newInjected == null ? null : InjectedLanguageUtil.getDocumentWindow(newInjected);
    if (documentWindow != null) {
      myEditor.getCaretModel().moveToOffset(documentWindow.injectedToHost(e.getOffset()));
      myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
    }
  }

  private static String fixQuotes(String padding) {
    if (padding.isEmpty()) return padding;
    if (padding.startsWith("'")) padding = '\"' + padding.substring(1);
    if (padding.endsWith("'")) padding = padding.substring(0, padding.length() - 1) + "\"";
    return padding;
  }

  private static void fixDocumentQuotes(Document doc, int offset) {
    if (doc.getCharsSequence().charAt(offset) == '\'') {
      doc.replaceString(offset, offset + 1, "\"");
    }
  }

  @Override
  public void dispose() {
    // noop
  }

  @TestOnly
  public PsiFile getNewFile() {
    return myNewFile;
  }

  public boolean changesRange(TextRange range) {
    if (myAltFullRange != null) {
      return range.intersects(myAltFullRange.getStartOffset(), myAltFullRange.getEndOffset());
    } else if (!myMarkers.isEmpty()) {
      TextRange hostRange =
          TextRange.create(
              myMarkers.get(0).first.getStartOffset(),
              myMarkers.get(myMarkers.size() - 1).first.getEndOffset());
      return range.intersects(hostRange);
    }
    return false;
  }

  private static class MyQuietHandler implements ReadonlyFragmentModificationHandler {
    @Override
    public void handle(final ReadOnlyFragmentModificationException e) {
      // nothing
    }
  }
}
/** @author ik Date: 24.10.2003 */
public class PsiClassImplUtil {
  private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.PsiClassImplUtil");
  private static final Key<ParameterizedCachedValue<MembersMap, PsiClass>> MAP_IN_CLASS_KEY =
      Key.create("MAP_KEY");

  private PsiClassImplUtil() {}

  public static void cacheEverything(PsiClass aClass) {
    getValues(aClass).getValue(aClass);
  }

  @NotNull
  public static PsiField[] getAllFields(@NotNull PsiClass aClass) {
    List<PsiField> map = getAllByMap(aClass, MemberType.FIELD);
    return map.toArray(new PsiField[map.size()]);
  }

  @NotNull
  public static PsiMethod[] getAllMethods(@NotNull PsiClass aClass) {
    List<PsiMethod> methods = getAllByMap(aClass, MemberType.METHOD);
    return methods.toArray(new PsiMethod[methods.size()]);
  }

  @NotNull
  public static PsiClass[] getAllInnerClasses(@NotNull PsiClass aClass) {
    List<PsiClass> classes = getAllByMap(aClass, MemberType.CLASS);
    return classes.toArray(new PsiClass[classes.size()]);
  }

  @Nullable
  public static PsiField findFieldByName(
      @NotNull PsiClass aClass, String name, boolean checkBases) {
    List<PsiMember> byMap = findByMap(aClass, name, checkBases, MemberType.FIELD);
    return byMap.isEmpty() ? null : (PsiField) byMap.get(0);
  }

  @NotNull
  public static PsiMethod[] findMethodsByName(
      @NotNull PsiClass aClass, String name, boolean checkBases) {
    List<PsiMember> methods = findByMap(aClass, name, checkBases, MemberType.METHOD);
    //noinspection SuspiciousToArrayCall
    return methods.toArray(new PsiMethod[methods.size()]);
  }

  @Nullable
  public static PsiMethod findMethodBySignature(
      @NotNull PsiClass aClass, @NotNull PsiMethod patternMethod, final boolean checkBases) {
    final List<PsiMethod> result = findMethodsBySignature(aClass, patternMethod, checkBases, true);
    return result.isEmpty() ? null : result.get(0);
  }

  // ----------------------------- findMethodsBySignature -----------------------------------

  @NotNull
  public static PsiMethod[] findMethodsBySignature(
      @NotNull PsiClass aClass, @NotNull PsiMethod patternMethod, final boolean checkBases) {
    List<PsiMethod> methods = findMethodsBySignature(aClass, patternMethod, checkBases, false);
    return methods.toArray(new PsiMethod[methods.size()]);
  }

  @NotNull
  private static List<PsiMethod> findMethodsBySignature(
      @NotNull PsiClass aClass,
      @NotNull PsiMethod patternMethod,
      boolean checkBases,
      boolean stopOnFirst) {
    final PsiMethod[] methodsByName = aClass.findMethodsByName(patternMethod.getName(), checkBases);
    if (methodsByName.length == 0) return Collections.emptyList();
    final List<PsiMethod> methods = new SmartList<PsiMethod>();
    final MethodSignature patternSignature = patternMethod.getSignature(PsiSubstitutor.EMPTY);
    for (final PsiMethod method : methodsByName) {
      final PsiClass superClass = method.getContainingClass();
      final PsiSubstitutor substitutor;
      if (checkBases && !aClass.equals(superClass)) {
        substitutor =
            TypeConversionUtil.getSuperClassSubstitutor(superClass, aClass, PsiSubstitutor.EMPTY);
      } else {
        substitutor = PsiSubstitutor.EMPTY;
      }
      final MethodSignature signature = method.getSignature(substitutor);
      if (signature.equals(patternSignature)) {
        methods.add(method);
        if (stopOnFirst) {
          break;
        }
      }
    }
    return methods;
  }

  // ----------------------------------------------------------------------------------------

  @Nullable
  public static PsiClass findInnerByName(
      @NotNull PsiClass aClass, String name, boolean checkBases) {
    List<PsiMember> byMap = findByMap(aClass, name, checkBases, MemberType.CLASS);
    return byMap.isEmpty() ? null : (PsiClass) byMap.get(0);
  }

  @NotNull
  private static List<PsiMember> findByMap(
      @NotNull PsiClass aClass, String name, boolean checkBases, @NotNull MemberType type) {
    if (name == null) return Collections.emptyList();

    if (checkBases) {
      Map<String, List<Pair<PsiMember, PsiSubstitutor>>> allMethodsMap = getMap(aClass, type);
      List<Pair<PsiMember, PsiSubstitutor>> list = allMethodsMap.get(name);
      if (list == null) return Collections.emptyList();
      List<PsiMember> ret = new ArrayList<PsiMember>(list.size());
      for (final Pair<PsiMember, PsiSubstitutor> info : list) {
        ret.add(info.getFirst());
      }

      return ret;
    } else {
      PsiMember[] members = null;
      switch (type) {
        case METHOD:
          members = aClass.getMethods();
          break;
        case CLASS:
          members = aClass.getInnerClasses();
          break;
        case FIELD:
          members = aClass.getFields();
          break;
      }

      List<PsiMember> list = new ArrayList<PsiMember>();
      for (PsiMember member : members) {
        if (name.equals(member.getName())) {
          list.add(member);
        }
      }
      return list;
    }
  }

  @NotNull
  public static <T extends PsiMember> List<Pair<T, PsiSubstitutor>> getAllWithSubstitutorsByMap(
      @NotNull PsiClass aClass, @NotNull MemberType type) {
    Map<String, List<Pair<PsiMember, PsiSubstitutor>>> allMap = getMap(aClass, type);
    //noinspection unchecked
    return (List) allMap.get(ALL);
  }

  @NotNull
  private static <T extends PsiMember> List<T> getAllByMap(
      @NotNull PsiClass aClass, @NotNull MemberType type) {
    List<Pair<T, PsiSubstitutor>> pairs = getAllWithSubstitutorsByMap(aClass, type);

    final List<T> ret = new ArrayList<T>(pairs.size());
    //noinspection ForLoopReplaceableByForEach
    for (int i = 0; i < pairs.size(); i++) {
      Pair<T, PsiSubstitutor> pair = pairs.get(i);
      T t = pair.getFirst();
      LOG.assertTrue(t != null, aClass);
      ret.add(t);
    }
    return ret;
  }

  @NonNls private static final String ALL = "Intellij-IDEA-ALL";

  public enum MemberType {
    CLASS,
    FIELD,
    METHOD
  }

  @NotNull
  private static MembersMap buildAllMaps(@NotNull PsiClass psiClass) {
    final List<Pair<PsiMember, PsiSubstitutor>> classes =
        new ArrayList<Pair<PsiMember, PsiSubstitutor>>();
    final List<Pair<PsiMember, PsiSubstitutor>> fields =
        new ArrayList<Pair<PsiMember, PsiSubstitutor>>();
    final List<Pair<PsiMember, PsiSubstitutor>> methods =
        new ArrayList<Pair<PsiMember, PsiSubstitutor>>();

    FilterScopeProcessor<MethodCandidateInfo> processor =
        new FilterScopeProcessor<MethodCandidateInfo>(
            new OrFilter(
                ElementClassFilter.METHOD, ElementClassFilter.FIELD, ElementClassFilter.CLASS)) {
          @Override
          protected void add(PsiElement element, PsiSubstitutor substitutor) {
            if (element instanceof PsiMethod) {
              methods.add(Pair.create((PsiMember) element, substitutor));
            } else if (element instanceof PsiField) {
              fields.add(Pair.create((PsiMember) element, substitutor));
            } else if (element instanceof PsiClass) {
              classes.add(Pair.create((PsiMember) element, substitutor));
            }
          }
        };
    processDeclarationsInClassNotCached(
        psiClass,
        processor,
        ResolveState.initial(),
        null,
        null,
        psiClass,
        false,
        PsiUtil.getLanguageLevel(psiClass));

    MembersMap result = new MembersMap(MemberType.class);
    result.put(MemberType.CLASS, generateMapByList(classes));
    result.put(MemberType.METHOD, generateMapByList(methods));
    result.put(MemberType.FIELD, generateMapByList(fields));
    return result;
  }

  @NotNull
  private static Map<String, List<Pair<PsiMember, PsiSubstitutor>>> generateMapByList(
      @NotNull final List<Pair<PsiMember, PsiSubstitutor>> list) {
    Map<String, List<Pair<PsiMember, PsiSubstitutor>>> map =
        new THashMap<String, List<Pair<PsiMember, PsiSubstitutor>>>();
    map.put(ALL, list);
    for (final Pair<PsiMember, PsiSubstitutor> info : list) {
      PsiMember element = info.getFirst();
      String currentName = element.getName();
      List<Pair<PsiMember, PsiSubstitutor>> listByName = map.get(currentName);
      if (listByName == null) {
        listByName = new ArrayList<Pair<PsiMember, PsiSubstitutor>>(1);
        map.put(currentName, listByName);
      }
      listByName.add(info);
    }
    return map;
  }

  private static Map<String, List<Pair<PsiMember, PsiSubstitutor>>> getMap(
      @NotNull PsiClass aClass, @NotNull MemberType type) {
    ParameterizedCachedValue<MembersMap, PsiClass> value = getValues(aClass);
    return value.getValue(aClass).get(type);
  }

  @NotNull
  private static ParameterizedCachedValue<MembersMap, PsiClass> getValues(
      @NotNull PsiClass aClass) {
    ParameterizedCachedValue<MembersMap, PsiClass> value = aClass.getUserData(MAP_IN_CLASS_KEY);
    if (value == null) {
      value =
          CachedValuesManager.getManager(aClass.getProject())
              .createParameterizedCachedValue(ByNameCachedValueProvider.INSTANCE, false);
      // Do not cache for nonphysical elements
      if (aClass.isPhysical()) {
        value = ((UserDataHolderEx) aClass).putUserDataIfAbsent(MAP_IN_CLASS_KEY, value);
      }
    }
    return value;
  }

  private static class ClassIconRequest {
    @NotNull private final PsiClass psiClass;
    private final int flags;
    private final Icon symbolIcon;

    private ClassIconRequest(@NotNull PsiClass psiClass, int flags, Icon symbolIcon) {
      this.psiClass = psiClass;
      this.flags = flags;
      this.symbolIcon = symbolIcon;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (!(o instanceof ClassIconRequest)) return false;

      ClassIconRequest that = (ClassIconRequest) o;

      return flags == that.flags && psiClass.equals(that.psiClass);
    }

    @Override
    public int hashCode() {
      int result = psiClass.hashCode();
      result = 31 * result + flags;
      return result;
    }
  }

  private static final Function<ClassIconRequest, Icon> FULL_ICON_EVALUATOR =
      new NullableFunction<ClassIconRequest, Icon>() {
        @Override
        public Icon fun(ClassIconRequest r) {
          if (!r.psiClass.isValid() || r.psiClass.getProject().isDisposed()) return null;

          final boolean isLocked =
              (r.flags & Iconable.ICON_FLAG_READ_STATUS) != 0 && !r.psiClass.isWritable();
          Icon symbolIcon =
              r.symbolIcon != null
                  ? r.symbolIcon
                  : ElementPresentationUtil.getClassIconOfKind(
                      r.psiClass, ElementPresentationUtil.getClassKind(r.psiClass));
          RowIcon baseIcon =
              ElementPresentationUtil.createLayeredIcon(symbolIcon, r.psiClass, isLocked);
          return ElementPresentationUtil.addVisibilityIcon(r.psiClass, r.flags, baseIcon);
        }
      };

  public static Icon getClassIcon(final int flags, @NotNull PsiClass aClass) {
    return getClassIcon(flags, aClass, null);
  }

  public static Icon getClassIcon(int flags, @NotNull PsiClass aClass, @Nullable Icon symbolIcon) {
    Icon base = Iconable.LastComputedIcon.get(aClass, flags);
    if (base == null) {
      if (symbolIcon == null) {
        symbolIcon =
            ElementPresentationUtil.getClassIconOfKind(
                aClass, ElementPresentationUtil.getBasicClassKind(aClass));
      }
      RowIcon baseIcon = ElementBase.createLayeredIcon(aClass, symbolIcon, 0);
      base = ElementPresentationUtil.addVisibilityIcon(aClass, flags, baseIcon);
    }

    return IconDeferrer.getInstance()
        .defer(base, new ClassIconRequest(aClass, flags, symbolIcon), FULL_ICON_EVALUATOR);
  }

  @NotNull
  public static SearchScope getClassUseScope(@NotNull PsiClass aClass) {
    if (aClass instanceof PsiAnonymousClass) {
      return new LocalSearchScope(aClass);
    }
    final GlobalSearchScope maximalUseScope = ResolveScopeManager.getElementUseScope(aClass);
    PsiFile file = aClass.getContainingFile();
    if (PsiImplUtil.isInServerPage(file)) return maximalUseScope;
    final PsiClass containingClass = aClass.getContainingClass();
    if (aClass.hasModifierProperty(PsiModifier.PUBLIC)
        || aClass.hasModifierProperty(PsiModifier.PROTECTED)) {
      return containingClass == null ? maximalUseScope : containingClass.getUseScope();
    } else if (aClass.hasModifierProperty(PsiModifier.PRIVATE)
        || aClass instanceof PsiTypeParameter) {
      PsiClass topClass = PsiUtil.getTopLevelClass(aClass);
      return new LocalSearchScope(topClass == null ? aClass.getContainingFile() : topClass);
    } else {
      PsiPackage aPackage = null;
      if (file instanceof PsiJavaFile) {
        aPackage =
            JavaPsiFacade.getInstance(aClass.getProject())
                .findPackage(((PsiJavaFile) file).getPackageName());
      }

      if (aPackage == null) {
        PsiDirectory dir = file.getContainingDirectory();
        if (dir != null) {
          aPackage = JavaDirectoryService.getInstance().getPackage(dir);
        }
      }

      if (aPackage != null) {
        SearchScope scope = PackageScope.packageScope(aPackage, false);
        scope = scope.intersectWith(maximalUseScope);
        return scope;
      }

      return new LocalSearchScope(file);
    }
  }

  public static boolean isMainOrPremainMethod(@NotNull PsiMethod method) {
    if (!PsiType.VOID.equals(method.getReturnType())) return false;
    String name = method.getName();
    if (!("main".equals(name) || "premain".equals(name))) return false;

    PsiElementFactory factory = JavaPsiFacade.getInstance(method.getProject()).getElementFactory();
    MethodSignature signature = method.getSignature(PsiSubstitutor.EMPTY);
    try {
      MethodSignature main = createSignatureFromText(factory, "void main(String[] args);");
      if (MethodSignatureUtil.areSignaturesEqual(signature, main)) return true;
      MethodSignature premain =
          createSignatureFromText(
              factory, "void premain(String args, java.lang.instrument.Instrumentation i);");
      if (MethodSignatureUtil.areSignaturesEqual(signature, premain)) return true;
    } catch (IncorrectOperationException e) {
      LOG.error(e);
    }

    return false;
  }

  @NotNull
  private static MethodSignature createSignatureFromText(
      @NotNull PsiElementFactory factory, @NotNull String text) {
    return factory.createMethodFromText(text, null).getSignature(PsiSubstitutor.EMPTY);
  }

  private static class MembersMap
      extends EnumMap<MemberType, Map<String, List<Pair<PsiMember, PsiSubstitutor>>>> {
    public MembersMap(@NotNull Class<MemberType> keyType) {
      super(keyType);
    }
  }

  private static class ByNameCachedValueProvider
      implements ParameterizedCachedValueProvider<MembersMap, PsiClass> {
    private static final ByNameCachedValueProvider INSTANCE = new ByNameCachedValueProvider();

    @Override
    public CachedValueProvider.Result<MembersMap> compute(@NotNull PsiClass myClass) {
      MembersMap map = buildAllMaps(myClass);
      return new CachedValueProvider.Result<MembersMap>(
          map, PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT);
    }
  }

  public static boolean processDeclarationsInClass(
      @NotNull PsiClass aClass,
      @NotNull final PsiScopeProcessor processor,
      @NotNull ResolveState state,
      @Nullable Set<PsiClass> visited,
      PsiElement last,
      @NotNull PsiElement place,
      boolean isRaw) {
    if (last instanceof PsiTypeParameterList || last instanceof PsiModifierList) {
      return true; // TypeParameterList and ModifierList do not see our declarations
    }
    if (visited != null && visited.contains(aClass)) return true;

    PsiSubstitutor substitutor = state.get(PsiSubstitutor.KEY);
    isRaw = isRaw || PsiUtil.isRawSubstitutor(aClass, substitutor);

    ParameterizedCachedValue<MembersMap, PsiClass> cache =
        getValues(aClass); // aClass.getUserData(MAP_IN_CLASS_KEY);
    boolean upToDate = cache.hasUpToDateValue();
    LanguageLevel languageLevel = PsiUtil.getLanguageLevel(place);
    if (
    /*true || */ upToDate) {
      final NameHint nameHint = processor.getHint(NameHint.KEY);
      if (nameHint != null) {
        String name = nameHint.getName(state);
        return processCachedMembersByName(
            aClass,
            processor,
            state,
            visited,
            last,
            place,
            isRaw,
            substitutor,
            cache.getValue(aClass),
            name,
            languageLevel);
      }
    }
    return processDeclarationsInClassNotCached(
        aClass, processor, state, visited, last, place, isRaw, languageLevel);
  }

  private static boolean processCachedMembersByName(
      @NotNull PsiClass aClass,
      @NotNull PsiScopeProcessor processor,
      @NotNull ResolveState state,
      @Nullable Set<PsiClass> visited,
      PsiElement last,
      @NotNull PsiElement place,
      boolean isRaw,
      @NotNull PsiSubstitutor substitutor,
      @NotNull MembersMap value,
      String name,
      @NotNull LanguageLevel languageLevel) {
    final ElementClassHint classHint = processor.getHint(ElementClassHint.KEY);

    PsiElementFactory factory = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory();

    if (classHint == null || classHint.shouldProcess(ElementClassHint.DeclarationKind.FIELD)) {
      final PsiField fieldByName = aClass.findFieldByName(name, false);
      if (fieldByName != null) {
        processor.handleEvent(PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, aClass);
        if (!processor.execute(fieldByName, state)) return false;
      } else {
        final Map<String, List<Pair<PsiMember, PsiSubstitutor>>> allFieldsMap =
            value.get(MemberType.FIELD);

        final List<Pair<PsiMember, PsiSubstitutor>> list = allFieldsMap.get(name);
        if (list != null) {
          for (final Pair<PsiMember, PsiSubstitutor> candidate : list) {
            PsiMember candidateField = candidate.getFirst();
            PsiSubstitutor finalSubstitutor =
                obtainFinalSubstitutor(
                    candidateField.getContainingClass(),
                    candidate.getSecond(),
                    aClass,
                    substitutor,
                    factory,
                    languageLevel);

            processor.handleEvent(
                PsiScopeProcessor.Event.SET_DECLARATION_HOLDER,
                candidateField.getContainingClass());
            if (!processor.execute(candidateField, state.put(PsiSubstitutor.KEY, finalSubstitutor)))
              return false;
          }
        }
      }
    }
    if (classHint == null || classHint.shouldProcess(ElementClassHint.DeclarationKind.CLASS)) {
      if (last != null && last.getParent() == aClass) {
        if (last instanceof PsiClass) {
          if (!processor.execute(last, state)) return false;
        }
        // Parameters
        final PsiTypeParameterList list = aClass.getTypeParameterList();
        if (list != null && !list.processDeclarations(processor, state, last, place)) return false;
      }
      if (!(last instanceof PsiReferenceList)) {
        final PsiClass classByName = aClass.findInnerClassByName(name, false);
        if (classByName != null) {
          processor.handleEvent(PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, aClass);
          if (!processor.execute(classByName, state)) return false;
        } else {
          Map<String, List<Pair<PsiMember, PsiSubstitutor>>> allClassesMap =
              value.get(MemberType.CLASS);

          List<Pair<PsiMember, PsiSubstitutor>> list = allClassesMap.get(name);
          if (list != null) {
            for (final Pair<PsiMember, PsiSubstitutor> candidate : list) {
              PsiMember inner = candidate.getFirst();
              PsiClass containingClass = inner.getContainingClass();
              if (containingClass != null) {
                PsiSubstitutor finalSubstitutor =
                    obtainFinalSubstitutor(
                        containingClass,
                        candidate.getSecond(),
                        aClass,
                        substitutor,
                        factory,
                        languageLevel);
                processor.handleEvent(
                    PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, containingClass);
                if (!processor.execute(inner, state.put(PsiSubstitutor.KEY, finalSubstitutor)))
                  return false;
              }
            }
          }
        }
      }
    }
    if (classHint == null || classHint.shouldProcess(ElementClassHint.DeclarationKind.METHOD)) {
      if (processor instanceof MethodResolverProcessor) {
        final MethodResolverProcessor methodResolverProcessor = (MethodResolverProcessor) processor;
        if (methodResolverProcessor.isConstructor()) {
          final PsiMethod[] constructors = aClass.getConstructors();
          methodResolverProcessor.handleEvent(
              PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, aClass);
          for (PsiMethod constructor : constructors) {
            if (!methodResolverProcessor.execute(constructor, state)) return false;
          }
          return true;
        }
      }
      Map<String, List<Pair<PsiMember, PsiSubstitutor>>> allMethodsMap =
          value.get(MemberType.METHOD);
      List<Pair<PsiMember, PsiSubstitutor>> list = allMethodsMap.get(name);
      if (list != null) {
        for (final Pair<PsiMember, PsiSubstitutor> candidate : list) {
          ProgressIndicatorProvider.checkCanceled();
          PsiMethod candidateMethod = (PsiMethod) candidate.getFirst();
          if (processor instanceof MethodResolverProcessor) {
            if (candidateMethod.isConstructor()
                != ((MethodResolverProcessor) processor).isConstructor()) continue;
          }
          final PsiClass containingClass = candidateMethod.getContainingClass();
          if (visited != null && visited.contains(candidateMethod.getContainingClass())) {
            continue;
          }

          PsiSubstitutor finalSubstitutor =
              obtainFinalSubstitutor(
                  containingClass,
                  candidate.getSecond(),
                  aClass,
                  substitutor,
                  factory,
                  languageLevel);
          finalSubstitutor = checkRaw(isRaw, factory, candidateMethod, finalSubstitutor);
          processor.handleEvent(PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, containingClass);
          if (!processor.execute(candidateMethod, state.put(PsiSubstitutor.KEY, finalSubstitutor)))
            return false;
        }

        if (visited != null) {
          for (Pair<PsiMember, PsiSubstitutor> aList : list) {
            visited.add(aList.getFirst().getContainingClass());
          }
        }
      }
    }
    return true;
  }

  private static PsiSubstitutor checkRaw(
      boolean isRaw,
      @NotNull PsiElementFactory factory,
      @NotNull PsiMethod candidateMethod,
      @NotNull PsiSubstitutor substitutor) {
    if (isRaw
        && !candidateMethod.hasModifierProperty(
            PsiModifier.STATIC)) { // static methods are not erased due to raw overriding
      PsiTypeParameter[] methodTypeParameters = candidateMethod.getTypeParameters();
      substitutor = factory.createRawSubstitutor(substitutor, methodTypeParameters);
    }
    return substitutor;
  }

  public static PsiSubstitutor obtainFinalSubstitutor(
      @NotNull PsiClass candidateClass,
      @NotNull PsiSubstitutor candidateSubstitutor,
      @NotNull PsiClass aClass,
      @NotNull PsiSubstitutor substitutor,
      @NotNull PsiElementFactory elementFactory,
      @NotNull LanguageLevel languageLevel) {
    if (PsiUtil.isRawSubstitutor(aClass, substitutor)) {
      return elementFactory.createRawSubstitutor(candidateClass);
    }
    final PsiType containingType =
        elementFactory.createType(candidateClass, candidateSubstitutor, languageLevel);
    PsiType type = substitutor.substitute(containingType);
    if (!(type instanceof PsiClassType)) return candidateSubstitutor;
    return ((PsiClassType) type).resolveGenerics().getSubstitutor();
  }

  private static boolean processDeclarationsInClassNotCached(
      @NotNull PsiClass aClass,
      @NotNull PsiScopeProcessor processor,
      @NotNull ResolveState state,
      @Nullable Set<PsiClass> visited,
      PsiElement last,
      @NotNull PsiElement place,
      boolean isRaw,
      @NotNull LanguageLevel languageLevel) {
    if (visited == null) visited = new THashSet<PsiClass>();
    if (!visited.add(aClass)) return true;
    processor.handleEvent(PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, aClass);
    final ElementClassHint classHint = processor.getHint(ElementClassHint.KEY);
    final NameHint nameHint = processor.getHint(NameHint.KEY);

    if (classHint == null || classHint.shouldProcess(ElementClassHint.DeclarationKind.FIELD)) {
      if (nameHint != null) {
        final PsiField fieldByName = aClass.findFieldByName(nameHint.getName(state), false);
        if (fieldByName != null && !processor.execute(fieldByName, state)) return false;
      } else {
        final PsiField[] fields = aClass.getFields();
        for (final PsiField field : fields) {
          if (!processor.execute(field, state)) return false;
        }
      }
    }

    PsiElementFactory factory = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory();

    if (classHint == null || classHint.shouldProcess(ElementClassHint.DeclarationKind.METHOD)) {
      PsiSubstitutor baseSubstitutor = state.get(PsiSubstitutor.KEY);
      final PsiMethod[] methods =
          nameHint != null
              ? aClass.findMethodsByName(nameHint.getName(state), false)
              : aClass.getMethods();
      for (final PsiMethod method : methods) {
        PsiSubstitutor finalSubstitutor = checkRaw(isRaw, factory, method, baseSubstitutor);
        ResolveState methodState =
            finalSubstitutor == baseSubstitutor
                ? state
                : state.put(PsiSubstitutor.KEY, finalSubstitutor);
        if (!processor.execute(method, methodState)) return false;
      }
    }

    if (classHint == null || classHint.shouldProcess(ElementClassHint.DeclarationKind.CLASS)) {
      if (last != null && last.getParent() == aClass) {
        // Parameters
        final PsiTypeParameterList list = aClass.getTypeParameterList();
        if (list != null
            && !list.processDeclarations(processor, ResolveState.initial(), last, place))
          return false;
      }

      if (!(last instanceof PsiReferenceList) && !(last instanceof PsiModifierList)) {
        // Inners
        if (nameHint != null) {
          final PsiClass inner = aClass.findInnerClassByName(nameHint.getName(state), false);
          if (inner != null) {
            if (!processor.execute(inner, state)) return false;
          }
        } else {
          final PsiClass[] inners = aClass.getInnerClasses();
          for (final PsiClass inner : inners) {
            if (!processor.execute(inner, state)) return false;
          }
        }
      }
    }

    return last instanceof PsiReferenceList
        || processSuperTypes(
            aClass, processor, visited, last, place, state, isRaw, factory, languageLevel);
  }

  private static boolean processSuperTypes(
      @NotNull PsiClass aClass,
      @NotNull PsiScopeProcessor processor,
      @Nullable Set<PsiClass> visited,
      PsiElement last,
      @NotNull PsiElement place,
      @NotNull ResolveState state,
      boolean isRaw,
      @NotNull PsiElementFactory factory,
      @NotNull LanguageLevel languageLevel) {
    boolean resolved = false;
    for (final PsiClassType superType : aClass.getSuperTypes()) {
      final PsiClassType.ClassResolveResult superTypeResolveResult = superType.resolveGenerics();
      PsiClass superClass = superTypeResolveResult.getElement();
      if (superClass == null) continue;
      PsiSubstitutor finalSubstitutor =
          obtainFinalSubstitutor(
              superClass,
              superTypeResolveResult.getSubstitutor(),
              aClass,
              state.get(PsiSubstitutor.KEY),
              factory,
              languageLevel);
      if (aClass instanceof PsiTypeParameter
          && PsiUtil.isRawSubstitutor(superClass, finalSubstitutor)) {
        finalSubstitutor = PsiSubstitutor.EMPTY;
      }
      if (!processDeclarationsInClass(
          superClass,
          processor,
          state.put(PsiSubstitutor.KEY, finalSubstitutor),
          visited,
          last,
          place,
          isRaw)) {
        resolved = true;
      }
    }
    return !resolved;
  }

  @Nullable
  public static PsiClass getSuperClass(@NotNull PsiClass psiClass) {
    PsiManager manager = psiClass.getManager();
    GlobalSearchScope resolveScope = psiClass.getResolveScope();

    final JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject());
    if (psiClass.isInterface()) {
      return facade.findClass(CommonClassNames.JAVA_LANG_OBJECT, resolveScope);
    }
    if (psiClass.isEnum()) {
      return facade.findClass(CommonClassNames.JAVA_LANG_ENUM, resolveScope);
    }

    if (psiClass instanceof PsiAnonymousClass) {
      PsiClassType baseClassReference = ((PsiAnonymousClass) psiClass).getBaseClassType();
      PsiClass baseClass = baseClassReference.resolve();
      if (baseClass == null || baseClass.isInterface())
        return facade.findClass(CommonClassNames.JAVA_LANG_OBJECT, resolveScope);
      return baseClass;
    }

    if (CommonClassNames.JAVA_LANG_OBJECT.equals(psiClass.getQualifiedName())) return null;

    final PsiClassType[] referenceElements = psiClass.getExtendsListTypes();

    if (referenceElements.length == 0)
      return facade.findClass(CommonClassNames.JAVA_LANG_OBJECT, resolveScope);

    PsiClass psiResoved = referenceElements[0].resolve();
    return psiResoved == null
        ? facade.findClass(CommonClassNames.JAVA_LANG_OBJECT, resolveScope)
        : psiResoved;
  }

  @NotNull
  public static PsiClass[] getSupers(@NotNull PsiClass psiClass) {
    final PsiClass[] supers = getSupersInner(psiClass);
    for (final PsiClass aSuper : supers) {
      LOG.assertTrue(aSuper != null);
    }
    return supers;
  }

  @NotNull
  private static PsiClass[] getSupersInner(@NotNull PsiClass psiClass) {
    PsiClassType[] extendsListTypes = psiClass.getExtendsListTypes();
    PsiClassType[] implementsListTypes = psiClass.getImplementsListTypes();

    if (psiClass.isInterface()) {
      return resolveClassReferenceList(
          extendsListTypes, psiClass.getManager(), psiClass.getResolveScope(), true);
    }

    if (psiClass instanceof PsiAnonymousClass) {
      PsiAnonymousClass psiAnonymousClass = (PsiAnonymousClass) psiClass;
      PsiClassType baseClassReference = psiAnonymousClass.getBaseClassType();
      PsiClass baseClass = baseClassReference.resolve();
      if (baseClass != null) {
        if (baseClass.isInterface()) {
          PsiClass objectClass =
              JavaPsiFacade.getInstance(psiClass.getProject())
                  .findClass(CommonClassNames.JAVA_LANG_OBJECT, psiClass.getResolveScope());
          return objectClass != null
              ? new PsiClass[] {objectClass, baseClass}
              : new PsiClass[] {baseClass};
        }
        return new PsiClass[] {baseClass};
      }

      PsiClass objectClass =
          JavaPsiFacade.getInstance(psiClass.getProject())
              .findClass(CommonClassNames.JAVA_LANG_OBJECT, psiClass.getResolveScope());
      return objectClass != null ? new PsiClass[] {objectClass} : PsiClass.EMPTY_ARRAY;
    }
    if (psiClass instanceof PsiTypeParameter) {
      if (extendsListTypes.length == 0) {
        final PsiClass objectClass =
            JavaPsiFacade.getInstance(psiClass.getProject())
                .findClass(CommonClassNames.JAVA_LANG_OBJECT, psiClass.getResolveScope());
        return objectClass != null ? new PsiClass[] {objectClass} : PsiClass.EMPTY_ARRAY;
      }
      return resolveClassReferenceList(
          extendsListTypes, psiClass.getManager(), psiClass.getResolveScope(), false);
    }

    PsiClass[] interfaces =
        resolveClassReferenceList(
            implementsListTypes, psiClass.getManager(), psiClass.getResolveScope(), false);

    PsiClass superClass = getSuperClass(psiClass);
    if (superClass == null) return interfaces;
    PsiClass[] types = new PsiClass[interfaces.length + 1];
    types[0] = superClass;
    System.arraycopy(interfaces, 0, types, 1, interfaces.length);

    return types;
  }

  @NotNull
  public static PsiClassType[] getSuperTypes(@NotNull PsiClass psiClass) {
    if (psiClass instanceof PsiAnonymousClass) {
      PsiClassType baseClassType = ((PsiAnonymousClass) psiClass).getBaseClassType();
      PsiClass baseClass = baseClassType.resolve();
      if (baseClass == null || !baseClass.isInterface()) {
        return new PsiClassType[] {baseClassType};
      } else {
        PsiClassType objectType =
            PsiType.getJavaLangObject(psiClass.getManager(), psiClass.getResolveScope());
        return new PsiClassType[] {objectType, baseClassType};
      }
    }

    PsiClassType[] extendsTypes = psiClass.getExtendsListTypes();
    PsiClassType[] implementsTypes = psiClass.getImplementsListTypes();
    boolean hasExtends = extendsTypes.length != 0;
    int extendsListLength = extendsTypes.length + (hasExtends ? 0 : 1);
    PsiClassType[] result = new PsiClassType[extendsListLength + implementsTypes.length];

    System.arraycopy(extendsTypes, 0, result, 0, extendsTypes.length);
    if (!hasExtends) {
      if (CommonClassNames.JAVA_LANG_OBJECT.equals(psiClass.getQualifiedName())) {
        return PsiClassType.EMPTY_ARRAY;
      }
      PsiManager manager = psiClass.getManager();
      PsiClassType objectType = PsiType.getJavaLangObject(manager, psiClass.getResolveScope());
      result[0] = objectType;
    }
    System.arraycopy(implementsTypes, 0, result, extendsListLength, implementsTypes.length);
    for (int i = 0; i < result.length; i++) {
      PsiClassType type = result[i];
      result[i] = (PsiClassType) PsiUtil.captureToplevelWildcards(type, psiClass);
    }
    return result;
  }

  @NotNull
  private static PsiClassType getAnnotationSuperType(
      @NotNull PsiClass psiClass, @NotNull PsiElementFactory factory) {
    return factory.createTypeByFQClassName(
        "java.lang.annotation.Annotation", psiClass.getResolveScope());
  }

  private static PsiClassType getEnumSuperType(
      @NotNull PsiClass psiClass, @NotNull PsiElementFactory factory) {
    PsiClassType superType;
    final PsiManager manager = psiClass.getManager();
    final PsiClass enumClass =
        JavaPsiFacade.getInstance(manager.getProject())
            .findClass("java.lang.Enum", psiClass.getResolveScope());
    if (enumClass == null) {
      try {
        superType = (PsiClassType) factory.createTypeFromText("java.lang.Enum", null);
      } catch (IncorrectOperationException e) {
        superType = null;
      }
    } else {
      final PsiTypeParameter[] typeParameters = enumClass.getTypeParameters();
      PsiSubstitutor substitutor = PsiSubstitutor.EMPTY;
      if (typeParameters.length == 1) {
        substitutor = substitutor.put(typeParameters[0], factory.createType(psiClass));
      }
      superType = new PsiImmediateClassType(enumClass, substitutor);
    }
    return superType;
  }

  @NotNull
  public static PsiClass[] getInterfaces(@NotNull PsiTypeParameter typeParameter) {
    final PsiClassType[] referencedTypes = typeParameter.getExtendsListTypes();
    if (referencedTypes.length == 0) {
      return PsiClass.EMPTY_ARRAY;
    }
    final List<PsiClass> result = new ArrayList<PsiClass>(referencedTypes.length);
    for (PsiClassType referencedType : referencedTypes) {
      final PsiClass psiClass = referencedType.resolve();
      if (psiClass != null && psiClass.isInterface()) {
        result.add(psiClass);
      }
    }
    return result.toArray(new PsiClass[result.size()]);
  }

  @NotNull
  public static PsiClass[] getInterfaces(@NotNull PsiClass psiClass) {
    if (psiClass.isInterface()) {
      final PsiClassType[] extendsListTypes = psiClass.getExtendsListTypes();
      return resolveClassReferenceList(
          extendsListTypes, psiClass.getManager(), psiClass.getResolveScope(), false);
    }

    if (psiClass instanceof PsiAnonymousClass) {
      PsiClassType baseClassReference = ((PsiAnonymousClass) psiClass).getBaseClassType();
      PsiClass baseClass = baseClassReference.resolve();
      return baseClass != null && baseClass.isInterface()
          ? new PsiClass[] {baseClass}
          : PsiClass.EMPTY_ARRAY;
    }

    final PsiClassType[] implementsListTypes = psiClass.getImplementsListTypes();
    return resolveClassReferenceList(
        implementsListTypes, psiClass.getManager(), psiClass.getResolveScope(), false);
  }

  @NotNull
  private static PsiClass[] resolveClassReferenceList(
      @NotNull PsiClassType[] listOfTypes,
      @NotNull PsiManager manager,
      @NotNull GlobalSearchScope resolveScope,
      boolean includeObject) {
    PsiClass objectClass =
        JavaPsiFacade.getInstance(manager.getProject())
            .findClass(CommonClassNames.JAVA_LANG_OBJECT, resolveScope);
    if (objectClass == null) includeObject = false;
    if (listOfTypes.length == 0) {
      if (includeObject) return new PsiClass[] {objectClass};
      return PsiClass.EMPTY_ARRAY;
    }

    int referenceCount = listOfTypes.length;
    if (includeObject) referenceCount++;

    PsiClass[] resolved = new PsiClass[referenceCount];
    int resolvedCount = 0;

    if (includeObject) resolved[resolvedCount++] = objectClass;
    for (PsiClassType reference : listOfTypes) {
      PsiClass refResolved = reference.resolve();
      if (refResolved != null) resolved[resolvedCount++] = refResolved;
    }

    if (resolvedCount < referenceCount) {
      PsiClass[] shorter = new PsiClass[resolvedCount];
      System.arraycopy(resolved, 0, shorter, 0, resolvedCount);
      resolved = shorter;
    }

    return resolved;
  }

  @NotNull
  public static List<Pair<PsiMethod, PsiSubstitutor>> findMethodsAndTheirSubstitutorsByName(
      @NotNull PsiClass psiClass, String name, boolean checkBases) {
    if (!checkBases) {
      final PsiMethod[] methodsByName = psiClass.findMethodsByName(name, false);
      final List<Pair<PsiMethod, PsiSubstitutor>> ret =
          new ArrayList<Pair<PsiMethod, PsiSubstitutor>>(methodsByName.length);
      for (final PsiMethod method : methodsByName) {
        ret.add(new Pair<PsiMethod, PsiSubstitutor>(method, PsiSubstitutor.EMPTY));
      }
      return ret;
    }
    Map<String, List<Pair<PsiMember, PsiSubstitutor>>> map = getMap(psiClass, MemberType.METHOD);
    @SuppressWarnings("unchecked")
    List<Pair<PsiMethod, PsiSubstitutor>> list = (List) map.get(name);
    return list == null
        ? Collections.<Pair<PsiMethod, PsiSubstitutor>>emptyList()
        : Collections.unmodifiableList(list);
  }

  @NotNull
  public static PsiClassType[] getExtendsListTypes(@NotNull PsiClass psiClass) {
    if (psiClass.isEnum()) {
      PsiClassType enumSuperType =
          getEnumSuperType(
              psiClass, JavaPsiFacade.getInstance(psiClass.getProject()).getElementFactory());
      return enumSuperType == null ? PsiClassType.EMPTY_ARRAY : new PsiClassType[] {enumSuperType};
    }
    if (psiClass.isAnnotationType()) {
      return new PsiClassType[] {
        getAnnotationSuperType(
            psiClass, JavaPsiFacade.getInstance(psiClass.getProject()).getElementFactory())
      };
    }
    final PsiReferenceList extendsList = psiClass.getExtendsList();
    if (extendsList != null) {
      return extendsList.getReferencedTypes();
    }
    return PsiClassType.EMPTY_ARRAY;
  }

  @NotNull
  public static PsiClassType[] getImplementsListTypes(@NotNull PsiClass psiClass) {
    final PsiReferenceList extendsList = psiClass.getImplementsList();
    if (extendsList != null) {
      return extendsList.getReferencedTypes();
    }
    return PsiClassType.EMPTY_ARRAY;
  }

  public static boolean isClassEquivalentTo(@NotNull PsiClass aClass, PsiElement another) {
    if (aClass == another) return true;
    if (!(another instanceof PsiClass)) return false;
    String name1 = aClass.getName();
    if (name1 == null) return false;
    if (!another.isValid()) return false;
    String name2 = ((PsiClass) another).getName();
    if (name2 == null) return false;
    if (name1.hashCode() != name2.hashCode()) return false;
    if (!name1.equals(name2)) return false;
    String qName1 = aClass.getQualifiedName();
    String qName2 = ((PsiClass) another).getQualifiedName();
    if (qName1 == null || qName2 == null) {
      //noinspection StringEquality
      if (qName1 != qName2) return false;

      if (aClass instanceof PsiTypeParameter && another instanceof PsiTypeParameter) {
        PsiTypeParameter p1 = (PsiTypeParameter) aClass;
        PsiTypeParameter p2 = (PsiTypeParameter) another;

        return p1.getIndex() == p2.getIndex()
            && aClass.getManager().areElementsEquivalent(p1.getOwner(), p2.getOwner());
      } else {
        return false;
      }
    }
    if (qName1.hashCode() != qName2.hashCode() || !qName1.equals(qName2)) {
      return false;
    }

    if (originalElement(aClass).equals(originalElement((PsiClass) another))) {
      return true;
    }

    final PsiFile file1 = aClass.getContainingFile().getOriginalFile();
    final PsiFile file2 = another.getContainingFile().getOriginalFile();

    // see com.intellij.openapi.vcs.changes.PsiChangeTracker
    // see com.intellij.psi.impl.PsiFileFactoryImpl#createFileFromText(CharSequence,PsiFile)
    final PsiFile original1 = file1.getUserData(PsiFileFactory.ORIGINAL_FILE);
    final PsiFile original2 = file2.getUserData(PsiFileFactory.ORIGINAL_FILE);
    if (original1 == original2 && original1 != null
        || original1 == file2
        || original2 == file1
        || file1 == file2) {
      return compareClassSeqNumber(aClass, (PsiClass) another);
    }

    final FileIndexFacade fileIndex =
        ServiceManager.getService(file1.getProject(), FileIndexFacade.class);
    final VirtualFile vfile1 = file1.getViewProvider().getVirtualFile();
    final VirtualFile vfile2 = file2.getViewProvider().getVirtualFile();
    boolean lib1 = fileIndex.isInLibraryClasses(vfile1);
    boolean lib2 = fileIndex.isInLibraryClasses(vfile2);

    return (fileIndex.isInSource(vfile1) || lib1) && (fileIndex.isInSource(vfile2) || lib2);
  }

  private static boolean compareClassSeqNumber(
      @NotNull PsiClass aClass, @NotNull PsiClass another) {
    // there may be several classes in one file, they must not be equal
    int index1 = getSeqNumber(aClass);
    if (index1 == -1) return true;
    int index2 = getSeqNumber(another);
    return index1 == index2;
  }

  private static int getSeqNumber(@NotNull PsiClass aClass) {
    // sequence number of this class among its parent' child classes named the same
    PsiElement parent = aClass.getParent();
    if (parent == null) return -1;
    int seqNo = 0;
    for (PsiElement child : parent.getChildren()) {
      if (child == aClass) return seqNo;
      if (child instanceof PsiClass
          && Comparing.strEqual(aClass.getName(), ((PsiClass) child).getName())) {
        seqNo++;
      }
    }
    return -1;
  }

  @NotNull
  private static PsiElement originalElement(@NotNull PsiClass aClass) {
    final PsiElement originalElement = aClass.getOriginalElement();
    ASTNode node = originalElement.getNode();
    if (node != null) {
      final PsiCompiledElement compiled = node.getUserData(ClsElementImpl.COMPILED_ELEMENT);
      if (compiled != null) {
        return compiled;
      }
    }
    return originalElement;
  }

  public static boolean isFieldEquivalentTo(@NotNull PsiField field, PsiElement another) {
    if (!(another instanceof PsiField)) return false;
    String name1 = field.getName();
    if (name1 == null) return false;
    if (!another.isValid()) return false;

    String name2 = ((PsiField) another).getName();
    if (!name1.equals(name2)) return false;
    PsiClass aClass1 = field.getContainingClass();
    PsiClass aClass2 = ((PsiField) another).getContainingClass();
    return aClass1 != null
        && aClass2 != null
        && field.getManager().areElementsEquivalent(aClass1, aClass2);
  }

  public static boolean isMethodEquivalentTo(@NotNull PsiMethod method1, PsiElement another) {
    if (method1 == another) return true;
    if (!(another instanceof PsiMethod)) return false;
    PsiMethod method2 = (PsiMethod) another;
    if (!another.isValid()) return false;
    if (!method1.getName().equals(method2.getName())) return false;
    PsiClass aClass1 = method1.getContainingClass();
    PsiClass aClass2 = method2.getContainingClass();
    PsiManager manager = method1.getManager();
    if (!(aClass1 != null && aClass2 != null && manager.areElementsEquivalent(aClass1, aClass2)))
      return false;

    PsiParameter[] parameters1 = method1.getParameterList().getParameters();
    PsiParameter[] parameters2 = method2.getParameterList().getParameters();
    if (parameters1.length != parameters2.length) return false;
    for (int i = 0; i < parameters1.length; i++) {
      PsiParameter parameter1 = parameters1[i];
      PsiParameter parameter2 = parameters2[i];
      PsiType type1 = parameter1.getType();
      PsiType type2 = parameter2.getType();
      if (!compareParamTypes(manager, type1, type2)) return false;
    }
    return true;
  }

  private static boolean compareParamTypes(
      @NotNull PsiManager manager, @NotNull PsiType type1, @NotNull PsiType type2) {
    if (type1 instanceof PsiArrayType) {
      return type2 instanceof PsiArrayType
          && compareParamTypes(
              manager,
              ((PsiArrayType) type1).getComponentType(),
              ((PsiArrayType) type2).getComponentType());
    }

    if (!(type1 instanceof PsiClassType) || !(type2 instanceof PsiClassType)) {
      return type1.equals(type2);
    }

    PsiClass class1 = ((PsiClassType) type1).resolve();
    PsiClass class2 = ((PsiClassType) type2).resolve();

    if (class1 instanceof PsiTypeParameter && class2 instanceof PsiTypeParameter) {
      return Comparing.equal(class1.getName(), class2.getName())
          && ((PsiTypeParameter) class1).getIndex() == ((PsiTypeParameter) class2).getIndex();
    }

    return manager.areElementsEquivalent(class1, class2);
  }
}
/** @author gregsh */
public abstract class EditorTextFieldCellRenderer implements TableCellRenderer, Disposable {

  private static final Key<SimpleRendererComponent> MY_PANEL_PROPERTY =
      Key.create("EditorTextFieldCellRenderer.MyEditorPanel");

  private final Project myProject;
  private final FileType myFileType;
  private final boolean myInheritFontFromLaF;

  protected EditorTextFieldCellRenderer(
      @Nullable Project project, @Nullable FileType fileType, @NotNull Disposable parent) {
    this(project, fileType, true, parent);
  }

  protected EditorTextFieldCellRenderer(
      @Nullable Project project,
      @Nullable FileType fileType,
      boolean inheritFontFromLaF,
      @NotNull Disposable parent) {
    myProject = project;
    myFileType = fileType;
    myInheritFontFromLaF = inheritFontFromLaF;
    Disposer.register(parent, this);
  }

  protected abstract String getText(JTable table, Object value, int row, int column);

  @Nullable
  protected TextAttributes getTextAttributes(JTable table, Object value, int row, int column) {
    return null;
  }

  @NotNull
  protected EditorColorsScheme getColorScheme(final JTable table) {
    return getEditorPanel(table).getEditor().getColorsScheme();
  }

  protected void customizeEditor(
      @NotNull EditorEx editor, JTable table, Object value, boolean selected, int row, int column) {
    String text = getText(table, value, row, column);
    getEditorPanel(table).setText(text, getTextAttributes(table, value, row, column), selected);
  }

  @Override
  public Component getTableCellRendererComponent(
      JTable table, Object value, boolean selected, boolean focused, int row, int column) {
    RendererComponent panel = getEditorPanel(table);
    EditorEx editor = panel.getEditor();
    editor.getColorsScheme().setEditorFontSize(table.getFont().getSize());

    editor
        .getColorsScheme()
        .setColor(EditorColors.SELECTION_BACKGROUND_COLOR, table.getSelectionBackground());
    editor
        .getColorsScheme()
        .setColor(EditorColors.SELECTION_FOREGROUND_COLOR, table.getSelectionForeground());
    editor.setBackgroundColor(selected ? table.getSelectionBackground() : table.getBackground());
    panel.setSelected(!Comparing.equal(editor.getBackgroundColor(), table.getBackground()));

    panel.setBorder(
        null); // prevents double border painting when ExtendedItemRendererComponentWrapper is used

    customizeEditor(editor, table, value, selected, row, column);
    return panel;
  }

  @NotNull
  private RendererComponent getEditorPanel(final JTable table) {
    RendererComponent panel = UIUtil.getClientProperty(table, MY_PANEL_PROPERTY);
    if (panel != null) {
      DelegateColorScheme scheme = (DelegateColorScheme) panel.getEditor().getColorsScheme();
      scheme.setDelegate(EditorColorsManager.getInstance().getGlobalScheme());
      return panel;
    }

    panel = createRendererComponent(myProject, myFileType, myInheritFontFromLaF);
    Disposer.register(this, panel);
    Disposer.register(
        this,
        new Disposable() {
          @Override
          public void dispose() {
            UIUtil.putClientProperty(table, MY_PANEL_PROPERTY, null);
          }
        });

    table.putClientProperty(MY_PANEL_PROPERTY, panel);
    return panel;
  }

  @NotNull
  protected RendererComponent createRendererComponent(
      @Nullable Project project, @Nullable FileType fileType, boolean inheritFontFromLaF) {
    return new AbbreviatingRendererComponent(project, fileType, inheritFontFromLaF);
  }

  @Override
  public void dispose() {}

  public abstract static class RendererComponent extends CellRendererPanel implements Disposable {
    private final EditorEx myEditor;
    private final EditorTextField myTextField;
    protected TextAttributes myTextAttributes;
    private boolean mySelected;

    public RendererComponent(
        Project project, @Nullable FileType fileType, boolean inheritFontFromLaF) {
      Pair<EditorTextField, EditorEx> pair = createEditor(project, fileType, inheritFontFromLaF);
      myTextField = pair.first;
      myEditor = pair.second;
      add(myEditor.getContentComponent());
    }

    public EditorEx getEditor() {
      return myEditor;
    }

    @NotNull
    private static Pair<EditorTextField, EditorEx> createEditor(
        Project project, @Nullable FileType fileType, boolean inheritFontFromLaF) {
      EditorTextField field =
          new EditorTextField(new MyDocument(), project, fileType, false, false);
      field.setSupplementary(true);
      field.setFontInheritedFromLAF(inheritFontFromLaF);
      field.addNotify(); // creates editor

      EditorEx editor = (EditorEx) ObjectUtils.assertNotNull(field.getEditor());
      editor.setRendererMode(true);

      editor.setColorsScheme(editor.createBoundColorSchemeDelegate(null));
      editor.getSettings().setCaretRowShown(false);

      editor.getScrollPane().setBorder(null);

      return Pair.create(field, editor);
    }

    public void setText(String text, @Nullable TextAttributes textAttributes, boolean selected) {
      myTextAttributes = textAttributes;
      mySelected = selected;
      setText(text);
    }

    public abstract void setText(String text);

    @Override
    public void setBackground(Color bg) {
      // allows for striped tables
      if (myEditor != null) {
        myEditor.setBackgroundColor(bg);
      }
      super.setBackground(bg);
    }

    @Override
    public void dispose() {
      remove(myEditor.getContentComponent());
      myTextField.removeNotify();
    }

    protected void setTextToEditor(String text) {
      myEditor.getMarkupModel().removeAllHighlighters();
      myEditor.getDocument().setText(text);
      ((EditorImpl) myEditor).resetSizes();
      myEditor.getHighlighter().setText(text);
      if (myTextAttributes != null) {
        myEditor
            .getMarkupModel()
            .addRangeHighlighter(
                0,
                myEditor.getDocument().getTextLength(),
                HighlighterLayer.ADDITIONAL_SYNTAX,
                myTextAttributes,
                HighlighterTargetArea.EXACT_RANGE);
      }

      ((EditorImpl) myEditor).setPaintSelection(mySelected);
      SelectionModel selectionModel = myEditor.getSelectionModel();
      selectionModel.setSelection(0, mySelected ? myEditor.getDocument().getTextLength() : 0);
    }
  }

  public static class SimpleRendererComponent extends RendererComponent implements Disposable {
    public SimpleRendererComponent(
        Project project, @Nullable FileType fileType, boolean inheritFontFromLaF) {
      super(project, fileType, inheritFontFromLaF);
    }

    public void setText(String text) {
      setTextToEditor(text);
    }
  }

  public static class AbbreviatingRendererComponent extends RendererComponent {
    private static final char ABBREVIATION_SUFFIX = '\u2026'; // 2026 '...'
    private static final char RETURN_SYMBOL = '\u23ce';

    private final StringBuilder myDocumentTextBuilder = new StringBuilder();

    private Dimension myPreferredSize;
    private String myRawText;

    public AbbreviatingRendererComponent(
        Project project, @Nullable FileType fileType, boolean inheritFontFromLaF) {
      super(project, fileType, inheritFontFromLaF);
    }

    @Override
    public void setText(String text) {
      myRawText = text;
      myPreferredSize = null;
    }

    @Override
    public Dimension getPreferredSize() {
      if (myPreferredSize == null) {
        int maxLineLength = 0;
        int linesCount = 0;

        for (LineTokenizer lt = new LineTokenizer(myRawText); !lt.atEnd(); lt.advance()) {
          maxLineLength = Math.max(maxLineLength, lt.getLength());
          linesCount++;
        }

        FontMetrics fontMetrics =
            ((EditorImpl) getEditor())
                .getFontMetrics(
                    myTextAttributes != null ? myTextAttributes.getFontType() : Font.PLAIN);
        int preferredHeight = getEditor().getLineHeight() * Math.max(1, linesCount);
        int preferredWidth = fontMetrics.charWidth('m') * maxLineLength;

        Insets insets = getInsets();
        if (insets != null) {
          preferredHeight += insets.top + insets.bottom;
          preferredWidth += insets.left + insets.right;
        }

        myPreferredSize = new Dimension(preferredWidth, preferredHeight);
      }
      return myPreferredSize;
    }

    @Override
    protected void paintChildren(Graphics g) {
      updateText(g.getClipBounds());
      super.paintChildren(g);
    }

    private void updateText(Rectangle clip) {
      FontMetrics fontMetrics =
          ((EditorImpl) getEditor())
              .getFontMetrics(
                  myTextAttributes != null ? myTextAttributes.getFontType() : Font.PLAIN);
      Insets insets = getInsets();
      int maxLineWidth = getWidth() - (insets != null ? insets.left + insets.right : 0);

      myDocumentTextBuilder.setLength(0);

      boolean singleLineMode = getHeight() / (float) getEditor().getLineHeight() < 1.1f;
      if (singleLineMode) {
        appendAbbreviated(
            myDocumentTextBuilder,
            myRawText,
            0,
            myRawText.length(),
            fontMetrics,
            maxLineWidth,
            true);
      } else {
        int lineHeight = getEditor().getLineHeight();
        int firstVisibleLine = clip.y / lineHeight;
        float visibleLinesCountFractional = clip.height / (float) lineHeight;
        int linesToAppend = 1 + (int) visibleLinesCountFractional;

        LineTokenizer lt = new LineTokenizer(myRawText);
        for (int line = 0; !lt.atEnd() && line < firstVisibleLine; lt.advance(), line++) {
          myDocumentTextBuilder.append('\n');
        }

        for (int line = 0; !lt.atEnd() && line < linesToAppend; lt.advance(), line++) {
          int start = lt.getOffset();
          int end = start + lt.getLength();
          appendAbbreviated(
              myDocumentTextBuilder, myRawText, start, end, fontMetrics, maxLineWidth, false);
          if (lt.getLineSeparatorLength() > 0) {
            myDocumentTextBuilder.append('\n');
          }
        }
      }

      setTextToEditor(myDocumentTextBuilder.toString());
    }

    private static void appendAbbreviated(
        StringBuilder to,
        String text,
        int start,
        int end,
        FontMetrics metrics,
        int maxWidth,
        boolean replaceLineTerminators) {
      int abbreviationLength =
          abbreviationLength(text, start, end, metrics, maxWidth, replaceLineTerminators);

      if (!replaceLineTerminators) {
        to.append(text, start, start + abbreviationLength);
      } else {
        CharSequenceSubSequence subSeq =
            new CharSequenceSubSequence(text, start, start + abbreviationLength);
        for (LineTokenizer lt = new LineTokenizer(subSeq); !lt.atEnd(); lt.advance()) {
          to.append(subSeq, lt.getOffset(), lt.getOffset() + lt.getLength());
          if (lt.getLineSeparatorLength() > 0) {
            to.append(RETURN_SYMBOL);
          }
        }
      }

      if (abbreviationLength != end - start) {
        to.append(ABBREVIATION_SUFFIX);
      }
    }

    private static int abbreviationLength(
        String text,
        int start,
        int end,
        FontMetrics metrics,
        int maxWidth,
        boolean replaceSeparators) {
      if (metrics.charWidth('m') * (end - start) <= maxWidth) return end - start;

      int abbrWidth = metrics.charWidth(ABBREVIATION_SUFFIX);
      int abbrLength = 0;

      CharSequenceSubSequence subSeq = new CharSequenceSubSequence(text, start, end);
      for (LineTokenizer lt = new LineTokenizer(subSeq); !lt.atEnd(); lt.advance()) {
        for (int i = 0; i < lt.getLength(); i++, abbrLength++) {
          abbrWidth += metrics.charWidth(subSeq.charAt(lt.getOffset() + i));
          if (abbrWidth >= maxWidth) return abbrLength;
        }
        if (replaceSeparators && lt.getLineSeparatorLength() != 0) {
          abbrWidth += metrics.charWidth(RETURN_SYMBOL);
          if (abbrWidth >= maxWidth) return abbrLength;
          abbrLength += lt.getLineSeparatorLength();
        }
      }

      return abbrLength;
    }
  }

  private static class MyDocument extends UserDataHolderBase implements DocumentEx {

    RangeMarkerTree<RangeMarkerEx> myRangeMarkers = new RangeMarkerTree<RangeMarkerEx>(this) {};
    char[] myChars = ArrayUtil.EMPTY_CHAR_ARRAY;
    String myString = "";
    LineSet myLineSet = LineSet.createLineSet(myString);

    @Override
    public void setText(@NotNull CharSequence text) {
      String s = StringUtil.convertLineSeparators(text.toString());
      myChars = new char[s.length()];
      s.getChars(0, s.length(), myChars, 0);
      myString = new String(myChars);
      myLineSet = LineSet.createLineSet(myString);
    }

    @Override
    public void setStripTrailingSpacesEnabled(boolean isEnabled) {}

    @NotNull
    @Override
    public LineIterator createLineIterator() {
      return myLineSet.createIterator();
    }

    @Override
    public void setModificationStamp(long modificationStamp) {}

    @Override
    public void addEditReadOnlyListener(@NotNull EditReadOnlyListener listener) {}

    @Override
    public void removeEditReadOnlyListener(@NotNull EditReadOnlyListener listener) {}

    @Override
    public void replaceText(@NotNull CharSequence chars, long newModificationStamp) {}

    @Override
    public void moveText(int srcStart, int srcEnd, int dstOffset) {}

    @Override
    public void suppressGuardedExceptions() {}

    @Override
    public void unSuppressGuardedExceptions() {}

    @Override
    public boolean isInEventsHandling() {
      return false;
    }

    @Override
    public void clearLineModificationFlags() {}

    @Override
    public boolean removeRangeMarker(@NotNull RangeMarkerEx rangeMarker) {
      return myRangeMarkers.removeInterval(rangeMarker);
    }

    @Override
    public void registerRangeMarker(
        @NotNull RangeMarkerEx rangeMarker,
        int start,
        int end,
        boolean greedyToLeft,
        boolean greedyToRight,
        int layer) {
      myRangeMarkers.addInterval(rangeMarker, start, end, greedyToLeft, greedyToRight, layer);
    }

    @Override
    public boolean isInBulkUpdate() {
      return false;
    }

    @Override
    public void setInBulkUpdate(boolean value) {}

    @NotNull
    @Override
    public List<RangeMarker> getGuardedBlocks() {
      return Collections.emptyList();
    }

    @Override
    public boolean processRangeMarkers(@NotNull Processor<? super RangeMarker> processor) {
      return myRangeMarkers.process(processor);
    }

    @Override
    public boolean processRangeMarkersOverlappingWith(
        int start, int end, @NotNull Processor<? super RangeMarker> processor) {
      return myRangeMarkers.processOverlappingWith(start, end, processor);
    }

    @NotNull
    @Override
    public String getText() {
      return myString;
    }

    @NotNull
    @Override
    public String getText(@NotNull TextRange range) {
      return range.substring(getText());
    }

    @NotNull
    @Override
    public CharSequence getCharsSequence() {
      return myString;
    }

    @NotNull
    @Override
    public CharSequence getImmutableCharSequence() {
      return getText();
    }

    @NotNull
    @Override
    public char[] getChars() {
      return myChars;
    }

    @Override
    public int getTextLength() {
      return myChars.length;
    }

    @Override
    public int getLineCount() {
      return myLineSet.findLineIndex(myChars.length) + 1;
    }

    @Override
    public int getLineNumber(int offset) {
      return myLineSet.findLineIndex(offset);
    }

    @Override
    public int getLineStartOffset(int line) {
      return myChars.length == 0 ? 0 : myLineSet.getLineStart(line);
    }

    @Override
    public int getLineEndOffset(int line) {
      return myChars.length == 0 ? 0 : myLineSet.getLineEnd(line);
    }

    @Override
    public void insertString(int offset, @NotNull CharSequence s) {}

    @Override
    public void deleteString(int startOffset, int endOffset) {}

    @Override
    public void replaceString(int startOffset, int endOffset, @NotNull CharSequence s) {}

    @Override
    public boolean isWritable() {
      return false;
    }

    @Override
    public long getModificationStamp() {
      return 0;
    }

    @Override
    public void fireReadOnlyModificationAttempt() {}

    @Override
    public void addDocumentListener(@NotNull DocumentListener listener) {}

    @Override
    public void addDocumentListener(
        @NotNull DocumentListener listener, @NotNull Disposable parentDisposable) {}

    @Override
    public void removeDocumentListener(@NotNull DocumentListener listener) {}

    @NotNull
    @Override
    public RangeMarker createRangeMarker(int startOffset, int endOffset) {
      return new RangeMarkerImpl(this, startOffset, endOffset, true) {};
    }

    @NotNull
    @Override
    public RangeMarker createRangeMarker(
        int startOffset, int endOffset, boolean surviveOnExternalChange) {
      return null;
    }

    @Override
    public void addPropertyChangeListener(@NotNull PropertyChangeListener listener) {}

    @Override
    public void removePropertyChangeListener(@NotNull PropertyChangeListener listener) {}

    @Override
    public void setReadOnly(boolean isReadOnly) {}

    @NotNull
    @Override
    public RangeMarker createGuardedBlock(int startOffset, int endOffset) {
      return null;
    }

    @Override
    public void removeGuardedBlock(@NotNull RangeMarker block) {}

    @Nullable
    @Override
    public RangeMarker getOffsetGuard(int offset) {
      return null;
    }

    @Nullable
    @Override
    public RangeMarker getRangeGuard(int start, int end) {
      return null;
    }

    @Override
    public void startGuardedBlockChecking() {}

    @Override
    public void stopGuardedBlockChecking() {}

    @Override
    public void setCyclicBufferSize(int bufferSize) {}

    @NotNull
    @Override
    public RangeMarker createRangeMarker(@NotNull TextRange textRange) {
      return null;
    }

    @Override
    public int getLineSeparatorLength(int line) {
      return 0;
    }

    @Override
    public int getModificationSequence() {
      return 0;
    }
  }
}