com.intellij.usages.impl.UsageViewImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.intellij.usages.impl.UsageViewImpl.java

Source

/*
 * Copyright 2000-2014 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.intellij.usages.impl;

import com.intellij.find.FindManager;
import com.intellij.icons.AllIcons;
import com.intellij.ide.*;
import com.intellij.ide.actions.CloseTabToolbarAction;
import com.intellij.navigation.NavigationItem;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.ide.CopyPasteManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.progress.util.ProgressWrapper;
import com.intellij.openapi.progress.util.TooManyUsagesStatus;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.SimpleToolWindowPanel;
import com.intellij.openapi.ui.Splitter;
import com.intellij.openapi.util.*;
import com.intellij.openapi.vfs.ReadonlyStatusHandler;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.Navigatable;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.SmartPointerManager;
import com.intellij.psi.SmartPsiElementPointer;
import com.intellij.psi.impl.PsiDocumentManagerBase;
import com.intellij.ui.*;
import com.intellij.ui.components.JBTabbedPane;
import com.intellij.ui.content.Content;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewBundle;
import com.intellij.usageView.UsageViewManager;
import com.intellij.usages.*;
import com.intellij.usages.rules.*;
import com.intellij.util.Alarm;
import com.intellij.util.Consumer;
import com.intellij.util.EditSourceOnDoubleClickHandler;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Convertor;
import com.intellij.util.containers.TransferToEDTQueue;
import com.intellij.util.enumeration.EmptyEnumeration;
import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.ui.DialogUtil;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.tree.TreeUtil;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.awt.event.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author max
 */
public class UsageViewImpl implements UsageView, UsageModelTracker.UsageModelTrackerListener {
    @NonNls
    public static final String SHOW_RECENT_FIND_USAGES_ACTION_ID = "UsageView.ShowRecentFindUsages";

    private final UsageNodeTreeBuilder myBuilder;
    private final MyPanel myRootPanel;
    private final JTree myTree;
    private Content myContent;

    private final UsageViewPresentation myPresentation;
    private final UsageTarget[] myTargets;
    private final Factory<UsageSearcher> myUsageSearcherFactory;
    private final Project myProject;

    private volatile boolean mySearchInProgress = true;
    private final ExporterToTextFile myTextFileExporter = new ExporterToTextFile(this);
    private final Alarm myUpdateAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);

    private final UsageModelTracker myModelTracker;
    private final Map<Usage, UsageNode> myUsageNodes = new ConcurrentHashMap<Usage, UsageNode>();
    public static final UsageNode NULL_NODE = new UsageNode(NullUsage.INSTANCE,
            new UsageViewTreeModelBuilder(new UsageViewPresentation(), UsageTarget.EMPTY_ARRAY));
    private final ButtonPanel myButtonPanel = new ButtonPanel();
    private volatile boolean isDisposed;
    private volatile boolean myChangesDetected = false;
    public static final Comparator<Usage> USAGE_COMPARATOR = new Comparator<Usage>() {
        @Override
        public int compare(final Usage o1, final Usage o2) {
            if (o1 == o2)
                return 0;
            if (o1 == NULL_NODE)
                return -1;
            if (o2 == NULL_NODE)
                return 1;
            if (o1 instanceof Comparable && o2 instanceof Comparable) {
                final int selfcompared = ((Comparable<Usage>) o1).compareTo(o2);
                if (selfcompared != 0)
                    return selfcompared;

                if (o1 instanceof UsageInFile && o2 instanceof UsageInFile) {
                    UsageInFile u1 = (UsageInFile) o1;
                    UsageInFile u2 = (UsageInFile) o2;

                    VirtualFile f1 = u1.getFile();
                    VirtualFile f2 = u2.getFile();

                    if (f1 != null && f1.isValid() && f2 != null && f2.isValid()) {
                        return f1.getPresentableUrl().compareTo(f2.getPresentableUrl());
                    }
                }

                return 0;
            }
            return o1.toString().compareTo(o2.toString());
        }
    };
    @NonNls
    private static final String HELP_ID = "ideaInterface.find";
    private UsageContextPanel myCurrentUsageContextPanel;
    private List<UsageContextPanel.Provider> myUsageContextPanelProviders;
    private UsageContextPanel.Provider myCurrentUsageContextProvider;

    private JPanel myCentralPanel;
    private final GroupNode myRoot;
    private final UsageViewTreeModelBuilder myModel;
    private final Object lock = new Object();
    private Splitter myPreviewSplitter;
    private volatile ProgressIndicator associatedProgress; // the progress that current find usages is running under

    // true if usages tree is currently expanding
    // (either at the end of find usages thanks to the 'expand usages after find' setting or
    // because the user pressed 'expand all' button. During this, some ugly hacks applied
    // to speed up the expanding (see getExpandedDescendants() here and UsageViewTreeCellRenderer.customizeCellRenderer())
    private boolean expandingAll;
    private final UsageViewTreeCellRenderer myUsageViewTreeCellRenderer;

    UsageViewImpl(@NotNull final Project project, @NotNull UsageViewPresentation presentation,
            @NotNull UsageTarget[] targets, Factory<UsageSearcher> usageSearcherFactory) {
        myPresentation = presentation;
        myTargets = targets;
        myUsageSearcherFactory = usageSearcherFactory;
        myProject = project;
        myTree = new Tree() {
            {
                ToolTipManager.sharedInstance().registerComponent(this);
            }

            @Override
            public String getToolTipText(MouseEvent e) {
                TreePath path = getPathForLocation(e.getX(), e.getY());
                if (path != null) {
                    if (getCellRenderer() instanceof UsageViewTreeCellRenderer) {
                        return UsageViewTreeCellRenderer.getTooltipFromPresentation(path.getLastPathComponent());
                    }
                }
                return null;
            }

            @Override
            public boolean isPathEditable(final TreePath path) {
                return path.getLastPathComponent() instanceof UsageViewTreeModelBuilder.TargetsRootNode;
            }

            // hack to avoid quadratic expandAll()
            @Override
            public Enumeration<TreePath> getExpandedDescendants(TreePath parent) {
                return expandingAll ? EmptyEnumeration.<TreePath>getInstance()
                        : super.getExpandedDescendants(parent);
            }
        };
        myRootPanel = new MyPanel(myTree);
        Disposer.register(this, myRootPanel);
        myModelTracker = new UsageModelTracker(project);
        Disposer.register(this, myModelTracker);

        myModel = new UsageViewTreeModelBuilder(myPresentation, targets);
        myRoot = (GroupNode) myModel.getRoot();
        myBuilder = new UsageNodeTreeBuilder(myTargets, getActiveGroupingRules(project),
                getActiveFilteringRules(project), myRoot);

        final MessageBusConnection messageBusConnection = myProject.getMessageBus().connect(this);
        messageBusConnection.subscribe(UsageFilteringRuleProvider.RULES_CHANGED, new Runnable() {
            @Override
            public void run() {
                rulesChanged();
            }
        });

        myUsageViewTreeCellRenderer = new UsageViewTreeCellRenderer(this);
        if (!myPresentation.isDetachedMode()) {
            UIUtil.invokeLaterIfNeeded(new Runnable() {
                @Override
                public void run() {
                    // lock here to avoid concurrent execution of this init and dispose in other thread
                    synchronized (lock) {
                        if (isDisposed)
                            return;
                        myTree.setModel(myModel);

                        myRootPanel.setLayout(new BorderLayout());

                        SimpleToolWindowPanel toolWindowPanel = new SimpleToolWindowPanel(false, true);
                        myRootPanel.add(toolWindowPanel, BorderLayout.CENTER);

                        JPanel toolbarPanel = new JPanel(new BorderLayout());
                        toolbarPanel.add(createActionsToolbar(), BorderLayout.WEST);
                        toolbarPanel.add(createFiltersToolbar(), BorderLayout.CENTER);
                        toolWindowPanel.setToolbar(toolbarPanel);

                        myCentralPanel = new JPanel(new BorderLayout());
                        setupCentralPanel();

                        initTree();
                        toolWindowPanel.setContent(myCentralPanel);

                        myTree.setCellRenderer(myUsageViewTreeCellRenderer);
                        collapseAll();

                        myModelTracker.addListener(UsageViewImpl.this);

                        if (myPresentation.isShowCancelButton()) {
                            addButtonToLowerPane(new Runnable() {
                                @Override
                                public void run() {
                                    close();
                                }
                            }, UsageViewBundle.message("usage.view.cancel.button"));
                        }

                        myTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
                            @Override
                            public void valueChanged(final TreeSelectionEvent e) {
                                SwingUtilities.invokeLater(new Runnable() {
                                    @Override
                                    public void run() {
                                        if (isDisposed || myProject.isDisposed())
                                            return;
                                        updateOnSelectionChanged();
                                    }
                                });
                            }
                        });
                    }
                }
            });
        }
        myTransferToEDTQueue = new TransferToEDTQueue<Runnable>("Insert usages", new Processor<Runnable>() {
            @Override
            public boolean process(Runnable runnable) {
                runnable.run();
                return true;
            }
        }, new Condition<Object>() {
            @Override
            public boolean value(Object o) {
                return isDisposed || project.isDisposed();
            }
        }, 200);
    }

    protected boolean searchHasBeenCancelled() {
        ProgressIndicator progress = associatedProgress;
        return progress != null && progress.isCanceled();
    }

    protected void cancelCurrentSearch() {
        ProgressIndicator progress = associatedProgress;
        if (progress != null) {
            ProgressWrapper.unwrap(progress).cancel();
        }
    }

    private void setupCentralPanel() {
        myCentralPanel.removeAll();
        disposeUsageContextPanels();

        JScrollPane treePane = ScrollPaneFactory.createScrollPane(myTree);
        // add reaction to scrolling:
        // since the UsageViewTreeCellRenderer ignores invisible nodes (outside the viewport), their preferred size is incorrect
        // and we need to recalculate it when the node scrolled into visible rectangle
        treePane.getViewport().addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                // clear renderer cache of node preferred size
                myTree.setCellRenderer(myUsageViewTreeCellRenderer);
            }
        });
        myPreviewSplitter = new Splitter(false, 0.5f, 0.1f, 0.9f);
        myPreviewSplitter.setFirstComponent(treePane);

        myCentralPanel.add(myPreviewSplitter, BorderLayout.CENTER);

        if (UsageViewSettings.getInstance().IS_PREVIEW_USAGES) {
            myPreviewSplitter.setProportion(UsageViewSettings.getInstance().PREVIEW_USAGES_SPLITTER_PROPORTIONS);
            treePane.putClientProperty(UIUtil.KEEP_BORDER_SIDES, SideBorder.RIGHT);
            final JBTabbedPane tabbedPane = new JBTabbedPane(SwingConstants.BOTTOM) {
                @NotNull
                @Override
                protected Insets getInsetsForTabComponent() {
                    return new Insets(0, 0, 0, 0);
                }
            };

            UsageContextPanel.Provider[] extensions = Extensions.getExtensions(UsageContextPanel.Provider.EP_NAME,
                    myProject);
            myUsageContextPanelProviders = ContainerUtil.filter(extensions,
                    new Condition<UsageContextPanel.Provider>() {
                        @Override
                        public boolean value(UsageContextPanel.Provider provider) {
                            return provider.isAvailableFor(UsageViewImpl.this);
                        }
                    });
            for (UsageContextPanel.Provider provider : myUsageContextPanelProviders) {
                JComponent component;
                if (myCurrentUsageContextProvider == null || myCurrentUsageContextProvider == provider) {
                    myCurrentUsageContextProvider = provider;
                    myCurrentUsageContextPanel = provider.create(this);
                    component = myCurrentUsageContextPanel.createComponent();
                } else {
                    component = new JLabel();
                }

                tabbedPane.addTab(provider.getTabTitle(), component);
            }
            int index = myUsageContextPanelProviders.indexOf(myCurrentUsageContextProvider);
            tabbedPane.setSelectedIndex(index);
            tabbedPane.addChangeListener(new ChangeListener() {
                @Override
                public void stateChanged(ChangeEvent e) {
                    int currentIndex = tabbedPane.getSelectedIndex();
                    UsageContextPanel.Provider selectedProvider = myUsageContextPanelProviders.get(currentIndex);
                    if (selectedProvider != myCurrentUsageContextProvider) {
                        tabSelected(selectedProvider);
                    }
                }
            });
            tabbedPane.setBorder(IdeBorderFactory.createBorder(SideBorder.LEFT));
            myPreviewSplitter.setSecondComponent(tabbedPane);
        } else {
            myPreviewSplitter.setProportion(1);
        }

        myCentralPanel.add(myButtonPanel, BorderLayout.SOUTH);

        myRootPanel.revalidate();
        myRootPanel.repaint();
    }

    private void tabSelected(@NotNull final UsageContextPanel.Provider provider) {
        myCurrentUsageContextProvider = provider;
        setupCentralPanel();
        updateOnSelectionChanged();
    }

    private void disposeUsageContextPanels() {
        if (myCurrentUsageContextPanel != null) {
            saveSplitterProportions();
            Disposer.dispose(myCurrentUsageContextPanel);
            myCurrentUsageContextPanel = null;
        }
    }

    private static UsageFilteringRule[] getActiveFilteringRules(final Project project) {
        final UsageFilteringRuleProvider[] providers = Extensions.getExtensions(UsageFilteringRuleProvider.EP_NAME);
        List<UsageFilteringRule> list = new ArrayList<UsageFilteringRule>(providers.length);
        for (UsageFilteringRuleProvider provider : providers) {
            ContainerUtil.addAll(list, provider.getActiveRules(project));
        }
        return list.toArray(new UsageFilteringRule[list.size()]);
    }

    private static UsageGroupingRule[] getActiveGroupingRules(@NotNull final Project project) {
        final UsageGroupingRuleProvider[] providers = Extensions.getExtensions(UsageGroupingRuleProvider.EP_NAME);
        List<UsageGroupingRule> list = new ArrayList<UsageGroupingRule>(providers.length);
        for (UsageGroupingRuleProvider provider : providers) {
            ContainerUtil.addAll(list, provider.getActiveRules(project));
        }

        Collections.sort(list, new Comparator<UsageGroupingRule>() {
            @Override
            public int compare(final UsageGroupingRule o1, final UsageGroupingRule o2) {
                return getRank(o1) - getRank(o2);
            }

            private int getRank(final UsageGroupingRule rule) {
                if (rule instanceof OrderableUsageGroupingRule) {
                    return ((OrderableUsageGroupingRule) rule).getRank();
                }

                return Integer.MAX_VALUE;
            }
        });

        return list.toArray(new UsageGroupingRule[list.size()]);
    }

    @Override
    public void modelChanged(boolean isPropertyChange) {
        if (!isPropertyChange) {
            myChangesDetected = true;
        }
        updateLater();
    }

    private void initTree() {
        myTree.setRootVisible(false);
        myTree.setShowsRootHandles(true);
        SmartExpander.installOn(myTree);
        TreeUtil.installActions(myTree);
        EditSourceOnDoubleClickHandler.install(myTree);
        myTree.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                if (KeyEvent.VK_ENTER == e.getKeyCode()) {
                    TreePath leadSelectionPath = myTree.getLeadSelectionPath();
                    if (leadSelectionPath == null)
                        return;

                    DefaultMutableTreeNode node = (DefaultMutableTreeNode) leadSelectionPath.getLastPathComponent();
                    if (node instanceof UsageNode) {
                        final Usage usage = ((UsageNode) node).getUsage();
                        usage.navigate(false);
                        usage.highlightInEditor();
                    } else if (node.isLeaf()) {
                        Navigatable navigatable = getNavigatableForNode(node);
                        if (navigatable != null && navigatable.canNavigate()) {
                            navigatable.navigate(false);
                        }
                    }
                }
            }
        });

        TreeUtil.selectFirstNode(myTree);
        PopupHandler.installPopupHandler(myTree, IdeActions.GROUP_USAGE_VIEW_POPUP, ActionPlaces.USAGE_VIEW_POPUP);

        myTree.addTreeExpansionListener(new TreeExpansionListener() {
            @Override
            public void treeExpanded(TreeExpansionEvent event) {
                TreePath path = event.getPath();
                Object component = path.getLastPathComponent();
                if (!(component instanceof Node))
                    return;
                Node node = (Node) component;
                if (!expandingAll && node.needsUpdate()) {
                    checkNodeValidity(node, path);
                }
            }

            @Override
            public void treeCollapsed(TreeExpansionEvent event) {
            }
        });

        TreeUIHelper.getInstance().installTreeSpeedSearch(myTree, new Convertor<TreePath, String>() {
            @Override
            public String convert(TreePath o) {
                Object value = o.getLastPathComponent();
                TreeCellRenderer renderer = myTree.getCellRenderer();
                if (renderer instanceof UsageViewTreeCellRenderer) {
                    UsageViewTreeCellRenderer coloredRenderer = (UsageViewTreeCellRenderer) renderer;
                    return coloredRenderer.getPlainTextForNode(value);
                }
                return value == null ? null : value.toString();
            }
        }, true);
    }

    @NotNull
    private JComponent createActionsToolbar() {
        DefaultActionGroup group = new DefaultActionGroup() {
            @Override
            public void update(AnActionEvent e) {
                super.update(e);
                myButtonPanel.update();
            }

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

        AnAction[] actions = createActions();
        for (final AnAction action : actions) {
            if (action != null) {
                group.add(action);
            }
        }
        return toUsageViewToolbar(group);
    }

    @NotNull
    private JComponent toUsageViewToolbar(@NotNull DefaultActionGroup group) {
        ActionToolbar actionToolbar = ActionManager.getInstance()
                .createActionToolbar(ActionPlaces.USAGE_VIEW_TOOLBAR, group, false);
        actionToolbar.setTargetComponent(myRootPanel);
        return actionToolbar.getComponent();
    }

    @NotNull
    private JComponent createFiltersToolbar() {
        final DefaultActionGroup group = new DefaultActionGroup();

        final AnAction[] groupingActions = createGroupingActions();
        for (AnAction groupingAction : groupingActions) {
            group.add(groupingAction);
        }

        addFilteringActions(group);
        group.add(new PreviewUsageAction(this));

        group.add(new SortMembersAlphabeticallyAction(this));
        return toUsageViewToolbar(group);
    }

    public void addFilteringActions(@NotNull DefaultActionGroup group) {
        final JComponent component = getComponent();

        if (getPresentation().isMergeDupLinesAvailable()) {
            final MergeDupLines mergeDupLines = new MergeDupLines();
            mergeDupLines.registerCustomShortcutSet(mergeDupLines.getShortcutSet(), component, this);
            group.add(mergeDupLines);
        }

        final UsageFilteringRuleProvider[] providers = Extensions.getExtensions(UsageFilteringRuleProvider.EP_NAME);
        for (UsageFilteringRuleProvider provider : providers) {
            AnAction[] actions = provider.createFilteringActions(this);
            for (AnAction action : actions) {
                group.add(action);
            }
        }
    }

    public void scheduleDisposeOnClose(@NotNull Disposable disposable) {
        Disposer.register(this, disposable);
    }

    @NotNull
    private AnAction[] createActions() {
        final TreeExpander treeExpander = new TreeExpander() {
            @Override
            public void expandAll() {
                UsageViewImpl.this.expandAll();
                UsageViewSettings.getInstance().setExpanded(true);
            }

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

            @Override
            public void collapseAll() {
                UsageViewImpl.this.collapseAll();
                UsageViewSettings.getInstance().setExpanded(false);
            }

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

        CommonActionsManager actionsManager = CommonActionsManager.getInstance();

        final JComponent component = getComponent();

        final AnAction expandAllAction = actionsManager.createExpandAllAction(treeExpander, component);
        final AnAction collapseAllAction = actionsManager.createCollapseAllAction(treeExpander, component);

        scheduleDisposeOnClose(new Disposable() {
            @Override
            public void dispose() {
                expandAllAction.unregisterCustomShortcutSet(component);
                collapseAllAction.unregisterCustomShortcutSet(component);
            }
        });

        return new AnAction[] { canShowSettings() ? showSettings() : null,
                canPerformReRun() ? new ReRunAction() : null, new CloseAction(),
                ActionManager.getInstance().getAction("PinToolwindowTab"), createRecentFindUsagesAction(),
                expandAllAction, collapseAllAction, actionsManager.createPrevOccurenceAction(myRootPanel),
                actionsManager.createNextOccurenceAction(myRootPanel),
                actionsManager.installAutoscrollToSourceHandler(myProject, myTree,
                        new MyAutoScrollToSourceOptionProvider()),
                actionsManager.createExportToTextFileAction(myTextFileExporter),
                actionsManager.createHelpAction(HELP_ID) };
    }

    private boolean canShowSettings() {
        if (myTargets.length == 0)
            return false;
        NavigationItem target = myTargets[0];
        return target instanceof ConfigurableUsageTarget;
    }

    @NotNull
    private AnAction showSettings() {
        final ConfigurableUsageTarget configurableUsageTarget = getConfigurableTarget(myTargets);
        String description = configurableUsageTarget == null ? "Show find usages settings dialog"
                : "Show settings for " + configurableUsageTarget.getLongDescriptiveName();
        return new AnAction("Settings...", description, AllIcons.General.ProjectSettings) {
            {
                KeyboardShortcut shortcut = configurableUsageTarget == null ? getShowUsagesWithSettingsShortcut()
                        : configurableUsageTarget.getShortcut();
                if (shortcut != null) {
                    registerCustomShortcutSet(new CustomShortcutSet(shortcut), getComponent());
                }
            }

            @Override
            public void actionPerformed(AnActionEvent e) {
                FindManager.getInstance(getProject()).showSettingsAndFindUsages(myTargets);
            }
        };
    }

    private static ConfigurableUsageTarget getConfigurableTarget(@NotNull UsageTarget[] targets) {
        ConfigurableUsageTarget configurableUsageTarget = null;
        if (targets.length != 0) {
            NavigationItem target = targets[0];
            if (target instanceof ConfigurableUsageTarget) {
                configurableUsageTarget = (ConfigurableUsageTarget) target;
            }
        }
        return configurableUsageTarget;
    }

    @NotNull
    private AnAction createRecentFindUsagesAction() {
        AnAction action = ActionManager.getInstance().getAction(SHOW_RECENT_FIND_USAGES_ACTION_ID);
        action.registerCustomShortcutSet(action.getShortcutSet(), getComponent());
        return action;
    }

    @NotNull
    private AnAction[] createGroupingActions() {
        final UsageGroupingRuleProvider[] providers = Extensions.getExtensions(UsageGroupingRuleProvider.EP_NAME);
        List<AnAction> list = new ArrayList<AnAction>(providers.length);
        for (UsageGroupingRuleProvider provider : providers) {
            ContainerUtil.addAll(list, provider.createGroupingActions(this));
        }
        return list.toArray(new AnAction[list.size()]);
    }

    private void rulesChanged() {
        ApplicationManager.getApplication().assertIsDispatchThread();
        final List<UsageState> states = new ArrayList<UsageState>();
        captureUsagesExpandState(new TreePath(myTree.getModel().getRoot()), states);
        final List<Usage> allUsages = new ArrayList<Usage>(myUsageNodes.keySet());
        Collections.sort(allUsages, USAGE_COMPARATOR);
        final Set<Usage> excludedUsages = getExcludedUsages();
        reset();
        myBuilder.setGroupingRules(getActiveGroupingRules(myProject));
        myBuilder.setFilteringRules(getActiveFilteringRules(myProject));
        ApplicationManager.getApplication().runReadAction(new Runnable() {
            @Override
            public void run() {
                for (Usage usage : allUsages) {
                    if (!usage.isValid()) {
                        continue;
                    }
                    if (usage instanceof MergeableUsage) {
                        ((MergeableUsage) usage).reset();
                    }
                    appendUsage(usage);
                }
            }
        });
        excludeUsages(excludedUsages.toArray(new Usage[excludedUsages.size()]));
        if (myCentralPanel != null) {
            setupCentralPanel();
        }
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                if (isDisposed)
                    return;
                restoreUsageExpandState(states);
                updateImmediately();
            }
        });
    }

    private void captureUsagesExpandState(TreePath pathFrom, final Collection<UsageState> states) {
        if (!myTree.isExpanded(pathFrom)) {
            return;
        }
        final DefaultMutableTreeNode node = (DefaultMutableTreeNode) pathFrom.getLastPathComponent();
        final int childCount = node.getChildCount();
        for (int idx = 0; idx < childCount; idx++) {
            final TreeNode child = node.getChildAt(idx);
            if (child instanceof UsageNode) {
                final Usage usage = ((UsageNode) child).getUsage();
                states.add(new UsageState(usage,
                        myTree.getSelectionModel().isPathSelected(pathFrom.pathByAddingChild(child))));
            } else {
                captureUsagesExpandState(pathFrom.pathByAddingChild(child), states);
            }
        }
    }

    private void restoreUsageExpandState(@NotNull Collection<UsageState> states) {
        //always expand the last level group
        final DefaultMutableTreeNode root = (DefaultMutableTreeNode) myTree.getModel().getRoot();
        for (int i = root.getChildCount() - 1; i >= 0; i--) {
            final DefaultMutableTreeNode child = (DefaultMutableTreeNode) root.getChildAt(i);
            if (child instanceof GroupNode) {
                final TreePath treePath = new TreePath(child.getPath());
                myTree.expandPath(treePath);
            }
        }
        myTree.getSelectionModel().clearSelection();
        for (final UsageState usageState : states) {
            usageState.restore();
        }
    }

    private void expandAll() {
        expandingAll = true;
        try {
            TreeUtil.expandAll(myTree);
        } finally {
            expandingAll = false;
        }
    }

    private void collapseAll() {
        TreeUtil.collapseAll(myTree, 3);
        TreeUtil.expand(myTree, 2);
    }

    public DefaultMutableTreeNode getModelRoot() {
        return (DefaultMutableTreeNode) myTree.getModel().getRoot();
    }

    public void select() {
        if (myTree != null) {
            myTree.requestFocusInWindow();
        }
    }

    @NotNull
    public Project getProject() {
        return myProject;
    }

    @Nullable
    public static KeyboardShortcut getShowUsagesWithSettingsShortcut() {
        return ActionManager.getInstance().getKeyboardShortcut("ShowSettingsAndFindUsages");
    }

    static KeyboardShortcut getShowUsagesWithSettingsShortcut(@NotNull UsageTarget[] targets) {
        ConfigurableUsageTarget configurableTarget = getConfigurableTarget(targets);
        return configurableTarget == null ? getShowUsagesWithSettingsShortcut() : configurableTarget.getShortcut();
    }

    void associateProgress(ProgressIndicator indicator) {
        associatedProgress = indicator;
    }

    private class CloseAction extends CloseTabToolbarAction {
        @Override
        public void update(AnActionEvent e) {
            super.update(e);
            e.getPresentation().setVisible(myContent != null);
        }

        @Override
        public void actionPerformed(AnActionEvent e) {
            close();
        }
    }

    private class MergeDupLines extends RuleAction {
        private MergeDupLines() {
            super(UsageViewImpl.this, UsageViewBundle.message("action.merge.same.line"),
                    AllIcons.Toolbar.Filterdups);
            setShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_DOWN_MASK)));
        }

        @Override
        protected boolean getOptionValue() {
            return UsageViewSettings.getInstance().isFilterDuplicatedLine();
        }

        @Override
        protected void setOptionValue(boolean value) {
            UsageViewSettings.getInstance().setFilterDuplicatedLine(value);
        }
    }

    private class ReRunAction extends AnAction implements DumbAware {
        private ReRunAction() {
            super(UsageViewBundle.message("action.rerun"), UsageViewBundle.message("action.description.rerun"),
                    AllIcons.Actions.Rerun);
            registerCustomShortcutSet(CommonShortcuts.getRerun(), myRootPanel);
        }

        @Override
        public void actionPerformed(AnActionEvent e) {
            refreshUsages();
        }

        @Override
        public void update(AnActionEvent e) {
            super.update(e);
            final Presentation presentation = e.getPresentation();
            presentation.setEnabled(allTargetsAreValid());
        }
    }

    private void refreshUsages() {
        reset();
        doReRun();
    }

    private void doReRun() {
        final AtomicInteger usageCountWithoutDefinition = new AtomicInteger(0);
        final Project project = myProject;
        Task.Backgroundable task = new Task.Backgroundable(project,
                UsageViewManagerImpl.getProgressTitle(myPresentation)) {
            @Override
            public void run(@NotNull final ProgressIndicator indicator) {
                final TooManyUsagesStatus tooManyUsagesStatus = TooManyUsagesStatus.createFor(indicator);
                setSearchInProgress(true);
                associateProgress(indicator);

                myChangesDetected = false;
                UsageSearcher usageSearcher = myUsageSearcherFactory.create();
                usageSearcher.generate(new Processor<Usage>() {
                    @Override
                    public boolean process(final Usage usage) {
                        if (searchHasBeenCancelled())
                            return false;
                        TooManyUsagesStatus.getFrom(indicator).pauseProcessingIfTooManyUsages();

                        boolean incrementCounter = !com.intellij.usages.UsageViewManager.isSelfUsage(usage,
                                myTargets);

                        if (incrementCounter) {
                            final int usageCount = usageCountWithoutDefinition.incrementAndGet();
                            if (usageCount > UsageLimitUtil.USAGES_LIMIT) {
                                if (tooManyUsagesStatus.switchTooManyUsagesStatus()) {
                                    UsageViewManagerImpl.showTooManyUsagesWarning(project, tooManyUsagesStatus,
                                            indicator, getPresentation(), usageCountWithoutDefinition.get(),
                                            UsageViewImpl.this);
                                }
                            }
                            ApplicationManager.getApplication().runReadAction(new Runnable() {
                                @Override
                                public void run() {
                                    appendUsage(usage);
                                }
                            });
                        }
                        return !indicator.isCanceled();
                    }
                });
                drainQueuedUsageNodes();
                setSearchInProgress(false);
            }
        };
        ProgressManager.getInstance().run(task);
    }

    private void reset() {
        ApplicationManager.getApplication().assertIsDispatchThread();
        myUsageNodes.clear();
        myModel.reset();
        if (!myPresentation.isDetachedMode()) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    if (isDisposed)
                        return;
                    TreeUtil.expand(myTree, 2);
                }
            });
        }
    }

    private final TransferToEDTQueue<Runnable> myTransferToEDTQueue;

    public void drainQueuedUsageNodes() {
        assert !ApplicationManager.getApplication().isDispatchThread() : Thread.currentThread();
        UIUtil.invokeAndWaitIfNeeded(new Runnable() {
            @Override
            public void run() {
                myTransferToEDTQueue.drain();
            }
        });
    }

    @Override
    public void appendUsage(@NotNull Usage usage) {
        doAppendUsage(usage);
    }

    @Nullable
    public UsageNode doAppendUsage(@NotNull Usage usage) {
        // invoke in ReadAction to be be sure that usages are not invalidated while the tree is being built
        ApplicationManager.getApplication().assertReadAccessAllowed();
        if (!usage.isValid()) {
            // because the view is built incrementally, the usage may be already invalid, so need to filter such cases
            return null;
        }
        UsageNode node = myBuilder.appendUsage(usage, new Consumer<Runnable>() {
            @Override
            public void consume(Runnable runnable) {
                myTransferToEDTQueue.offer(runnable);
            }
        });
        if (node != null) {
            // update and cache flags while the node is still hot
            node.update(this);
        }
        myUsageNodes.put(usage, node == null ? NULL_NODE : node);
        return node;
    }

    @Override
    public void removeUsage(@NotNull Usage usage) {
        final UsageNode node = myUsageNodes.remove(usage);
        if (node != NULL_NODE && node != null && !myPresentation.isDetachedMode()) {
            UIUtil.invokeLaterIfNeeded(new Runnable() {
                @Override
                public void run() {
                    if (isDisposed)
                        return;
                    TreeModel treeModel = myTree.getModel();
                    ((DefaultTreeModel) treeModel).removeNodeFromParent(node);
                    ((GroupNode) myTree.getModel().getRoot()).removeUsage(node);
                }
            });
        }
    }

    @Override
    public void removeUsagesBulk(@NotNull Collection<Usage> usages) {
        final Set<UsageNode> nodes = new THashSet<UsageNode>(usages.size());
        for (Usage usage : usages) {
            UsageNode node = myUsageNodes.remove(usage);
            if (node != null && node != NULL_NODE) {
                nodes.add(node);
            }
        }
        if (!nodes.isEmpty() && !myPresentation.isDetachedMode()) {
            UIUtil.invokeLaterIfNeeded(new Runnable() {
                @Override
                public void run() {
                    if (isDisposed)
                        return;
                    DefaultTreeModel treeModel = (DefaultTreeModel) myTree.getModel();
                    for (UsageNode node : nodes) {
                        MutableTreeNode parent = (MutableTreeNode) node.getParent();
                        int childIndex = parent.getIndex(node);
                        if (childIndex != -1) {
                            parent.remove(childIndex);
                        }
                    }
                    ((GroupNode) myTree.getModel().getRoot()).removeUsagesBulk(nodes);

                    treeModel.reload();
                }
            });
        }
    }

    @Override
    public void includeUsages(@NotNull Usage[] usages) {
        List<TreeNode> nodes = new ArrayList<TreeNode>(usages.length);
        for (Usage usage : usages) {
            final UsageNode node = myUsageNodes.get(usage);
            if (node != NULL_NODE && node != null) {
                node.setUsageExcluded(false);
                nodes.add(node);
            }
        }
        updateImmediatelyNodesUpToRoot(nodes);
    }

    @Override
    public void excludeUsages(@NotNull Usage[] usages) {
        List<TreeNode> nodes = new ArrayList<TreeNode>(usages.length);
        for (Usage usage : usages) {
            final UsageNode node = myUsageNodes.get(usage);
            if (node != NULL_NODE && node != null) {
                node.setUsageExcluded(true);
                nodes.add(node);
            }
        }
        updateImmediatelyNodesUpToRoot(nodes);
    }

    @Override
    public void selectUsages(@NotNull Usage[] usages) {
        List<TreePath> paths = new LinkedList<TreePath>();

        for (Usage usage : usages) {
            final UsageNode node = myUsageNodes.get(usage);

            if (node != NULL_NODE && node != null) {
                paths.add(new TreePath(node.getPath()));
            }
        }

        myTree.setSelectionPaths(paths.toArray(new TreePath[paths.size()]));
        if (!paths.isEmpty())
            myTree.scrollPathToVisible(paths.get(0));
    }

    @Override
    @NotNull
    public JComponent getComponent() {
        return myRootPanel;
    }

    @Override
    public int getUsagesCount() {
        return myUsageNodes.size();
    }

    void setContent(@NotNull Content content) {
        myContent = content;
        content.setDisposer(this);
    }

    private void updateImmediately() {
        if (myProject.isDisposed())
            return;
        TreeNode root = (TreeNode) myTree.getModel().getRoot();
        checkNodeValidity(root, new TreePath(root));
        updateOnSelectionChanged();
    }

    private void updateImmediatelyNodesUpToRoot(@NotNull List<TreeNode> nodes) {
        if (myProject.isDisposed())
            return;
        TreeNode root = (TreeNode) myTree.getModel().getRoot();

        for (int i = 0; i < nodes.size(); i++) {
            TreeNode node = nodes.get(i);
            if (node instanceof Node) {
                ((Node) node).update(this);
                TreeNode parent = node.getParent();
                if (parent != root && parent != null) {
                    nodes.add(parent);
                }
            }
        }
        updateImmediately();
    }

    private void updateOnSelectionChanged() {
        List<UsageInfo> infos = getSelectedUsageInfos();
        if (myCurrentUsageContextPanel != null) {
            myCurrentUsageContextPanel.updateLayout(infos);
        }
    }

    private void checkNodeValidity(@NotNull TreeNode node, @NotNull TreePath path) {
        boolean shouldCheckChildren = true;
        if (myTree.isCollapsed(path)) {
            if (node instanceof Node) {
                ((Node) node).markNeedUpdate();
            }
            shouldCheckChildren = false;
            // optimization: do not call expensive update() on invisible node
        }
        UsageViewTreeCellRenderer.RowLocation isVisible = myUsageViewTreeCellRenderer.isRowVisible(
                myTree.getRowForPath(new TreePath(((DefaultMutableTreeNode) node).getPath())),
                myTree.getVisibleRect());

        // if row is below visible rectangle, no sense to update it or any children
        if (shouldCheckChildren && isVisible != UsageViewTreeCellRenderer.RowLocation.AFTER_VISIBLE_RECT) {
            for (int i = 0; i < node.getChildCount(); i++) {
                TreeNode child = node.getChildAt(i);
                checkNodeValidity(child, path.pathByAddingChild(child));
            }
        }

        // call update last, to let children a chance to update their cache first
        if (node instanceof Node && node != getModelRoot()
                && isVisible == UsageViewTreeCellRenderer.RowLocation.INSIDE_VISIBLE_RECT) {
            ((Node) node).update(this);
        }
    }

    private void updateLater() {
        myUpdateAlarm.cancelAllRequests();
        myUpdateAlarm.addRequest(new Runnable() {
            @Override
            public void run() {
                if (myProject.isDisposed())
                    return;
                PsiDocumentManagerBase documentManager = (PsiDocumentManagerBase) PsiDocumentManager
                        .getInstance(myProject);
                documentManager.cancelAndRunWhenAllCommitted("UpdateUsageView", new Runnable() {
                    @Override
                    public void run() {
                        updateImmediately();
                    }
                });
            }
        }, 300);
    }

    @Override
    public void close() {
        cancelCurrentSearch();
        UsageViewManager.getInstance(myProject).closeContent(myContent);
    }

    private void saveSplitterProportions() {
        UsageViewSettings.getInstance().PREVIEW_USAGES_SPLITTER_PROPORTIONS = myPreviewSplitter.getProportion();
    }

    @Override
    public void dispose() {
        disposeUsageContextPanels();
        synchronized (lock) {
            isDisposed = true;
            ToolTipManager.sharedInstance().unregisterComponent(myTree);
            myModelTracker.removeListener(this);
            myUpdateAlarm.cancelAllRequests();
        }
        disposeSmartPointers();
    }

    private void disposeSmartPointers() {
        SmartPointerManager pointerManager = SmartPointerManager.getInstance(getProject());
        for (Usage usage : myUsageNodes.keySet()) {
            if (usage instanceof UsageInfo2UsageAdapter) {
                SmartPsiElementPointer<?> pointer = ((UsageInfo2UsageAdapter) usage).getUsageInfo()
                        .getSmartPointer();
                if (pointer != null) {
                    pointerManager.removePointer(pointer);
                }
            }
        }
    }

    @Override
    public boolean isSearchInProgress() {
        return mySearchInProgress;
    }

    public void setSearchInProgress(boolean searchInProgress) {
        mySearchInProgress = searchInProgress;
        if (!myPresentation.isDetachedMode()) {
            myTransferToEDTQueue.offer(new Runnable() {
                @Override
                public void run() {
                    if (isDisposed)
                        return;
                    final UsageNode firstUsageNode = myModel.getFirstUsageNode();
                    if (firstUsageNode == null)
                        return;

                    Node node = getSelectedNode();
                    if (node != null
                            && !Comparing.equal(new TreePath(node.getPath()), TreeUtil.getFirstNodePath(myTree))) {
                        // user has selected node already
                        return;
                    }
                    showNode(firstUsageNode);
                    if (UsageViewSettings.getInstance().isExpanded()) {
                        expandAll();
                    }
                }
            });
        }
    }

    public boolean isDisposed() {
        return isDisposed;
    }

    private void showNode(@NotNull final UsageNode node) {
        if (!myPresentation.isDetachedMode()) {
            UIUtil.invokeLaterIfNeeded(new Runnable() {
                @Override
                public void run() {
                    if (isDisposed)
                        return;
                    TreePath usagePath = new TreePath(node.getPath());
                    myTree.expandPath(usagePath.getParentPath());
                    TreeUtil.selectPath(myTree, usagePath);
                }
            });
        }
    }

    @Override
    public void addButtonToLowerPane(@NotNull Runnable runnable, @NotNull String text) {
        int index = myButtonPanel.getComponentCount();
        if (!SystemInfo.isMac && index > 0 && myPresentation.isShowCancelButton())
            index--;
        myButtonPanel.addButtonRunnable(index, runnable, text);
    }

    @Override
    public void addButtonToLowerPane(@NotNull final Runnable runnable, @NotNull String text, char mnemonic) {
        // implemented method is deprecated, so, it just calls non-deprecated overloading one
        addButtonToLowerPane(runnable, text);
    }

    @Override
    public void addPerformOperationAction(@NotNull final Runnable processRunnable, final String commandName,
            final String cannotMakeString, @NotNull String shortDescription) {
        addPerformOperationAction(processRunnable, commandName, cannotMakeString, shortDescription, true);
    }

    @Override
    public void addPerformOperationAction(@NotNull Runnable processRunnable, String commandName,
            String cannotMakeString, @NotNull String shortDescription, boolean checkReadOnlyStatus) {
        addButtonToLowerPane(
                newPerformOperationRunnable(processRunnable, commandName, cannotMakeString, checkReadOnlyStatus),
                shortDescription);
    }

    public MyPerformOperationRunnable newPerformOperationRunnable(Runnable processRunnable, String commandName,
            String cannotMakeString, boolean checkReadOnlyStatus) {
        return new MyPerformOperationRunnable(cannotMakeString, processRunnable, commandName, checkReadOnlyStatus);
    }

    private boolean allTargetsAreValid() {
        for (UsageTarget target : myTargets) {
            if (!target.isValid()) {
                return false;
            }
        }

        return true;
    }

    @NotNull
    @Override
    public UsageViewPresentation getPresentation() {
        return myPresentation;
    }

    private boolean canPerformReRun() {
        return myUsageSearcherFactory != null;
    }

    private boolean checkReadonlyUsages() {
        final Set<VirtualFile> readOnlyUsages = getReadOnlyUsagesFiles();

        return readOnlyUsages.isEmpty() || !ReadonlyStatusHandler.getInstance(myProject)
                .ensureFilesWritable(VfsUtilCore.toVirtualFileArray(readOnlyUsages)).hasReadonlyFiles();
    }

    @NotNull
    private Set<Usage> getReadOnlyUsages() {
        final Set<Usage> result = new THashSet<Usage>();
        final Set<Map.Entry<Usage, UsageNode>> usages = myUsageNodes.entrySet();
        for (Map.Entry<Usage, UsageNode> entry : usages) {
            Usage usage = entry.getKey();
            UsageNode node = entry.getValue();
            if (node != null && node != NULL_NODE && !node.isExcluded() && usage.isReadOnly()) {
                result.add(usage);
            }
        }
        return result;
    }

    @NotNull
    private Set<VirtualFile> getReadOnlyUsagesFiles() {
        Set<Usage> usages = getReadOnlyUsages();
        Set<VirtualFile> result = new THashSet<VirtualFile>();
        for (Usage usage : usages) {
            if (usage instanceof UsageInFile) {
                UsageInFile usageInFile = (UsageInFile) usage;
                VirtualFile file = usageInFile.getFile();
                if (file != null)
                    result.add(file);
            }

            if (usage instanceof UsageInFiles) {
                UsageInFiles usageInFiles = (UsageInFiles) usage;
                ContainerUtil.addAll(result, usageInFiles.getFiles());
            }
        }
        for (UsageTarget target : myTargets) {
            VirtualFile[] files = target.getFiles();
            if (files == null)
                continue;
            ContainerUtil.addAll(result, files);
        }
        return result;
    }

    @Override
    @NotNull
    public Set<Usage> getExcludedUsages() {
        Set<Usage> result = new THashSet<Usage>();
        for (Map.Entry<Usage, UsageNode> entry : myUsageNodes.entrySet()) {
            UsageNode node = entry.getValue();
            Usage usage = entry.getKey();
            if (node == NULL_NODE || node == null) {
                continue;
            }
            if (node.isExcluded()) {
                result.add(usage);
            }
        }

        return result;
    }

    @Nullable
    private Node getSelectedNode() {
        TreePath leadSelectionPath = myTree.getLeadSelectionPath();
        if (leadSelectionPath == null)
            return null;

        DefaultMutableTreeNode node = (DefaultMutableTreeNode) leadSelectionPath.getLastPathComponent();
        return node instanceof Node ? (Node) node : null;
    }

    @Nullable
    private Node[] getSelectedNodes() {
        TreePath[] leadSelectionPath = myTree.getSelectionPaths();
        if (leadSelectionPath == null || leadSelectionPath.length == 0)
            return null;

        final List<Node> result = new ArrayList<Node>();
        for (TreePath comp : leadSelectionPath) {
            final Object lastPathComponent = comp.getLastPathComponent();
            if (lastPathComponent instanceof Node) {
                final Node node = (Node) lastPathComponent;
                result.add(node);
            }
        }
        return result.isEmpty() ? null : result.toArray(new Node[result.size()]);
    }

    @Override
    @Nullable
    public Set<Usage> getSelectedUsages() {
        TreePath[] selectionPaths = myTree.getSelectionPaths();
        if (selectionPaths == null) {
            return null;
        }

        Set<Usage> usages = new THashSet<Usage>();
        for (TreePath selectionPath : selectionPaths) {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) selectionPath.getLastPathComponent();
            collectUsages(node, usages);
        }

        return usages;
    }

    @Override
    @NotNull
    public Set<Usage> getUsages() {
        return myUsageNodes.keySet();
    }

    @Override
    @NotNull
    public List<Usage> getSortedUsages() {
        List<Usage> usages = new ArrayList<Usage>(getUsages());
        Collections.sort(usages, USAGE_COMPARATOR);
        return usages;
    }

    private static void collectUsages(@NotNull DefaultMutableTreeNode node, @NotNull Set<Usage> usages) {
        if (node instanceof UsageNode) {
            UsageNode usageNode = (UsageNode) node;
            final Usage usage = usageNode.getUsage();
            usages.add(usage);
        }

        Enumeration enumeration = node.children();
        while (enumeration.hasMoreElements()) {
            DefaultMutableTreeNode child = (DefaultMutableTreeNode) enumeration.nextElement();
            collectUsages(child, usages);
        }
    }

    @Nullable
    private UsageTarget[] getSelectedUsageTargets() {
        TreePath[] selectionPaths = myTree.getSelectionPaths();
        if (selectionPaths == null)
            return null;

        Set<UsageTarget> targets = new THashSet<UsageTarget>();
        for (TreePath selectionPath : selectionPaths) {
            Object lastPathComponent = selectionPath.getLastPathComponent();
            if (lastPathComponent instanceof UsageTargetNode) {
                UsageTargetNode usageTargetNode = (UsageTargetNode) lastPathComponent;
                UsageTarget target = usageTargetNode.getTarget();
                if (target.isValid()) {
                    targets.add(target);
                }
            }
        }

        return targets.isEmpty() ? null : targets.toArray(new UsageTarget[targets.size()]);
    }

    @Nullable
    private static Navigatable getNavigatableForNode(@NotNull DefaultMutableTreeNode node) {
        Object userObject = node.getUserObject();
        if (userObject instanceof Navigatable) {
            final Navigatable navigatable = (Navigatable) userObject;
            return navigatable.canNavigate() ? navigatable : null;
        }
        return null;
    }

    /* nodes with non-valid data are not included */
    private static Navigatable[] getNavigatablesForNodes(Node[] nodes) {
        if (nodes == null) {
            return null;
        }
        final List<Navigatable> result = new ArrayList<Navigatable>();
        for (final Node node : nodes) {
            /*
            if (!node.isDataValid()) {
              continue;
            }
            */
            Object userObject = node.getUserObject();
            if (userObject instanceof Navigatable) {
                result.add((Navigatable) userObject);
            }
        }
        return result.toArray(new Navigatable[result.size()]);
    }

    public boolean areTargetsValid() {
        return myModel.areTargetsValid();
    }

    private class MyPanel extends JPanel
            implements TypeSafeDataProvider, OccurenceNavigator, Disposable, CopyProvider {
        @Nullable
        private OccurenceNavigatorSupport mySupport;

        private MyPanel(@NotNull JTree tree) {
            mySupport = new OccurenceNavigatorSupport(tree) {
                @Override
                protected Navigatable createDescriptorForNode(DefaultMutableTreeNode node) {
                    if (node.getChildCount() > 0)
                        return null;
                    if (node instanceof Node && ((Node) node).isExcluded())
                        return null;
                    return getNavigatableForNode(node);
                }

                @Override
                public String getNextOccurenceActionName() {
                    return UsageViewBundle.message("action.next.occurrence");
                }

                @Override
                public String getPreviousOccurenceActionName() {
                    return UsageViewBundle.message("action.previous.occurrence");
                }
            };
        }

        @Override
        public void dispose() {
            mySupport = null;
        }

        @Override
        public boolean hasNextOccurence() {
            return mySupport != null && mySupport.hasNextOccurence();
        }

        @Override
        public boolean hasPreviousOccurence() {
            return mySupport != null && mySupport.hasPreviousOccurence();
        }

        @Override
        public OccurenceInfo goNextOccurence() {
            return mySupport != null ? mySupport.goNextOccurence() : null;
        }

        @Override
        public OccurenceInfo goPreviousOccurence() {
            return mySupport != null ? mySupport.goPreviousOccurence() : null;
        }

        @Override
        public String getNextOccurenceActionName() {
            return mySupport != null ? mySupport.getNextOccurenceActionName() : "";
        }

        @Override
        public String getPreviousOccurenceActionName() {
            return mySupport != null ? mySupport.getPreviousOccurenceActionName() : "";
        }

        @Override
        public void performCopy(@NotNull DataContext dataContext) {
            final Node selectedNode = getSelectedNode();
            assert selectedNode != null;
            final String plainText = selectedNode.getText(UsageViewImpl.this);
            CopyPasteManager.getInstance().setContents(new StringSelection(plainText.trim()));
        }

        @Override
        public boolean isCopyEnabled(@NotNull DataContext dataContext) {
            return getSelectedNode() != null;
        }

        @Override
        public boolean isCopyVisible(@NotNull DataContext dataContext) {
            return true;
        }

        @Override
        public void calcData(final DataKey key, final DataSink sink) {
            Node node = getSelectedNode();

            if (key == CommonDataKeys.PROJECT) {
                sink.put(CommonDataKeys.PROJECT, myProject);
            } else if (key == USAGE_VIEW_KEY) {
                sink.put(USAGE_VIEW_KEY, UsageViewImpl.this);
            }

            else if (key == CommonDataKeys.NAVIGATABLE_ARRAY) {
                sink.put(CommonDataKeys.NAVIGATABLE_ARRAY, getNavigatablesForNodes(getSelectedNodes()));
            }

            else if (key == PlatformDataKeys.EXPORTER_TO_TEXT_FILE) {
                sink.put(PlatformDataKeys.EXPORTER_TO_TEXT_FILE, myTextFileExporter);
            }

            else if (key == USAGES_KEY) {
                final Set<Usage> selectedUsages = getSelectedUsages();
                sink.put(USAGES_KEY,
                        selectedUsages != null ? selectedUsages.toArray(new Usage[selectedUsages.size()]) : null);
            }

            else if (key == USAGE_TARGETS_KEY) {
                sink.put(USAGE_TARGETS_KEY, getSelectedUsageTargets());
            }

            else if (key == CommonDataKeys.VIRTUAL_FILE_ARRAY) {
                final Set<Usage> usages = getSelectedUsages();
                Usage[] ua = usages != null ? usages.toArray(new Usage[usages.size()]) : null;
                VirtualFile[] data = UsageDataUtil.provideVirtualFileArray(ua, getSelectedUsageTargets());
                sink.put(CommonDataKeys.VIRTUAL_FILE_ARRAY, data);
            }

            else if (key == PlatformDataKeys.HELP_ID) {
                sink.put(PlatformDataKeys.HELP_ID, HELP_ID);
            } else if (key == PlatformDataKeys.COPY_PROVIDER) {
                sink.put(PlatformDataKeys.COPY_PROVIDER, this);
            } else if (node != null) {
                Object userObject = node.getUserObject();
                if (userObject instanceof TypeSafeDataProvider) {
                    ((TypeSafeDataProvider) userObject).calcData(key, sink);
                } else if (userObject instanceof DataProvider) {
                    DataProvider dataProvider = (DataProvider) userObject;
                    Object data = dataProvider.getData(key.getName());
                    if (data != null) {
                        sink.put(key, data);
                    }
                }
            }
        }
    }

    private static class MyAutoScrollToSourceOptionProvider implements AutoScrollToSourceOptionProvider {
        @Override
        public boolean isAutoScrollMode() {
            return UsageViewSettings.getInstance().IS_AUTOSCROLL_TO_SOURCE;
        }

        @Override
        public void setAutoScrollMode(boolean state) {
            UsageViewSettings.getInstance().IS_AUTOSCROLL_TO_SOURCE = state;
        }
    }

    private final class ButtonPanel extends JPanel {
        private ButtonPanel() {
            setLayout(new FlowLayout(FlowLayout.LEFT, 8, 0));
        }

        public void addButtonRunnable(int index, final Runnable runnable, String text) {
            if (getBorder() == null)
                setBorder(IdeBorderFactory.createBorder(SideBorder.TOP));

            final JButton button = new JButton(UIUtil.replaceMnemonicAmpersand(text));
            DialogUtil.registerMnemonic(button);

            button.setFocusable(false);
            button.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    runnable.run();
                }
            });

            add(button, index);

            invalidate();
            if (getParent() != null) {
                getParent().validate();
            }
        }

        void update() {
            for (int i = 0; i < getComponentCount(); ++i) {
                Component component = getComponent(i);
                if (component instanceof JButton) {
                    final JButton button = (JButton) component;
                    button.setEnabled(!isSearchInProgress());
                }
            }
        }
    }

    private class UsageState {
        private final Usage myUsage;
        private final boolean mySelected;

        private UsageState(@NotNull Usage usage, boolean isSelected) {
            myUsage = usage;
            mySelected = isSelected;
        }

        public void restore() {
            final UsageNode node = myUsageNodes.get(myUsage);
            if (node == NULL_NODE || node == null) {
                return;
            }
            final DefaultMutableTreeNode parentGroupingNode = (DefaultMutableTreeNode) node.getParent();
            if (parentGroupingNode != null) {
                final TreePath treePath = new TreePath(parentGroupingNode.getPath());
                myTree.expandPath(treePath);
                if (mySelected) {
                    myTree.addSelectionPath(treePath.pathByAddingChild(node));
                }
            }
        }
    }

    private class MyPerformOperationRunnable implements Runnable {
        private final String myCannotMakeString;
        private final Runnable myProcessRunnable;
        private final String myCommandName;
        private final boolean myCheckReadOnlyStatus;

        private MyPerformOperationRunnable(final String cannotMakeString, final Runnable processRunnable,
                final String commandName, boolean checkReadOnlyStatus) {
            myCannotMakeString = cannotMakeString;
            myProcessRunnable = processRunnable;
            myCommandName = commandName;
            myCheckReadOnlyStatus = checkReadOnlyStatus;
        }

        @Override
        public void run() {
            if (myCheckReadOnlyStatus && !checkReadonlyUsages())
                return;
            PsiDocumentManager.getInstance(myProject).commitAllDocuments();
            if (myCannotMakeString != null && myChangesDetected) {
                String title = UsageViewBundle.message("changes.detected.error.title");
                if (canPerformReRun() && allTargetsAreValid()) {
                    String[] options = { UsageViewBundle.message("action.description.rerun"),
                            UsageViewBundle.message("usage.view.cancel.button") };
                    String message = myCannotMakeString + "\n\n" + UsageViewBundle.message("dialog.rerun.search");
                    int answer = Messages.showOkCancelDialog(myProject, message, title, options[0], options[1],
                            Messages.getErrorIcon());
                    if (answer == Messages.OK) {
                        refreshUsages();
                    }
                } else {
                    Messages.showMessageDialog(myProject, myCannotMakeString, title, Messages.getErrorIcon());
                    //todo[myakovlev] request focus to tree
                    //myUsageView.getTree().requestFocus();
                }
                return;
            }

            close();

            CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
                @Override
                public void run() {
                    myProcessRunnable.run();
                }
            }, myCommandName, null);
        }
    }

    private List<UsageInfo> getSelectedUsageInfos() {
        return USAGE_INFO_LIST_KEY.getData(DataManager.getInstance().getDataContext(myRootPanel));
    }

    public GroupNode getRoot() {
        return myRoot;
    }

    public boolean isVisible(@NotNull Usage usage) {
        return myBuilder != null && myBuilder.isVisible(usage);
    }

    @NotNull
    public UsageTarget[] getTargets() {
        return myTargets;
    }
}