diff --git a/java/java-tests/testSrc/com/intellij/openapi/roots/impl/DirectoryIndexTest.java b/java/java-tests/testSrc/com/intellij/openapi/roots/impl/DirectoryIndexTest.java index 79d9410e110f..bf4d6b65f0b9 100644 --- a/java/java-tests/testSrc/com/intellij/openapi/roots/impl/DirectoryIndexTest.java +++ b/java/java-tests/testSrc/com/intellij/openapi/roots/impl/DirectoryIndexTest.java @@ -461,16 +461,23 @@ public class DirectoryIndexTest extends IdeaTestCase { }); } + private static OrderEntry[] toArray(Collection orderEntries) { + return orderEntries.toArray(new OrderEntry[orderEntries.size()]); + } + public void testModuleSourceAsLibrarySource() throws Exception { ModuleRootModificationUtil.addModuleLibrary(myModule, "someLib", Collections.emptyList(), Arrays.asList(mySrcDir1.getUrl())); checkInfo(mySrcDir1, myModule, false, true, "", JavaSourceRootType.SOURCE, myModule, myModule); - OrderEntry[] entries = myIndex.getOrderEntries(myIndex.getInfoForFile(mySrcDir1)); + Collection entriesResult = myIndex.getOrderEntries(myIndex.getInfoForFile(mySrcDir1)); + OrderEntry[] entries = toArray(entriesResult); + assertInstanceOf(entries[0], LibraryOrderEntry.class); assertInstanceOf(entries[1], ModuleSourceOrderEntry.class); checkInfo(myTestSrc1, myModule, false, true, "testSrc", JavaSourceRootType.TEST_SOURCE, myModule, myModule); - entries = myIndex.getOrderEntries(myIndex.getInfoForFile(myTestSrc1)); + entriesResult = myIndex.getOrderEntries(myIndex.getInfoForFile(myTestSrc1)); + entries = toArray(entriesResult); assertInstanceOf(entries[0], LibraryOrderEntry.class); assertInstanceOf(entries[1], ModuleSourceOrderEntry.class); } @@ -478,7 +485,7 @@ public class DirectoryIndexTest extends IdeaTestCase { public void testModuleSourceAsLibraryClasses() throws Exception { ModuleRootModificationUtil.addModuleLibrary(myModule, "someLib", Arrays.asList(mySrcDir1.getUrl()), Collections.emptyList()); checkInfo(mySrcDir1, myModule, true, false, "", JavaSourceRootType.SOURCE, myModule); - assertInstanceOf(assertOneElement(myIndex.getOrderEntries(assertInProject(mySrcDir1))), ModuleSourceOrderEntry.class); + assertInstanceOf(assertOneElement(toArray(myIndex.getOrderEntries(assertInProject(mySrcDir1)))), ModuleSourceOrderEntry.class); } public void testModulesWithSameSourceContentRoot() { @@ -631,7 +638,7 @@ public class DirectoryIndexTest extends IdeaTestCase { checkInfo(myLibSrcDir, myModule, true, true, "", null, myModule, myModule3); checkInfo(myResDir, myModule, true, false, "", JavaResourceRootType.RESOURCE, myModule); - assertInstanceOf(assertOneElement(myIndex.getOrderEntries(assertInProject(myResDir))), ModuleSourceOrderEntry.class); + assertInstanceOf(assertOneElement(toArray(myIndex.getOrderEntries(assertInProject(myResDir)))), ModuleSourceOrderEntry.class); checkInfo(myExcludedLibSrcDir, null, true, false, "lib.src.exc", null, myModule3, myModule); checkInfo(myExcludedLibClsDir, null, true, false, "lib.cls.exc", null, myModule3); @@ -899,10 +906,10 @@ public class DirectoryIndexTest extends IdeaTestCase { assertEquals(packageName, myFileIndex.getPackageNameByDirectory(file)); } - assertEquals(Arrays.toString(myIndex.getOrderEntries(info)), modulesOfOrderEntries.length, myIndex.getOrderEntries(info).length); + assertEquals(Arrays.toString(toArray(myIndex.getOrderEntries(info))), modulesOfOrderEntries.length, toArray(myIndex.getOrderEntries(info)).length); for (Module aModule : modulesOfOrderEntries) { OrderEntry found = ModuleFileIndexImpl.findOrderEntryWithOwnerModule(aModule, myIndex.getOrderEntries(info)); - assertNotNull("not found: " + aModule + " in " + Arrays.toString(myIndex.getOrderEntries(info)), found); + assertNotNull("not found: " + aModule + " in " + Arrays.toString(toArray(myIndex.getOrderEntries(info))), found); } } diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/impl/DirectoryIndexImpl.java b/platform/lang-impl/src/com/intellij/openapi/roots/impl/DirectoryIndexImpl.java index a793b05250b9..3a2117f8413e 100644 --- a/platform/lang-impl/src/com/intellij/openapi/roots/impl/DirectoryIndexImpl.java +++ b/platform/lang-impl/src/com/intellij/openapi/roots/impl/DirectoryIndexImpl.java @@ -162,16 +162,16 @@ public class DirectoryIndexImpl extends DirectoryIndex { @NotNull @Override - public OrderEntry[] getOrderEntries(@NotNull DirectoryInfo info) { + public List getOrderEntries(@NotNull DirectoryInfo info) { checkAvailability(); return getRootIndex().getOrderEntries(info); } @TestOnly void assertConsistency(DirectoryInfo info) { - OrderEntry[] entries = getOrderEntries(info); - for (int i = 1; i < entries.length; i++) { - assert RootIndex.BY_OWNER_MODULE.compare(entries[i - 1], entries[i]) <= 0; + List entries = getOrderEntries(info); + for (int i = 1; i < entries.size(); i++) { + assert RootIndex.BY_OWNER_MODULE.compare(entries.get(i - 1), entries.get(i)) <= 0; } } diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/OrderEnumerationHandler.java b/platform/projectModel-api/src/com/intellij/openapi/roots/OrderEnumerationHandler.java similarity index 98% rename from platform/projectModel-impl/src/com/intellij/openapi/roots/OrderEnumerationHandler.java rename to platform/projectModel-api/src/com/intellij/openapi/roots/OrderEnumerationHandler.java index 6cd1ad41a6bd..a227ce64ff6a 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/roots/OrderEnumerationHandler.java +++ b/platform/projectModel-api/src/com/intellij/openapi/roots/OrderEnumerationHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2010 JetBrains s.r.o. + * Copyright 2000-2015 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. diff --git a/platform/projectModel-api/src/com/intellij/openapi/roots/OrderEnumerator.java b/platform/projectModel-api/src/com/intellij/openapi/roots/OrderEnumerator.java index 7a2d0b089565..03affd0742d9 100644 --- a/platform/projectModel-api/src/com/intellij/openapi/roots/OrderEnumerator.java +++ b/platform/projectModel-api/src/com/intellij/openapi/roots/OrderEnumerator.java @@ -25,6 +25,8 @@ import com.intellij.util.PathsList; import com.intellij.util.Processor; import org.jetbrains.annotations.NotNull; +import java.util.List; + /** * Interface for convenient processing dependencies of a module or a project. Allows to process {@link OrderEntry}s and collect classes * and source roots.

@@ -118,6 +120,21 @@ public abstract class OrderEnumerator { */ public abstract OrderEnumerator using(@NotNull RootModelProvider provider); + /** + * Determine if, given the current enumerator settings and handlers for a module, should the + * enumerator recurse to further modules based on the given ModuleOrderEntry? + * + * @param entry the ModuleOrderEntry in question (m1 -> m2) + * @param handlers custom handlers registered to the module + * @return true if the enumerator would have recursively processed the given ModuleOrderEntry. + */ + public abstract boolean shouldRecurse(@NotNull ModuleOrderEntry entry, @NotNull List handlers); + + /** + * @return a list of {@link OrderEnumerationHandler} registered to a module. + */ + public abstract List getCustomHandlers(@NotNull Module module); + /** * @return {@link OrderRootsEnumerator} instance for processing classes roots */ diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/OrderEnumeratorSettings.java b/platform/projectModel-api/src/com/intellij/openapi/roots/OrderEnumeratorSettings.java similarity index 94% rename from platform/projectModel-impl/src/com/intellij/openapi/roots/OrderEnumeratorSettings.java rename to platform/projectModel-api/src/com/intellij/openapi/roots/OrderEnumeratorSettings.java index 15f38d43fd55..509be0860b41 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/roots/OrderEnumeratorSettings.java +++ b/platform/projectModel-api/src/com/intellij/openapi/roots/OrderEnumeratorSettings.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2011 JetBrains s.r.o. + * Copyright 2000-2015 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. diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/DirectoryIndex.java b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/DirectoryIndex.java index 70eb213d86c8..88c0ec7b04f2 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/DirectoryIndex.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/DirectoryIndex.java @@ -25,6 +25,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.model.module.JpsModuleSourceRootType; +import java.util.List; + public abstract class DirectoryIndex { public static DirectoryIndex getInstance(Project project) { assert !project.isDefault() : "Must not call DirectoryIndex for default project"; @@ -61,5 +63,5 @@ public abstract class DirectoryIndex { } @NotNull - public abstract OrderEntry[] getOrderEntries(@NotNull DirectoryInfo info); + public abstract List getOrderEntries(@NotNull DirectoryInfo info); } diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/ModuleFileIndexImpl.java b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/ModuleFileIndexImpl.java index 2a3b3e5c5c27..1ea6a212a9c5 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/ModuleFileIndexImpl.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/ModuleFileIndexImpl.java @@ -31,10 +31,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes; import org.jetbrains.jps.model.module.JpsModuleSourceRootType; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Set; +import java.util.*; public class ModuleFileIndexImpl extends FileIndexBase implements ModuleFileIndex { private final Module myModule; @@ -116,42 +113,41 @@ public class ModuleFileIndexImpl extends FileIndexBase implements ModuleFileInde } @Nullable - static OrderEntry findOrderEntryWithOwnerModule(@NotNull Module ownerModule, @NotNull OrderEntry[] orderEntries) { - if (orderEntries.length < 10) { - for (OrderEntry entry : orderEntries) { - if (entry.getOwnerModule() == ownerModule) return entry; + static OrderEntry findOrderEntryWithOwnerModule(@NotNull Module ownerModule, @NotNull List orderEntries) { + if (orderEntries.size() < 10) { + for (OrderEntry orderEntry : orderEntries) { + if (orderEntry.getOwnerModule() == ownerModule) { + return orderEntry; + } } return null; } - int index = Arrays.binarySearch(orderEntries, new FakeOrderEntry(ownerModule), RootIndex.BY_OWNER_MODULE); - return index < 0 ? null : orderEntries[index]; + int index = Collections.binarySearch(orderEntries, new FakeOrderEntry(ownerModule), RootIndex.BY_OWNER_MODULE); + return index < 0 ? null : orderEntries.get(index); } @NotNull - private static List findAllOrderEntriesWithOwnerModule(@NotNull Module ownerModule, @NotNull OrderEntry[] entries) { - if (entries.length == 0) return Collections.emptyList(); + private static List findAllOrderEntriesWithOwnerModule(@NotNull Module ownerModule, @NotNull List entries) { + if (entries.size() == 0) return Collections.emptyList(); - if (entries.length == 1) { - OrderEntry entry = entries[0]; - return entry.getOwnerModule() == ownerModule ? Arrays.asList(entries) : Collections.emptyList(); + if (entries.size() == 1) { + OrderEntry entry = entries.get(0); + return entry.getOwnerModule() == ownerModule ? + ContainerUtil.newArrayList(entries) : Collections.emptyList(); } - int index = Arrays.binarySearch(entries, new FakeOrderEntry(ownerModule), RootIndex.BY_OWNER_MODULE); + int index = Collections.binarySearch(entries, new FakeOrderEntry(ownerModule), RootIndex.BY_OWNER_MODULE); if (index < 0) { return Collections.emptyList(); } int firstIndex = index; - while (firstIndex - 1 >= 0 && entries[firstIndex - 1].getOwnerModule() == ownerModule) { + while (firstIndex - 1 >= 0 && entries.get(firstIndex - 1).getOwnerModule() == ownerModule) { firstIndex--; } int lastIndex = index + 1; - while (lastIndex < entries.length && entries[lastIndex].getOwnerModule() == ownerModule) { + while (lastIndex < entries.size() && entries.get(lastIndex).getOwnerModule() == ownerModule) { lastIndex++; } - - OrderEntry[] subArray = new OrderEntry[lastIndex - firstIndex]; - System.arraycopy(entries, firstIndex, subArray, 0, lastIndex - firstIndex); - - return Arrays.asList(subArray); + return ContainerUtil.newArrayList(entries.subList(firstIndex, lastIndex)); } private static class FakeOrderEntry implements OrderEntry { diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/OrderEnumeratorBase.java b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/OrderEnumeratorBase.java index 2195e887b031..14d7bb6f656b 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/OrderEnumeratorBase.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/OrderEnumeratorBase.java @@ -55,7 +55,8 @@ abstract class OrderEnumeratorBase extends OrderEnumerator implements OrderEnume myCache = cache; } - protected List getCustomHandlers(Module module) { + @Override + public List getCustomHandlers(@NotNull Module module) { List customHandlers = null; for (OrderEnumerationHandler.Factory handlerFactory : OrderEnumerationHandler.EP_NAME.getExtensions()) { if (handlerFactory.isApplicable(module)) { @@ -197,63 +198,108 @@ abstract class OrderEnumeratorBase extends OrderEnumerator implements OrderEnume return flags; } + @Override + public boolean shouldRecurse(@NotNull ModuleOrderEntry entry, @NotNull List handlers) { + ProcessEntryAction action = shouldAddOrRecurse(entry, true, handlers); + return action.type == ProcessEntryActionType.RECURSE; + } + + // Should process, should recurse, or not process at all. + protected enum ProcessEntryActionType { + SKIP, + RECURSE, + PROCESS + } + + protected static class ProcessEntryAction { + public ProcessEntryActionType type; + @Nullable public Module recurseOnModule; + + private ProcessEntryAction(ProcessEntryActionType type) { + this.type = type; + } + + public static final ProcessEntryAction SKIP = new ProcessEntryAction(ProcessEntryActionType.SKIP); + + public static ProcessEntryAction RECURSE(@NotNull Module module) { + ProcessEntryAction result = new ProcessEntryAction(ProcessEntryActionType.RECURSE); + result.recurseOnModule = module; + return result; + } + + public static final ProcessEntryAction PROCESS = new ProcessEntryAction(ProcessEntryActionType.PROCESS); + } + + protected ProcessEntryAction shouldAddOrRecurse(OrderEntry entry, boolean firstLevel, List customHandlers) { + if (myCondition != null && !myCondition.value(entry)) return ProcessEntryAction.SKIP; + + if (entry instanceof JdkOrderEntry && (myWithoutJdk || !firstLevel)) return ProcessEntryAction.SKIP; + if (myWithoutLibraries && entry instanceof LibraryOrderEntry) return ProcessEntryAction.SKIP; + if (myWithoutDepModules) { + if (!myRecursively && entry instanceof ModuleOrderEntry) return ProcessEntryAction.SKIP; + if (entry instanceof ModuleSourceOrderEntry && !isRootModuleModel(((ModuleSourceOrderEntry)entry).getRootModel())) { + return ProcessEntryAction.SKIP; + } + } + if (myWithoutModuleSourceEntries && entry instanceof ModuleSourceOrderEntry) return ProcessEntryAction.SKIP; + + OrderEnumerationHandler.AddDependencyType shouldAdd = OrderEnumerationHandler.AddDependencyType.DEFAULT; + for (OrderEnumerationHandler handler : customHandlers) { + shouldAdd = handler.shouldAddDependency(entry, this); + if (shouldAdd != OrderEnumerationHandler.AddDependencyType.DEFAULT) break; + } + if (shouldAdd == OrderEnumerationHandler.AddDependencyType.DO_NOT_ADD) { + return ProcessEntryAction.SKIP; + } + + boolean exported = !(entry instanceof JdkOrderEntry); + if (entry instanceof ExportableOrderEntry) { + ExportableOrderEntry exportableEntry = (ExportableOrderEntry)entry; + if (shouldAdd == OrderEnumerationHandler.AddDependencyType.DEFAULT) { + final DependencyScope scope = exportableEntry.getScope(); + boolean forTestCompile = scope.isForTestCompile() || + scope == DependencyScope.RUNTIME && shouldAddRuntimeDependenciesToTestCompilationClasspath(customHandlers); + if (myCompileOnly && !scope.isForProductionCompile() && !forTestCompile) return ProcessEntryAction.SKIP; + if (myRuntimeOnly && !scope.isForProductionRuntime() && !scope.isForTestRuntime()) return ProcessEntryAction.SKIP; + if (myProductionOnly) { + if (!scope.isForProductionCompile() && !scope.isForProductionRuntime() || + myCompileOnly && !scope.isForProductionCompile() || + myRuntimeOnly && !scope.isForProductionRuntime()) { + return ProcessEntryAction.SKIP; + } + } + } + exported = exportableEntry.isExported(); + } + if (!exported) { + if (myExportedOnly) return ProcessEntryAction.SKIP; + if (myRecursivelyExportedOnly && !firstLevel) return ProcessEntryAction.SKIP; + } + if (myRecursively && entry instanceof ModuleOrderEntry) { + ModuleOrderEntry moduleOrderEntry = (ModuleOrderEntry)entry; + final Module depModule = moduleOrderEntry.getModule(); + if (depModule != null && shouldProcessRecursively(customHandlers)) { + return ProcessEntryAction.RECURSE(depModule); + } + } + if (myWithoutDepModules && entry instanceof ModuleOrderEntry) return ProcessEntryAction.SKIP; + return ProcessEntryAction.PROCESS; + } + protected void processEntries(final ModuleRootModel rootModel, PairProcessor> processor, Set processed, boolean firstLevel, final List customHandlers) { if (processed != null && !processed.add(rootModel.getModule())) return; for (OrderEntry entry : rootModel.getOrderEntries()) { - if (myCondition != null && !myCondition.value(entry)) continue; - - if (entry instanceof JdkOrderEntry && (myWithoutJdk || !firstLevel)) continue; - if (myWithoutLibraries && entry instanceof LibraryOrderEntry) continue; - if (myWithoutDepModules) { - if (!myRecursively && entry instanceof ModuleOrderEntry) continue; - if (entry instanceof ModuleSourceOrderEntry && !isRootModuleModel(((ModuleSourceOrderEntry)entry).getRootModel())) continue; + ProcessEntryAction action = shouldAddOrRecurse(entry, firstLevel, customHandlers); + if (action.type == ProcessEntryActionType.SKIP) { + continue; } - if (myWithoutModuleSourceEntries && entry instanceof ModuleSourceOrderEntry) continue; - - OrderEnumerationHandler.AddDependencyType shouldAdd = OrderEnumerationHandler.AddDependencyType.DEFAULT; - for (OrderEnumerationHandler handler : customHandlers) { - shouldAdd = handler.shouldAddDependency(entry, this); - if (shouldAdd != OrderEnumerationHandler.AddDependencyType.DEFAULT) break; + if (action.type == ProcessEntryActionType.RECURSE) { + processEntries(getRootModel(action.recurseOnModule), processor, processed, false, customHandlers); + continue; } - if (shouldAdd == OrderEnumerationHandler.AddDependencyType.DO_NOT_ADD) continue; - - boolean exported = !(entry instanceof JdkOrderEntry); - - if (entry instanceof ExportableOrderEntry) { - ExportableOrderEntry exportableEntry = (ExportableOrderEntry)entry; - if (shouldAdd == OrderEnumerationHandler.AddDependencyType.DEFAULT) { - final DependencyScope scope = exportableEntry.getScope(); - boolean forTestCompile = scope.isForTestCompile() || scope == DependencyScope.RUNTIME && shouldAddRuntimeDependenciesToTestCompilationClasspath( - customHandlers); - if (myCompileOnly && !scope.isForProductionCompile() && !forTestCompile) continue; - if (myRuntimeOnly && !scope.isForProductionRuntime() && !scope.isForTestRuntime()) continue; - if (myProductionOnly) { - if (!scope.isForProductionCompile() && !scope.isForProductionRuntime() - || myCompileOnly && !scope.isForProductionCompile() - || myRuntimeOnly && !scope.isForProductionRuntime()) { - continue; - } - } - } - exported = exportableEntry.isExported(); - } - if (!exported) { - if (myExportedOnly) continue; - if (myRecursivelyExportedOnly && !firstLevel) continue; - } - - if (myRecursively && entry instanceof ModuleOrderEntry) { - ModuleOrderEntry moduleOrderEntry = (ModuleOrderEntry)entry; - final Module module = moduleOrderEntry.getModule(); - if (module != null && shouldProcessRecursively(customHandlers)) { - processEntries(getRootModel(module), processor, processed, false, customHandlers); - continue; - } - } - - if (myWithoutDepModules && entry instanceof ModuleOrderEntry) continue; + assert action.type == ProcessEntryActionType.PROCESS; if (!processor.process(entry, customHandlers)) { return; } diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/ProjectFileIndexImpl.java b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/ProjectFileIndexImpl.java index df33d358b370..28cc4c0730d3 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/ProjectFileIndexImpl.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/ProjectFileIndexImpl.java @@ -37,7 +37,6 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes; import org.jetbrains.jps.model.module.JpsModuleSourceRootType; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; @@ -131,7 +130,7 @@ public class ProjectFileIndexImpl extends FileIndexBase implements ProjectFileIn @Override @NotNull public List getOrderEntriesForFile(@NotNull VirtualFile file) { - return Arrays.asList(myDirectoryIndex.getOrderEntries(getInfoForFileOrDirectory(file))); + return myDirectoryIndex.getOrderEntries(getInfoForFileOrDirectory(file)); } @Override diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/RootIndex.java b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/RootIndex.java index 629d121bf6e2..08dce126d189 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/RootIndex.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/RootIndex.java @@ -34,6 +34,7 @@ import com.intellij.util.CollectionQuery; import com.intellij.util.Query; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; +import com.intellij.util.containers.SynchronizedSLRUCache; import gnu.trove.TObjectIntHashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -50,6 +51,7 @@ public class RootIndex { return name1.compareTo(name2); } }; + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.RootIndex"); private final Map myPackagePrefixByRoot = ContainerUtil.newHashMap(); @@ -59,7 +61,7 @@ public class RootIndex { private final TObjectIntHashMap> myRootTypeId = new TObjectIntHashMap>(); @NotNull private final Project myProject; private final PackageDirectoryCache myPackageDirectoryCache; - private volatile Map myOrderEntries; + private OrderEntryGraph myOrderEntryGraph; // made public for Upsource public RootIndex(@NotNull Project project, @NotNull InfoCache cache) { @@ -162,79 +164,198 @@ public class RootIndex { } @NotNull - private Map getOrderEntries() { - Map result = myOrderEntries; - if (result != null) return result; + private synchronized OrderEntryGraph getOrderEntryGraph() { + if (myOrderEntryGraph == null) { + RootInfo rootInfo = buildRootInfo(myProject); + myOrderEntryGraph = new OrderEntryGraph(myProject, rootInfo); + } + return myOrderEntryGraph; + } - MultiMap libClassRootEntries = MultiMap.createSmart(); - MultiMap libSourceRootEntries = MultiMap.createSmart(); - MultiMap depEntries = MultiMap.createSmart(); + /** + * A reverse dependency graph of (library, jdk, module, module source) -> (module). + * + *

Each edge carries with it the associated OrderEntry that caused the dependency. + */ + private static class OrderEntryGraph { - for (final Module module : ModuleManager.getInstance(myProject).getModules()) { - final ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module); - for (OrderEntry orderEntry : moduleRootManager.getOrderEntries()) { - if (orderEntry instanceof ModuleOrderEntry) { - final Module depModule = ((ModuleOrderEntry)orderEntry).getModule(); - if (depModule != null) { - VirtualFile[] importedClassRoots = - OrderEnumerator.orderEntries(depModule).exportedOnly().recursively().classes().usingCache().getRoots(); - for (VirtualFile importedClassRoot : importedClassRoots) { - depEntries.putValue(importedClassRoot, orderEntry); - } - } - for (VirtualFile sourceRoot : orderEntry.getFiles(OrderRootType.SOURCES)) { - depEntries.putValue(sourceRoot, orderEntry); - } - } - else if (orderEntry instanceof LibraryOrSdkOrderEntry) { - final LibraryOrSdkOrderEntry entry = (LibraryOrSdkOrderEntry)orderEntry; - for (final VirtualFile sourceRoot : entry.getRootFiles(OrderRootType.SOURCES)) { - libSourceRootEntries.putValue(sourceRoot, orderEntry); - } - for (final VirtualFile classRoot : entry.getRootFiles(OrderRootType.CLASSES)) { - libClassRootEntries.putValue(classRoot, orderEntry); - } - } + private static class Edge { + Module myKey; + OrderEntry myOrderEntry; // Order entry from myKey -> the node containing the edge + boolean myRecursive; // Whether this edge should be descended into during graph walk + + public Edge(Module key, OrderEntry orderEntry, boolean recursive) { + myKey = key; + myOrderEntry = orderEntry; + myRecursive = recursive; + } + + @Override + public String toString() { + return myOrderEntry.toString(); } } - RootInfo rootInfo = buildRootInfo(myProject); - result = ContainerUtil.newHashMap(); - Set allRoots = rootInfo.getAllRoots(); - for (VirtualFile file : allRoots) { - List hierarchy = getHierarchy(file, allRoots, rootInfo); - result.put(file, hierarchy == null - ? OrderEntry.EMPTY_ARRAY - : calcOrderEntries(rootInfo, depEntries, libClassRootEntries, libSourceRootEntries, hierarchy)); - } - myOrderEntries = result; - return result; - } + private static class Node { + Module myKey; + List myEdges = new ArrayList(); - private static OrderEntry[] calcOrderEntries(@NotNull RootInfo info, - @NotNull MultiMap depEntries, - @NotNull MultiMap libClassRootEntries, - @NotNull MultiMap libSourceRootEntries, - @NotNull List hierarchy) { - @Nullable VirtualFile libraryClassRoot = info.findLibraryRootInfo(hierarchy, false); - @Nullable VirtualFile librarySourceRoot = info.findLibraryRootInfo(hierarchy, true); - Set orderEntries = ContainerUtil.newLinkedHashSet(); - orderEntries - .addAll(info.getLibraryOrderEntries(hierarchy, libraryClassRoot, librarySourceRoot, libClassRootEntries, libSourceRootEntries)); - for (VirtualFile root : hierarchy) { - orderEntries.addAll(depEntries.get(root)); - } - VirtualFile moduleContentRoot = info.findModuleRootInfo(hierarchy); - if (moduleContentRoot != null) { - ContainerUtil.addIfNotNull(orderEntries, info.getModuleSourceEntry(hierarchy, moduleContentRoot, libClassRootEntries)); - } - if (orderEntries.isEmpty()) { - return null; + @Override + public String toString() { + return myKey.toString(); + } } - OrderEntry[] array = orderEntries.toArray(new OrderEntry[orderEntries.size()]); - Arrays.sort(array, BY_OWNER_MODULE); - return array; + private static class Graph { + Map myNodes = new HashMap(); + } + + final Project myProject; + final RootInfo myRootInfo; + final Set myAllRoots; + Graph myGraph; + MultiMap myRoots; // Map of roots to their root nodes, eg. library jar -> library node + final SynchronizedSLRUCache> myCache; + private MultiMap myLibClassRootEntries; + private MultiMap myLibSourceRootEntries; + + public OrderEntryGraph(Project project, RootInfo rootInfo) { + myProject = project; + myRootInfo = rootInfo; + myAllRoots = myRootInfo.getAllRoots(); + int cacheSize = Math.max(25, (myAllRoots.size() / 100) * 2); + myCache = new SynchronizedSLRUCache>(cacheSize, cacheSize) { + @NotNull + @Override + public List createValue(VirtualFile key) { + return collectOrderEntries(key); + } + }; + initGraph(); + initLibraryRoots(); + } + + private void initGraph() { + Graph graph = new Graph(); + + MultiMap roots = MultiMap.createSmart(); + Map> handlersMap = ContainerUtil.newHashMap(); + + for (final Module module : ModuleManager.getInstance(myProject).getModules()) { + final ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module); + for (OrderEntry orderEntry : moduleRootManager.getOrderEntries()) { + if (orderEntry instanceof ModuleOrderEntry) { + ModuleOrderEntry moduleOrderEntry = (ModuleOrderEntry)orderEntry; + final Module depModule = moduleOrderEntry.getModule(); + if (depModule != null) { + Node node = graph.myNodes.get(depModule); + OrderEnumerator en = OrderEnumerator.orderEntries(depModule).exportedOnly(); + if (node == null) { + node = new Node(); + node.myKey = depModule; + graph.myNodes.put(depModule, node); + + VirtualFile[] importedClassRoots = en.classes().usingCache().getRoots(); + for (VirtualFile importedClassRoot : importedClassRoots) { + roots.putValue(importedClassRoot, node); + } + + VirtualFile[] importedSourceRoots = en.sources().usingCache().getRoots(); + for (VirtualFile sourceRoot : importedSourceRoots) { + roots.putValue(sourceRoot, node); + } + } + List handlers = handlersMap.get(depModule); + if (handlers == null) { + handlers = en.getCustomHandlers(depModule); + handlersMap.put(depModule, handlers); + } + boolean shouldRecurse = en.shouldRecurse(moduleOrderEntry, handlers); + node.myEdges.add(new Edge(module, orderEntry, shouldRecurse)); + } + } + } + } + + myGraph = graph; + myRoots = roots; + } + + private void initLibraryRoots() { + MultiMap libClassRootEntries = MultiMap.createSmart(); + MultiMap libSourceRootEntries = MultiMap.createSmart(); + + for (final Module module : ModuleManager.getInstance(myProject).getModules()) { + final ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module); + for (OrderEntry orderEntry : moduleRootManager.getOrderEntries()) { + if (orderEntry instanceof LibraryOrSdkOrderEntry) { + final LibraryOrSdkOrderEntry entry = (LibraryOrSdkOrderEntry)orderEntry; + for (final VirtualFile sourceRoot : entry.getRootFiles(OrderRootType.SOURCES)) { + libSourceRootEntries.putValue(sourceRoot, orderEntry); + } + for (final VirtualFile classRoot : entry.getRootFiles(OrderRootType.CLASSES)) { + libClassRootEntries.putValue(classRoot, orderEntry); + } + } + } + } + + myLibClassRootEntries = libClassRootEntries; + myLibSourceRootEntries = libSourceRootEntries; + } + + private List getOrderEntries(@NotNull VirtualFile file) { + return myCache.get(file); + } + + /** + * Traverses the graph from the given file, collecting all encountered order entries. + */ + private List collectOrderEntries(@NotNull VirtualFile file) { + List roots = getHierarchy(file, myAllRoots, myRootInfo); + if (roots == null) { + return Collections.emptyList(); + } + List result = new ArrayList(); + Stack stack = new Stack(); + for (VirtualFile root : roots) { + Collection nodes = myRoots.get(root); + for (Node node : nodes) { + stack.push(node); + } + } + + Set seen = new HashSet(); + while (!stack.isEmpty()) { + Node node = stack.pop(); + if (seen.contains(node)) { + continue; + } + seen.add(node); + + for (Edge edge : node.myEdges) { + result.add(edge.myOrderEntry); + + if (edge.myRecursive) { + Node targetNode = myGraph.myNodes.get(edge.myKey); + if (targetNode != null) { + stack.push(targetNode); + } + } + } + } + + @Nullable VirtualFile libraryClassRoot = myRootInfo.findLibraryRootInfo(roots, false); + @Nullable VirtualFile librarySourceRoot = myRootInfo.findLibraryRootInfo(roots, true); + result.addAll(myRootInfo.getLibraryOrderEntries(roots, libraryClassRoot, librarySourceRoot, myLibClassRootEntries, myLibSourceRootEntries)); + + VirtualFile moduleContentRoot = myRootInfo.findModuleRootInfo(roots); + if (moduleContentRoot != null) { + ContainerUtil.addIfNotNull(result, myRootInfo.getModuleSourceEntry(roots, moduleContentRoot, myLibClassRootEntries)); + } + Collections.sort(result, BY_OWNER_MODULE); + return result; + } } private int getRootTypeId(@NotNull JpsModuleSourceRootType rootType) { @@ -508,6 +629,7 @@ public class RootIndex { return orderEntries; } + @Nullable private ModuleSourceOrderEntry getModuleSourceEntry(@NotNull List hierarchy, @NotNull VirtualFile moduleContentRoot, @@ -525,7 +647,6 @@ public class RootIndex { } } - @NotNull private static Pair calcDirectoryInfo(@NotNull final VirtualFile root, @NotNull final List hierarchy, @@ -562,10 +683,9 @@ public class RootIndex { } @NotNull - public OrderEntry[] getOrderEntries(@NotNull DirectoryInfo info) { - if (!(info instanceof DirectoryInfoImpl)) return OrderEntry.EMPTY_ARRAY; - OrderEntry[] entries = this.getOrderEntries().get(((DirectoryInfoImpl)info).getRoot()); - return entries == null ? OrderEntry.EMPTY_ARRAY : entries; + public List getOrderEntries(@NotNull DirectoryInfo info) { + if (!(info instanceof DirectoryInfoImpl)) return Collections.emptyList(); + return getOrderEntryGraph().getOrderEntries(((DirectoryInfoImpl)info).getRoot()); } public interface InfoCache { diff --git a/platform/util/src/com/intellij/util/containers/SynchronizedSLRUCache.java b/platform/util/src/com/intellij/util/containers/SynchronizedSLRUCache.java new file mode 100644 index 000000000000..d1b0d3be639d --- /dev/null +++ b/platform/util/src/com/intellij/util/containers/SynchronizedSLRUCache.java @@ -0,0 +1,50 @@ +/* + * Copyright 2000-2015 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.util.containers; + +import org.jetbrains.annotations.NotNull; + +/** + * An LRU cache with synchronization around the primary cache operations (get() and insertion + * of a newly created value). Other map operations are not synchronized. + */ +public abstract class SynchronizedSLRUCache extends SLRUMap { + protected final Object myLock = new Object(); + + protected SynchronizedSLRUCache(final int protectedQueueSize, final int probationalQueueSize) { + super(protectedQueueSize, probationalQueueSize); + } + + @NotNull + public abstract V createValue(K key); + + @Override + @NotNull + public V get(K key) { + V value; + synchronized (myLock) { + value = super.get(key); + if (value != null) { + return value; + } + } + value = createValue(key); + synchronized (myLock) { + put(key, value); + } + return value; + } +} \ No newline at end of file