com.intellij.packageDependencies.ui.DependenciesPanel.java Source code

Java tutorial

Introduction

Here is the source code for com.intellij.packageDependencies.ui.DependenciesPanel.java

Source

/*
 * Copyright 2000-2013 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.packageDependencies.ui;

import com.intellij.CommonBundle;
import com.intellij.analysis.AnalysisScope;
import com.intellij.analysis.AnalysisScopeBundle;
import com.intellij.analysis.PerformAnalysisInBackgroundOption;
import com.intellij.codeInsight.hint.HintUtil;
import com.intellij.icons.AllIcons;
import com.intellij.ide.CommonActionsManager;
import com.intellij.ide.ExporterToTextFile;
import com.intellij.ide.actions.ContextHelpAction;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.ex.ComboBoxAction;
import com.intellij.openapi.components.PathMacroManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.Splitter;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.StatusBar;
import com.intellij.packageDependencies.*;
import com.intellij.packageDependencies.actions.AnalyzeDependenciesHandler;
import com.intellij.packageDependencies.actions.BackwardDependenciesHandler;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.search.scope.packageSet.NamedScope;
import com.intellij.psi.search.scope.packageSet.PackageSet;
import com.intellij.ui.*;
import com.intellij.ui.content.Content;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.usageView.UsageViewBundle;
import com.intellij.util.*;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.tree.TreeUtil;
import com.intellij.xml.util.XmlStringUtil;
import org.jdom.Document;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.mustbe.consulo.RequiredDispatchThread;

import javax.swing.*;
import javax.swing.event.ChangeListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.util.*;
import java.util.List;

public class DependenciesPanel extends JPanel implements Disposable, DataProvider {
    private final Map<PsiFile, Set<PsiFile>> myDependencies;
    private Map<PsiFile, Map<DependencyRule, Set<PsiFile>>> myIllegalDependencies;
    private final MyTree myLeftTree = new MyTree();
    private final MyTree myRightTree = new MyTree();
    private final DependenciesUsagesPanel myUsagesPanel;

    private static final HashSet<PsiFile> EMPTY_FILE_SET = new HashSet<PsiFile>(0);
    private final TreeExpansionMonitor myRightTreeExpansionMonitor;
    private final TreeExpansionMonitor myLeftTreeExpansionMonitor;

    private final Marker myRightTreeMarker;
    private final Marker myLeftTreeMarker;
    private Set<PsiFile> myIllegalsInRightTree = new HashSet<PsiFile>();

    private final Project myProject;
    private List<DependenciesBuilder> myBuilders;
    private final Set<PsiFile> myExcluded;
    private Content myContent;
    private final DependencyPanelSettings mySettings = new DependencyPanelSettings();
    private static final Logger LOG = Logger.getInstance("#" + DependenciesPanel.class.getName());

    private final boolean myForward;
    private final AnalysisScope myScopeOfInterest;
    private final int myTransitiveBorder;

    public DependenciesPanel(Project project, final DependenciesBuilder builder) {
        this(project, Collections.singletonList(builder), new HashSet<PsiFile>());
    }

    public DependenciesPanel(Project project, final List<DependenciesBuilder> builders,
            final Set<PsiFile> excluded) {
        super(new BorderLayout());
        myBuilders = builders;
        myExcluded = excluded;
        final DependenciesBuilder main = myBuilders.get(0);
        myForward = !main.isBackward();
        myScopeOfInterest = main.getScopeOfInterest();
        myTransitiveBorder = main.getTransitiveBorder();
        myDependencies = new HashMap<PsiFile, Set<PsiFile>>();
        myIllegalDependencies = new HashMap<PsiFile, Map<DependencyRule, Set<PsiFile>>>();
        for (DependenciesBuilder builder : builders) {
            myDependencies.putAll(builder.getDependencies());
            myIllegalDependencies.putAll(builder.getIllegalDependencies());
        }
        exclude(excluded);
        myProject = project;
        myUsagesPanel = new DependenciesUsagesPanel(myProject, myBuilders);
        Disposer.register(this, myUsagesPanel);

        final Splitter treeSplitter = new Splitter();
        Disposer.register(this, new Disposable() {
            @Override
            public void dispose() {
                treeSplitter.dispose();
            }
        });
        treeSplitter.setFirstComponent(ScrollPaneFactory.createScrollPane(myLeftTree));
        treeSplitter.setSecondComponent(ScrollPaneFactory.createScrollPane(myRightTree));

        final Splitter splitter = new Splitter(true);
        Disposer.register(this, new Disposable() {
            @Override
            public void dispose() {
                splitter.dispose();
            }
        });
        splitter.setFirstComponent(treeSplitter);
        splitter.setSecondComponent(myUsagesPanel);
        add(splitter, BorderLayout.CENTER);
        add(createToolbar(), BorderLayout.NORTH);

        myRightTreeExpansionMonitor = PackageTreeExpansionMonitor.install(myRightTree, myProject);
        myLeftTreeExpansionMonitor = PackageTreeExpansionMonitor.install(myLeftTree, myProject);

        myRightTreeMarker = new Marker() {
            @Override
            public boolean isMarked(VirtualFile file) {
                return myIllegalsInRightTree.contains(file);
            }
        };

        myLeftTreeMarker = new Marker() {
            @Override
            public boolean isMarked(VirtualFile file) {
                return myIllegalDependencies.containsKey(file);
            }
        };

        updateLeftTreeModel();
        updateRightTreeModel();

        myLeftTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
            @Override
            public void valueChanged(TreeSelectionEvent e) {
                updateRightTreeModel();
                final StringBuffer denyRules = new StringBuffer();
                final StringBuffer allowRules = new StringBuffer();
                final TreePath[] paths = myLeftTree.getSelectionPaths();
                if (paths == null) {
                    return;
                }
                for (TreePath path : paths) {
                    PackageDependenciesNode selectedNode = (PackageDependenciesNode) path.getLastPathComponent();
                    traverseToLeaves(selectedNode, denyRules, allowRules);
                }
                if (denyRules.length() + allowRules.length() > 0) {
                    StatusBar.Info.set(
                            AnalysisScopeBundle.message("status.bar.rule.violation.message",
                                    ((denyRules.length() == 0 || allowRules.length() == 0) ? 1 : 2),
                                    (denyRules.length() > 0
                                            ? denyRules.toString() + (allowRules.length() > 0 ? "; " : "")
                                            : " ") + (allowRules.length() > 0 ? allowRules.toString() : " ")),
                            myProject);
                } else {
                    StatusBar.Info.set(AnalysisScopeBundle.message("status.bar.no.rule.violation.message"),
                            myProject);
                }
            }
        });

        myRightTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
            @Override
            public void valueChanged(TreeSelectionEvent e) {
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        final Set<PsiFile> searchIn = getSelectedScope(myLeftTree);
                        final Set<PsiFile> searchFor = getSelectedScope(myRightTree);
                        if (searchIn.isEmpty() || searchFor.isEmpty()) {
                            myUsagesPanel.setToInitialPosition();
                            processDependencies(searchIn, searchFor, new Processor<List<PsiFile>>() { //todo do not show too many usages
                                @Override
                                public boolean process(final List<PsiFile> path) {
                                    searchFor.add(path.get(1));
                                    return true;
                                }
                            });
                        } else {
                            myUsagesPanel.findUsages(searchIn, searchFor);
                        }
                    }
                });
            }
        });

        initTree(myLeftTree, false);
        initTree(myRightTree, true);

        if (builders.size() == 1) {
            AnalysisScope scope = builders.get(0).getScope();
            if (scope.getScopeType() == AnalysisScope.FILE) {
                Set<PsiFile> oneFileSet = myDependencies.keySet();
                if (oneFileSet.size() == 1) {
                    selectElementInLeftTree(oneFileSet.iterator().next());
                    return;
                }
            }
        }
        TreeUtil.selectFirstNode(myLeftTree);
    }

    private void processDependencies(final Set<PsiFile> searchIn, final Set<PsiFile> searchFor,
            Processor<List<PsiFile>> processor) {
        if (myTransitiveBorder == 0)
            return;
        Set<PsiFile> initialSearchFor = new HashSet<PsiFile>(searchFor);
        for (DependenciesBuilder builder : myBuilders) {
            for (PsiFile from : searchIn) {
                for (PsiFile to : initialSearchFor) {
                    final List<List<PsiFile>> paths = builder.findPaths(from, to);
                    Collections.sort(paths, new Comparator<List<PsiFile>>() {
                        @Override
                        public int compare(final List<PsiFile> p1, final List<PsiFile> p2) {
                            return p1.size() - p2.size();
                        }
                    });
                    for (List<PsiFile> path : paths) {
                        if (!path.isEmpty()) {
                            path.add(0, from);
                            path.add(to);
                            if (!processor.process(path))
                                return;
                        }
                    }
                }
            }
        }
    }

    private void exclude(final Set<PsiFile> excluded) {
        for (PsiFile psiFile : excluded) {
            myDependencies.remove(psiFile);
            myIllegalDependencies.remove(psiFile);
        }
    }

    private void traverseToLeaves(final PackageDependenciesNode treeNode, final StringBuffer denyRules,
            final StringBuffer allowRules) {
        final Enumeration enumeration = treeNode.breadthFirstEnumeration();
        while (enumeration.hasMoreElements()) {
            PsiElement childPsiElement = ((PackageDependenciesNode) enumeration.nextElement()).getPsiElement();
            if (myIllegalDependencies.containsKey(childPsiElement)) {
                final Map<DependencyRule, Set<PsiFile>> illegalDeps = myIllegalDependencies.get(childPsiElement);
                for (final DependencyRule rule : illegalDeps.keySet()) {
                    if (rule.isDenyRule()) {
                        if (denyRules.indexOf(rule.getDisplayText()) == -1) {
                            denyRules.append(rule.getDisplayText());
                            denyRules.append("\n");
                        }
                    } else {
                        if (allowRules.indexOf(rule.getDisplayText()) == -1) {
                            allowRules.append(rule.getDisplayText());
                            allowRules.append("\n");
                        }
                    }
                }
            }
        }
    }

    private JComponent createToolbar() {
        DefaultActionGroup group = new DefaultActionGroup();
        group.add(new CloseAction());
        group.add(new RerunAction(this));
        group.add(new FlattenPackagesAction());
        group.add(new ShowFilesAction());
        if (ModuleManager.getInstance(myProject).getModules().length > 1) {
            group.add(new ShowModulesAction());
            group.add(new ShowModuleGroupsAction());
        }
        group.add(new GroupByScopeTypeAction());
        //group.add(new GroupByFilesAction());
        group.add(new FilterLegalsAction());
        group.add(new MarkAsIllegalAction());
        group.add(new ChooseScopeTypeAction());
        group.add(new EditDependencyRulesAction());
        group.add(CommonActionsManager.getInstance()
                .createExportToTextFileAction(new DependenciesExporterToTextFile()));
        group.add(new ContextHelpAction("dependency.viewer.tool.window"));

        ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, true);
        return toolbar.getComponent();
    }

    private void rebuild() {
        myIllegalDependencies = new HashMap<PsiFile, Map<DependencyRule, Set<PsiFile>>>();
        for (DependenciesBuilder builder : myBuilders) {
            myIllegalDependencies.putAll(builder.getIllegalDependencies());
        }
        updateLeftTreeModel();
        updateRightTreeModel();
    }

    private void initTree(final MyTree tree, boolean isRightTree) {
        tree.setCellRenderer(new MyTreeCellRenderer());
        tree.setRootVisible(false);
        tree.setShowsRootHandles(true);
        UIUtil.setLineStyleAngled(tree);

        TreeUtil.installActions(tree);
        SmartExpander.installOn(tree);
        EditSourceOnDoubleClickHandler.install(tree);
        new TreeSpeedSearch(tree);

        PopupHandler.installUnknownPopupHandler(tree, createTreePopupActions(isRightTree),
                ActionManager.getInstance());

    }

    private void updateRightTreeModel() {
        Set<PsiFile> deps = new HashSet<PsiFile>();
        Set<PsiFile> scope = getSelectedScope(myLeftTree);
        myIllegalsInRightTree = new HashSet<PsiFile>();
        for (PsiFile psiFile : scope) {
            Map<DependencyRule, Set<PsiFile>> illegalDeps = myIllegalDependencies.get(psiFile);
            if (illegalDeps != null) {
                for (final DependencyRule rule : illegalDeps.keySet()) {
                    myIllegalsInRightTree.addAll(illegalDeps.get(rule));
                }
            }
            final Set<PsiFile> psiFiles = myDependencies.get(psiFile);
            if (psiFiles != null) {
                for (PsiFile file : psiFiles) {
                    if (file != null && file.isValid()) {
                        deps.add(file);
                    }
                }
            }
        }
        deps.removeAll(scope);
        myRightTreeExpansionMonitor.freeze();
        myRightTree.setModel(buildTreeModel(deps, myRightTreeMarker));
        myRightTreeExpansionMonitor.restore();
        expandFirstLevel(myRightTree);
    }

    private ActionGroup createTreePopupActions(boolean isRightTree) {
        DefaultActionGroup group = new DefaultActionGroup();
        final ActionManager actionManager = ActionManager.getInstance();
        group.add(actionManager.getAction(IdeActions.ACTION_EDIT_SOURCE));
        group.add(actionManager.getAction(IdeActions.GROUP_VERSION_CONTROLS));

        if (isRightTree) {
            group.add(actionManager.getAction(IdeActions.GROUP_ANALYZE));
            group.add(new AddToScopeAction());
            group.add(new SelectInLeftTreeAction());
            group.add(new ShowDetailedInformationAction());
        } else {
            group.add(new RemoveFromScopeAction());
        }

        return group;
    }

    private TreeModel buildTreeModel(Set<PsiFile> deps, Marker marker) {
        return PatternDialectProvider.getInstance(mySettings.SCOPE_TYPE).createTreeModel(myProject, deps, marker,
                mySettings);
    }

    private void updateLeftTreeModel() {
        Set<PsiFile> psiFiles = myDependencies.keySet();
        myLeftTreeExpansionMonitor.freeze();
        myLeftTree.setModel(buildTreeModel(psiFiles, myLeftTreeMarker));
        myLeftTreeExpansionMonitor.restore();
        expandFirstLevel(myLeftTree);
    }

    private static void expandFirstLevel(Tree tree) {
        PackageDependenciesNode root = (PackageDependenciesNode) tree.getModel().getRoot();
        int count = root.getChildCount();
        if (count < 10) {
            for (int i = 0; i < count; i++) {
                PackageDependenciesNode child = (PackageDependenciesNode) root.getChildAt(i);
                expandNodeIfNotTooWide(tree, child);
            }
        }
    }

    private static void expandNodeIfNotTooWide(Tree tree, PackageDependenciesNode node) {
        int count = node.getChildCount();
        if (count > 5)
            return;
        //another level of nesting
        if (count == 1 && node.getChildAt(0).getChildCount() > 5) {
            return;
        }
        tree.expandPath(new TreePath(node.getPath()));
    }

    private Set<PsiFile> getSelectedScope(final Tree tree) {
        TreePath[] paths = tree.getSelectionPaths();
        if (paths == null)
            return EMPTY_FILE_SET;
        Set<PsiFile> result = new HashSet<PsiFile>();
        for (TreePath path : paths) {
            PackageDependenciesNode node = (PackageDependenciesNode) path.getLastPathComponent();
            node.fillFiles(result, !mySettings.UI_FLATTEN_PACKAGES);
        }
        return result;
    }

    public void setContent(Content content) {
        myContent = content;
    }

    public JTree getLeftTree() {
        return myLeftTree;
    }

    public JTree getRightTree() {
        return myRightTree;
    }

    @Override
    public void dispose() {
        FileTreeModelBuilder.clearCaches(myProject);
    }

    @Override
    @Nullable
    @NonNls
    public Object getData(@NonNls String dataId) {
        if (LangDataKeys.PSI_ELEMENT.is(dataId)) {
            final PackageDependenciesNode selectedNode = myRightTree.getSelectedNode();
            if (selectedNode != null) {
                final PsiElement element = selectedNode.getPsiElement();
                return element != null && element.isValid() ? element : null;
            }
        }
        if (PlatformDataKeys.HELP_ID.is(dataId)) {
            return "dependency.viewer.tool.window";
        }
        return null;
    }

    private static class MyTreeCellRenderer extends ColoredTreeCellRenderer {
        @Override
        public void customizeCellRenderer(JTree tree, Object value, boolean selected, boolean expanded,
                boolean leaf, int row, boolean hasFocus) {
            PackageDependenciesNode node = (PackageDependenciesNode) value;
            if (node.isValid()) {
                setIcon(node.getIcon());
            } else {
                append(UsageViewBundle.message("node.invalid") + " ", SimpleTextAttributes.ERROR_ATTRIBUTES);
            }
            append(node.toString(), node.hasMarked() && !selected ? SimpleTextAttributes.ERROR_ATTRIBUTES
                    : SimpleTextAttributes.REGULAR_ATTRIBUTES);
            append(node.getPresentableFilesCount(), SimpleTextAttributes.GRAYED_ATTRIBUTES);
        }
    }

    private final class CloseAction extends AnAction implements DumbAware {
        public CloseAction() {
            super(CommonBundle.message("action.close"),
                    AnalysisScopeBundle.message("action.close.dependency.description"), AllIcons.Actions.Cancel);
        }

        @Override
        public void actionPerformed(AnActionEvent e) {
            Disposer.dispose(myUsagesPanel);
            DependenciesToolWindow.getInstance(myProject).closeContent(myContent);
            mySettings.copyToApplicationDependencySettings();
        }
    }

    private final class FlattenPackagesAction extends ToggleAction {
        FlattenPackagesAction() {
            super(AnalysisScopeBundle.message("action.flatten.packages"),
                    AnalysisScopeBundle.message("action.flatten.packages"), AllIcons.ObjectBrowser.FlattenPackages);
        }

        @Override
        public boolean isSelected(AnActionEvent event) {
            return mySettings.UI_FLATTEN_PACKAGES;
        }

        @Override
        public void setSelected(AnActionEvent event, boolean flag) {
            DependencyUISettings.getInstance().UI_FLATTEN_PACKAGES = flag;
            mySettings.UI_FLATTEN_PACKAGES = flag;
            rebuild();
        }
    }

    private final class ShowFilesAction extends ToggleAction {
        ShowFilesAction() {
            super(AnalysisScopeBundle.message("action.show.files"),
                    AnalysisScopeBundle.message("action.show.files.description"), AllIcons.FileTypes.Unknown);
        }

        @Override
        public boolean isSelected(AnActionEvent event) {
            return mySettings.UI_SHOW_FILES;
        }

        @Override
        public void setSelected(AnActionEvent event, boolean flag) {
            DependencyUISettings.getInstance().UI_SHOW_FILES = flag;
            mySettings.UI_SHOW_FILES = flag;
            if (!flag && myLeftTree.getSelectionPath() != null
                    && myLeftTree.getSelectionPath().getLastPathComponent() instanceof FileNode) {
                TreeUtil.selectPath(myLeftTree, myLeftTree.getSelectionPath().getParentPath());
            }
            rebuild();
        }
    }

    /* private final class GroupByFilesAction extends ToggleAction {
       private GroupByFilesAction() {
         super(IdeBundle.message("action.show.file.structure"),
        IdeBundle.message("action.description.show.file.structure"), IconLoader.getIcon("/objectBrowser/showGlobalInspections.png"));
       }
        
       public boolean isSelected(final AnActionEvent e) {
         return mySettings.SCOPE_TYPE;
       }
        
       public void setSelected(final AnActionEvent e, final boolean state) {
         mySettings.SCOPE_TYPE = state;
         mySettings.copyToApplicationDependencySettings();
         rebuild();
       }
     }*/

    private final class ShowModulesAction extends ToggleAction {
        ShowModulesAction() {
            super(AnalysisScopeBundle.message("action.show.modules"),
                    AnalysisScopeBundle.message("action.show.modules.description"), AllIcons.Actions.GroupByModule);
        }

        @Override
        public boolean isSelected(AnActionEvent event) {
            return mySettings.UI_SHOW_MODULES;
        }

        @Override
        public void setSelected(AnActionEvent event, boolean flag) {
            DependencyUISettings.getInstance().UI_SHOW_MODULES = flag;
            mySettings.UI_SHOW_MODULES = flag;
            rebuild();
        }
    }

    private final class ShowModuleGroupsAction extends ToggleAction {
        ShowModuleGroupsAction() {
            super("Show module groups", "Show module groups", AllIcons.Actions.GroupByModuleGroup);
        }

        @Override
        public boolean isSelected(AnActionEvent event) {
            return mySettings.UI_SHOW_MODULE_GROUPS;
        }

        @Override
        public void setSelected(AnActionEvent event, boolean flag) {
            DependencyUISettings.getInstance().UI_SHOW_MODULE_GROUPS = flag;
            mySettings.UI_SHOW_MODULE_GROUPS = flag;
            rebuild();
        }

        @Override
        public void update(final AnActionEvent e) {
            super.update(e);
            e.getPresentation().setEnabled(mySettings.UI_SHOW_MODULES);
        }
    }

    private final class GroupByScopeTypeAction extends ToggleAction {
        GroupByScopeTypeAction() {
            super(AnalysisScopeBundle.message("action.group.by.scope.type"),
                    AnalysisScopeBundle.message("action.group.by.scope.type.description"),
                    AllIcons.Actions.GroupByTestProduction);
        }

        @Override
        public boolean isSelected(AnActionEvent event) {
            return mySettings.UI_GROUP_BY_SCOPE_TYPE;
        }

        @Override
        public void setSelected(AnActionEvent event, boolean flag) {
            DependencyUISettings.getInstance().UI_GROUP_BY_SCOPE_TYPE = flag;
            mySettings.UI_GROUP_BY_SCOPE_TYPE = flag;
            rebuild();
        }

        @Override
        public void update(final AnActionEvent e) {
            super.update(e);
        }
    }

    private final class FilterLegalsAction extends ToggleAction {
        FilterLegalsAction() {
            super(AnalysisScopeBundle.message("action.show.illegals.only"),
                    AnalysisScopeBundle.message("action.show.illegals.only.description"), AllIcons.General.Filter);
        }

        @Override
        public boolean isSelected(AnActionEvent event) {
            return mySettings.UI_FILTER_LEGALS;
        }

        @Override
        public void setSelected(AnActionEvent event, boolean flag) {
            DependencyUISettings.getInstance().UI_FILTER_LEGALS = flag;
            mySettings.UI_FILTER_LEGALS = flag;
            rebuild();
        }
    }

    private final class EditDependencyRulesAction extends AnAction {
        public EditDependencyRulesAction() {
            super(AnalysisScopeBundle.message("action.edit.rules"),
                    AnalysisScopeBundle.message("action.edit.rules.description"), AllIcons.General.Settings);
        }

        @Override
        public void actionPerformed(AnActionEvent e) {
            boolean applied = ShowSettingsUtil.getInstance().editConfigurable(DependenciesPanel.this,
                    new DependencyConfigurable(myProject));
            if (applied) {
                rebuild();
            }
        }
    }

    private class DependenciesExporterToTextFile implements ExporterToTextFile {

        @Override
        public JComponent getSettingsEditor() {
            return null;
        }

        @Override
        public void addSettingsChangedListener(ChangeListener listener) throws TooManyListenersException {
        }

        @Override
        public void removeSettingsChangedListener(ChangeListener listener) {
        }

        @Override
        public String getReportText() {
            final Element rootElement = new Element("root");
            rootElement.setAttribute("isBackward", String.valueOf(!myForward));
            final List<PsiFile> files = new ArrayList<PsiFile>(myDependencies.keySet());
            Collections.sort(files, new Comparator<PsiFile>() {
                @Override
                public int compare(PsiFile f1, PsiFile f2) {
                    final VirtualFile virtualFile1 = f1.getVirtualFile();
                    final VirtualFile virtualFile2 = f2.getVirtualFile();
                    if (virtualFile1 != null && virtualFile2 != null) {
                        return virtualFile1.getPath().compareToIgnoreCase(virtualFile2.getPath());
                    }
                    return 0;
                }
            });
            for (PsiFile file : files) {
                final Element fileElement = new Element("file");
                fileElement.setAttribute("path", file.getVirtualFile().getPath());
                for (PsiFile dep : myDependencies.get(file)) {
                    Element depElement = new Element("dependency");
                    depElement.setAttribute("path", dep.getVirtualFile().getPath());
                    fileElement.addContent(depElement);
                }
                rootElement.addContent(fileElement);
            }
            PathMacroManager.getInstance(myProject).collapsePaths(rootElement);
            return JDOMUtil.writeDocument(new Document(rootElement), SystemProperties.getLineSeparator());
        }

        @Override
        public String getDefaultFilePath() {
            return "";
        }

        @Override
        public void exportedTo(String filePath) {
        }

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

    private class RerunAction extends AnAction {
        public RerunAction(JComponent comp) {
            super(CommonBundle.message("action.rerun"), AnalysisScopeBundle.message("action.rerun.dependency"),
                    AllIcons.Actions.Rerun);
            registerCustomShortcutSet(CommonShortcuts.getRerun(), comp);
        }

        @Override
        public void update(AnActionEvent e) {
            boolean enabled = true;
            for (DependenciesBuilder builder : myBuilders) {
                enabled &= builder.getScope().isValid();
            }
            e.getPresentation().setEnabled(enabled);
        }

        @Override
        public void actionPerformed(AnActionEvent e) {
            DependenciesToolWindow.getInstance(myProject).closeContent(myContent);
            mySettings.copyToApplicationDependencySettings();
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    final List<AnalysisScope> scopes = new ArrayList<AnalysisScope>();
                    for (DependenciesBuilder builder : myBuilders) {
                        final AnalysisScope scope = builder.getScope();
                        scope.invalidate();
                        scopes.add(scope);
                    }
                    if (!myForward) {
                        new BackwardDependenciesHandler(myProject, scopes, myScopeOfInterest, myExcluded).analyze();
                    } else {
                        new AnalyzeDependenciesHandler(myProject, scopes, myTransitiveBorder, myExcluded).analyze();
                    }
                }
            });
        }
    }

    private static class MyTree extends Tree implements DataProvider {
        @Override
        public Object getData(String dataId) {
            PackageDependenciesNode node = getSelectedNode();
            if (PlatformDataKeys.NAVIGATABLE.is(dataId)) {
                return node;
            }
            if (LangDataKeys.PSI_ELEMENT.is(dataId) && node != null) {
                final PsiElement element = node.getPsiElement();
                return element != null && element.isValid() ? element : null;
            }
            return null;
        }

        @Nullable
        public PackageDependenciesNode getSelectedNode() {
            TreePath[] paths = getSelectionPaths();
            if (paths == null || paths.length != 1)
                return null;
            return (PackageDependenciesNode) paths[0].getLastPathComponent();
        }
    }

    private class ShowDetailedInformationAction extends AnAction {
        private ShowDetailedInformationAction() {
            super("Show indirect dependencies");
        }

        @Override
        public void actionPerformed(final AnActionEvent e) {
            @NonNls
            final String delim = "&nbsp;-&gt;&nbsp;";
            final StringBuffer buf = new StringBuffer();
            processDependencies(getSelectedScope(myLeftTree), getSelectedScope(myRightTree),
                    new Processor<List<PsiFile>>() {
                        @Override
                        public boolean process(final List<PsiFile> path) {
                            if (buf.length() > 0)
                                buf.append("<br>");
                            buf.append(StringUtil.join(path, new Function<PsiFile, String>() {
                                @Override
                                public String fun(final PsiFile psiFile) {
                                    return psiFile.getName();
                                }
                            }, delim));
                            return true;
                        }
                    });
            final JEditorPane pane = new JEditorPane(UIUtil.HTML_MIME, XmlStringUtil.wrapInHtml(buf));
            pane.setForeground(JBColor.foreground());
            pane.setBackground(HintUtil.INFORMATION_COLOR);
            pane.setOpaque(true);
            final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(pane);
            final Dimension dimension = pane.getPreferredSize();
            scrollPane.setMinimumSize(new Dimension(dimension.width, dimension.height + 20));
            scrollPane.setPreferredSize(new Dimension(dimension.width, dimension.height + 20));
            JBPopupFactory.getInstance().createComponentPopupBuilder(scrollPane, pane).setTitle("Dependencies")
                    .setMovable(true).createPopup().showInBestPositionFor(e.getDataContext());
        }

        @Override
        public void update(final AnActionEvent e) {
            final boolean[] direct = new boolean[] { true };
            processDependencies(getSelectedScope(myLeftTree), getSelectedScope(myRightTree),
                    new Processor<List<PsiFile>>() {
                        @Override
                        public boolean process(final List<PsiFile> path) {
                            direct[0] = false;
                            return false;
                        }
                    });
            e.getPresentation().setEnabled(!direct[0]);
        }
    }

    private class RemoveFromScopeAction extends AnAction {
        private RemoveFromScopeAction() {
            super("Remove from scope");
        }

        @Override
        public void update(final AnActionEvent e) {
            super.update(e);
            e.getPresentation().setEnabled(!getSelectedScope(myLeftTree).isEmpty());
        }

        @Override
        public void actionPerformed(final AnActionEvent e) {
            final Set<PsiFile> selectedScope = getSelectedScope(myLeftTree);
            exclude(selectedScope);
            myExcluded.addAll(selectedScope);
            final TreePath[] paths = myLeftTree.getSelectionPaths();
            for (TreePath path : paths) {
                TreeUtil.removeLastPathComponent(myLeftTree, path);
            }
        }
    }

    private class AddToScopeAction extends AnAction {
        private AddToScopeAction() {
            super("Add to scope");
        }

        @Override
        public void update(final AnActionEvent e) {
            super.update(e);
            e.getPresentation().setEnabled(getScope() != null);
        }

        @Override
        public void actionPerformed(final AnActionEvent e) {
            final AnalysisScope scope = getScope();
            LOG.assertTrue(scope != null);
            final DependenciesBuilder builder;
            if (!myForward) {
                builder = new BackwardDependenciesBuilder(myProject, scope, myScopeOfInterest);
            } else {
                builder = new ForwardDependenciesBuilder(myProject, scope, myTransitiveBorder);
            }
            ProgressManager.getInstance().runProcessWithProgressAsynchronously(myProject,
                    AnalysisScopeBundle.message("package.dependencies.progress.title"), new Runnable() {
                        @Override
                        public void run() {
                            builder.analyze();
                        }
                    }, new Runnable() {
                        @Override
                        public void run() {
                            myBuilders.add(builder);
                            myDependencies.putAll(builder.getDependencies());
                            myIllegalDependencies.putAll(builder.getIllegalDependencies());
                            exclude(myExcluded);
                            rebuild();
                        }
                    }, null, new PerformAnalysisInBackgroundOption(myProject));
        }

        @Nullable
        private AnalysisScope getScope() {
            final Set<PsiFile> selectedScope = getSelectedScope(myRightTree);
            Set<PsiFile> result = new HashSet<PsiFile>();
            ((PackageDependenciesNode) myLeftTree.getModel().getRoot()).fillFiles(result,
                    !mySettings.UI_FLATTEN_PACKAGES);
            selectedScope.removeAll(result);
            if (selectedScope.isEmpty())
                return null;
            List<VirtualFile> files = new ArrayList<VirtualFile>();
            final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
            for (PsiFile psiFile : selectedScope) {
                final VirtualFile file = psiFile.getVirtualFile();
                LOG.assertTrue(file != null);
                if (fileIndex.isInContent(file)) {
                    files.add(file);
                }
            }
            if (!files.isEmpty()) {
                return new AnalysisScope(myProject, files);
            }
            return null;
        }
    }

    private class SelectInLeftTreeAction extends AnAction {
        public SelectInLeftTreeAction() {
            super(AnalysisScopeBundle.message("action.select.in.left.tree"),
                    AnalysisScopeBundle.message("action.select.in.left.tree.description"), null);
        }

        @Override
        public void update(AnActionEvent e) {
            PackageDependenciesNode node = myRightTree.getSelectedNode();
            e.getPresentation().setEnabled(node != null && node.canSelectInLeftTree(myDependencies));
        }

        @Override
        public void actionPerformed(AnActionEvent e) {
            PackageDependenciesNode node = myRightTree.getSelectedNode();
            if (node != null) {
                PsiElement elt = node.getPsiElement();
                if (elt != null) {
                    DependencyUISettings.getInstance().UI_FILTER_LEGALS = false;
                    mySettings.UI_FILTER_LEGALS = false;
                    selectElementInLeftTree(elt);

                }
            }
        }
    }

    private void selectElementInLeftTree(PsiElement elt) {
        PsiManager manager = PsiManager.getInstance(myProject);

        PackageDependenciesNode root = (PackageDependenciesNode) myLeftTree.getModel().getRoot();
        Enumeration enumeration = root.breadthFirstEnumeration();
        while (enumeration.hasMoreElements()) {
            PackageDependenciesNode child = (PackageDependenciesNode) enumeration.nextElement();
            if (manager.areElementsEquivalent(child.getPsiElement(), elt)) {
                myLeftTree.setSelectionPath(
                        new TreePath(((DefaultTreeModel) myLeftTree.getModel()).getPathToRoot(child)));
                break;
            }
        }
    }

    private class MarkAsIllegalAction extends AnAction {
        public MarkAsIllegalAction() {
            super(AnalysisScopeBundle.message("mark.dependency.illegal.text"),
                    AnalysisScopeBundle.message("mark.dependency.illegal.text"), AllIcons.Actions.Lightning);
        }

        @Override
        public void actionPerformed(AnActionEvent e) {
            final PackageDependenciesNode leftNode = myLeftTree.getSelectedNode();
            final PackageDependenciesNode rightNode = myRightTree.getSelectedNode();
            if (leftNode != null && rightNode != null) {
                boolean hasDirectDependencies = myTransitiveBorder == 0;
                if (myTransitiveBorder > 0) {
                    final Set<PsiFile> searchIn = getSelectedScope(myLeftTree);
                    final Set<PsiFile> searchFor = getSelectedScope(myRightTree);
                    for (DependenciesBuilder builder : myBuilders) {
                        if (hasDirectDependencies)
                            break;
                        for (PsiFile from : searchIn) {
                            if (hasDirectDependencies)
                                break;
                            for (PsiFile to : searchFor) {
                                if (hasDirectDependencies)
                                    break;
                                final List<List<PsiFile>> paths = builder.findPaths(from, to);
                                for (List<PsiFile> path : paths) {
                                    if (path.isEmpty()) {
                                        hasDirectDependencies = true;
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
                final PatternDialectProvider provider = PatternDialectProvider.getInstance(mySettings.SCOPE_TYPE);
                PackageSet leftPackageSet = provider.createPackageSet(leftNode, true);
                if (leftPackageSet == null) {
                    leftPackageSet = provider.createPackageSet(leftNode, false);
                }
                LOG.assertTrue(leftPackageSet != null);
                PackageSet rightPackageSet = provider.createPackageSet(rightNode, true);
                if (rightPackageSet == null) {
                    rightPackageSet = provider.createPackageSet(rightNode, false);
                }
                LOG.assertTrue(rightPackageSet != null);
                if (hasDirectDependencies) {
                    DependencyValidationManager.getInstance(myProject)
                            .addRule(new DependencyRule(new NamedScope.UnnamedScope(leftPackageSet),
                                    new NamedScope.UnnamedScope(rightPackageSet), true));
                    rebuild();
                } else {
                    Messages.showErrorDialog(DependenciesPanel.this,
                            "Rule was not added.\n There is no direct dependency between \'"
                                    + leftPackageSet.getText() + "\' and \'" + rightPackageSet.getText() + "\'",
                            AnalysisScopeBundle.message("mark.dependency.illegal.text"));
                }
            }
        }

        @Override
        public void update(final AnActionEvent e) {
            final Presentation presentation = e.getPresentation();
            presentation.setEnabled(false);
            final PackageDependenciesNode leftNode = myLeftTree.getSelectedNode();
            final PackageDependenciesNode rightNode = myRightTree.getSelectedNode();
            if (leftNode != null && rightNode != null) {
                final PatternDialectProvider provider = PatternDialectProvider.getInstance(mySettings.SCOPE_TYPE);
                presentation.setEnabled((provider.createPackageSet(leftNode, true) != null
                        || provider.createPackageSet(leftNode, false) != null)
                        && (provider.createPackageSet(rightNode, true) != null
                                || provider.createPackageSet(rightNode, false) != null));
            }
        }
    }

    private final class ChooseScopeTypeAction extends ComboBoxAction {
        @Override
        @NotNull
        protected DefaultActionGroup createPopupActionGroup(final JComponent button) {
            final DefaultActionGroup group = new DefaultActionGroup();
            for (final PatternDialectProvider provider : Extensions.getExtensions(PatternDialectProvider.EP_NAME)) {
                group.add(new AnAction(provider.getDisplayName()) {
                    @Override
                    public void actionPerformed(final AnActionEvent e) {
                        mySettings.SCOPE_TYPE = provider.getShortName();
                        DependencyUISettings.getInstance().SCOPE_TYPE = provider.getShortName();
                        rebuild();
                    }
                });
            }
            return group;
        }

        @RequiredDispatchThread
        @Override
        public void update(final AnActionEvent e) {
            super.update(e);
            final PatternDialectProvider provider = PatternDialectProvider.getInstance(mySettings.SCOPE_TYPE);
            e.getPresentation().setText(provider.getDisplayName());
            e.getPresentation().setIcon(provider.getIcon());
        }
    }

    public static class DependencyPanelSettings {
        public boolean UI_FLATTEN_PACKAGES = true;
        public boolean UI_SHOW_FILES = false;
        public boolean UI_SHOW_MODULES = true;
        public boolean UI_SHOW_MODULE_GROUPS = true;
        public boolean UI_FILTER_LEGALS = false;
        public boolean UI_GROUP_BY_SCOPE_TYPE = true;
        public String SCOPE_TYPE;
        public boolean UI_COMPACT_EMPTY_MIDDLE_PACKAGES = true;
        public boolean UI_FILTER_OUT_OF_CYCLE_PACKAGES = true;

        public DependencyPanelSettings() {
            final DependencyUISettings settings = DependencyUISettings.getInstance();
            UI_FLATTEN_PACKAGES = settings.UI_FLATTEN_PACKAGES;
            UI_SHOW_FILES = settings.UI_SHOW_FILES;
            UI_SHOW_MODULES = settings.UI_SHOW_MODULES;
            UI_SHOW_MODULE_GROUPS = settings.UI_SHOW_MODULE_GROUPS;
            UI_FILTER_LEGALS = settings.UI_FILTER_LEGALS;
            UI_GROUP_BY_SCOPE_TYPE = settings.UI_GROUP_BY_SCOPE_TYPE;
            SCOPE_TYPE = settings.SCOPE_TYPE;
            UI_COMPACT_EMPTY_MIDDLE_PACKAGES = settings.UI_COMPACT_EMPTY_MIDDLE_PACKAGES;
            UI_FILTER_OUT_OF_CYCLE_PACKAGES = settings.UI_FILTER_OUT_OF_CYCLE_PACKAGES;
        }

        public void copyToApplicationDependencySettings() {
            final DependencyUISettings settings = DependencyUISettings.getInstance();
            settings.UI_FLATTEN_PACKAGES = UI_FLATTEN_PACKAGES;
            settings.UI_SHOW_FILES = UI_SHOW_FILES;
            settings.UI_SHOW_MODULES = UI_SHOW_MODULES;
            settings.UI_SHOW_MODULE_GROUPS = UI_SHOW_MODULE_GROUPS;
            settings.UI_FILTER_LEGALS = UI_FILTER_LEGALS;
            settings.UI_GROUP_BY_SCOPE_TYPE = UI_GROUP_BY_SCOPE_TYPE;
            settings.SCOPE_TYPE = SCOPE_TYPE;
            settings.UI_COMPACT_EMPTY_MIDDLE_PACKAGES = UI_COMPACT_EMPTY_MIDDLE_PACKAGES;
            settings.UI_FILTER_OUT_OF_CYCLE_PACKAGES = UI_FILTER_OUT_OF_CYCLE_PACKAGES;
        }
    }
}