2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com
.intellij
.moduleDependencies
;
19 import com
.intellij
.CommonBundle
;
20 import com
.intellij
.ProjectTopics
;
21 import com
.intellij
.analysis
.AnalysisScopeBundle
;
22 import com
.intellij
.cyclicDependencies
.CyclicGraphUtil
;
23 import com
.intellij
.ide
.CommonActionsManager
;
24 import com
.intellij
.ide
.TreeExpander
;
25 import com
.intellij
.ide
.actions
.ContextHelpAction
;
26 import com
.intellij
.ide
.util
.PropertiesComponent
;
27 import com
.intellij
.openapi
.Disposable
;
28 import com
.intellij
.openapi
.actionSystem
.*;
29 import com
.intellij
.openapi
.module
.Module
;
30 import com
.intellij
.openapi
.module
.ModuleManager
;
31 import com
.intellij
.openapi
.progress
.ProgressIndicator
;
32 import com
.intellij
.openapi
.progress
.ProgressManager
;
33 import com
.intellij
.openapi
.project
.Project
;
34 import com
.intellij
.openapi
.roots
.ModuleRootEvent
;
35 import com
.intellij
.openapi
.roots
.ModuleRootListener
;
36 import com
.intellij
.openapi
.ui
.Splitter
;
37 import com
.intellij
.openapi
.util
.IconLoader
;
38 import com
.intellij
.ui
.*;
39 import com
.intellij
.ui
.content
.Content
;
40 import com
.intellij
.ui
.treeStructure
.Tree
;
41 import com
.intellij
.util
.containers
.HashMap
;
42 import com
.intellij
.util
.graph
.DFSTBuilder
;
43 import com
.intellij
.util
.graph
.Graph
;
44 import com
.intellij
.util
.ui
.UIUtil
;
45 import com
.intellij
.util
.ui
.tree
.TreeUtil
;
46 import org
.jetbrains
.annotations
.NonNls
;
49 import javax
.swing
.event
.TreeExpansionEvent
;
50 import javax
.swing
.event
.TreeExpansionListener
;
51 import javax
.swing
.event
.TreeSelectionEvent
;
52 import javax
.swing
.event
.TreeSelectionListener
;
53 import javax
.swing
.tree
.DefaultMutableTreeNode
;
54 import javax
.swing
.tree
.DefaultTreeModel
;
55 import javax
.swing
.tree
.TreePath
;
56 import javax
.swing
.tree
.TreeSelectionModel
;
59 import java
.util
.List
;
65 public class ModulesDependenciesPanel
extends JPanel
implements ModuleRootListener
, Disposable
{
66 @NonNls private static final String DIRECTION
= "FORWARD_ANALIZER";
67 private Content myContent
;
68 private final Project myProject
;
69 private Tree myLeftTree
;
70 private DefaultTreeModel myLeftTreeModel
;
72 private final Tree myRightTree
;
73 private final DefaultTreeModel myRightTreeModel
;
75 private Graph
<Module
> myModulesGraph
;
76 private final Module
[] myModules
;
78 private final Splitter mySplitter
;
79 @NonNls private static final String ourHelpID
= "module.dependencies.tool.window";
81 public ModulesDependenciesPanel(final Project project
, final Module
[] modules
) {
82 super(new BorderLayout());
86 //noinspection HardCodedStringLiteral
87 myRightTreeModel
= new DefaultTreeModel(new DefaultMutableTreeNode("Root"));
88 myRightTree
= new Tree(myRightTreeModel
);
89 initTree(myRightTree
, true);
93 mySplitter
= new Splitter();
94 mySplitter
.setFirstComponent(new MyTreePanel(myLeftTree
, myProject
));
95 mySplitter
.setSecondComponent(new MyTreePanel(myRightTree
, myProject
));
97 setSplitterProportion();
98 add(mySplitter
, BorderLayout
.CENTER
);
99 add(createNorthPanel(), BorderLayout
.NORTH
);
101 project
.getMessageBus().connect(this).subscribe(ProjectTopics
.PROJECT_ROOTS
, this);
104 private void setSplitterProportion() {
105 if (mySplitter
== null){
108 myModulesGraph
= buildGraph();
109 DFSTBuilder
<Module
> builder
= new DFSTBuilder
<Module
>(myModulesGraph
);
111 if (builder
.isAcyclic()){
112 mySplitter
.setProportion(1.f
);
114 mySplitter
.setProportion(0.5f
);
118 public void dispose() {
121 public ModulesDependenciesPanel(final Project project
) {
122 this(project
, ModuleManager
.getInstance(project
).getModules());
125 private JComponent
createNorthPanel(){
126 DefaultActionGroup group
= new DefaultActionGroup();
128 group
.add(new AnAction(CommonBundle
.message("action.close"), AnalysisScopeBundle
.message("action.close.modules.dependencies.description"), IconLoader
.getIcon("/actions/cancel.png")){
129 public void actionPerformed(AnActionEvent e
) {
130 DependenciesAnalyzeManager
.getInstance(myProject
).closeContent(myContent
);
134 appendDependenciesAction(group
);
136 group
.add(new ToggleAction(AnalysisScopeBundle
.message("action.module.dependencies.direction"), "", isForwardDirection() ? IconLoader
.getIcon("/actions/sortAsc.png") : IconLoader
.getIcon("/actions/sortDesc.png")){
137 public boolean isSelected(AnActionEvent e
) {
138 return isForwardDirection();
141 public void setSelected(AnActionEvent e
, boolean state
) {
142 PropertiesComponent
.getInstance(myProject
).setValue(DIRECTION
, String
.valueOf(state
));
146 public void update(final AnActionEvent e
) {
147 e
.getPresentation().setIcon(isForwardDirection() ? IconLoader
.getIcon("/actions/sortAsc.png") : IconLoader
.getIcon("/actions/sortDesc.png"));
151 group
.add(new ContextHelpAction(ourHelpID
));
153 ActionToolbar toolbar
= ActionManager
.getInstance().createActionToolbar(ActionPlaces
.UNKNOWN
, group
, true);
154 return toolbar
.getComponent();
157 private boolean isForwardDirection() {
158 final String value
= PropertiesComponent
.getInstance(myProject
).getValue(DIRECTION
);
159 return value
== null || Boolean
.parseBoolean(value
);
162 private static void appendDependenciesAction(final DefaultActionGroup group
) {
163 final AnAction analyzeDepsAction
= ActionManager
.getInstance().getAction(IdeActions
.ACTION_ANALYZE_DEPENDENCIES
);
164 group
.add(new AnAction(analyzeDepsAction
.getTemplatePresentation().getText(),
165 analyzeDepsAction
.getTemplatePresentation().getDescription(),
166 IconLoader
.getIcon("/general/toolWindowInspection.png")){
168 public void actionPerformed(AnActionEvent e
) {
169 analyzeDepsAction
.actionPerformed(e
);
173 public void update(AnActionEvent e
) {
174 analyzeDepsAction
.update(e
);
179 private void buildRightTree(Module module
){
180 final DefaultMutableTreeNode root
= (DefaultMutableTreeNode
)myRightTreeModel
.getRoot();
181 root
.removeAllChildren();
182 final Set
<List
<Module
>> cycles
= CyclicGraphUtil
.getNodeCycles(myModulesGraph
, module
);
184 for (List
<Module
> modules
: cycles
) {
185 final DefaultMutableTreeNode cycle
= new DefaultMutableTreeNode(
186 AnalysisScopeBundle
.message("module.dependencies.cycle.node.text", Integer
.toString(index
++).toUpperCase()));
188 cycle
.add(new DefaultMutableTreeNode(new MyUserObject(false, module
)));
189 for (Module moduleInCycle
: modules
) {
190 cycle
.add(new DefaultMutableTreeNode(new MyUserObject(false, moduleInCycle
)));
193 ((DefaultTreeModel
)myRightTree
.getModel()).reload();
194 TreeUtil
.expandAll(myRightTree
);
197 private void initLeftTreeModel(){
198 final DefaultMutableTreeNode root
= (DefaultMutableTreeNode
)myLeftTreeModel
.getRoot();
199 root
.removeAllChildren();
200 myModulesGraph
= buildGraph();
201 setSplitterProportion();
202 ProgressManager
.getInstance().runProcessWithProgressSynchronously(new Runnable() {
204 final ProgressIndicator progressIndicator
= ProgressManager
.getInstance().getProgressIndicator();
205 final Map
<Module
, Boolean
> inCycle
= new HashMap
<Module
, Boolean
>();
206 for (Module module
: myModules
) {
207 if (progressIndicator
!= null) {
208 if (progressIndicator
.isCanceled()) return;
209 progressIndicator
.setText(AnalysisScopeBundle
.message("update.module.tree.progress.text", module
.getName()));
211 if (!module
.isDisposed()) {
212 Boolean isInCycle
= inCycle
.get(module
);
213 if (isInCycle
== null) {
214 isInCycle
= !CyclicGraphUtil
.getNodeCycles(myModulesGraph
, module
).isEmpty();
215 inCycle
.put(module
, isInCycle
);
217 final DefaultMutableTreeNode moduleNode
= new DefaultMutableTreeNode(new MyUserObject(isInCycle
.booleanValue(), module
));
218 root
.add(moduleNode
);
219 final Iterator
<Module
> out
= myModulesGraph
.getOut(module
);
220 while (out
.hasNext()) {
221 moduleNode
.add(new DefaultMutableTreeNode(new MyUserObject(false, out
.next())));
226 }, AnalysisScopeBundle
.message("update.module.tree.progress.title"), true, myProject
);
228 myLeftTreeModel
.reload();
231 private static void sortSubTree(final DefaultMutableTreeNode root
) {
232 TreeUtil
.sort(root
, new Comparator() {
233 public int compare(final Object o1
, final Object o2
) {
234 DefaultMutableTreeNode node1
= (DefaultMutableTreeNode
)o1
;
235 DefaultMutableTreeNode node2
= (DefaultMutableTreeNode
)o2
;
236 if (!(node1
.getUserObject() instanceof MyUserObject
)){
239 else if (!(node2
.getUserObject() instanceof MyUserObject
)){
242 return (node1
.getUserObject().toString().compareTo(node2
.getUserObject().toString()));
247 private void selectCycleUpward(final DefaultMutableTreeNode selection
){
248 ArrayList
<DefaultMutableTreeNode
> selectionNodes
= new ArrayList
<DefaultMutableTreeNode
>();
249 selectionNodes
.add(selection
);
250 DefaultMutableTreeNode current
= (DefaultMutableTreeNode
)selection
.getParent();
251 boolean flag
= false;
252 while (current
!= null && current
.getUserObject() != null){
253 if (current
.getUserObject().equals(selection
.getUserObject())){
255 selectionNodes
.add(current
);
258 selectionNodes
.add(current
);
259 current
= (DefaultMutableTreeNode
)current
.getParent();
262 for (DefaultMutableTreeNode node
: selectionNodes
) {
263 ((MyUserObject
)node
.getUserObject()).setInCycle(true);
266 if (current
!= null) current
= (DefaultMutableTreeNode
)current
.getParent();
267 while (current
!= null) {
268 final Object userObject
= current
.getUserObject();
269 if (userObject
instanceof MyUserObject
) {
270 ((MyUserObject
)userObject
).setInCycle(false);
272 current
= (DefaultMutableTreeNode
)current
.getParent();
274 myLeftTree
.repaint();
277 private void initLeftTree(){
278 //noinspection HardCodedStringLiteral
279 final DefaultMutableTreeNode root
= new DefaultMutableTreeNode("Root");
280 myLeftTreeModel
= new DefaultTreeModel(root
);
282 myLeftTree
= new Tree(myLeftTreeModel
);
283 initTree(myLeftTree
, false);
285 myLeftTree
.addTreeExpansionListener(new TreeExpansionListener() {
286 public void treeCollapsed(TreeExpansionEvent event
) {
289 public void treeExpanded(TreeExpansionEvent event
) {
290 final DefaultMutableTreeNode expandedNode
= (DefaultMutableTreeNode
)event
.getPath().getLastPathComponent();
291 for(int i
= 0; i
< expandedNode
.getChildCount(); i
++){
292 DefaultMutableTreeNode child
= (DefaultMutableTreeNode
)expandedNode
.getChildAt(i
);
293 if (child
.getChildCount() == 0){
294 Module module
= ((MyUserObject
)child
.getUserObject()).getModule();
295 final Iterator
<Module
> out
= myModulesGraph
.getOut(module
);
296 while (out
.hasNext()) {
297 final Module nextModule
= out
.next();
298 child
.add(new DefaultMutableTreeNode(new MyUserObject(false, nextModule
)));
306 myLeftTree
.addTreeSelectionListener(new TreeSelectionListener() {
307 public void valueChanged(TreeSelectionEvent e
) {
308 final TreePath selectionPath
= myLeftTree
.getSelectionPath();
309 if (selectionPath
!= null) {
310 final DefaultMutableTreeNode selection
= (DefaultMutableTreeNode
)selectionPath
.getLastPathComponent();
311 if (selection
!= null){
312 TreeUtil
.traverseDepth(selection
, new TreeUtil
.Traverse() {
313 public boolean accept(Object node
) {
314 DefaultMutableTreeNode treeNode
= (DefaultMutableTreeNode
)node
;
315 if (treeNode
.getUserObject() instanceof MyUserObject
){
316 ((MyUserObject
)treeNode
.getUserObject()).setInCycle(false);
321 selectCycleUpward(selection
);
322 buildRightTree(((MyUserObject
)selection
.getUserObject()).getModule());
327 TreeUtil
.selectFirstNode(myLeftTree
);
330 private static ActionGroup
createTreePopupActions(final boolean isRightTree
, final Tree tree
) {
331 DefaultActionGroup group
= new DefaultActionGroup();
332 final TreeExpander treeExpander
= new TreeExpander() {
333 public void expandAll() {
334 TreeUtil
.expandAll(tree
);
337 public boolean canExpand() {
341 public void collapseAll() {
342 TreeUtil
.collapseAll(tree
, 3);
345 public boolean canCollapse() {
350 final CommonActionsManager actionManager
= CommonActionsManager
.getInstance();
352 group
.add(actionManager
.createExpandAllAction(treeExpander
, tree
));
354 group
.add(actionManager
.createCollapseAllAction(treeExpander
, tree
));
355 group
.add(ActionManager
.getInstance().getAction(IdeActions
.MODULE_SETTINGS
));
356 appendDependenciesAction(group
);
360 private static void initTree(Tree tree
, boolean isRightTree
) {
361 tree
.getSelectionModel().setSelectionMode(TreeSelectionModel
.SINGLE_TREE_SELECTION
);
362 tree
.setCellRenderer(new MyTreeCellRenderer());
363 tree
.setRootVisible(false);
364 tree
.setShowsRootHandles(true);
365 UIUtil
.setLineStyleAngled(tree
);
367 TreeToolTipHandler
.install(tree
);
368 TreeUtil
.installActions(tree
);
369 new TreeSpeedSearch(tree
);
370 PopupHandler
.installUnknownPopupHandler(tree
, createTreePopupActions(isRightTree
, tree
), ActionManager
.getInstance());
374 private Graph
<Module
> buildGraph() {
375 final Graph
<Module
> graph
= ModuleManager
.getInstance(myProject
).moduleGraph();
376 if (isForwardDirection()) {
380 return new Graph
<Module
>() {
381 public Collection
<Module
> getNodes() {
382 return graph
.getNodes();
385 public Iterator
<Module
> getIn(final Module n
) {
386 return graph
.getOut(n
);
389 public Iterator
<Module
> getOut(final Module n
) {
390 return graph
.getIn(n
);
397 public void setContent(final Content content
) {
401 public void beforeRootsChange(ModuleRootEvent event
) {
404 public void rootsChanged(ModuleRootEvent event
) {
406 TreeUtil
.selectFirstNode(myLeftTree
);
409 private static class MyUserObject
{
410 private boolean myInCycle
;
411 private final Module myModule
;
413 public MyUserObject(final boolean inCycle
, final Module module
) {
418 public boolean isInCycle() {
422 public void setInCycle(final boolean inCycle
) {
426 public Module
getModule() {
430 public boolean equals(Object object
) {
431 return object
instanceof MyUserObject
&& myModule
.equals(((MyUserObject
)object
).getModule());
434 public int hashCode() {
435 return myModule
.hashCode();
438 public String
toString() {
439 return myModule
.getName();
443 private static class MyTreePanel
extends JPanel
implements DataProvider
{
444 private final Tree myTree
;
445 private final Project myProject
;
446 public MyTreePanel(final Tree tree
, Project project
) {
447 super(new BorderLayout());
450 add(ScrollPaneFactory
.createScrollPane(myTree
), BorderLayout
.CENTER
);
453 public Object
getData(String dataId
) {
454 if (PlatformDataKeys
.PROJECT
.is(dataId
)){
457 if (LangDataKeys
.MODULE_CONTEXT
.is(dataId
)){
458 final TreePath selectionPath
= myTree
.getLeadSelectionPath();
459 if (selectionPath
!= null && selectionPath
.getLastPathComponent() instanceof DefaultMutableTreeNode
){
460 DefaultMutableTreeNode node
= (DefaultMutableTreeNode
)selectionPath
.getLastPathComponent();
461 if (node
.getUserObject() instanceof MyUserObject
){
462 return ((MyUserObject
)node
.getUserObject()).getModule();
466 if (PlatformDataKeys
.HELP_ID
.is(dataId
)) {
472 private static class MyTreeCellRenderer
extends ColoredTreeCellRenderer
{
473 public void customizeCellRenderer(
482 final Object userObject
= ((DefaultMutableTreeNode
)value
).getUserObject();
483 if (!(userObject
instanceof MyUserObject
)){
484 if (userObject
!= null){
485 append(userObject
.toString(), SimpleTextAttributes
.REGULAR_BOLD_ATTRIBUTES
);
489 MyUserObject node
= (MyUserObject
)userObject
;
490 Module module
= node
.getModule();
491 setIcon(module
.getModuleType().getNodeIcon(expanded
));
492 if (node
.isInCycle()){
493 append(module
.getName(), SimpleTextAttributes
.ERROR_ATTRIBUTES
);
495 append(module
.getName(), SimpleTextAttributes
.REGULAR_ATTRIBUTES
);