blob: 9d09e393a2cc71afdfa6e27dfcf42ffec2a864aa [file] [log] [blame]
/*
* 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.cyclicDependencies.ui;
import com.intellij.CommonBundle;
import com.intellij.analysis.AnalysisScopeBundle;
import com.intellij.cyclicDependencies.CyclicDependenciesBuilder;
import com.intellij.cyclicDependencies.actions.CyclicDependenciesHandler;
import com.intellij.icons.AllIcons;
import com.intellij.ide.actions.ContextHelpAction;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Splitter;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.packageDependencies.DependenciesToolWindow;
import com.intellij.packageDependencies.DependencyUISettings;
import com.intellij.packageDependencies.ui.*;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiPackage;
import com.intellij.ui.*;
import com.intellij.ui.content.Content;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.util.EditSourceOnDoubleClickHandler;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.tree.TreeUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import java.awt.*;
import java.util.*;
import java.util.List;
/**
* User: anna
* Date: Jan 31, 2005
*/
public class CyclicDependenciesPanel extends JPanel implements Disposable, DataProvider {
private static final HashSet<PsiFile> EMPTY_FILE_SET = new HashSet<PsiFile>(0);
private final HashMap<PsiPackage, Set<List<PsiPackage>>> myDependencies;
private final MyTree myLeftTree = new MyTree();
private final MyTree myRightTree = new MyTree();
private final DependenciesUsagesPanel myUsagesPanel;
private final TreeExpansionMonitor myRightTreeExpansionMonitor;
private final TreeExpansionMonitor myLeftTreeExpansionMonitor;
private final Project myProject;
private final CyclicDependenciesBuilder myBuilder;
private Content myContent;
private final DependenciesPanel.DependencyPanelSettings mySettings = new DependenciesPanel.DependencyPanelSettings();
public static final String DEFAULT_PACKAGE_ABBREVIATION = AnalysisScopeBundle.message("dependencies.tree.node.default.package.abbreviation");
public CyclicDependenciesPanel(Project project, final CyclicDependenciesBuilder builder) {
super(new BorderLayout());
myDependencies = builder.getCyclicDependencies();
myBuilder = builder;
myProject = project;
myUsagesPanel =
new DependenciesUsagesPanel(myProject, Collections.singletonList(builder.getForwardBuilder()));
Disposer.register(this, myUsagesPanel);
mySettings.UI_SHOW_MODULES = false; //exist without modules - and doesn't with
final Splitter treeSplitter = new Splitter();
Disposer.register(this, new Disposable(){
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() {
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);
updateLeftTreeModel();
updateRightTreeModel();
myLeftTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
public void valueChanged(TreeSelectionEvent e) {
updateRightTreeModel();
myUsagesPanel.setToInitialPosition();
}
});
myRightTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
public void valueChanged(TreeSelectionEvent e) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Set<PsiFile> searchIn = getSelectedScope(myRightTree);
final PackageNode selectedPackageNode = getSelectedPackage(myRightTree);
if (selectedPackageNode == null) {
return;
}
final PackageDependenciesNode nextPackageNode = getNextPackageNode(selectedPackageNode);
Set<PsiFile> searchFor = new HashSet<PsiFile>();
Set<PackageNode> packNodes = new HashSet<PackageNode>();
getPackageNodesHierarchy(selectedPackageNode, packNodes);
for (PackageNode packageNode : packNodes) {
searchFor.addAll(myBuilder.getDependentFilesInPackage((PsiPackage)packageNode.getPsiElement(),
((PsiPackage)nextPackageNode.getPsiElement())));
}
if (searchIn.isEmpty() || searchFor.isEmpty()) {
myUsagesPanel.setToInitialPosition();
}
else {
myBuilder.setRootNodeNameInUsageView(AnalysisScopeBundle.message("cyclic.dependencies.usage.view.root.node.text", ((PsiPackage)nextPackageNode.getPsiElement()).getQualifiedName(), ((PsiPackage)selectedPackageNode.getPsiElement()).getQualifiedName()));
myUsagesPanel.findUsages(searchIn, searchFor);
}
}
});
}
});
initTree(myLeftTree);
initTree(myRightTree);
mySettings.UI_FILTER_LEGALS = false;
mySettings.UI_FLATTEN_PACKAGES = false;
TreeUtil.selectFirstNode(myLeftTree);
}
private static void getPackageNodesHierarchy(PackageNode node, Set<PackageNode> result){
result.add(node);
for (int i = 0; i < node.getChildCount(); i++){
final TreeNode child = node.getChildAt(i);
if (child instanceof PackageNode){
final PackageNode packNode = (PackageNode)child;
if (!result.contains(packNode)){
getPackageNodesHierarchy(packNode, result);
}
}
}
}
@Nullable
private static PackageDependenciesNode getNextPackageNode(DefaultMutableTreeNode node) {
DefaultMutableTreeNode child = node;
while (node != null) {
if (node instanceof CycleNode) {
final TreeNode packageDependenciesNode = child.getNextSibling() != null
? child.getNextSibling()
: node.getChildAt(0);
if (packageDependenciesNode instanceof PackageNode){
return (PackageNode)packageDependenciesNode;
}
if (packageDependenciesNode instanceof ModuleNode){
return (PackageNode)packageDependenciesNode.getChildAt(0);
}
}
child = node;
node = (DefaultMutableTreeNode)node.getParent();
}
return null;
}
private static PackageDependenciesNode hideEmptyMiddlePackages(PackageDependenciesNode node, StringBuffer result){
if (node.getChildCount() == 0 || node.getChildCount() > 1 || (node.getChildCount() == 1 && node.getChildAt(0) instanceof FileNode)){
result.append(result.length() != 0 ? "." : "").append(node.toString().equals(DEFAULT_PACKAGE_ABBREVIATION) ? "" : node.toString());//toString()
} else {
if (node.getChildCount() == 1){
PackageDependenciesNode child = (PackageDependenciesNode)node.getChildAt(0);
if (!(node instanceof PackageNode)){ //e.g. modules node
node.removeAllChildren();
child = hideEmptyMiddlePackages(child, result);
node.add(child);
} else {
if (child instanceof PackageNode){
node.removeAllChildren();
result.append(result.length() != 0 ? "." : "")
.append(node.toString().equals(DEFAULT_PACKAGE_ABBREVIATION) ? "" : node.toString());
node = hideEmptyMiddlePackages(child, result);
((PackageNode)node).setPackageName(result.toString());//toString()
}
}
}
}
return node;
}
private JComponent createToolbar() {
DefaultActionGroup group = new DefaultActionGroup();
group.add(new CloseAction());
group.add(new RerunAction(this));
group.add(new ShowFilesAction());
group.add(new HideOutOfCyclePackagesAction());
group.add(new GroupByScopeTypeAction());
group.add(new ContextHelpAction("dependency.viewer.tool.window"));
ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, true);
return toolbar.getComponent();
}
private void rebuild() {
updateLeftTreeModel();
updateRightTreeModel();
}
private void initTree(final MyTree tree) {
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
tree.setCellRenderer(new MyTreeCellRenderer(tree == myLeftTree));
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(), ActionManager.getInstance());
}
private void updateLeftTreeModel() {
final Set<PsiPackage> psiPackages = myDependencies.keySet();
final Set<PsiFile> psiFiles = new HashSet<PsiFile>();
for (PsiPackage psiPackage : psiPackages) {
final Set<List<PsiPackage>> cycles = myDependencies.get(psiPackage);
if (!mySettings.UI_FILTER_OUT_OF_CYCLE_PACKAGES || cycles != null && !cycles.isEmpty()) {
psiFiles.addAll(getPackageFiles(psiPackage));
}
}
boolean showFiles = mySettings.UI_SHOW_FILES; //do not show files in the left tree
mySettings.UI_FLATTEN_PACKAGES = true;
mySettings.UI_SHOW_FILES = false;
myLeftTreeExpansionMonitor.freeze();
myLeftTree.setModel(TreeModelBuilder.createTreeModel(myProject, false, psiFiles, new Marker() {
public boolean isMarked(VirtualFile file) {
return false;
}
}, mySettings));
myLeftTreeExpansionMonitor.restore();
expandFirstLevel(myLeftTree);
mySettings.UI_SHOW_FILES = showFiles;
mySettings.UI_FLATTEN_PACKAGES = false;
}
private static ActionGroup createTreePopupActions() {
DefaultActionGroup group = new DefaultActionGroup();
group.add(ActionManager.getInstance().getAction(IdeActions.ACTION_EDIT_SOURCE));
group.add(ActionManager.getInstance().getAction(IdeActions.GROUP_VERSION_CONTROLS));
return group;
}
private void updateRightTreeModel() {
PackageDependenciesNode root = new RootNode(myProject);
final PackageNode packageNode = getSelectedPackage(myLeftTree);
if (packageNode != null) {
boolean group = mySettings.UI_GROUP_BY_SCOPE_TYPE;
mySettings.UI_GROUP_BY_SCOPE_TYPE = false;
final PsiPackage aPackage = (PsiPackage)packageNode.getPsiElement();
final Set<List<PsiPackage>> cyclesOfPackages = myDependencies.get(aPackage);
for (List<PsiPackage> packCycle : cyclesOfPackages) {
PackageDependenciesNode[] nodes = new PackageDependenciesNode[packCycle.size()];
for (int i = packCycle.size() - 1; i >= 0; i--) {
final PsiPackage psiPackage = packCycle.get(i);
PsiPackage nextPackage = packCycle.get(i == 0 ? packCycle.size() - 1 : i - 1);
PsiPackage prevPackage = packCycle.get(i == packCycle.size() - 1 ? 0 : i + 1);
final Set<PsiFile> dependentFilesInPackage = myBuilder.getDependentFilesInPackage(prevPackage, psiPackage, nextPackage);
final PackageDependenciesNode pack = (PackageDependenciesNode)TreeModelBuilder
.createTreeModel(myProject, false, dependentFilesInPackage, new Marker() {
public boolean isMarked(VirtualFile file) {
return false;
}
}, mySettings).getRoot();
nodes[i] = hideEmptyMiddlePackages((PackageDependenciesNode)pack.getChildAt(0), new StringBuffer());
}
PackageDependenciesNode cycleNode = new CycleNode(myProject);
for (PackageDependenciesNode node : nodes) {
node.setEquals(true);
cycleNode.insert(node, 0);
}
root.add(cycleNode);
}
mySettings.UI_GROUP_BY_SCOPE_TYPE = group;
}
myRightTreeExpansionMonitor.freeze();
myRightTree.setModel(new TreeModel(root, -1, -1));
myRightTreeExpansionMonitor.restore();
expandFirstLevel(myRightTree);
}
private HashSet<PsiFile> getPackageFiles(final PsiPackage psiPackage) {
final HashSet<PsiFile> psiFiles = new HashSet<PsiFile>();
final PsiClass[] classes = psiPackage.getClasses();
for (PsiClass aClass : classes) {
final PsiFile file = aClass.getContainingFile();
if (myBuilder.getScope().contains(file)) {
psiFiles.add(file);
}
}
return psiFiles;
}
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;
tree.expandPath(new TreePath(node.getPath()));
}
@Nullable
private static PackageNode getSelectedPackage(final Tree tree) {
TreePath[] paths = tree.getSelectionPaths();
if (paths == null || paths.length != 1) return null;
PackageDependenciesNode node = (PackageDependenciesNode)paths[0].getLastPathComponent();
if (node.isRoot()) return null;
if (node instanceof PackageNode) {
return (PackageNode)node;
}
if (node instanceof FileNode) {
return (PackageNode)node.getParent();
}
if (node instanceof ModuleNode){
return (PackageNode)node.getChildAt(0);
}
return null;
}
private static Set<PsiFile> getSelectedScope(final Tree tree) {
TreePath[] paths = tree.getSelectionPaths();
if (paths == null || paths.length != 1) return EMPTY_FILE_SET;
PackageDependenciesNode node = (PackageDependenciesNode)paths[0].getLastPathComponent();
if (node.isRoot()) return EMPTY_FILE_SET;
Set<PsiFile> result = new HashSet<PsiFile>();
node.fillFiles(result, true);
return result;
}
public void setContent(Content content) {
myContent = content;
}
public void dispose() {
TreeModelBuilder.clearCaches(myProject);
}
@Nullable
@NonNls
public Object getData(@NonNls String dataId) {
if (PlatformDataKeys.HELP_ID.is(dataId)) {
return "dependency.viewer.tool.window";
}
return null;
}
private class MyTreeCellRenderer extends ColoredTreeCellRenderer {
private final boolean myLeftTree;
public MyTreeCellRenderer(boolean isLeftTree) {
myLeftTree = isLeftTree;
}
public void customizeCellRenderer(JTree tree,
Object value,
boolean selected,
boolean expanded,
boolean leaf,
int row,
boolean hasFocus) {
SimpleTextAttributes attributes = SimpleTextAttributes.REGULAR_ATTRIBUTES;
final PackageDependenciesNode node;
if (value instanceof PackageDependenciesNode){
node = (PackageDependenciesNode)value;
if (myLeftTree && !mySettings.UI_FILTER_OUT_OF_CYCLE_PACKAGES) {
final PsiElement element = node.getPsiElement();
if (element instanceof PsiPackage) {
final PsiPackage aPackage = (PsiPackage)element;
final Set<List<PsiPackage>> packageDependencies = myDependencies.get(aPackage);
if (packageDependencies != null && !packageDependencies.isEmpty()) {
attributes = SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES;
}
}
}
} else {
node = (PackageDependenciesNode)((DefaultMutableTreeNode)value).getUserObject(); //cycle node children
}
append(node.toString(), attributes);
setIcon(node.getIcon());
}
}
private final class CloseAction extends AnAction implements DumbAware {
public CloseAction() {
super(CommonBundle.message("action.close"), AnalysisScopeBundle.message("action.close.dependency.description"),
AllIcons.Actions.Cancel);
}
public void actionPerformed(AnActionEvent e) {
Disposer.dispose(myUsagesPanel);
DependenciesToolWindow.getInstance(myProject).closeContent(myContent);
mySettings.copyToApplicationDependencySettings();
}
}
private final class ShowFilesAction extends ToggleAction {
ShowFilesAction() {
super(AnalysisScopeBundle.message("action.show.files"), AnalysisScopeBundle.message("action.show.files.description"),
AllIcons.FileTypes.Java);
}
public boolean isSelected(AnActionEvent event) {
return mySettings.UI_SHOW_FILES;
}
public void setSelected(AnActionEvent event, boolean flag) {
DependencyUISettings.getInstance().UI_SHOW_FILES = flag;
mySettings.UI_SHOW_FILES = flag;
rebuild();
}
}
private final class HideOutOfCyclePackagesAction extends ToggleAction {
@NonNls public static final String SHOW_PACKAGES_FROM_CYCLES_ONLY = "Hide packages without cyclic dependencies";
HideOutOfCyclePackagesAction() {
super(SHOW_PACKAGES_FROM_CYCLES_ONLY, SHOW_PACKAGES_FROM_CYCLES_ONLY, AllIcons.General.Filter);
}
@Override
public boolean isSelected(AnActionEvent e) {
return mySettings.UI_FILTER_OUT_OF_CYCLE_PACKAGES;
}
@Override
public void setSelected(AnActionEvent e, boolean state) {
DependencyUISettings.getInstance().UI_FILTER_OUT_OF_CYCLE_PACKAGES = state;
mySettings.UI_FILTER_OUT_OF_CYCLE_PACKAGES = state;
rebuild();
}
}
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);
}
public boolean isSelected(AnActionEvent event) {
return mySettings.UI_GROUP_BY_SCOPE_TYPE;
}
public void setSelected(AnActionEvent event, boolean flag) {
DependencyUISettings.getInstance().UI_GROUP_BY_SCOPE_TYPE = flag;
mySettings.UI_GROUP_BY_SCOPE_TYPE = flag;
rebuild();
}
}
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);
}
public void update(AnActionEvent e) {
e.getPresentation().setEnabled(myBuilder.getScope().isValid());
}
public void actionPerformed(AnActionEvent e) {
DependenciesToolWindow.getInstance(myProject).closeContent(myContent);
mySettings.copyToApplicationDependencySettings();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new CyclicDependenciesHandler(myProject, myBuilder.getScope()).analyze();
}
});
}
}
private static class MyTree extends Tree implements DataProvider {
public Object getData(String dataId) {
PackageDependenciesNode node = getSelectedNode();
if (CommonDataKeys.NAVIGATABLE.is(dataId)) {
return node;
}
return null;
}
@Nullable
public PackageDependenciesNode getSelectedNode() {
TreePath[] paths = getSelectionPaths();
if (paths == null || paths.length != 1) return null;
final Object lastPathComponent = paths[0].getLastPathComponent();
if (lastPathComponent instanceof PackageDependenciesNode) {
return (PackageDependenciesNode)lastPathComponent;
} else {
return (PackageDependenciesNode)((DefaultMutableTreeNode)lastPathComponent).getUserObject();
}
}
}
}