blob: 0059172cdab50e640a772f7c5d739a4662e5ad76 [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.packageDependencies.ui;
import com.intellij.analysis.AnalysisScopeBundle;
import com.intellij.ide.dnd.aware.DnDAwareTree;
import com.intellij.ide.projectView.impl.ModuleGroup;
import com.intellij.ide.projectView.impl.nodes.ProjectViewDirectoryHelper;
import com.intellij.ide.scopeView.nodes.BasePsiNode;
import com.intellij.openapi.diagnostic.Logger;
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.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ContentIterator;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiFileSystemItem;
import com.intellij.util.containers.HashSet;
import com.intellij.util.ui.tree.TreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class FileTreeModelBuilder {
public static final Key<Integer> FILE_COUNT = Key.create("FILE_COUNT");
public static final String SCANNING_PACKAGES_MESSAGE = AnalysisScopeBundle.message("package.dependencies.build.progress.text");
private final ProjectFileIndex myFileIndex;
private final Project myProject;
private static final Logger LOG = Logger.getInstance("com.intellij.packageDependencies.ui.TreeModelBuilder");
private final boolean myShowModuleGroups;
private final boolean myShowModules;
private final boolean myFlattenPackages;
private final boolean myCompactEmptyMiddlePackages;
private boolean myShowFiles;
private final Marker myMarker;
private final boolean myAddUnmarkedFiles;
private final PackageDependenciesNode myRoot;
private final Map<VirtualFile,DirectoryNode> myModuleDirNodes = new HashMap<VirtualFile, DirectoryNode>();
private final Map<Module, ModuleNode> myModuleNodes = new HashMap<Module, ModuleNode>();
private final Map<String, ModuleGroupNode> myModuleGroupNodes = new HashMap<String, ModuleGroupNode>();
private int myScannedFileCount = 0;
private int myTotalFileCount = 0;
private int myMarkedFileCount = 0;
private JTree myTree;
protected final VirtualFile myBaseDir;
protected VirtualFile[] myContentRoots;
public FileTreeModelBuilder(Project project, Marker marker, DependenciesPanel.DependencyPanelSettings settings) {
myProject = project;
myBaseDir = myProject.getBaseDir();
myContentRoots = ProjectRootManager.getInstance(myProject).getContentRoots();
final boolean multiModuleProject = ModuleManager.getInstance(myProject).getModules().length > 1;
myShowModules = settings.UI_SHOW_MODULES && multiModuleProject;
myFlattenPackages = ProjectViewDirectoryHelper.getInstance(myProject).supportsFlattenPackages() && settings.UI_FLATTEN_PACKAGES;
myCompactEmptyMiddlePackages = settings.UI_COMPACT_EMPTY_MIDDLE_PACKAGES;
myShowFiles = settings.UI_SHOW_FILES;
myShowModuleGroups = settings.UI_SHOW_MODULE_GROUPS && multiModuleProject;
myMarker = marker;
myAddUnmarkedFiles = !settings.UI_FILTER_LEGALS;
myRoot = new RootNode(myProject);
myFileIndex = ProjectRootManager.getInstance(project).getFileIndex();
}
public void setTree(DnDAwareTree tree) {
myTree = tree;
}
public static synchronized TreeModel createTreeModel(Project project, boolean showProgress, Set<PsiFile> files, Marker marker, DependenciesPanel.DependencyPanelSettings settings) {
return new FileTreeModelBuilder(project, marker, settings).build(files, showProgress);
}
public static synchronized TreeModel createTreeModel(Project project, Marker marker, DependenciesPanel.DependencyPanelSettings settings) {
return new FileTreeModelBuilder(project, marker, settings).build(project, false);
}
public static synchronized TreeModel createTreeModel(Project project, boolean showProgress, Marker marker) {
return new FileTreeModelBuilder(project, marker, new DependenciesPanel.DependencyPanelSettings()).build(project, showProgress);
}
private void countFiles(Project project) {
final Integer fileCount = project.getUserData(FILE_COUNT);
if (fileCount == null) {
myFileIndex.iterateContent(new ContentIterator() {
@Override
public boolean processFile(VirtualFile fileOrDir) {
if (!fileOrDir.isDirectory()) {
counting();
}
return true;
}
});
project.putUserData(FILE_COUNT, myTotalFileCount);
} else {
myTotalFileCount = fileCount.intValue();
}
}
public static void clearCaches(Project project) {
project.putUserData(FILE_COUNT, null);
}
public TreeModel build(final Project project, boolean showProgress) {
return build(project, showProgress, null);
}
public TreeModel build(final Project project, final boolean showProgress, @Nullable final Runnable successRunnable) {
final Runnable buildingRunnable = new Runnable() {
@Override
public void run() {
ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
if (indicator != null) {
indicator.setText(SCANNING_PACKAGES_MESSAGE);
indicator.setIndeterminate(true);
}
countFiles(project);
if (indicator != null) {
indicator.setIndeterminate(false);
}
myFileIndex.iterateContent(new MyContentIterator());
}
};
final TreeModel treeModel = new TreeModel(myRoot);
if (showProgress) {
final Task.Backgroundable backgroundable =
new Task.Backgroundable(project, AnalysisScopeBundle.message("package.dependencies.build.process.title")) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
buildingRunnable.run();
}
@Override
public void onSuccess() {
if (project.isDisposed()) return;
myRoot.setSorted(false);
myRoot.sortChildren();
treeModel.reload(myRoot);
if (successRunnable != null) {
successRunnable.run();
}
}
};
ProgressManager.getInstance().run(backgroundable);
}
else {
buildingRunnable.run();
}
treeModel.setTotalFileCount(myTotalFileCount);
treeModel.setMarkedFileCount(myMarkedFileCount);
return treeModel;
}
private void counting() {
myTotalFileCount++;
ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
if (indicator != null) {
update(indicator, true, -1);
}
}
private static void update(ProgressIndicator indicator, boolean indeterminate, double fraction) {
if (indicator instanceof PanelProgressIndicator) {
((PanelProgressIndicator)indicator).update(SCANNING_PACKAGES_MESSAGE, indeterminate, fraction);
} else {
if (fraction != -1) {
indicator.setFraction(fraction);
}
}
}
private TreeModel build(final Set<PsiFile> files, boolean showProgress) {
if (files.size() == 1) {
myShowFiles = true;
}
Runnable buildingRunnable = new Runnable() {
@Override
public void run() {
for (final PsiFile file : files) {
if (file != null) {
buildFileNode(file.getVirtualFile(), null);
}
}
}
};
if (showProgress) {
ProgressManager.getInstance().runProcessWithProgressSynchronously(buildingRunnable, AnalysisScopeBundle
.message("package.dependencies.build.process.title"), false, myProject);
}
else {
buildingRunnable.run();
}
TreeUtil.sort(myRoot, new DependencyNodeComparator());
return new TreeModel(myRoot, myTotalFileCount, myMarkedFileCount);
}
private PackageDependenciesNode buildFileNode(VirtualFile file, PackageDependenciesNode lastParent) {
ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
if (file == null || !file.isValid()) return null;
if (indicator != null) {
update(indicator, false, ((double)myScannedFileCount++) / myTotalFileCount);
}
boolean isMarked = myMarker != null && myMarker.isMarked(file);
if (isMarked) myMarkedFileCount++;
if (isMarked || myAddUnmarkedFiles) {
PackageDependenciesNode dirNode = !myCompactEmptyMiddlePackages && lastParent != null ? lastParent : getFileParentNode(file);
if (myShowFiles) {
FileNode fileNode = new FileNode(file, myProject, isMarked);
dirNode.add(fileNode);
}
else {
dirNode.addFile(file, isMarked);
}
return dirNode;
}
return null;
}
public @NotNull PackageDependenciesNode getFileParentNode(VirtualFile file) {
LOG.assertTrue(file != null);
final VirtualFile containingDirectory = file.getParent();
return getModuleDirNode(containingDirectory, myFileIndex.getModuleForFile(file), null);
}
public boolean hasFileNode(@NotNull VirtualFile file) {
return myModuleDirNodes.containsKey(file);
}
@Nullable
public DefaultMutableTreeNode removeNode(final PsiElement element, PsiDirectory parent) {
LOG.assertTrue(parent != null, element instanceof PsiFile && ((PsiFile)element).getVirtualFile() != null ? ((PsiFile)element).getVirtualFile().getPath() : element);
final VirtualFile parentVirtualFile = parent.getVirtualFile();
Module module = myFileIndex.getModuleForFile(parentVirtualFile);
if (element instanceof PsiDirectory && myFlattenPackages) {
final PackageDependenciesNode moduleNode = getModuleNode(module);
final PsiDirectory psiDirectory = (PsiDirectory)element;
final VirtualFile virtualFile = psiDirectory.getVirtualFile();
final PackageDependenciesNode dirNode =
getModuleDirNode(virtualFile, myFileIndex.getModuleForFile(virtualFile), null);
dirNode.removeFromParent();
return moduleNode;
}
DefaultMutableTreeNode dirNode = myModuleDirNodes.get(parentVirtualFile);
if (dirNode == null) return null;
if (dirNode instanceof DirectoryNode) {
DirectoryNode wrapper = ((DirectoryNode)dirNode).getWrapper();
while (wrapper != null) {
dirNode = wrapper;
myModuleDirNodes.put(wrapper.getDirectory(), null);
wrapper = ((DirectoryNode)dirNode).getWrapper();
}
}
final PackageDependenciesNode[] classOrDirNodes = findNodeForPsiElement((PackageDependenciesNode)dirNode, element);
if (classOrDirNodes != null){
for (PackageDependenciesNode classNode : classOrDirNodes) {
classNode.removeFromParent();
}
}
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)dirNode.getParent();
DefaultMutableTreeNode node = dirNode;
if (element == parent) {
myModuleDirNodes.put(parentVirtualFile, null);
dirNode.removeFromParent();
node = parentNode;
}
while (node != null && node.getChildCount() == 0) {
PsiDirectory directory = parent.getParentDirectory();
parentNode = (DefaultMutableTreeNode)node.getParent();
node.removeFromParent();
if (node instanceof DirectoryNode) {
while (node != null) { //clear all compacted links
myModuleDirNodes.put(((DirectoryNode)node).getDirectory(), null);
node = ((DirectoryNode)node).getCompactedDirNode();
}
} else if (node instanceof ModuleNode) {
myModuleNodes.put(((ModuleNode)node).getModule(), null);
} else if (node instanceof ModuleGroupNode) {
myModuleGroupNodes.put(((ModuleGroupNode)node).getModuleGroupName(), null);
}
node = parentNode;
parent = directory;
}
if (myCompactEmptyMiddlePackages && node instanceof DirectoryNode && node.getChildCount() == 1) { //compact
final TreeNode treeNode = node.getChildAt(0);
if (treeNode instanceof DirectoryNode){
node.removeAllChildren();
for (int i = treeNode.getChildCount() - 1; i >= 0; i--){
node.add((MutableTreeNode)treeNode.getChildAt(i));
}
((DirectoryNode)node).setCompactedDirNode((DirectoryNode)treeNode);
}
}
return parentNode != null ? parentNode : myRoot;
}
@Nullable
public PackageDependenciesNode addFileNode(final PsiFile file){
boolean isMarked = myMarker != null && myMarker.isMarked(file.getVirtualFile());
if (!isMarked) return null;
final VirtualFile vFile = file.getVirtualFile();
LOG.assertTrue(vFile != null);
VirtualFile dirToReload = vFile.getParent();
PackageDependenciesNode rootToReload = myModuleDirNodes.get(dirToReload);
if (rootToReload == null && myFlattenPackages) {
final Module module = myFileIndex.getModuleForFile(vFile);
final boolean moduleNodeExist = myModuleNodes.get(module) != null;
rootToReload = getModuleNode(module);
if (!moduleNodeExist) {
rootToReload = null; //need to reload from parent / mostly for problems view
}
} else {
//reload parents of compacted nodes as getFileParentNode() may expand them
while ((rootToReload == null || (rootToReload instanceof DirectoryNode && ((DirectoryNode)rootToReload).getCompactedDirNode() != null)) && dirToReload != null){
dirToReload = dirToReload.getParent();
rootToReload = myModuleDirNodes.get(dirToReload);
}
}
PackageDependenciesNode dirNode = getFileParentNode(vFile);
if (findNodeForPsiElement(dirNode, file) == null) { //check if dir node already contains child
dirNode.add(new FileNode(vFile, myProject, isMarked));
}
return rootToReload;
}
@Nullable
public PackageDependenciesNode addDirNode(PsiDirectory dir) {
final VirtualFile vFile = dir.getVirtualFile();
if (myMarker == null) return null;
final boolean[] isMarked = new boolean[]{myMarker.isMarked(vFile)};
VirtualFile dirToReload = vFile.getParent();
PackageDependenciesNode rootToReload = myModuleDirNodes.get(dirToReload);
if (rootToReload == null && myFlattenPackages) {
final Module module = myFileIndex.getModuleForFile(vFile);
final boolean moduleNodeExist = myModuleNodes.get(module) != null;
rootToReload = getModuleNode(module);
if (!moduleNodeExist) {
rootToReload = null; //need to reload from parent / mostly for problems view
}
}
else {
while (rootToReload == null && dirToReload != null) {
dirToReload = dirToReload.getParent();
rootToReload = myModuleDirNodes.get(dirToReload);
}
}
myFileIndex.iterateContentUnderDirectory(vFile, new MyContentIterator() {
@Override
public boolean processFile(VirtualFile fileOrDir) {
isMarked[0] |= myMarker.isMarked(fileOrDir);
return super.processFile(fileOrDir);
}
});
if (!isMarked[0]) return null;
getModuleDirNode(vFile, myFileIndex.getModuleForFile(vFile), null);
return rootToReload != null ? rootToReload : myRoot;
}
@Nullable
public PackageDependenciesNode findNode(PsiFileSystemItem file, final PsiElement psiElement) {
if (file instanceof PsiDirectory) {
return getModuleDirNode(file.getVirtualFile(), myFileIndex.getModuleForFile(file.getVirtualFile()), null);
}
PackageDependenciesNode parent = getFileParentNode(file.getVirtualFile());
PackageDependenciesNode[] nodes = findNodeForPsiElement(parent, file);
if (nodes == null || nodes.length == 0) {
return null;
}
else {
for (PackageDependenciesNode node : nodes) {
if (node.getPsiElement() == psiElement) return node;
}
return nodes[0];
}
}
@Nullable
public static PackageDependenciesNode[] findNodeForPsiElement(PackageDependenciesNode parent, PsiElement element){
final Set<PackageDependenciesNode> result = new HashSet<PackageDependenciesNode>();
for (int i = 0; i < parent.getChildCount(); i++){
final TreeNode treeNode = parent.getChildAt(i);
if (treeNode instanceof PackageDependenciesNode){
final PackageDependenciesNode node = (PackageDependenciesNode)treeNode;
if (element instanceof PsiDirectory && node.getPsiElement() == element){
return new PackageDependenciesNode[] {node};
}
if (element instanceof PsiFile) {
PsiFile psiFile = null;
if (node instanceof BasePsiNode) {
psiFile = ((BasePsiNode)node).getContainingFile();
}
else if (node instanceof FileNode) { //non java files
psiFile = ((PsiFile)node.getPsiElement());
}
if (psiFile != null && Comparing.equal(psiFile.getVirtualFile(), ((PsiFile)element).getVirtualFile())) {
result.add(node);
}
}
}
}
return result.isEmpty() ? null : result.toArray(new PackageDependenciesNode[result.size()]);
}
private PackageDependenciesNode getModuleDirNode(VirtualFile virtualFile, Module module, DirectoryNode childNode) {
if (virtualFile == null) {
return getModuleNode(module);
}
PackageDependenciesNode directoryNode = myModuleDirNodes.get(virtualFile);
if (directoryNode != null) {
if (myCompactEmptyMiddlePackages) {
final DirectoryNode nestedNode = ((DirectoryNode)directoryNode).getCompactedDirNode();
if (nestedNode != null) { //decompact
boolean expand = false;
if (myTree != null){
expand = !myTree.isCollapsed(new TreePath(directoryNode.getPath()));
}
DirectoryNode parentWrapper = nestedNode.getWrapper();
while (parentWrapper.getWrapper() != null) {
parentWrapper = parentWrapper.getWrapper();
}
for (int i = parentWrapper.getChildCount() - 1; i >= 0; i--) {
nestedNode.add((MutableTreeNode)parentWrapper.getChildAt(i));
}
((DirectoryNode)directoryNode).setCompactedDirNode(null);
parentWrapper.add(nestedNode);
nestedNode.removeUpReference();
if (myTree != null && expand) {
final Runnable expandRunnable = new Runnable() {
@Override
public void run() {
myTree.expandPath(new TreePath(nestedNode.getPath()));
}
};
SwingUtilities.invokeLater(expandRunnable);
}
return parentWrapper;
}
if (directoryNode.getParent() == null) { //find first node in tree
DirectoryNode parentWrapper = ((DirectoryNode)directoryNode).getWrapper();
if (parentWrapper != null) {
while (parentWrapper.getWrapper() != null) {
parentWrapper = parentWrapper.getWrapper();
}
return parentWrapper;
}
}
}
return directoryNode;
}
final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
final VirtualFile sourceRoot = fileIndex.getSourceRootForFile(virtualFile);
final VirtualFile contentRoot = fileIndex.getContentRootForFile(virtualFile);
directoryNode = new DirectoryNode(virtualFile, myProject, myCompactEmptyMiddlePackages, myFlattenPackages, myBaseDir,
myContentRoots);
myModuleDirNodes.put(virtualFile, (DirectoryNode)directoryNode);
final VirtualFile directory = virtualFile.getParent();
if (!myFlattenPackages && directory != null) {
if (myCompactEmptyMiddlePackages && !Comparing.equal(sourceRoot, virtualFile) && !Comparing.equal(contentRoot, virtualFile)) {//compact
((DirectoryNode)directoryNode).setCompactedDirNode(childNode);
}
if (fileIndex.getModuleForFile(directory) == module) {
DirectoryNode parentDirectoryNode = myModuleDirNodes.get(directory);
if (parentDirectoryNode != null
|| !myCompactEmptyMiddlePackages
|| (sourceRoot != null && VfsUtil.isAncestor(directory, sourceRoot, false) && fileIndex.getSourceRootForFile(directory) != null)
|| Comparing.equal(directory, contentRoot)) {
getModuleDirNode(directory, module, (DirectoryNode)directoryNode).add(directoryNode);
}
else {
directoryNode = getModuleDirNode(directory, module, (DirectoryNode)directoryNode);
}
}
else {
getModuleNode(module).add(directoryNode);
}
}
else {
if (Comparing.equal(contentRoot, virtualFile)) {
getModuleNode(module).add(directoryNode);
}
else {
final VirtualFile root;
if (!Comparing.equal(sourceRoot, virtualFile) && sourceRoot != null) {
root = sourceRoot;
}
else if (contentRoot != null) {
root = contentRoot;
}
else {
root = null;
}
if (root != null) {
getModuleDirNode(root, module, null).add(directoryNode);
}
}
}
return directoryNode;
}
@Nullable
private PackageDependenciesNode getModuleNode(Module module) {
if (module == null || !myShowModules) {
return myRoot;
}
ModuleNode node = myModuleNodes.get(module);
if (node != null) return node;
node = new ModuleNode(module);
final ModuleManager moduleManager = ModuleManager.getInstance(myProject);
final String[] groupPath = moduleManager.getModuleGroupPath(module);
if (groupPath == null) {
myModuleNodes.put(module, node);
myRoot.add(node);
return node;
}
myModuleNodes.put(module, node);
if (myShowModuleGroups) {
getParentModuleGroup(groupPath).add(node);
} else {
myRoot.add(node);
}
return node;
}
private PackageDependenciesNode getParentModuleGroup(String[] groupPath){
final String key = StringUtil.join(groupPath);
ModuleGroupNode groupNode = myModuleGroupNodes.get(key);
if (groupNode == null) {
groupNode = new ModuleGroupNode(new ModuleGroup(groupPath), myProject);
myModuleGroupNodes.put(key, groupNode);
myRoot.add(groupNode);
}
if (groupPath.length > 1) {
String [] path = new String[groupPath.length - 1];
System.arraycopy(groupPath, 0, path, 0, groupPath.length - 1);
final PackageDependenciesNode node = getParentModuleGroup(path);
node.add(groupNode);
}
return groupNode;
}
private class MyContentIterator implements ContentIterator {
PackageDependenciesNode lastParent = null;
VirtualFile dir;
@Override
public boolean processFile(VirtualFile fileOrDir) {
if (!fileOrDir.isDirectory()) {
if (lastParent != null && !Comparing.equal(dir, fileOrDir.getParent())) {
lastParent = null;
}
lastParent = buildFileNode(fileOrDir, lastParent);
dir = fileOrDir.getParent();
} else {
lastParent = null;
}
return true;
}
}
}