root index: compute dependent unloaded modules

We need to show a warning about possible incomplete results if there is an unloaded module which depends on a module from the project and e.g. 'Find Usages' is invoked on an element from such module. In order to implement that we need to quickly determine if there exists an unloading module which depends on a given loaded module.
This commit is contained in:
nik
2017-06-08 18:02:30 +02:00
parent 72ec545aef
commit 9bc6d2fc15
4 changed files with 108 additions and 2 deletions

View File

@@ -17,6 +17,7 @@ package com.intellij.openapi.roots.impl;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.roots.DependencyScope;
import com.intellij.openapi.roots.ModuleRootModificationUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
@@ -43,6 +44,21 @@ public class DirectoryIndexForUnloadedModuleTest extends DirectoryIndexTestCase
assertFromUnloadedModule(contentRoot, "unloaded");
}
public void testDependentUnloadedModules() {
Module unloadedModule = createModule("unloaded");
Module main = createModule("main");
Module util = createModule("util");
Module common = createModule("common");
ModuleRootModificationUtil.addDependency(unloadedModule, main);
ModuleRootModificationUtil.addDependency(main, util);
ModuleRootModificationUtil.addDependency(main, common, DependencyScope.COMPILE, true);
ModuleManager.getInstance(myProject).setUnloadedModules(Arrays.asList("unloaded"));
assertSameElements(myIndex.getDependentUnloadedModules(main), "unloaded");
assertEmpty(myIndex.getDependentUnloadedModules(util));
assertSameElements(myIndex.getDependentUnloadedModules(common), "unloaded");
}
private void assertFromUnloadedModule(VirtualFile file, String moduleName) {
DirectoryInfo info = myIndex.getInfoForFile(file);
assertTrue(info.toString(), info.isExcluded(file));

View File

@@ -20,6 +20,7 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileTypes.FileTypeEvent;
import com.intellij.openapi.fileTypes.FileTypeListener;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ModuleRootEvent;
@@ -42,6 +43,7 @@ import org.jetbrains.annotations.TestOnly;
import org.jetbrains.jps.model.module.JpsModuleSourceRootType;
import java.util.List;
import java.util.Set;
public class DirectoryIndexImpl extends DirectoryIndex {
private static final Logger LOG = Logger.getInstance(DirectoryIndexImpl.class);
@@ -170,6 +172,13 @@ public class DirectoryIndexImpl extends DirectoryIndex {
return getRootIndex().getOrderEntries(info);
}
@Override
@NotNull
public Set<String> getDependentUnloadedModules(@NotNull Module module) {
checkAvailability();
return getRootIndex().getDependentUnloadedModules(module);
}
@TestOnly
public void assertConsistency(DirectoryInfo info) {
List<OrderEntry> entries = getOrderEntries(info);

View File

@@ -17,6 +17,7 @@
package com.intellij.openapi.roots.impl;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.OrderEntry;
import com.intellij.openapi.vfs.VirtualFile;
@@ -26,6 +27,7 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.module.JpsModuleSourceRootType;
import java.util.List;
import java.util.Set;
/**
* This class is internal low-level API. Consider using {@link com.intellij.openapi.roots.ProjectFileIndex} instead of using this class directly.
@@ -67,4 +69,10 @@ public abstract class DirectoryIndex {
@NotNull
public abstract List<OrderEntry> getOrderEntries(@NotNull DirectoryInfo info);
/**
* @return names of unloaded modules which directly or transitively via exported dependencies depend on the specified module
*/
@NotNull
public abstract Set<String> getDependentUnloadedModules(@NotNull Module module);
}

View File

@@ -20,9 +20,9 @@ import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.fileTypes.FileNameMatcherEx;
import com.intellij.openapi.fileTypes.FileTypeRegistry;
import com.intellij.openapi.fileTypes.impl.FileTypeAssocTable;
import com.intellij.openapi.module.UnloadedModuleDescription;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.module.UnloadedModuleDescription;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.roots.impl.libraries.LibraryEx;
@@ -264,6 +264,7 @@ public class RootIndex {
private static class Node {
Module myKey;
List<Edge> myEdges = new ArrayList<>();
Set<String> myUnloadedDependentModules;
@Override
public String toString() {
@@ -281,6 +282,7 @@ public class RootIndex {
Graph myGraph;
MultiMap<VirtualFile, Node> myRoots; // Map of roots to their root nodes, eg. library jar -> library node
final SynchronizedSLRUCache<VirtualFile, List<OrderEntry>> myCache;
final SynchronizedSLRUCache<Module, Set<String>> myDependentUnloadedModulesCache;
private MultiMap<VirtualFile, OrderEntry> myLibClassRootEntries;
private MultiMap<VirtualFile, OrderEntry> myLibSourceRootEntries;
@@ -296,6 +298,14 @@ public class RootIndex {
return collectOrderEntries(key);
}
};
int dependentUnloadedModulesCacheSize = ModuleManager.getInstance(project).getModules().length / 2;
myDependentUnloadedModulesCache = new SynchronizedSLRUCache<Module, Set<String>>(dependentUnloadedModulesCacheSize, dependentUnloadedModulesCacheSize) {
@NotNull
@Override
public Set<String> createValue(Module key) {
return collectDependentUnloadedModules(key);
}
};
initGraph();
initLibraryRoots();
}
@@ -305,7 +315,8 @@ public class RootIndex {
MultiMap<VirtualFile, Node> roots = MultiMap.createSmart();
for (final Module module : ModuleManager.getInstance(myProject).getModules()) {
ModuleManager moduleManager = ModuleManager.getInstance(myProject);
for (final Module module : moduleManager.getModules()) {
final ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module);
List<OrderEnumerationHandler> handlers = OrderEnumeratorBase.getCustomHandlers(module);
for (OrderEntry orderEntry : moduleRootManager.getOrderEntries()) {
@@ -336,6 +347,23 @@ public class RootIndex {
}
}
}
for (UnloadedModuleDescription description : moduleManager.getUnloadedModuleDescriptions()) {
for (String depName : description.getDependencyModuleNames()) {
Module depModule = moduleManager.findModuleByName(depName);
if (depModule != null) {
Node node = graph.myNodes.get(depModule);
if (node == null) {
node = new Node();
node.myKey = depModule;
graph.myNodes.put(depModule, node);
}
if (node.myUnloadedDependentModules == null) {
node.myUnloadedDependentModules = new LinkedHashSet<>();
}
node.myUnloadedDependentModules.add(description.getName());
}
}
}
myGraph = graph;
myRoots = roots;
@@ -419,8 +447,48 @@ public class RootIndex {
Collections.sort(result, BY_OWNER_MODULE);
return result;
}
public Set<String> getDependentUnloadedModules(@NotNull Module module) {
return myDependentUnloadedModulesCache.get(module);
}
/**
* @return names of unloaded modules which directly or transitively via exported dependencies depend on the specified module
*/
private Set<String> collectDependentUnloadedModules(@NotNull Module module) {
ArrayDeque<OrderEntryGraph.Node> stack = new ArrayDeque<>();
Node start = myGraph.myNodes.get(module);
if (start == null) return Collections.emptySet();
stack.push(start);
Set<Node> seen = new HashSet<>();
Set<String> result = null;
while (!stack.isEmpty()) {
Node node = stack.pop();
if (!seen.add(node)) {
continue;
}
if (node.myUnloadedDependentModules != null) {
if (result == null) {
result = new LinkedHashSet<>(node.myUnloadedDependentModules);
}
else {
result.addAll(node.myUnloadedDependentModules);
}
}
for (Edge edge : node.myEdges) {
if (edge.myRecursive) {
Node targetNode = myGraph.myNodes.get(edge.myKey);
if (targetNode != null) {
stack.push(targetNode);
}
}
}
}
return result != null ? result : Collections.emptySet();
}
}
private int getRootTypeId(@NotNull JpsModuleSourceRootType<?> rootType) {
if (myRootTypeId.containsKey(rootType)) {
return myRootTypeId.get(rootType);
@@ -862,6 +930,11 @@ public class RootIndex {
return getOrderEntryGraph().getOrderEntries(((DirectoryInfoImpl)info).getRoot());
}
@NotNull
public Set<String> getDependentUnloadedModules(@NotNull Module module) {
return getOrderEntryGraph().getDependentUnloadedModules(module);
}
public interface InfoCache {
@Nullable
DirectoryInfo getCachedInfo(@NotNull VirtualFile dir);