blob: 994f2dda4796372627a5aa84b8f0899bfe1c54e7 [file] [log] [blame]
/*
* Copyright 2000-2014 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.icons.AllIcons;
import com.intellij.ide.projectView.impl.ModuleGroup;
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.project.Project;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.roots.libraries.LibraryUtil;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileVisitor;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiPackage;
import com.intellij.util.ui.tree.TreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class TreeModelBuilder {
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;
protected final JavaPsiFacade myJavaPsiFacade;
private static final Key<Integer> FILE_COUNT = Key.create("packages.FILE_COUNT");
private enum ScopeType {
TEST, SOURCE, LIB
}
private final boolean myShowModules;
private final boolean myGroupByScopeType;
private final boolean myFlattenPackages;
private boolean myShowFiles;
private final boolean myShowIndividualLibs;
private final Marker myMarker;
private final boolean myAddUnmarkedFiles;
private final PackageDependenciesNode myRoot;
private final Map<ScopeType, Map<Pair<Module, PsiPackage>, PackageNode>> myModulePackageNodes = new HashMap<ScopeType, Map<Pair<Module, PsiPackage>, PackageNode>>();
private final Map<ScopeType, Map<Pair<OrderEntry, PsiPackage>, PackageNode>> myLibraryPackageNodes = new HashMap<ScopeType, Map<Pair<OrderEntry, PsiPackage>, PackageNode>>();
private final Map<ScopeType, Map<Module, ModuleNode>> myModuleNodes = new HashMap<ScopeType, Map<Module, ModuleNode>>();
private final Map<ScopeType, Map<String, ModuleGroupNode>> myModuleGroupNodes = new HashMap<ScopeType, Map<String, ModuleGroupNode>>();
private final Map<ScopeType, Map<OrderEntry, LibraryNode>> myLibraryNodes = new HashMap<ScopeType, Map<OrderEntry, LibraryNode>>();
private int myScannedFileCount = 0;
private int myTotalFileCount = 0;
private int myMarkedFileCount = 0;
private GeneralGroupNode myAllLibsNode = null;
private GeneralGroupNode mySourceRoot = null;
private GeneralGroupNode myTestRoot = null;
private GeneralGroupNode myLibsRoot = null;
public static final String PRODUCTION_NAME = AnalysisScopeBundle.message("package.dependencies.production.node.text");
public static final String TEST_NAME = AnalysisScopeBundle.message("package.dependencies.test.node.text");
public static final String LIBRARY_NAME = AnalysisScopeBundle.message("package.dependencies.library.node.text");
public TreeModelBuilder(Project project, boolean showIndividualLibs, Marker marker, DependenciesPanel.DependencyPanelSettings settings) {
myProject = project;
final boolean multiModuleProject = ModuleManager.getInstance(project).getModules().length > 1;
myShowModules = settings.UI_SHOW_MODULES && multiModuleProject;
myGroupByScopeType = settings.UI_GROUP_BY_SCOPE_TYPE;
myFlattenPackages = settings.UI_FLATTEN_PACKAGES;
myShowFiles = settings.UI_SHOW_FILES;
myShowIndividualLibs = showIndividualLibs;
myShowModuleGroups = settings.UI_SHOW_MODULE_GROUPS && multiModuleProject;
myMarker = marker;
myAddUnmarkedFiles = !settings.UI_FILTER_LEGALS;
myRoot = new RootNode(project);
myFileIndex = ProjectRootManager.getInstance(project).getFileIndex();
createMaps(ScopeType.LIB);
createMaps(ScopeType.SOURCE);
createMaps(ScopeType.TEST);
if (myGroupByScopeType) {
mySourceRoot = new GeneralGroupNode(PRODUCTION_NAME, AllIcons.Modules.SourceFolder, project);
myTestRoot = new GeneralGroupNode(TEST_NAME, AllIcons.Modules.TestSourceFolder, project);
myLibsRoot = new GeneralGroupNode(LIBRARY_NAME, AllIcons.Nodes.PpLibFolder, project);
myRoot.add(mySourceRoot);
myRoot.add(myTestRoot);
myRoot.add(myLibsRoot);
}
myJavaPsiFacade = JavaPsiFacade.getInstance(myProject);
}
private void createMaps(ScopeType scopeType) {
myModulePackageNodes.put(scopeType, new HashMap<Pair<Module, PsiPackage>, PackageNode>());
myLibraryPackageNodes.put(scopeType, new HashMap<Pair<OrderEntry, PsiPackage>, PackageNode>());
myModuleGroupNodes.put(scopeType, new HashMap<String, ModuleGroupNode>());
myModuleNodes.put(scopeType, new HashMap<Module, ModuleNode>());
myLibraryNodes.put(scopeType, new HashMap<OrderEntry, LibraryNode>());
}
public static synchronized TreeModel createTreeModel(Project project, boolean showProgress, Set<PsiFile> files, Marker marker, DependenciesPanel.DependencyPanelSettings settings) {
return new TreeModelBuilder(project, true, marker, settings).build(files, showProgress);
}
public static synchronized TreeModel createTreeModel(Project project, Marker marker, DependenciesPanel.DependencyPanelSettings settings) {
return new TreeModelBuilder(project, true, marker, settings).build(project);
}
public static synchronized TreeModel createTreeModel(Project project,
boolean showIndividualLibs,
Marker marker) {
return new TreeModelBuilder(project, showIndividualLibs, marker, new DependenciesPanel.DependencyPanelSettings()).build(project);
}
private void countFiles(Project project) {
final Integer fileCount = project.getUserData(FILE_COUNT);
if (fileCount == null) {
myFileIndex.iterateContent(new ContentIterator() {
public boolean processFile(VirtualFile fileOrDir) {
if (!fileOrDir.isDirectory()) {
counting();
}
return true;
}
});
for (VirtualFile root : LibraryUtil.getLibraryRoots(project)) {
countFilesRecursively(root);
}
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) {
Runnable buildingRunnable = new Runnable() {
public void run() {
countFiles(project);
myFileIndex.iterateContent(new ContentIterator() {
PackageDependenciesNode lastParent = null;
VirtualFile dir = null;
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;
}
});
for (VirtualFile root : LibraryUtil.getLibraryRoots(project)) {
processFilesRecursively(root);
}
}
};
buildingRunnable.run();
return new TreeModel(myRoot, myTotalFileCount, myMarkedFileCount);
}
private void processFilesRecursively(@NotNull VirtualFile file) {
VfsUtilCore.visitChildrenRecursively(file, new VirtualFileVisitor() {
private PackageDependenciesNode parent = null;
@Override
public boolean visitFile(@NotNull VirtualFile file) {
if (file.isDirectory()) {
parent = null;
}
else {
parent = buildFileNode(file, parent);
}
return true;
}
@Override
public void afterChildrenVisited(@NotNull VirtualFile file) {
if (file.isDirectory()) {
parent = null;
}
}
});
}
private void countFilesRecursively(VirtualFile file) {
VfsUtilCore.visitChildrenRecursively(file, new VirtualFileVisitor() {
@Override
public boolean visitFile(@NotNull VirtualFile file) {
if (!file.isDirectory()) {
counting();
}
return true;
}
});
}
private void counting() {
myTotalFileCount++;
final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
if (indicator != null) {
((PanelProgressIndicator)indicator).update(SCANNING_PACKAGES_MESSAGE, true, 0);
}
}
private TreeModel build(final Set<PsiFile> files, boolean showProgress) {
if (files.size() == 1) {
myShowFiles = true;
}
Runnable buildingRunnable = new Runnable() {
public void run() {
for (final PsiFile file : files) {
if (file != null) {
buildFileNode(file.getVirtualFile(), null);
}
}
}
};
if (showProgress) {
final String title = AnalysisScopeBundle.message("package.dependencies.build.process.title");
ProgressManager.getInstance().runProcessWithProgressSynchronously(buildingRunnable, title, false, myProject);
}
else {
buildingRunnable.run();
}
TreeUtil.sort(myRoot, new DependencyNodeComparator());
return new TreeModel(myRoot, myTotalFileCount, myMarkedFileCount);
}
@Nullable
private PackageDependenciesNode buildFileNode(final VirtualFile file, @Nullable PackageDependenciesNode parent) {
final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
if (indicator != null) {
((PanelProgressIndicator)indicator).update(SCANNING_PACKAGES_MESSAGE, false, ((double)myScannedFileCount++) / myTotalFileCount);
}
boolean isMarked = myMarker != null && myMarker.isMarked(file);
if (isMarked) myMarkedFileCount++;
if (isMarked || myAddUnmarkedFiles) {
PackageDependenciesNode dirNode = parent != null ? parent : getFileParentNode(file);
if (dirNode == null) return null;
if (myShowFiles) {
FileNode fileNode = new FileNode(file, myProject, isMarked);
dirNode.add(fileNode);
}
else {
dirNode.addFile(file, isMarked);
}
return dirNode;
}
return null;
}
public @Nullable PackageDependenciesNode getFileParentNode(VirtualFile vFile) {
LOG.assertTrue(vFile != null);
final VirtualFile containingDirectory = vFile.getParent();
LOG.assertTrue(containingDirectory != null);
PsiPackage aPackage = null;
final String packageName = myFileIndex.getPackageNameByDirectory(containingDirectory);
if (packageName != null) {
aPackage = myJavaPsiFacade.findPackage(packageName);
}
if (aPackage != null) {
if (myFileIndex.isInLibrarySource(vFile) || myFileIndex.isInLibraryClasses(vFile)) {
return getLibraryDirNode(aPackage, getLibraryForFile(vFile));
}
else {
return getModuleDirNode(aPackage, myFileIndex.getModuleForFile(vFile), getFileScopeType(vFile));
}
}
return myFileIndex.isInLibrarySource(vFile) ? null : getModuleNode(myFileIndex.getModuleForFile(vFile), getFileScopeType(vFile));
}
private ScopeType getFileScopeType(VirtualFile file) {
if (myFileIndex.isLibraryClassFile(file) || myFileIndex.isInLibrarySource(file)) return ScopeType.LIB;
if (myFileIndex.isInTestSourceContent(file)) return ScopeType.TEST;
return ScopeType.SOURCE;
}
@Nullable
private OrderEntry getLibraryForFile(VirtualFile virtualFile) {
if (virtualFile == null) return null;
List<OrderEntry> orders = myFileIndex.getOrderEntriesForFile(virtualFile);
for (OrderEntry order : orders) {
if (order instanceof LibraryOrderEntry || order instanceof JdkOrderEntry) return order;
}
return null;
}
private <T> T getMap(Map<ScopeType, T> map, ScopeType scopeType) {
return map.get(myGroupByScopeType ? scopeType : ScopeType.SOURCE);
}
private PackageDependenciesNode getLibraryDirNode(PsiPackage aPackage, OrderEntry libraryOrJdk) {
if (aPackage == null || aPackage.getName() == null) {
return getLibraryOrJDKNode(libraryOrJdk);
}
if (!myShowModules && !myGroupByScopeType) {
return getModuleDirNode(aPackage, null, ScopeType.LIB);
}
Pair<OrderEntry, PsiPackage> descriptor = Pair.create(myShowIndividualLibs ? libraryOrJdk : null, aPackage);
PackageNode node = getMap(myLibraryPackageNodes, ScopeType.LIB).get(descriptor);
if (node != null) return node;
node = new PackageNode(aPackage, myFlattenPackages);
getMap(myLibraryPackageNodes, ScopeType.LIB).put(descriptor, node);
if (myFlattenPackages) {
getLibraryOrJDKNode(libraryOrJdk).add(node);
}
else {
getLibraryDirNode(aPackage.getParentPackage(), libraryOrJdk).add(node);
}
return node;
}
private PackageDependenciesNode getModuleDirNode(PsiPackage aPackage, Module module, ScopeType scopeType) {
if (aPackage == null) {
return getModuleNode(module, scopeType);
}
Pair<Module, PsiPackage> descriptor = Pair.create(myShowModules ? module : null, aPackage);
PackageNode node = getMap(myModulePackageNodes, scopeType).get(descriptor);
if (node != null) return node;
node = new PackageNode(aPackage, myFlattenPackages);
getMap(myModulePackageNodes, scopeType).put(descriptor, node);
if (myFlattenPackages) {
getModuleNode(module, scopeType).add(node);
}
else {
getModuleDirNode(aPackage.getParentPackage(), module, scopeType).add(node);
}
return node;
}
@Nullable
private PackageDependenciesNode getModuleNode(Module module, ScopeType scopeType) {
if (module == null || !myShowModules) {
return getRootNode(scopeType);
}
ModuleNode node = getMap(myModuleNodes, scopeType).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) {
getMap(myModuleNodes, scopeType).put(module, node);
getRootNode(scopeType).add(node);
return node;
}
getMap(myModuleNodes, scopeType).put(module, node);
if (myShowModuleGroups) {
getParentModuleGroup(groupPath, scopeType).add(node);
} else {
getRootNode(scopeType).add(node);
}
return node;
}
private PackageDependenciesNode getParentModuleGroup(String [] groupPath, ScopeType scopeType){
final String key = StringUtil.join(groupPath, ",");
ModuleGroupNode groupNode = getMap(myModuleGroupNodes, scopeType).get(key);
if (groupNode == null) {
groupNode = new ModuleGroupNode(new ModuleGroup(groupPath), myProject);
getMap(myModuleGroupNodes, scopeType).put(key, groupNode);
getRootNode(scopeType).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, scopeType);
node.add(groupNode);
}
return groupNode;
}
private PackageDependenciesNode getLibraryOrJDKNode(OrderEntry libraryOrJdk) {
if (libraryOrJdk == null || !myShowModules) {
return getRootNode(ScopeType.LIB);
}
if (!myShowIndividualLibs) {
if (myGroupByScopeType) return getRootNode(ScopeType.LIB);
if (myAllLibsNode == null) {
myAllLibsNode = new GeneralGroupNode(AnalysisScopeBundle.message("dependencies.libraries.node.text"),
AllIcons.Nodes.PpLibFolder,
myProject);
getRootNode(ScopeType.LIB).add(myAllLibsNode);
}
return myAllLibsNode;
}
LibraryNode node = getMap(myLibraryNodes, ScopeType.LIB).get(libraryOrJdk);
if (node != null) return node;
node = new LibraryNode(libraryOrJdk, myProject);
getMap(myLibraryNodes, ScopeType.LIB).put(libraryOrJdk, node);
getRootNode(ScopeType.LIB).add(node);
return node;
}
@NotNull
private PackageDependenciesNode getRootNode(ScopeType scopeType) {
if (!myGroupByScopeType) {
return myRoot;
}
else {
if (scopeType == ScopeType.TEST) {
return myTestRoot;
}
else if (scopeType == ScopeType.SOURCE) {
return mySourceRoot;
}
else {
return myLibsRoot;
}
}
}
}