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 5b577e11c719..3ab98fde776e 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 @@ -84,6 +84,8 @@ public class DirectoryIndexImpl extends DirectoryIndex { private volatile boolean myInitialized = false; private volatile boolean myDisposed = false; private final PackageSink mySink = new PackageSink(); + private static final boolean ourUseRootIndex = false; + private volatile RootIndex myRootIndex = null; public DirectoryIndexImpl(@NotNull ManagingFS managingFS, @NotNull Project project, @NotNull StartupManager startupManager) { myPersistence = managingFS; @@ -342,6 +344,10 @@ public class DirectoryIndexImpl extends DirectoryIndex { @Override public void before(@NotNull List events) { + if (ourUseRootIndex) { + return; + } + myBatchChangePlanned = false; int directoriesRemoved = 0; int directoriesCreated = 0; @@ -386,6 +392,11 @@ public class DirectoryIndexImpl extends DirectoryIndex { @Override public void after(@NotNull List events) { + if (ourUseRootIndex) { + myRootIndex = null; + return; + } + if (myBatchChangePlanned) { myBatchChangePlanned = false; long started = System.currentTimeMillis(); @@ -455,12 +466,35 @@ public class DirectoryIndexImpl extends DirectoryIndex { @Override @NotNull public Query getDirectoriesByPackageName(@NotNull String packageName, boolean includeLibrarySources) { + RootIndex rootIndex = getRootIndex(); + if (rootIndex != null) { + return rootIndex.getDirectoriesByPackageName(packageName, includeLibrarySources); + } + return mySink.search(packageName, includeLibrarySources); } + @Nullable + private RootIndex getRootIndex() { + if (!ourUseRootIndex) { + return null; + } + RootIndex rootIndex = myRootIndex; + if (rootIndex == null) { + rootIndex = myRootIndex = new RootIndex(myProject); + } + return rootIndex; + } + @Override @TestOnly public void checkConsistency() { + RootIndex rootIndex = getRootIndex(); + if (rootIndex != null) { + rootIndex.checkConsistency(); + return; + } + doCheckConsistency(false); doCheckConsistency(true); } @@ -515,6 +549,11 @@ public class DirectoryIndexImpl extends DirectoryIndex { } private void doInitialize() { + if (ourUseRootIndex) { + myRootIndex = null; + return; + } + IndexState newState = new IndexState(); newState.doInitialize(false); replaceState(newState); @@ -546,6 +585,11 @@ public class DirectoryIndexImpl extends DirectoryIndex { checkAvailability(); dispatchPendingEvents(); + RootIndex rootIndex = getRootIndex(); + if (rootIndex != null) { + return rootIndex.getInfoForDirectory(dir); + } + if (!(dir instanceof NewVirtualFile)) return null; return myState.getInfo(((NewVirtualFile)dir).getId()); } @@ -553,6 +597,11 @@ public class DirectoryIndexImpl extends DirectoryIndex { @Override @Nullable public JpsModuleSourceRootType getSourceRootType(@NotNull DirectoryInfo info) { + RootIndex rootIndex = getRootIndex(); + if (rootIndex != null) { + return rootIndex.getSourceRootType(info); + } + if (info.isInModuleSource()) { return myState.getRootTypeById(info.getSourceRootTypeId()); } @@ -562,6 +611,11 @@ public class DirectoryIndexImpl extends DirectoryIndex { @Override public boolean isProjectExcludeRoot(@NotNull VirtualFile dir) { checkAvailability(); + RootIndex rootIndex = getRootIndex(); + if (rootIndex != null) { + return rootIndex.isProjectExcludeRoot(dir); + } + return dir instanceof NewVirtualFile && myState.myProjectExcludeRoots.contains(((NewVirtualFile)dir).getId()); } @@ -573,6 +627,12 @@ public class DirectoryIndexImpl extends DirectoryIndex { public String getPackageName(@NotNull VirtualFile dir) { checkAvailability(); if (!(dir instanceof NewVirtualFile)) return null; + + RootIndex rootIndex = getRootIndex(); + if (rootIndex != null) { + return rootIndex.getPackageName(dir); + } + return myState.getPackageNameForDirectory((NewVirtualFile)dir); } diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/impl/RootIndex.java b/platform/lang-impl/src/com/intellij/openapi/roots/impl/RootIndex.java new file mode 100644 index 000000000000..ec3894c89774 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/openapi/roots/impl/RootIndex.java @@ -0,0 +1,361 @@ +/* + * 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.openapi.roots.impl; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.fileTypes.FileTypeRegistry; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.*; +import com.intellij.openapi.roots.impl.libraries.LibraryEx; +import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.CollectionQuery; +import com.intellij.util.Query; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.MultiMap; +import gnu.trove.TObjectIntHashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.model.java.JavaSourceRootType; +import org.jetbrains.jps.model.module.JpsModuleSourceRootType; + +import java.util.*; + +class RootIndex { + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.RootIndex"); + private final Project myProject; + + private final Set myProjectExcludedRoots = ContainerUtil.newHashSet(); + private final Set myLibraryExcludedRoots = ContainerUtil.newHashSet(); + private final Map myRoots = ContainerUtil.newHashMap(); + private final Map> myPackagePrefixRoots = ContainerUtil.newHashMap(); + + private final Map> myDirectoriesByPackageNameCache = ContainerUtil.newConcurrentMap(); + private final List> myRootTypes = ContainerUtil.newArrayList(); + private final TObjectIntHashMap> myRootTypeId = new TObjectIntHashMap>(); + + public RootIndex(@NotNull final Project project) { + myProject = project; + + initialize(); + } + + public void checkConsistency() { + for (VirtualFile file : myProjectExcludedRoots) { + assert file.exists() : file.getPath() + " does not exist"; + } + for (VirtualFile file : myLibraryExcludedRoots) { + assert file.exists() : file.getPath() + " does not exist"; + } + + for (VirtualFile file : myRoots.keySet()) { + assert file.exists() : file.getPath() + " does not exist"; + } + + for (HashSet virtualFiles : myPackagePrefixRoots.values()) { + for (VirtualFile file : virtualFiles) { + assert file.exists() : file.getPath() + " does not exist"; + } + } + } + + public void initialize() { + final MultiMap depEntries = new MultiMap(); + final MultiMap libClassRootEntries = new MultiMap(); + final MultiMap libSourceRootEntries = new MultiMap(); + + for (final Module module : ModuleManager.getInstance(myProject).getModules()) { + final ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module); + final OrderEntry[] orderEntries = moduleRootManager.getOrderEntries(); + final ContentEntry[] contentEntries = moduleRootManager.getContentEntries(); + final VirtualFile[] contentRoots = moduleRootManager.getContentRoots(); + + for (final VirtualFile contentRoot : contentRoots) { + myRoots.put(contentRoot, getOrCreateDirectoryInfo(contentRoot).with(module, contentRoot, null, null, (byte) 0, null)); + } + + for (ContentEntry contentEntry : contentEntries) { + // Init excluded roots + for (ExcludeFolder excludeRoot : contentEntry.getExcludeFolders()) { + final VirtualFile excludeRootFile = excludeRoot.getFile(); + if (excludeRootFile != null) { + myProjectExcludedRoots.add(excludeRootFile); + } + } + + // Init module sources + SourceFolder[] sourceFolders = contentEntry.getSourceFolders(); + for (final SourceFolder sourceFolder : sourceFolders) { + final VirtualFile sourceFolderRoot = sourceFolder.getFile(); + if (sourceFolderRoot != null) { + final DirectoryInfo info = getOrCreateDirectoryInfo(sourceFolderRoot); + + VirtualFile contentRoot = null; + for (VirtualFile root : contentRoots) { + if (VfsUtilCore.isAncestor(root, sourceFolderRoot, false)) { + contentRoot = root; + break; + } + } + + int rootTypeId = getRootTypeId(sourceFolder.getRootType()); + + int sourceRootTypeData = DirectoryInfo.createSourceRootTypeData(true, info.isInLibrarySource(), rootTypeId); + DirectoryInfo info1 = info.with(module, contentRoot, sourceFolderRoot, null, sourceRootTypeData, null); + + myRoots.put(sourceFolderRoot, info1); + initializePrefix(sourceFolderRoot, sourceFolder.getPackagePrefix()); + } + } + } + + for (OrderEntry orderEntry : orderEntries) { + // init ordered entries + 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 ModuleSourceOrderEntry) { + final VirtualFile[] sourceRoots = ((ModuleSourceOrderEntry)orderEntry).getRootModel().getSourceRoots(); + for (VirtualFile sourceRoot : sourceRoots) { + fillMapWithOrderEntries(sourceRoot, Arrays.asList(orderEntry), orderEntry.getOwnerModule(), null, null); + } + } + else if (orderEntry instanceof LibraryOrSdkOrderEntry) { + final LibraryOrSdkOrderEntry entry = (LibraryOrSdkOrderEntry)orderEntry; + final VirtualFile[] sourceRoots = entry.getRootFiles(OrderRootType.SOURCES); + final VirtualFile[] classRoots = entry.getRootFiles(OrderRootType.CLASSES); + + // init library classes + for (final VirtualFile classRoot : classRoots) { + libClassRootEntries.putValue(classRoot, orderEntry); + final DirectoryInfo info = getOrCreateDirectoryInfo(classRoot).with(null, null, null, classRoot, (byte) 0, null); + myRoots.put(classRoot, info); + if (!info.isInModuleSource() && !info.isInLibrarySource()) { + initializePrefix(classRoot, ""); + } + } + + // Init library sources + for (final VirtualFile sourceRoot : sourceRoots) { + libSourceRootEntries.putValue(sourceRoot, orderEntry); + final DirectoryInfo info = getOrCreateDirectoryInfo(sourceRoot); + + int sourceRootTypeData = DirectoryInfo.createSourceRootTypeData(info.isInModuleSource(), true, getRootTypeId(JavaSourceRootType.SOURCE)); + + myRoots.put(sourceRoot, info.with(null, null, sourceRoot, null, sourceRootTypeData, null)); + initializePrefix(sourceRoot, ""); + } + + if (orderEntry instanceof LibraryOrderEntry) { + Library library = ((LibraryOrderEntry)orderEntry).getLibrary(); + if (library != null) { + Collections.addAll(myLibraryExcludedRoots, ((LibraryEx)library).getExcludedRoots()); + } + } + + } + } + } + + for (DirectoryIndexExcludePolicy policy : Extensions.getExtensions(DirectoryIndexExcludePolicy.EP_NAME, myProject)) { + Collections.addAll(myProjectExcludedRoots, policy.getExcludeRootsForProject()); + } + + // fill ordered entries + for (Map.Entry> mapEntry : depEntries.entrySet()) { + fillMapWithOrderEntries(mapEntry.getKey(), mapEntry.getValue(), null, null, null); + } + for (Map.Entry> mapEntry : libClassRootEntries.entrySet()) { + fillMapWithOrderEntries(mapEntry.getKey(), mapEntry.getValue(), null, mapEntry.getKey(), null); + } + for (Map.Entry> mapEntry : libSourceRootEntries.entrySet()) { + fillMapWithOrderEntries(mapEntry.getKey(), mapEntry.getValue(), null, null, mapEntry.getKey()); + } + } + + private int getRootTypeId(JpsModuleSourceRootType rootType) { + if (myRootTypeId.containsKey(rootType)) { + return myRootTypeId.get(rootType); + } + + int id = myRootTypes.size(); + if (id > DirectoryInfo.MAX_ROOT_TYPE_ID) { + LOG.error("Too many different types of module source roots (" + id + ") registered: " + myRootTypes); + } + myRootTypes.add(rootType); + myRootTypeId.put(rootType, id); + return id; + } + + + private void fillMapWithOrderEntries(final VirtualFile root, + @NotNull final Collection orderEntries, + @Nullable final Module module, + @Nullable final VirtualFile libraryClassRoot, + @Nullable final VirtualFile librarySourceRoot) { + + assert root.exists(); + + if (FileTypeRegistry.getInstance().isFileIgnored(root)) { + return; + } + + DirectoryInfo info = myRoots.get(root); + if (info == null) return; + + if (module != null) { + if (info.getModule() != module) return; + if (!info.isInModuleSource()) return; + } else if (libraryClassRoot != null) { + if (info.getLibraryClassRoot() != libraryClassRoot) return; + if (info.isInModuleSource()) return; + } else if (librarySourceRoot != null) { + if (!info.isInLibrarySource()) return; + if (info.getSourceRoot() != librarySourceRoot) return; + if (info.getLibraryClassRoot() != null) return; + } + + OrderEntry[] orderEntriesArray = orderEntries.toArray(new OrderEntry[orderEntries.size()]); + Arrays.sort(orderEntriesArray, DirectoryInfo.BY_OWNER_MODULE); + + myRoots.put(root, info.withInternedEntries(orderEntriesArray)); + } + + @NotNull + private DirectoryInfo getOrCreateDirectoryInfo(VirtualFile root) { + final DirectoryInfo existingInfo = myRoots.get(root); + if (existingInfo != null) { + return existingInfo; + } + + return DirectoryInfo.createNew(); + } + + private void initializePrefix(final VirtualFile root, final String packagePrefix) { + if (!myPackagePrefixRoots.containsKey(packagePrefix)) { + myPackagePrefixRoots.put(packagePrefix, new HashSet()); + } + myPackagePrefixRoots.get(packagePrefix).add(root); + } + + public DirectoryInfo getInfoForDirectory(@NotNull final VirtualFile dir) { + int count = 0; + for (VirtualFile root = dir; root != null; root = root.getParent()) { + if (++count > 1000) { + throw new IllegalStateException("Possible loop in tree"); + } + final DirectoryInfo info = myRoots.get(root); + if (info != null) { + return info; + } + if (myProjectExcludedRoots.contains(root) || myLibraryExcludedRoots.contains(root)) { + return null; + } + } + + return null; + } + + public boolean isProjectExcludeRoot(@NotNull final VirtualFile dir) { + return myProjectExcludedRoots.contains(dir); + } + + @NotNull + public Query getDirectoriesByPackageName(@NotNull final String packageName, final boolean includeLibrarySources) { + final List cachedResult = myDirectoriesByPackageNameCache.get(packageName); + if (cachedResult != null) { + return new CollectionQuery(cachedResult); + } + + final ArrayList result = ContainerUtil.newArrayList(); + for (Map.Entry> entry : myPackagePrefixRoots.entrySet()) { + if (!packageName.startsWith(entry.getKey())) { + continue; + } + + if (packageName.equals(entry.getKey())) { + for (VirtualFile file : entry.getValue()) { + result.add(file); + } + continue; + } + + final String nestedPackageName = entry.getKey().isEmpty() ? packageName : packageName.substring( entry.getKey().length()); + final List nestedPackages = StringUtil.split(nestedPackageName, "."); + + for (final VirtualFile root : entry.getValue()) { + VirtualFile file = root; + for (String name : nestedPackages) { + file = file.findChild(name); + if (file == null) { + break; + } + } + + if (file != null) { + result.add(file); + } + } + } + + myDirectoriesByPackageNameCache.put(packageName, result); + return new CollectionQuery(result); + } + + @Nullable + public String getPackageName(@NotNull final VirtualFile dir) { + if (dir.isDirectory()) { + for (final Map.Entry> entry : myPackagePrefixRoots.entrySet()) { + if (entry.getValue().contains(dir)) { + return entry.getKey(); + } + } + + final VirtualFile parent = dir.getParent(); + if (parent != null) { + return getPackageNameForSubdir(getPackageName(parent), dir.getName()); + } + } + + return null; + } + + @Nullable + protected static String getPackageNameForSubdir(String parentPackageName, String subdirName) { + if (parentPackageName == null) return null; + return parentPackageName.isEmpty() ? subdirName : parentPackageName + "." + subdirName; + } + + @Nullable + public JpsModuleSourceRootType getSourceRootType(@NotNull DirectoryInfo directoryInfo) { + return myRootTypes.get(directoryInfo.getSourceRootTypeId()); + } + +} \ No newline at end of file