| /* |
| * 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.module.ModuleType; |
| 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 final 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(Separator.getInstance()); |
| group.add(globalActionManager.getAction(IdeActions.ACTION_ANALYZE_DEPENDENCIES)); |
| group.add(globalActionManager.getAction(IdeActions.ACTION_ANALYZE_BACK_DEPENDENCIES)); |
| 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 (CommonDataKeys.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(ModuleType.get(module).getIcon()); |
| if (node.isInCycle()){ |
| append(module.getName(), SimpleTextAttributes.ERROR_ATTRIBUTES); |
| } else { |
| append(module.getName(), SimpleTextAttributes.REGULAR_ATTRIBUTES); |
| } |
| } |
| } |
| } |