blob: c6a3211dbee6b6d176f535826fc14d5ce8a0d1fb [file] [log] [blame]
/*
* 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.StatusText;
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 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 final 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);
setEmptyText(mySettings.UI_FILTER_LEGALS);
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 (CommonDataKeys.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"),
PlatformIcons.FLATTEN_PACKAGES_ICON);
}
@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;
setEmptyText(flag);
rebuild();
}
}
private void setEmptyText(boolean flag) {
final String emptyText = flag ? "No illegal dependencies found" : "Nothing to show";
myLeftTree.getEmptyText().setText(emptyText);
myRightTree.getEmptyText().setText(emptyText);
}
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 (CommonDataKeys.NAVIGATABLE.is(dataId)) {
return node;
}
if (CommonDataKeys.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;
}
@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;
}
}
}