com.intellij.moduleDependencies.ModulesDependenciesPanel.java Source code

Java tutorial

Introduction

Here is the source code for com.intellij.moduleDependencies.ModulesDependenciesPanel.java

Source

/*
 * Copyright 2000-2009 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.moduleDependencies;

import com.intellij.CommonBundle;
import com.intellij.ProjectTopics;
import com.intellij.analysis.AnalysisScopeBundle;
import com.intellij.icons.AllIcons;
import com.intellij.ide.CommonActionsManager;
import com.intellij.ide.TreeExpander;
import com.intellij.ide.actions.ContextHelpAction;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ModuleRootEvent;
import com.intellij.openapi.roots.ModuleRootListener;
import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService;
import com.intellij.openapi.ui.Splitter;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.pom.NavigatableWithText;
import com.intellij.ui.*;
import com.intellij.ui.content.Content;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.util.Function;
import com.intellij.util.containers.Convertor;
import com.intellij.util.containers.HashMap;
import com.intellij.util.graph.DFSTBuilder;
import com.intellij.util.graph.Graph;
import com.intellij.util.graph.GraphAlgorithms;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.tree.TreeUtil;
import org.jetbrains.annotations.NonNls;

import javax.swing.*;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import java.awt.*;
import java.util.*;
import java.util.List;

/**
 * User: anna
 * Date: Feb 10, 2005
 */
public class ModulesDependenciesPanel extends JPanel implements ModuleRootListener, Disposable {
    @NonNls
    private static final String DIRECTION = "FORWARD_ANALIZER";
    private Content myContent;
    private final Project myProject;
    private Tree myLeftTree;
    private DefaultTreeModel myLeftTreeModel;

    private final Tree myRightTree;
    private final DefaultTreeModel myRightTreeModel;

    private Graph<Module> myModulesGraph;
    private final Module[] myModules;

    private JTextField myPathField = new JTextField();

    private final Splitter mySplitter;
    @NonNls
    private static final String ourHelpID = "module.dependencies.tool.window";

    public ModulesDependenciesPanel(final Project project, final Module[] modules) {
        super(new BorderLayout());
        myProject = project;
        myModules = modules;

        //noinspection HardCodedStringLiteral
        myRightTreeModel = new DefaultTreeModel(new DefaultMutableTreeNode("Root"));
        myRightTree = new Tree(myRightTreeModel);
        initTree(myRightTree, true);

        initLeftTree();

        mySplitter = new Splitter();
        mySplitter.setFirstComponent(new MyTreePanel(myLeftTree, myProject));
        mySplitter.setSecondComponent(new MyTreePanel(myRightTree, myProject));

        setSplitterProportion();
        add(mySplitter, BorderLayout.CENTER);
        add(createNorthPanel(), BorderLayout.NORTH);

        project.getMessageBus().connect(this).subscribe(ProjectTopics.PROJECT_ROOTS, this);
    }

    private void setSplitterProportion() {
        if (mySplitter == null) {
            return;
        }
        myModulesGraph = buildGraph();
        DFSTBuilder<Module> builder = new DFSTBuilder<Module>(myModulesGraph);
        builder.buildDFST();
        if (builder.isAcyclic()) {
            mySplitter.setProportion(1.f);
        } else {
            mySplitter.setProportion(0.5f);
        }
    }

    @Override
    public void dispose() {
    }

    public ModulesDependenciesPanel(final Project project) {
        this(project, ModuleManager.getInstance(project).getModules());
    }

    private JComponent createNorthPanel() {
        DefaultActionGroup group = new DefaultActionGroup();

        group.add(new AnAction(CommonBundle.message("action.close"),
                AnalysisScopeBundle.message("action.close.modules.dependencies.description"),
                AllIcons.Actions.Cancel) {
            @Override
            public void actionPerformed(AnActionEvent e) {
                DependenciesAnalyzeManager.getInstance(myProject).closeContent(myContent);
            }
        });

        appendDependenciesAction(group);

        group.add(new ToggleAction(AnalysisScopeBundle.message("action.module.dependencies.direction"), "",
                isForwardDirection() ? AllIcons.Actions.SortAsc : AllIcons.Actions.SortDesc) {
            @Override
            public boolean isSelected(AnActionEvent e) {
                return isForwardDirection();
            }

            @Override
            public void setSelected(AnActionEvent e, boolean state) {
                PropertiesComponent.getInstance(myProject).setValue(DIRECTION, String.valueOf(state));
                initLeftTreeModel();
            }

            @Override
            public void update(final AnActionEvent e) {
                e.getPresentation()
                        .setIcon(isForwardDirection() ? AllIcons.Actions.SortAsc : AllIcons.Actions.SortDesc);
            }
        });

        group.add(new ContextHelpAction(ourHelpID));

        ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, true);
        final JPanel panel = new JPanel(new BorderLayout());
        panel.add(toolbar.getComponent(), BorderLayout.NORTH);
        panel.add(myPathField, BorderLayout.SOUTH);
        myPathField.setEditable(false);
        return panel;
    }

    private boolean isForwardDirection() {
        final String value = PropertiesComponent.getInstance(myProject).getValue(DIRECTION);
        return value == null || Boolean.parseBoolean(value);
    }

    private static void appendDependenciesAction(final DefaultActionGroup group) {
        final AnAction analyzeDepsAction = ActionManager.getInstance()
                .getAction(IdeActions.ACTION_ANALYZE_DEPENDENCIES);
        group.add(new AnAction(analyzeDepsAction.getTemplatePresentation().getText(),
                analyzeDepsAction.getTemplatePresentation().getDescription(),
                AllIcons.Toolwindows.ToolWindowInspection) {

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

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

    private void buildRightTree(Module module) {
        final DefaultMutableTreeNode root = (DefaultMutableTreeNode) myRightTreeModel.getRoot();
        root.removeAllChildren();
        final Set<List<Module>> cycles = GraphAlgorithms.getInstance().findCycles(myModulesGraph, module);
        int index = 1;
        for (List<Module> modules : cycles) {
            final DefaultMutableTreeNode cycle = new DefaultMutableTreeNode(AnalysisScopeBundle
                    .message("module.dependencies.cycle.node.text", Integer.toString(index++).toUpperCase()));
            root.add(cycle);
            cycle.add(new DefaultMutableTreeNode(new MyUserObject(false, module)));
            for (Module moduleInCycle : modules) {
                cycle.add(new DefaultMutableTreeNode(new MyUserObject(false, moduleInCycle)));
            }
        }
        ((DefaultTreeModel) myRightTree.getModel()).reload();
        TreeUtil.expandAll(myRightTree);
    }

    private void initLeftTreeModel() {
        final DefaultMutableTreeNode root = (DefaultMutableTreeNode) myLeftTreeModel.getRoot();
        root.removeAllChildren();
        myModulesGraph = buildGraph();
        setSplitterProportion();
        ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
            @Override
            public void run() {
                final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator();
                final Map<Module, Boolean> inCycle = new HashMap<Module, Boolean>();
                for (Module module : myModules) {
                    if (progressIndicator != null) {
                        if (progressIndicator.isCanceled())
                            return;
                        progressIndicator.setText(
                                AnalysisScopeBundle.message("update.module.tree.progress.text", module.getName()));
                    }
                    if (!module.isDisposed()) {
                        Boolean isInCycle = inCycle.get(module);
                        if (isInCycle == null) {
                            isInCycle = !GraphAlgorithms.getInstance().findCycles(myModulesGraph, module).isEmpty();
                            inCycle.put(module, isInCycle);
                        }
                        final DefaultMutableTreeNode moduleNode = new DefaultMutableTreeNode(
                                new MyUserObject(isInCycle.booleanValue(), module));
                        root.add(moduleNode);
                        final Iterator<Module> out = myModulesGraph.getOut(module);
                        while (out.hasNext()) {
                            moduleNode.add(new DefaultMutableTreeNode(new MyUserObject(false, out.next())));
                        }
                    }
                }
            }
        }, AnalysisScopeBundle.message("update.module.tree.progress.title"), true, myProject);
        sortSubTree(root);
        myLeftTreeModel.reload();
    }

    private static void sortSubTree(final DefaultMutableTreeNode root) {
        TreeUtil.sort(root, new Comparator() {
            @Override
            public int compare(final Object o1, final Object o2) {
                DefaultMutableTreeNode node1 = (DefaultMutableTreeNode) o1;
                DefaultMutableTreeNode node2 = (DefaultMutableTreeNode) o2;
                if (!(node1.getUserObject() instanceof MyUserObject)) {
                    return 1;
                } else if (!(node2.getUserObject() instanceof MyUserObject)) {
                    return -1;
                }
                return (node1.getUserObject().toString().compareToIgnoreCase(node2.getUserObject().toString()));
            }
        });
    }

    private void selectCycleUpward(final DefaultMutableTreeNode selection) {
        ArrayList<DefaultMutableTreeNode> selectionNodes = new ArrayList<DefaultMutableTreeNode>();
        selectionNodes.add(selection);
        DefaultMutableTreeNode current = (DefaultMutableTreeNode) selection.getParent();
        boolean flag = false;
        while (current != null && current.getUserObject() != null) {
            if (current.getUserObject().equals(selection.getUserObject())) {
                flag = true;
                selectionNodes.add(current);
                break;
            }
            selectionNodes.add(current);
            current = (DefaultMutableTreeNode) current.getParent();
        }
        if (flag) {
            for (DefaultMutableTreeNode node : selectionNodes) {
                ((MyUserObject) node.getUserObject()).setInCycle(true);
            }
        }
        if (current != null)
            current = (DefaultMutableTreeNode) current.getParent();
        while (current != null) {
            final Object userObject = current.getUserObject();
            if (userObject instanceof MyUserObject) {
                ((MyUserObject) userObject).setInCycle(false);
            }
            current = (DefaultMutableTreeNode) current.getParent();
        }
        myLeftTree.repaint();
    }

    private void initLeftTree() {
        //noinspection HardCodedStringLiteral
        final DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
        myLeftTreeModel = new DefaultTreeModel(root);
        initLeftTreeModel();
        myLeftTree = new Tree(myLeftTreeModel);
        initTree(myLeftTree, false);

        myLeftTree.addTreeExpansionListener(new TreeExpansionListener() {
            @Override
            public void treeCollapsed(TreeExpansionEvent event) {
            }

            @Override
            public void treeExpanded(TreeExpansionEvent event) {
                final DefaultMutableTreeNode expandedNode = (DefaultMutableTreeNode) event.getPath()
                        .getLastPathComponent();
                for (int i = 0; i < expandedNode.getChildCount(); i++) {
                    DefaultMutableTreeNode child = (DefaultMutableTreeNode) expandedNode.getChildAt(i);
                    if (child.getChildCount() == 0) {
                        Module module = ((MyUserObject) child.getUserObject()).getModule();
                        final Iterator<Module> out = myModulesGraph.getOut(module);
                        while (out.hasNext()) {
                            final Module nextModule = out.next();
                            child.add(new DefaultMutableTreeNode(new MyUserObject(false, nextModule)));
                        }
                        sortSubTree(child);
                    }
                }
            }
        });

        myLeftTree.addTreeSelectionListener(new TreeSelectionListener() {
            @Override
            public void valueChanged(TreeSelectionEvent e) {
                final TreePath selectionPath = myLeftTree.getSelectionPath();
                if (selectionPath != null) {

                    myPathField.setText(StringUtil.join(selectionPath.getPath(), new Function<Object, String>() {
                        @Override
                        public String fun(Object o) {
                            final Object userObject = ((DefaultMutableTreeNode) o).getUserObject();
                            if (userObject instanceof MyUserObject) {
                                return ((MyUserObject) userObject).getModule().getName();
                            }
                            return "";
                        }
                    }, ":"));

                    final DefaultMutableTreeNode selection = (DefaultMutableTreeNode) selectionPath
                            .getLastPathComponent();
                    if (selection != null) {
                        TreeUtil.traverseDepth(selection, new TreeUtil.Traverse() {
                            @Override
                            public boolean accept(Object node) {
                                DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) node;
                                if (treeNode.getUserObject() instanceof MyUserObject) {
                                    ((MyUserObject) treeNode.getUserObject()).setInCycle(false);
                                }
                                return true;
                            }
                        });
                        selectCycleUpward(selection);
                        buildRightTree(((MyUserObject) selection.getUserObject()).getModule());
                    }
                }
            }
        });
        TreeUtil.selectFirstNode(myLeftTree);
    }

    private static ActionGroup createTreePopupActions(final boolean isRightTree, final Tree tree) {
        DefaultActionGroup group = new DefaultActionGroup();
        final TreeExpander treeExpander = new TreeExpander() {
            @Override
            public void expandAll() {
                TreeUtil.expandAll(tree);
            }

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

            @Override
            public void collapseAll() {
                TreeUtil.collapseAll(tree, 3);
            }

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

        final CommonActionsManager actionManager = CommonActionsManager.getInstance();
        if (isRightTree) {
            group.add(actionManager.createExpandAllAction(treeExpander, tree));
        }
        group.add(actionManager.createCollapseAllAction(treeExpander, tree));
        final ActionManager globalActionManager = ActionManager.getInstance();
        group.add(globalActionManager.getAction(IdeActions.ACTION_EDIT_SOURCE));
        group.add(AnSeparator.getInstance());
        group.add(globalActionManager.getAction(IdeActions.ACTION_ANALYZE_DEPENDENCIES));
        group.add(globalActionManager.getAction(IdeActions.ACTION_ANALYZE_BACK_DEPENDENCIES));
        //non exists in platform group.add(globalActionManager.getAction(IdeActions.ACTION_ANALYZE_CYCLIC_DEPENDENCIES));
        return group;
    }

    private static void initTree(Tree tree, boolean isRightTree) {
        tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
        tree.setCellRenderer(new MyTreeCellRenderer());
        tree.setRootVisible(false);
        tree.setShowsRootHandles(true);
        UIUtil.setLineStyleAngled(tree);

        TreeUtil.installActions(tree);
        new TreeSpeedSearch(tree, new Convertor<TreePath, String>() {
            @Override
            public String convert(TreePath o) {
                return o.getLastPathComponent().toString();
            }
        }, true);
        PopupHandler.installUnknownPopupHandler(tree, createTreePopupActions(isRightTree, tree),
                ActionManager.getInstance());
    }

    private Graph<Module> buildGraph() {
        final Graph<Module> graph = ModuleManager.getInstance(myProject).moduleGraph();
        if (isForwardDirection()) {
            return graph;
        } else {
            return GraphAlgorithms.getInstance().invertEdgeDirections(graph);
        }
    }

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

    @Override
    public void beforeRootsChange(ModuleRootEvent event) {
    }

    @Override
    public void rootsChanged(ModuleRootEvent event) {
        initLeftTreeModel();
        TreeUtil.selectFirstNode(myLeftTree);
    }

    private static class MyUserObject implements NavigatableWithText {
        private boolean myInCycle;
        private final Module myModule;

        public MyUserObject(final boolean inCycle, final Module module) {
            myInCycle = inCycle;
            myModule = module;
        }

        public boolean isInCycle() {
            return myInCycle;
        }

        public void setInCycle(final boolean inCycle) {
            myInCycle = inCycle;
        }

        public Module getModule() {
            return myModule;
        }

        public boolean equals(Object object) {
            return object instanceof MyUserObject && myModule.equals(((MyUserObject) object).getModule());
        }

        public int hashCode() {
            return myModule.hashCode();
        }

        public String toString() {
            return myModule.getName();
        }

        @Override
        public void navigate(boolean requestFocus) {
            ProjectSettingsService.getInstance(myModule.getProject()).openModuleSettings(myModule);
        }

        @Override
        public boolean canNavigate() {
            return myModule != null && !myModule.isDisposed();
        }

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

        @Override
        public String getNavigateActionText(boolean focusEditor) {
            return "Open Module Settings";
        }
    }

    private static class MyTreePanel extends JPanel implements DataProvider {
        private final Tree myTree;
        private final Project myProject;

        public MyTreePanel(final Tree tree, Project project) {
            super(new BorderLayout());
            myTree = tree;
            myProject = project;
            add(ScrollPaneFactory.createScrollPane(myTree), BorderLayout.CENTER);
        }

        @Override
        public Object getData(String dataId) {
            if (CommonDataKeys.PROJECT.is(dataId)) {
                return myProject;
            }
            if (LangDataKeys.MODULE_CONTEXT.is(dataId)) {
                final TreePath selectionPath = myTree.getLeadSelectionPath();
                if (selectionPath != null
                        && selectionPath.getLastPathComponent() instanceof DefaultMutableTreeNode) {
                    DefaultMutableTreeNode node = (DefaultMutableTreeNode) selectionPath.getLastPathComponent();
                    if (node.getUserObject() instanceof MyUserObject) {
                        return ((MyUserObject) node.getUserObject()).getModule();
                    }
                }
            }
            if (PlatformDataKeys.HELP_ID.is(dataId)) {
                return ourHelpID;
            }
            if (PlatformDataKeys.NAVIGATABLE.is(dataId)) {
                final TreePath selectionPath = myTree.getLeadSelectionPath();
                if (selectionPath != null
                        && selectionPath.getLastPathComponent() instanceof DefaultMutableTreeNode) {
                    DefaultMutableTreeNode node = (DefaultMutableTreeNode) selectionPath.getLastPathComponent();
                    if (node.getUserObject() instanceof MyUserObject) {
                        return node.getUserObject();
                    }
                }
            }
            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) {
            final Object userObject = ((DefaultMutableTreeNode) value).getUserObject();
            if (!(userObject instanceof MyUserObject)) {
                if (userObject != null) {
                    append(userObject.toString(), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES);
                }
                return;
            }
            MyUserObject node = (MyUserObject) userObject;
            Module module = node.getModule();
            setIcon(AllIcons.Nodes.Module);
            if (node.isInCycle()) {
                append(module.getName(), SimpleTextAttributes.ERROR_ATTRIBUTES);
            } else {
                append(module.getName(), SimpleTextAttributes.REGULAR_ATTRIBUTES);
            }
        }
    }
}