From 1a2ac604c5516ec44a51598aeb944d418fe828b2 Mon Sep 17 00:00:00 2001 From: Alexey Kudravtsev Date: Wed, 14 Nov 2012 14:59:58 +0400 Subject: [PATCH] DirectoryInfo made immutable for less memory --- .../roots/impl/DirectoryIndexTest.java | 112 +++++++-------- .../roots/impl/DirectoryIndexComponent.java | 6 +- .../openapi/roots/impl/DirectoryIndex.java | 6 +- .../roots/impl/DirectoryIndexImpl.java | 100 +++++++------ .../openapi/roots/impl/DirectoryInfo.java | 131 +++++++++++------- 5 files changed, 195 insertions(+), 160 deletions(-) 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 15bc3fedfa7d..999eb62179a1 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 @@ -32,12 +32,12 @@ import com.intellij.testFramework.IdeaTestCase; import com.intellij.testFramework.PlatformTestCase; import com.intellij.testFramework.PsiTestUtil; import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -337,20 +337,20 @@ public class DirectoryIndexTest extends IdeaTestCase { VirtualFile output1 = myModule1Dir.createChildDirectory(this, "output1"); VirtualFile output2 = myModule1Dir.createChildDirectory(this, "output2"); - assertNotNull(myIndex.getInfoForDirectory(output1)); - assertNotNull(myIndex.getInfoForDirectory(output2)); + checkInfoNotNull(output1); + checkInfoNotNull(output2); getCompilerProjectExtension().setCompilerOutputUrl(output1.getUrl()); fireRootsChanged(); - assertNull(myIndex.getInfoForDirectory(output1)); - assertNotNull(myIndex.getInfoForDirectory(output2)); + checkInfoNull(output1); + checkInfoNotNull(output2); getCompilerProjectExtension().setCompilerOutputUrl(output2.getUrl()); fireRootsChanged(); - assertNotNull(myIndex.getInfoForDirectory(output1)); - assertNull(myIndex.getInfoForDirectory(output2)); + checkInfoNotNull(output1); + checkInfoNull(output2); } private void fireRootsChanged() { @@ -363,10 +363,10 @@ public class DirectoryIndexTest extends IdeaTestCase { VirtualFile module2Output = myModule1Dir.createChildDirectory(this, "module2Output"); VirtualFile module2TestOutput = myModule2Dir.createChildDirectory(this, "module2TestOutput"); - assertNotNull(myIndex.getInfoForDirectory(excluded)); - assertNotNull(myIndex.getInfoForDirectory(projectOutput)); - assertNotNull(myIndex.getInfoForDirectory(module2Output)); - assertNotNull(myIndex.getInfoForDirectory(module2TestOutput)); + checkInfoNotNull(excluded); + checkInfoNotNull(projectOutput); + checkInfoNotNull(module2Output); + checkInfoNotNull(module2TestOutput); getCompilerProjectExtension().setCompilerOutputUrl(projectOutput.getUrl()); @@ -375,44 +375,37 @@ public class DirectoryIndexTest extends IdeaTestCase { PsiTestUtil.setCompilerOutputPath(myModule2, module2TestOutput.getUrl(), true); PsiTestUtil.setExcludeCompileOutput(myModule2, true); - assertNull(myIndex.getInfoForDirectory(excluded)); - assertNull(myIndex.getInfoForDirectory(projectOutput)); - assertNull(myIndex.getInfoForDirectory(module2Output)); - assertNull(myIndex.getInfoForDirectory(module2TestOutput)); + checkInfoNull(excluded); + checkInfoNull(projectOutput); + checkInfoNull(module2Output); + checkInfoNull(module2TestOutput); excluded.delete(this); projectOutput.delete(this); module2Output.delete(this); module2TestOutput.delete(this); - final List isExcluded = new ArrayList(); + final List created = new ArrayList(); VirtualFileListener l = new VirtualFileAdapter() { @Override public void fileCreated(VirtualFileEvent e) { - isExcluded.add(myIndex.getInfoForDirectory(e.getFile()) == null); + VirtualFile file = e.getFile(); + checkInfoNull(file); + created.add(file); } }; - VirtualFileManager.getInstance().addVirtualFileListener(l); - try { - excluded = myModule1Dir.createChildDirectory(this, excluded.getName()); - projectOutput = myModule1Dir.createChildDirectory(this, projectOutput.getName()); - module2Output = myModule1Dir.createChildDirectory(this, module2Output.getName()); - module2TestOutput = myModule2Dir.createChildDirectory(this, module2TestOutput.getName()); - } - finally { - VirtualFileManager.getInstance().removeVirtualFileListener(l); - } + VirtualFileManager.getInstance().addVirtualFileListener(l, getTestRootDisposable()); + excluded = myModule1Dir.createChildDirectory(this, excluded.getName()); + projectOutput = myModule1Dir.createChildDirectory(this, projectOutput.getName()); + module2Output = myModule1Dir.createChildDirectory(this, module2Output.getName()); + module2TestOutput = myModule2Dir.createChildDirectory(this, module2TestOutput.getName()); - assertNull(myIndex.getInfoForDirectory(excluded)); - assertNull(myIndex.getInfoForDirectory(projectOutput)); - assertNull(myIndex.getInfoForDirectory(module2Output)); - assertNull(myIndex.getInfoForDirectory(module2TestOutput)); + checkInfoNull(excluded); + checkInfoNull(projectOutput); + checkInfoNull(module2Output); + checkInfoNull(module2TestOutput); - assertEquals(4, isExcluded.size()); - assertTrue(isExcluded.get(0)); - assertTrue(isExcluded.get(1)); - assertTrue(isExcluded.get(2)); - assertTrue(isExcluded.get(3)); + assertEquals(created.toString(), 4, created.size()); } public void testExcludesShouldBeRecognizedRightOnRefresh() throws Exception { @@ -430,28 +423,20 @@ public class DirectoryIndexTest extends IdeaTestCase { boolean created = new File(myModule1Dir.getPath(), "dir/excluded/foo").mkdirs(); assertTrue(created); - final List toAssert = new ArrayList(); VirtualFileListener l = new VirtualFileAdapter() { @Override public void fileCreated(VirtualFileEvent e) { assertEquals("dir", e.getFileName()); VirtualFile file = e.getFile(); - toAssert.add(myIndex.getInfoForDirectory(file) != null); - toAssert.add(myIndex.getInfoForDirectory(file.findFileByRelativePath("excluded")) == null); - toAssert.add(myIndex.getInfoForDirectory(file.findFileByRelativePath("excluded/foo")) == null); + checkInfoNotNull(file); + checkInfoNull(file.findFileByRelativePath("excluded")); + checkInfoNull(file.findFileByRelativePath("excluded/foo")); } }; - VirtualFileManager.getInstance().addVirtualFileListener(l); - try { - VirtualFileManager.getInstance().refresh(false); - } - finally { - VirtualFileManager.getInstance().removeVirtualFileListener(l); - } - - assertEquals(Arrays.asList(true, true, true), toAssert); + VirtualFileManager.getInstance().addVirtualFileListener(l, getTestRootDisposable()); + VirtualFileManager.getInstance().refresh(false); } public void testProcessingNestedContentRootsOfExcludedDirsOnCreation() { @@ -475,8 +460,8 @@ public class DirectoryIndexTest extends IdeaTestCase { }); - assertNull(myIndex.getInfoForDirectory(LocalFileSystem.getInstance().findFileByIoFile(f.getParentFile().getParentFile()))); - assertNotNull(myIndex.getInfoForDirectory(LocalFileSystem.getInstance().findFileByIoFile(f))); + checkInfoNull(LocalFileSystem.getInstance().findFileByIoFile(f.getParentFile().getParentFile())); + checkInfoNotNull(LocalFileSystem.getInstance().findFileByIoFile(f)); } public void testLibraryDirInContent() throws Exception { @@ -504,9 +489,7 @@ public class DirectoryIndexTest extends IdeaTestCase { boolean isInLibrarySource, @Nullable String packageName, Module... modulesOfOrderEntries) { - DirectoryInfo info = myIndex.getInfoForDirectory(dir); - assertNotNull(info); - info.assertConsistency(); + DirectoryInfo info = checkInfoNotNull(dir); assertEquals(module, info.getModule()); assertEquals(isInModuleSource, info.isInModuleSource()); assertEquals(isTestSource, info.isTestSource()); @@ -520,20 +503,19 @@ public class DirectoryIndexTest extends IdeaTestCase { assertEquals(modulesOfOrderEntries.length, info.getOrderEntries().length); for (Module aModule : modulesOfOrderEntries) { - boolean found = false; - for (OrderEntry orderEntry : info.getOrderEntries()) { - if (orderEntry.getOwnerModule() == aModule) { - found = true; - break; - } - } - assertTrue(found); + OrderEntry found = info.findOrderEntryWithOwnerModule(aModule); + assertNotNull(found); } } - private void checkInfoNull(VirtualFile dir) { - DirectoryInfo info = myIndex.getInfoForDirectory(dir); - assertNull(info); + private void checkInfoNull(@NotNull VirtualFile dir) { + assertNull(myIndex.getInfoForDirectory(dir)); + } + private DirectoryInfo checkInfoNotNull(@NotNull VirtualFile output2) { + DirectoryInfo info = myIndex.getInfoForDirectory(output2); + assertNotNull(output2.toString(), info); + info.assertConsistency(); + return info; } private void checkPackage(String packageName, VirtualFile... expectedDirs) { diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/impl/DirectoryIndexComponent.java b/platform/lang-impl/src/com/intellij/openapi/roots/impl/DirectoryIndexComponent.java index 2cf13e792611..f9559e9f8672 100644 --- a/platform/lang-impl/src/com/intellij/openapi/roots/impl/DirectoryIndexComponent.java +++ b/platform/lang-impl/src/com/intellij/openapi/roots/impl/DirectoryIndexComponent.java @@ -109,10 +109,12 @@ public class DirectoryIndexComponent extends DirectoryIndexImpl { myState = updateStateWithNewFile(file, parent); } + @NotNull private IndexState updateStateWithNewFile(@NotNull VirtualFile file, @NotNull VirtualFile parent) { final IndexState originalState = myState; IndexState state = originalState; - DirectoryInfo parentInfo = originalState.myDirToInfoMap.get(getId(parent)); + int parentId = getId(parent); + DirectoryInfo parentInfo = originalState.myDirToInfoMap.get(parentId); // fill info for all nested roots for (Module eachModule : ModuleManager.getInstance(myProject).getModules()) { @@ -147,7 +149,7 @@ public class DirectoryIndexComponent extends DirectoryIndexImpl { if (state == originalState) state = state.copy(); state.fillMapWithModuleContent(file, module, parentInfo.getContentRoot(), null); - String parentPackage = state.myDirToPackageName.get(getId(parent)); + String parentPackage = state.myDirToPackageName.get(parentId); if (module != null) { if (parentInfo.isInModuleSource()) { 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 37207b5d085c..dd4318de8937 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 @@ -32,16 +32,16 @@ public abstract class DirectoryIndex { @TestOnly public abstract void checkConsistency(); - public abstract DirectoryInfo getInfoForDirectory(VirtualFile dir); + public abstract DirectoryInfo getInfoForDirectory(@NotNull VirtualFile dir); - public abstract boolean isProjectExcludeRoot(VirtualFile dir); + public abstract boolean isProjectExcludeRoot(@NotNull VirtualFile dir); @NotNull public abstract Query getDirectoriesByPackageName(@NotNull String packageName, boolean includeLibrarySources); @Nullable - public abstract String getPackageName(VirtualFile dir); + public abstract String getPackageName(@NotNull VirtualFile dir); public abstract boolean isInitialized(); } diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/DirectoryIndexImpl.java b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/DirectoryIndexImpl.java index 5a76a1ec73c8..ee24da5dea91 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/DirectoryIndexImpl.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/DirectoryIndexImpl.java @@ -55,7 +55,7 @@ public class DirectoryIndexImpl extends DirectoryIndex { private boolean myInitialized = false; private boolean myDisposed = false; - public DirectoryIndexImpl(Project project) { + public DirectoryIndexImpl(@NotNull Project project) { myProject = project; myExcludePolicies = Extensions.getExtensions(DirectoryIndexExcludePolicy.EP_NAME, myProject); myState = new IndexState(); @@ -142,18 +142,20 @@ public class DirectoryIndexImpl extends DirectoryIndex { myState = newState; } - private boolean isExcludeRootForModule(Module module, VirtualFile excludeRoot) { + private boolean isExcludeRootForModule(@NotNull Module module, VirtualFile excludeRoot) { for (DirectoryIndexExcludePolicy policy : myExcludePolicies) { if (policy.isExcludeRootForModule(module, excludeRoot)) return true; } return false; } - protected static ContentEntry[] getContentEntries(Module module) { + @NotNull + protected static ContentEntry[] getContentEntries(@NotNull Module module) { return ModuleRootManager.getInstance(module).getContentEntries(); } - private static OrderEntry[] getOrderEntries(Module module) { + @NotNull + private static OrderEntry[] getOrderEntries(@NotNull Module module) { return ModuleRootManager.getInstance(module).getOrderEntries(); } @@ -162,7 +164,7 @@ public class DirectoryIndexImpl extends DirectoryIndex { } @Override - public DirectoryInfo getInfoForDirectory(VirtualFile dir) { + public DirectoryInfo getInfoForDirectory(@NotNull VirtualFile dir) { checkAvailability(); dispatchPendingEvents(); @@ -171,7 +173,7 @@ public class DirectoryIndexImpl extends DirectoryIndex { } @Override - public boolean isProjectExcludeRoot(VirtualFile dir) { + public boolean isProjectExcludeRoot(@NotNull VirtualFile dir) { checkAvailability(); return dir instanceof VirtualFileWithId && myState.myProjectExcludeRoots.contains(getId(dir)); } @@ -233,7 +235,7 @@ public class DirectoryIndexImpl extends DirectoryIndex { } @Override - public String getPackageName(VirtualFile dir) { + public String getPackageName(@NotNull VirtualFile dir) { checkAvailability(); if (!(dir instanceof VirtualFileWithId)) return null; return myState.myDirToPackageName.get(getId(dir)); @@ -317,11 +319,15 @@ public class DirectoryIndexImpl extends DirectoryIndex { private DirectoryInfo getOrCreateDirInfo(int dirId) { DirectoryInfo info = myDirToInfoMap.get(dirId); if (info == null) { - info = new DirectoryInfo(); + info = new DirectoryInfo(null, null,null,null,(byte)0,null); myDirToInfoMap.put(dirId, info); } return info; } + private DirectoryInfo storeInfo(DirectoryInfo info, int dirId) { + myDirToInfoMap.put(dirId, info); + return info; + } void fillMapWithModuleContent(@NotNull VirtualFile root, final Module module, @@ -330,7 +336,7 @@ public class DirectoryIndexImpl extends DirectoryIndex { final int contentRootId = contentRoot == null ? 0 : getId(contentRoot); VfsUtilCore.visitChildrenRecursively(root, new DirectoryVisitor() { @Override - protected DirectoryInfo updateInfo(VirtualFile file) { + protected DirectoryInfo updateInfo(@NotNull VirtualFile file) { if (progress != null) { progress.checkCanceled(); } @@ -349,9 +355,9 @@ public class DirectoryIndexImpl extends DirectoryIndex { } @Override - protected void afterChildrenVisited(DirectoryInfo info) { - info.setModule(module); - info.setContentRoot(contentRoot); + protected void afterChildrenVisited(@NotNull VirtualFile file, @NotNull DirectoryInfo info) { + info = info.withModule(module).withContentRoot(contentRoot); + storeInfo(info, getId(file)); } }); } @@ -372,13 +378,13 @@ public class DirectoryIndexImpl extends DirectoryIndex { @Override public void afterChildrenVisited(@NotNull VirtualFile file) { - afterChildrenVisited(myDirectoryInfoStack.pop()); + afterChildrenVisited(file, myDirectoryInfoStack.pop()); } @Nullable - protected abstract DirectoryInfo updateInfo(VirtualFile file); + protected abstract DirectoryInfo updateInfo(@NotNull VirtualFile file); - protected void afterChildrenVisited(DirectoryInfo info) {} + protected void afterChildrenVisited(@NotNull VirtualFile file, @NotNull DirectoryInfo info) {} } private boolean isExcluded(int root, @NotNull VirtualFile dir) { @@ -387,7 +393,7 @@ public class DirectoryIndexImpl extends DirectoryIndex { return excludes != null && excludes.contains(dir.getUrl()); } - private void initModuleContents(Module module, boolean reverseAllSets, ProgressIndicator progress) { + private void initModuleContents(@NotNull Module module, boolean reverseAllSets, @NotNull ProgressIndicator progress) { progress.checkCanceled(); progress.setText2(ProjectBundle.message("project.index.processing.module.content.progress", module.getName())); @@ -436,7 +442,7 @@ public class DirectoryIndexImpl extends DirectoryIndex { private final Stack myPackages = new Stack(); @Override - protected DirectoryInfo updateInfo(VirtualFile file) { + protected DirectoryInfo updateInfo(@NotNull VirtualFile file) { if (progress != null) { progress.checkCanceled(); } @@ -450,9 +456,10 @@ public class DirectoryIndexImpl extends DirectoryIndex { if (definedPackage != null && definedPackage.isEmpty()) return null; // another source root starts here } - info.setInModuleSource(true); - info.setTestSource(isTestSource); - info.setSourceRoot(sourceRoot); + info = info.withInModuleSource(true) + .withTestSource(isTestSource) + .withSourceRoot(sourceRoot); + storeInfo(info, id); String currentPackage = myPackages.isEmpty() ? packageName : getPackageNameForSubdir(myPackages.peek(), file.getName()); myPackages.push(currentPackage); @@ -461,8 +468,8 @@ public class DirectoryIndexImpl extends DirectoryIndex { } @Override - protected void afterChildrenVisited(DirectoryInfo info) { - super.afterChildrenVisited(info); + protected void afterChildrenVisited(@NotNull VirtualFile file, @NotNull DirectoryInfo info) { + super.afterChildrenVisited(file, info); myPackages.pop(); } }); @@ -501,8 +508,9 @@ public class DirectoryIndexImpl extends DirectoryIndex { if (definedPackage != null && definedPackage.isEmpty()) return false; // another library source root starts here } - info.setInLibrarySource(true); - info.setSourceRoot(sourceRoot); + info = info.withInLibrarySource(true) + .withSourceRoot(sourceRoot); + storeInfo(info, dirId); final String packageName = getCurrentValue(); final String newPackageName = Comparing.equal(file, dir) ? packageName : getPackageNameForSubdir(packageName, file.getName()); @@ -548,7 +556,8 @@ public class DirectoryIndexImpl extends DirectoryIndex { if (definedPackage != null && definedPackage.isEmpty()) return false; // another library root starts here } - info.setLibraryClassRoot(classRoot); + info = info.withLibraryClassRoot(classRoot); + storeInfo(info, dirId); final String packageName = getCurrentValue(); final String childPackageName = Comparing.equal(file, dir) ? packageName : getPackageNameForSubdir(packageName, file.getName()); @@ -656,13 +665,14 @@ public class DirectoryIndexImpl extends DirectoryIndex { private final Stack myEntries = new Stack(); @Override - protected DirectoryInfo updateInfo(VirtualFile dir) { + protected DirectoryInfo updateInfo(@NotNull VirtualFile dir) { if (progress != null) { progress.checkCanceled(); } if (isIgnored(dir)) return null; - DirectoryInfo info = myDirToInfoMap.get(getId(dir)); // do not create it here! + int dirId = getId(dir); + DirectoryInfo info = myDirToInfoMap.get(dirId); // do not create it here! if (info == null) return null; if (module != null) { @@ -682,12 +692,13 @@ public class DirectoryIndexImpl extends DirectoryIndex { OrderEntry[] oldParentEntries = myEntries.isEmpty() ? null : myEntries.peek(); OrderEntry[] oldEntries = info.getOrderEntries(); myEntries.push(oldEntries); - info.addOrderEntries(orderEntries, parentInfo, oldParentEntries); + info = info.withOrderEntries(orderEntries, parentInfo, oldParentEntries); + storeInfo(info, dirId); return info; } @Override - protected void afterChildrenVisited(DirectoryInfo info) { + protected void afterChildrenVisited(@NotNull VirtualFile file, @NotNull DirectoryInfo info) { myEntries.pop(); } }); @@ -730,11 +741,12 @@ public class DirectoryIndexImpl extends DirectoryIndex { } fillMapWithOrderEntries(depEntries, libClassRootEntries, libSourceRootEntries, progress); - killOrderEntryArrayDuplicates(); + internDirectoryInfos(); } - private void killOrderEntryArrayDuplicates() { - Map interner = new THashMap(new TObjectHashingStrategy() { + private void internDirectoryInfos() { + final Map diInterner = new THashMap(); + final Map oeInterner = new THashMap(new TObjectHashingStrategy() { @Override public int computeHashCode(OrderEntry[] object) { return Arrays.hashCode(object); @@ -745,17 +757,25 @@ public class DirectoryIndexImpl extends DirectoryIndex { return Arrays.equals(o1, o2); } }); - for (Object v : myDirToInfoMap.getValues()) { - DirectoryInfo info = (DirectoryInfo)v; - OrderEntry[] entries = info.getOrderEntries(); - if (entries.length != 0) { - OrderEntry[] interned = interner.get(entries); + + myDirToInfoMap.transformValues(new TObjectFunction() { + @Override + public DirectoryInfo execute(DirectoryInfo info) { + DirectoryInfo interned = diInterner.get(info); if (interned == null) { - interner.put(entries, interned = entries); + OrderEntry[] entries = info.getOrderEntries(); + OrderEntry[] internedEntries = oeInterner.get(entries); + if (internedEntries == null) { + oeInterner.put(entries, entries); + } + else if (internedEntries != entries) { + info = info.withInternedEntries(internedEntries); + } + diInterner.put(info, interned = info); } - info.setInternedOrderEntries(interned); + return interned; } - } + }); } private void initExcludedDirMap(Module[] modules, ProgressIndicator progress) { diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/DirectoryInfo.java b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/DirectoryInfo.java index 641b2b6df775..2c22eb16be97 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/DirectoryInfo.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/DirectoryInfo.java @@ -16,12 +16,10 @@ package com.intellij.openapi.roots.impl; -import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.roots.OrderEntry; import com.intellij.openapi.roots.OrderRootType; import com.intellij.openapi.roots.RootPolicy; -import com.intellij.openapi.util.Comparing; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.ArrayFactory; import com.intellij.util.ArrayUtil; @@ -32,52 +30,69 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; -import java.util.*; - +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; public final class DirectoryInfo { - private Module module; // module to which content it belongs or null - private VirtualFile libraryClassRoot; // class root in library - private VirtualFile contentRoot; - private VirtualFile sourceRoot; + private final Module module; // module to which content it belongs or null + private final VirtualFile libraryClassRoot; // class root in library + private final VirtualFile contentRoot; + private final VirtualFile sourceRoot; private static final byte TEST_SOURCE_FLAG = 1; // (makes sense only if MODULE_SOURCE_FLAG is set) private static final byte LIBRARY_SOURCE_FLAG = 2; // set if it's a directory with sources of some library private static final byte MODULE_SOURCE_FLAG = 4; // set if files in this directory belongs to sources of the module (if field 'module' is not null) @MagicConstant(flags = {TEST_SOURCE_FLAG, LIBRARY_SOURCE_FLAG, MODULE_SOURCE_FLAG}) - private byte sourceFlag; + private final byte sourceFlag; /** * orderEntry to (classes of) which a directory belongs + * MUST BE SORTED WITH {@link #BY_OWNER_MODULE} */ - private OrderEntry[] orderEntries; + private final OrderEntry[] orderEntries; - public DirectoryInfo() { + public DirectoryInfo(Module module, + VirtualFile contentRoot, + VirtualFile sourceRoot, + VirtualFile libraryClassRoot, + @MagicConstant(flags = {TEST_SOURCE_FLAG, LIBRARY_SOURCE_FLAG, MODULE_SOURCE_FLAG}) byte sourceFlag, + OrderEntry[] orderEntries) { + this.module = module; + this.libraryClassRoot = libraryClassRoot; + this.contentRoot = contentRoot; + this.sourceRoot = sourceRoot; + this.sourceFlag = sourceFlag; + this.orderEntries = orderEntries; } - @TestOnly - @SuppressWarnings({"unchecked"}) + @Override public boolean equals(Object o) { - assert ApplicationManager.getApplication().isUnitTestMode() : "DirectoryInfo.equals should only be used in tests"; - if (this == o) return true; - if (!(o instanceof DirectoryInfo)) return false; + if (o == null || getClass() != o.getClass()) return false; - final DirectoryInfo info = (DirectoryInfo)o; + DirectoryInfo info = (DirectoryInfo)o; if (sourceFlag != info.sourceFlag) return false; - if (getModule() != null ? !getModule().equals(info.getModule()) : info.getModule() != null) return false; - if (orderEntries != null ? !new HashSet(Arrays.asList(orderEntries)).equals(new HashSet(Arrays.asList(info.orderEntries))) : info.orderEntries != null) return false; - if (!Comparing.equal(getLibraryClassRoot(), info.getLibraryClassRoot())) return false; - if (!Comparing.equal(getContentRoot(), info.getContentRoot())) return false; - if (!Comparing.equal(getSourceRoot(), info.getSourceRoot())) return false; + if (contentRoot != null ? !contentRoot.equals(info.contentRoot) : info.contentRoot != null) return false; + if (libraryClassRoot != null ? !libraryClassRoot.equals(info.libraryClassRoot) : info.libraryClassRoot != null) return false; + if (module != null ? !module.equals(info.module) : info.module != null) return false; + if (!Arrays.equals(orderEntries, info.orderEntries)) return false; + if (sourceRoot != null ? !sourceRoot.equals(info.sourceRoot) : info.sourceRoot != null) return false; return true; } + @Override public int hashCode() { - throw new UnsupportedOperationException("DirectoryInfo shall not be used as a key to HashMap"); + int result = module != null ? module.hashCode() : 0; + result = 31 * result + (libraryClassRoot != null ? libraryClassRoot.hashCode() : 0); + result = 31 * result + (contentRoot != null ? contentRoot.hashCode() : 0); + result = 31 * result + (sourceRoot != null ? sourceRoot.hashCode() : 0); + result = 31 * result + (int)sourceFlag; + return result; } @SuppressWarnings({"HardCodedStringLiteral"}) @@ -95,7 +110,8 @@ public final class DirectoryInfo { @NotNull public OrderEntry[] getOrderEntries() { - return orderEntries == null ? OrderEntry.EMPTY_ARRAY : orderEntries; + OrderEntry[] entries = orderEntries; + return entries == null ? OrderEntry.EMPTY_ARRAY : entries; } @Nullable @@ -193,21 +209,25 @@ public final class DirectoryInfo { } // orderEntries must be sorted BY_OWNER_MODULE - void addOrderEntries(@NotNull OrderEntry[] orderEntries, - @Nullable final DirectoryInfo parentInfo, - @Nullable final OrderEntry[] oldParentEntries) { + @NotNull + DirectoryInfo withOrderEntries(@NotNull OrderEntry[] orderEntries, + @Nullable final DirectoryInfo parentInfo, + @Nullable final OrderEntry[] oldParentEntries) { + OrderEntry[] newOrderEntries; if (orderEntries.length == 0) { - this.orderEntries = null; + newOrderEntries = null; } else if (this.orderEntries == null) { - this.orderEntries = orderEntries; + newOrderEntries = orderEntries; } else if (parentInfo != null && oldParentEntries == this.orderEntries) { - this.orderEntries = parentInfo.orderEntries; + newOrderEntries = parentInfo.orderEntries; } else { - this.orderEntries = mergeWith(orderEntries); + newOrderEntries = mergeWith(orderEntries); } + + return new DirectoryInfo(module, contentRoot, sourceRoot, libraryClassRoot, sourceFlag, newOrderEntries); } // entries must be sorted BY_OWNER_MODULE @@ -249,10 +269,6 @@ public final class DirectoryInfo { } }; - public void setInternedOrderEntries(@NotNull OrderEntry[] internedOrderEntries) { - orderEntries = internedOrderEntries; - } - public VirtualFile getSourceRoot() { return sourceRoot; } @@ -275,48 +291,63 @@ public final class DirectoryInfo { return BitUtil.isSet(sourceFlag, MODULE_SOURCE_FLAG); } - public void setInModuleSource(boolean inModuleSource) { - sourceFlag = (byte)BitUtil.set(sourceFlag, MODULE_SOURCE_FLAG, inModuleSource); + @NotNull + DirectoryInfo withInModuleSource(boolean inModuleSource) { + byte sourceFlag = (byte)BitUtil.set(this.sourceFlag, MODULE_SOURCE_FLAG, inModuleSource); + return new DirectoryInfo(module, contentRoot, sourceRoot, libraryClassRoot, sourceFlag, orderEntries); } public boolean isTestSource() { return BitUtil.isSet(sourceFlag, TEST_SOURCE_FLAG); } - public void setTestSource(boolean testSource) { - sourceFlag = (byte)BitUtil.set(sourceFlag, TEST_SOURCE_FLAG, testSource); + @NotNull + DirectoryInfo withTestSource(boolean testSource) { + byte sourceFlag = (byte)BitUtil.set(this.sourceFlag, TEST_SOURCE_FLAG, testSource); + return new DirectoryInfo(module, contentRoot, sourceRoot, libraryClassRoot, sourceFlag, orderEntries); } public boolean isInLibrarySource() { return BitUtil.isSet(sourceFlag, LIBRARY_SOURCE_FLAG); } - public void setInLibrarySource(boolean inLibrarySource) { - sourceFlag = (byte)BitUtil.set(sourceFlag, LIBRARY_SOURCE_FLAG, inLibrarySource); + @NotNull + DirectoryInfo withInLibrarySource(boolean inLibrarySource) { + byte sourceFlag = (byte)BitUtil.set(this.sourceFlag, LIBRARY_SOURCE_FLAG, inLibrarySource); + return new DirectoryInfo(module, contentRoot, sourceRoot, libraryClassRoot, sourceFlag, orderEntries); } public Module getModule() { return module; } - public void setModule(Module module) { - this.module = module; + @NotNull + DirectoryInfo withModule(Module module) { + return new DirectoryInfo(module, contentRoot, sourceRoot, libraryClassRoot, sourceFlag, orderEntries); } - public void setLibraryClassRoot(@NotNull VirtualFile libraryClassRoot) { - this.libraryClassRoot = libraryClassRoot; + @NotNull + DirectoryInfo withLibraryClassRoot(@NotNull VirtualFile libraryClassRoot) { + return new DirectoryInfo(module, contentRoot, sourceRoot, libraryClassRoot, sourceFlag, orderEntries); } - public void setContentRoot(VirtualFile contentRoot) { - this.contentRoot = contentRoot; + @NotNull + DirectoryInfo withContentRoot(VirtualFile contentRoot) { + return new DirectoryInfo(module, contentRoot, sourceRoot, libraryClassRoot, sourceFlag, orderEntries); } - public void setSourceRoot(@NotNull VirtualFile sourceRoot) { - this.sourceRoot = sourceRoot; + @NotNull + DirectoryInfo withSourceRoot(@NotNull VirtualFile sourceRoot) { + return new DirectoryInfo(module, contentRoot, sourceRoot, libraryClassRoot, sourceFlag, orderEntries); + } + + @NotNull + DirectoryInfo withInternedEntries(@NotNull OrderEntry[] orderEntries) { + return new DirectoryInfo(module, contentRoot, sourceRoot, libraryClassRoot, sourceFlag, orderEntries); } @TestOnly - public void assertConsistency() { + void assertConsistency() { OrderEntry[] entries = getOrderEntries(); for (int i=1; i