diff --git a/platform/core-api/src/com/intellij/openapi/vfs/FileSystemPersistence.java b/platform/core-api/src/com/intellij/openapi/vfs/FileSystemPersistence.java new file mode 100644 index 000000000000..a883cd64cc24 --- /dev/null +++ b/platform/core-api/src/com/intellij/openapi/vfs/FileSystemPersistence.java @@ -0,0 +1,23 @@ +/* + * 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.vfs; + +import org.jetbrains.annotations.Nullable; + +public interface FileSystemPersistence { + @Nullable + VirtualFile findFileById(int id); +} 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 e8e302a5c074..10182a142a71 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 @@ -24,28 +24,23 @@ import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.*; import com.intellij.openapi.startup.StartupManager; -import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.*; import com.intellij.openapi.vfs.impl.BulkVirtualFileListenerAdapter; import com.intellij.openapi.vfs.newvfs.ManagingFS; import com.intellij.openapi.vfs.newvfs.NewVirtualFile; -import com.intellij.util.*; +import com.intellij.util.ArrayUtil; import com.intellij.util.messages.MessageBusConnection; +import gnu.trove.TIntArrayList; +import gnu.trove.TIntProcedure; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; -import java.util.List; - /** * Descendant of DirectoryIndex which can update itself automatically based on VFS changes. */ public class DirectoryIndexComponent extends DirectoryIndexImpl { private final MessageBusConnection myConnection; - private final ManagingFS myManagingFS; - private final PackageSink mySink; public DirectoryIndexComponent(@NotNull Project project, @NotNull StartupManager startupManager, @NotNull ManagingFS managingFS) { super(project); @@ -56,55 +51,6 @@ public class DirectoryIndexComponent extends DirectoryIndexImpl { initialize(); } }); - myManagingFS = managingFS; - mySink = new PackageSink(); - } - - private class PackageSink extends QueryFactory>> { - private final Condition IS_VALID = new Condition() { - @Override - public boolean value(final VirtualFile virtualFile) { - return virtualFile.isValid(); - } - }; - - private PackageSink() { - registerExecutor(new QueryExecutor>>() { - @Override - public boolean execute(@NotNull final Pair> stateAndDirs, - @NotNull final Processor consumer) { - for (VirtualFile dir : stateAndDirs.second) { - DirectoryInfo info = stateAndDirs.first.myDirToInfoMap.get(getId(dir)); - assert info != null; - - if (!info.isInLibrarySource() || info.isInModuleSource() || info.hasLibraryClassRoot()) { - if (!consumer.process(dir)) return false; - } - } - return true; - } - }); - } - - public Query search(@NotNull String packageName, boolean includeLibrarySources) { - checkAvailability(); - dispatchPendingEvents(); - - IndexState state = myState; - int[] allDirs = state.getDirsForPackage(packageName); - if (allDirs == null) allDirs = ArrayUtil.EMPTY_INT_ARRAY; - - List files = new ArrayList(allDirs.length); - for (int dir : allDirs) { - VirtualFile file = myManagingFS.findFileById(dir); - if (file != null) { - files.add(file); - } - } - - Query query = includeLibrarySources ? new CollectionQuery(files) : createQuery(Pair.create(state, files)); - return new FilteredQuery(query, IS_VALID); - } } @Override @@ -114,11 +60,6 @@ public class DirectoryIndexComponent extends DirectoryIndexImpl { markContentRootsForRefresh(); } - @Override - @NotNull - public Query getDirectoriesByPackageName(@NotNull String packageName, boolean includeLibrarySources) { - return mySink.search(packageName, includeLibrarySources); - } private void subscribeToFileChanges() { myConnection.subscribe(FileTypeManager.TOPIC, new FileTypeListener.Adapter() { @@ -156,8 +97,6 @@ public class DirectoryIndexComponent extends DirectoryIndexImpl { } private class MyVirtualFileListener extends VirtualFileAdapter { - private final Key> FILES_TO_RELEASE_KEY = Key.create("DirectoryIndexImpl.MyVirtualFileListener.FILES_TO_RELEASE_KEY"); - @Override public void fileCreated(VirtualFileEvent event) { VirtualFile file = event.getFile(); @@ -166,32 +105,38 @@ public class DirectoryIndexComponent extends DirectoryIndexImpl { VirtualFile parent = file.getParent(); if (parent == null) return; - + DirectoryInfo existing = myState.getInfo(getId(file)); + assert existing == null : file+" -> "+existing; myState = updateStateWithNewFile(file, parent); } + @NotNull private IndexState updateStateWithNewFile(@NotNull VirtualFile file, @NotNull VirtualFile parent) { final IndexState originalState = myState; IndexState state = originalState; int parentId = getId(parent); - DirectoryInfo parentInfo = originalState.myDirToInfoMap.get(parentId); + DirectoryInfo parentInfo = originalState.getInfo(parentId); + if (parentInfo != null) { + assertAncestor(parentInfo, parent, parentId); + } // fill info for all nested roots + String fileUrl = file.getUrl(); for (Module eachModule : ModuleManager.getInstance(myProject).getModules()) { - for (ContentEntry eachRoot : getContentEntries(eachModule)) { + for (ContentEntry contentRoot : getContentEntries(eachModule)) { if (parentInfo != null) { - VirtualFile contFile = eachRoot.getFile(); + VirtualFile contFile = contentRoot.getFile(); if (contFile != null && contFile.equals(parentInfo.getContentRoot())) continue; } - String url = eachRoot.getUrl(); - if (FileUtil.startsWith(url, file.getUrl())) { - String rel = FileUtil.getRelativePath(file.getUrl(), url, '/'); + String contentRootUrl = contentRoot.getUrl(); + if (FileUtil.startsWith(contentRootUrl, fileUrl)) { + String rel = FileUtil.getRelativePath(fileUrl, contentRootUrl, '/'); if (rel != null) { VirtualFile f = file.findFileByRelativePath(rel); if (f != null) { - if (state == originalState) state = state.copy(); + if (state == originalState) state = state.copy(null); state.fillMapWithModuleContent(f, eachModule, f, null); } } @@ -207,15 +152,16 @@ public class DirectoryIndexComponent extends DirectoryIndexImpl { if (policy.isExcludeRoot(file)) return state; } - if (state == originalState) state = state.copy(); - state.fillMapWithModuleContent(file, module, parentInfo.getContentRoot(), null); + if (state == originalState) state = state.copy(null); + VirtualFile parentContentRoot = parentInfo.getContentRoot(); + state.fillMapWithModuleContent(file, module, parentContentRoot, null); String parentPackage = state.myDirToPackageName.get(parentId); if (module != null) { if (parentInfo.isInModuleSource()) { String newDirPackageName = getPackageNameForSubdir(parentPackage, file.getName()); - state.fillMapWithModuleSource(file, module, newDirPackageName, parentInfo.getSourceRoot(), parentInfo.isTestSource(), null); + state.fillMapWithModuleSource(module, parentContentRoot, file, newDirPackageName, parentInfo.getSourceRoot(), parentInfo.isTestSource(), null); } } @@ -236,24 +182,25 @@ public class DirectoryIndexComponent extends DirectoryIndexImpl { return state; } + private final Key FILES_TO_RELEASE_KEY = Key.create("DirectoryIndexImpl.MyVirtualFileListener.FILES_TO_RELEASE_KEY"); + @Override public void beforeFileDeletion(VirtualFileEvent event) { VirtualFile file = event.getFile(); if (!file.isDirectory()) return; - if (!myState.myDirToInfoMap.containsKey(getId(file))) return; + if (myState.getInfo(getId(file)) == null) return; - final IndexState state = myState.copy(); - - ArrayList list = new ArrayList(); - addDirsRecursively(state, list, file); - file.putUserData(FILES_TO_RELEASE_KEY, list); - myState = state; + TIntArrayList list = new TIntArrayList(); + addDirsRecursively(myState, list, file); + file.putUserData(FILES_TO_RELEASE_KEY, list.toNativeArray()); } - private void addDirsRecursively(IndexState state, ArrayList list, VirtualFile dir) { - if (!(dir instanceof NewVirtualFile) || !state.myDirToInfoMap.containsKey(getId(dir))) return; + private void addDirsRecursively(@NotNull IndexState state, @NotNull TIntArrayList list, @NotNull VirtualFile dir) { + if (!(dir instanceof NewVirtualFile)) return; + int id = getId(dir); + if (state.getInfo(id) == null) return; - list.add(dir); + list.add(id); for (VirtualFile child : ((NewVirtualFile)dir).getCachedChildren()) { if (child.isDirectory()) { @@ -265,14 +212,18 @@ public class DirectoryIndexComponent extends DirectoryIndexImpl { @Override public void fileDeleted(VirtualFileEvent event) { VirtualFile file = event.getFile(); - List list = file.getUserData(FILES_TO_RELEASE_KEY); + final int[] list = file.getUserData(FILES_TO_RELEASE_KEY); if (list == null) return; IndexState copy = null; - for (VirtualFile dir : list) { - int id = getId(dir); - if (myState.myDirToInfoMap.containsKey(id)) { - if (copy == null) copy = myState.copy(); + for (int id : list) { + if (myState.getInfo(id) != null) { + if (copy == null) copy = myState.copy(new TIntProcedure() { + @Override + public boolean execute(int fid) { + return ArrayUtil.indexOf(list, fid) == -1; + } + }); copy.myDirToInfoMap.remove(id); copy.setPackageName(id, null); @@ -282,6 +233,12 @@ public class DirectoryIndexComponent extends DirectoryIndexImpl { if (copy != null) { myState = copy; } + myState.assertAncestorsConsistent(); + } + + @Override + public void beforeFileMovement(VirtualFileMoveEvent event) { + super.beforeFileMovement(event); } @Override @@ -290,6 +247,7 @@ public class DirectoryIndexComponent extends DirectoryIndexImpl { if (file.isDirectory()) { doInitialize(); } + myState.assertAncestorsConsistent(); } @Override @@ -300,6 +258,7 @@ public class DirectoryIndexComponent extends DirectoryIndexImpl { doInitialize(); } } + myState.assertAncestorsConsistent(); } } } diff --git a/platform/platform-api/src/com/intellij/openapi/vfs/newvfs/ManagingFS.java b/platform/platform-api/src/com/intellij/openapi/vfs/newvfs/ManagingFS.java index aba3996c8563..e663d5008c59 100644 --- a/platform/platform-api/src/com/intellij/openapi/vfs/newvfs/ManagingFS.java +++ b/platform/platform-api/src/com/intellij/openapi/vfs/newvfs/ManagingFS.java @@ -16,19 +16,18 @@ package com.intellij.openapi.vfs.newvfs; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.vfs.FileSystemPersistence; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.newvfs.events.VFileEvent; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.DataInputStream; import java.io.DataOutputStream; -import java.util.List; /** * @author max */ -public abstract class ManagingFS implements FileSystemInterface { +public abstract class ManagingFS implements FileSystemInterface, FileSystemPersistence { private static class ManagingFSHolder { private static final ManagingFS ourInstance = ApplicationManager.getApplication().getComponent(ManagingFS.class); } @@ -56,8 +55,6 @@ public abstract class ManagingFS implements FileSystemInterface { public abstract boolean wereChildrenAccessed(@NotNull VirtualFile dir); - public abstract void processEvents(@NotNull List events); - @Nullable public abstract NewVirtualFile findRoot(@NotNull String basePath, @NotNull NewVirtualFileSystem fs); @@ -70,6 +67,7 @@ public abstract class ManagingFS implements FileSystemInterface { @NotNull public abstract VirtualFile[] getLocalRoots(); + @Override @Nullable public abstract VirtualFile findFileById(int id); } diff --git a/platform/platform-api/src/com/intellij/openapi/vfs/newvfs/NewVirtualFile.java b/platform/platform-api/src/com/intellij/openapi/vfs/newvfs/NewVirtualFile.java index 47812101d7ee..43ac47e44f79 100644 --- a/platform/platform-api/src/com/intellij/openapi/vfs/newvfs/NewVirtualFile.java +++ b/platform/platform-api/src/com/intellij/openapi/vfs/newvfs/NewVirtualFile.java @@ -73,11 +73,11 @@ public abstract class NewVirtualFile extends VirtualFile implements VirtualFileW @Override public abstract int getId(); - @Nullable - public abstract NewVirtualFile findChildById(int id); + @Nullable @Deprecated + public NewVirtualFile findChildById(int id) {return null;} - @Nullable - public abstract NewVirtualFile findChildByIdIfCached(int id); + @Nullable @Deprecated + public NewVirtualFile findChildByIdIfCached(int id) {return null;} @Override public void refresh(final boolean asynchronous, final boolean recursive, final Runnable postRunnable) { diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemImpl.java index 3a6cd2666993..58520865b436 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemImpl.java @@ -28,7 +28,6 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.newvfs.ManagingFS; import com.intellij.openapi.vfs.newvfs.NewVirtualFile; import com.intellij.openapi.vfs.newvfs.RefreshQueue; -import com.intellij.openapi.vfs.newvfs.impl.VirtualDirectoryImpl; import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS; import com.intellij.util.Consumer; import com.intellij.util.TimeoutUtil; @@ -151,12 +150,6 @@ public final class LocalFileSystemImpl extends LocalFileSystemBase implements Ap }); PersistentFS.getInstance().clearIdCache(); - for (VirtualFile root : myManagingFS.getRoots(this)) { - if (root instanceof VirtualDirectoryImpl) { - ((VirtualDirectoryImpl)root).cleanupCachedChildren(survivors); - } - } - myRootsToWatch.clear(); } diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/RefreshSessionImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/RefreshSessionImpl.java index 3d202b6a5895..a6f469626993 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/RefreshSessionImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/RefreshSessionImpl.java @@ -25,6 +25,7 @@ import com.intellij.openapi.vfs.ex.VirtualFileManagerEx; import com.intellij.openapi.vfs.impl.local.FileWatcher; import com.intellij.openapi.vfs.impl.local.LocalFileSystemImpl; import com.intellij.openapi.vfs.newvfs.events.VFileEvent; +import com.intellij.openapi.vfs.newvfs.persistent.PersistentFSImpl; import com.intellij.openapi.vfs.newvfs.persistent.RefreshWorker; import com.intellij.util.concurrency.Semaphore; import org.jetbrains.annotations.NotNull; @@ -188,7 +189,7 @@ public class RefreshSessionImpl extends RefreshSession { manager.fireBeforeRefreshStart(myIsAsync); try { while (!myWorkQueue.isEmpty() || !myEvents.isEmpty()) { - ManagingFS.getInstance().processEvents(mergeEventsAndReset()); + ((PersistentFSImpl)ManagingFS.getInstance()).processEvents(mergeEventsAndReset()); scan(); } } diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/VfsImplUtil.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/VfsImplUtil.java index 54571cbd4f31..75211de37e63 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/VfsImplUtil.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/VfsImplUtil.java @@ -122,7 +122,7 @@ public class VfsImplUtil { @Nullable private static Pair> prepare(@NotNull NewVirtualFileSystem vfs, @NotNull String path) { - String normalizedPath = vfs.normalize(path); + String normalizedPath = normalize(vfs, path); if (normalizedPath == null) { return null; } @@ -141,4 +141,9 @@ public class VfsImplUtil { Iterable parts = StringUtil.tokenize(normalizedPath.substring(basePath.length()), FILE_SEPARATORS); return Pair.create(root, parts); } + + @Nullable + public static String normalize(@NotNull NewVirtualFileSystem vfs, @NotNull String path) { + return vfs.normalize(path); + } } diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/SubList.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/SubList.java new file mode 100644 index 000000000000..e84a2c2cfe1f --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/SubList.java @@ -0,0 +1,79 @@ +/* + * 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.vfs.newvfs.impl; + +import com.intellij.util.ArrayUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.AbstractList; +import java.util.Arrays; +import java.util.RandomAccess; + +class SubList extends AbstractList implements RandomAccess { + private final E[] a; + private final int start; + private final int end; + + SubList(@NotNull E[] array, int start, int end) { + a = array; + this.start = start; + this.end = end; + assert start <= a.length; + assert end <= a.length; + assert start <= end && start >= 0; + } + + @Override + public int size() { + return end - start; + } + + @NotNull + @Override + public Object[] toArray() { + return Arrays.copyOfRange(a, start, end); + } + + @NotNull + @Override + @SuppressWarnings("unchecked") + public T[] toArray(@NotNull T[] a) { + int size = size(); + if (a.length < size) { + return Arrays.copyOfRange(this.a, start, end, (Class)a.getClass()); + } + System.arraycopy(this.a, start, a, 0, size); + if (a.length > size) { + a[size] = null; + } + return a; + } + + @Override + public E get(int index) { + return a[index+start]; + } + + @Override + public int indexOf(Object o) { + return ArrayUtil.indexOf(a, o, start, end); + } + + @Override + public boolean contains(Object o) { + return indexOf(o) != -1; + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualDirectoryImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualDirectoryImpl.java index ce44af6c11e3..1eb154c5eabc 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualDirectoryImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualDirectoryImpl.java @@ -23,25 +23,20 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.roots.OrderEnumerator; import com.intellij.openapi.roots.ProjectRootManager; -import com.intellij.openapi.util.Condition; -import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileAttributes; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.JarFileSystem; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VfsUtilCore; -import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.*; import com.intellij.openapi.vfs.newvfs.NewVirtualFile; import com.intellij.openapi.vfs.newvfs.NewVirtualFileSystem; import com.intellij.openapi.vfs.newvfs.RefreshQueue; import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent; +import com.intellij.openapi.vfs.newvfs.persistent.FSRecords; import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS; import com.intellij.util.*; import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.text.CaseInsensitiveStringHashingStrategy; -import gnu.trove.THashMap; import gnu.trove.THashSet; +import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; @@ -58,16 +53,20 @@ import java.util.*; * @author max */ public class VirtualDirectoryImpl extends VirtualFileSystemEntry { - private static final VirtualFileSystemEntry NULL_VIRTUAL_FILE = new VirtualFileImpl("*?;%NULL", null, -42, 0) { + static final VirtualDirectoryImpl NULL_VIRTUAL_FILE = new VirtualDirectoryImpl("*?;%NULL", null, LocalFileSystem.getInstance(), -42, 0) { public String toString() { return "NULL"; } }; private final NewVirtualFileSystem myFS; - private Object myChildren; // guarded by this, either Map or VFile[] - public VirtualDirectoryImpl(@NotNull final String name, + // stores child files. The array is logically divided into the two halves: + // left subarray for storing real files, right subarray for storing fake files with "suspicious" names + // files in each subarray are sorted according to the compareNameTo() comparator + private VirtualFileSystemEntry[] myChildren = EMPTY_ARRAY; // guarded by this, either real file or fake file (meaning it's not a real child but suspicious name) + + public VirtualDirectoryImpl(@NonNls @NotNull final String name, @Nullable final VirtualDirectoryImpl parent, @NotNull final NewVirtualFileSystem fs, final int id, @@ -89,60 +88,63 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry { @NotNull NewVirtualFileSystem delegate) { VirtualFileSystemEntry result = doFindChild(name, ensureCanonicalName, delegate); if (result == NULL_VIRTUAL_FILE) { - return doRefresh ? createAndFindChildWithEventFire(name) : null; + result = doRefresh ? createAndFindChildWithEventFire(name, delegate) : null; + } + else if (result != null) { + if (doRefresh && delegate.isDirectory(result) != result.isDirectory()) { + RefreshQueue.getInstance().refresh(false, false, null, result); + result = findChild(name, false, ensureCanonicalName, delegate); + } } if (result == null) { - synchronized (this) { - Map map = asMap(); - if (map != null) { - map.put(name, NULL_VIRTUAL_FILE); - } - } + addToSuspiciousNames(name, !delegate.isCaseSensitive()); } - else if (doRefresh && delegate.isDirectory(result) != result.isDirectory()) { - RefreshQueue.getInstance().refresh(false, false, null, result); - result = doFindChild(name, ensureCanonicalName, delegate); - if (result == NULL_VIRTUAL_FILE) { - result = createAndFindChildWithEventFire(name); - } - } - return result; } - @Nullable - private VirtualFileSystemEntry doFindChild(@NotNull String name, - boolean ensureCanonicalName, - @NotNull NewVirtualFileSystem delegate) { + private synchronized void addToSuspiciousNames(@NotNull final String name, final boolean ignoreCase) { + if (allChildrenLoaded()) return; + int index = binSearch(myChildren, 0, myChildren.length, new Comparer() { + @Override + public int compareMyKeyTo(@NotNull VirtualFileSystemEntry file) { + if (!isSuspiciousName(file)) return 1; + return -file.compareNameTo(name, ignoreCase); + } + }); + if (index >= 0) return; // already added + insertChildAt(new VirtualFileImpl(name, NULL_VIRTUAL_FILE, -42, -1), index, myChildren, ignoreCase); + } + + @Nullable // null if there can't be a child with this name, NULL_VIRTUAL_FILE + private synchronized VirtualFileSystemEntry doFindChildInArray(@NotNull Comparer comparer) { + VirtualFileSystemEntry[] array = myChildren; + long r = findIndexInBoth(array, comparer); + int indexInReal = (int)(r >> 32); + int indexInSuspicious = (int)r; + if (indexInSuspicious >= 0) return NULL_VIRTUAL_FILE; + + if (indexInReal >= 0) { + return array[indexInReal]; + } + return null; + } + + @Nullable // null if there can't be a child with this name, NULL_VIRTUAL_FILE if cached as absent, the file if found + private VirtualFileSystemEntry doFindChild(@NotNull String name, boolean ensureCanonicalName, @NotNull NewVirtualFileSystem delegate) { if (name.isEmpty()) { return null; } - final VirtualFileSystemEntry[] array; - final Map map; - final VirtualFileSystemEntry file; - synchronized (this) { - array = asArray(); - if (array == null) { - map = ensureAsMap(); - file = map.get(name); - } - else { - file = null; - map = null; - } - } - if (array != null) { - final boolean ignoreCase = !getFileSystem().isCaseSensitive(); - for (VirtualFileSystemEntry vf : array) { - if (vf.nameMatches(name, ignoreCase)) return vf; - } + final boolean ignoreCase = !delegate.isCaseSensitive(); + Comparer comparer = getComparer(name, ignoreCase); + VirtualFileSystemEntry found = doFindChildInArray(comparer); + if (found != null) return found; + + if (allChildrenLoaded()) { return NULL_VIRTUAL_FILE; } - if (file != null) return file; - if (ensureCanonicalName) { VirtualFile fake = new FakeVirtualFile(this, name); name = delegate.getCanonicallyCasedName(fake); @@ -152,37 +154,57 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry { synchronized (this) { // do not extract getId outside the synchronized block since it will cause a concurrency problem. int id = ourPersistence.getId(this, name, delegate); - if (id > 0) { - // maybe another doFindChild() sneaked in the middle - VirtualFileSystemEntry lastTry = map.get(name); - if (lastTry != null) return lastTry; - - final String shorty = new String(name); - VirtualFileSystemEntry child = createChild(shorty, id); // So we don't hold whole char[] buffer of a lengthy path - map.put(shorty, child); - return child; + if (id <= 0) { + return null; + } + // maybe another doFindChild() sneaked in the middle + VirtualFileSystemEntry[] array = myChildren; + long r = findIndexInBoth(array, comparer); + int indexInReal = (int)(r >> 32); + int indexInSuspicious = (int)r; + if (indexInSuspicious >= 0) return NULL_VIRTUAL_FILE; + // double check + if (indexInReal >= 0) { + return array[indexInReal]; } - } - return null; + String shorty = new String(name); + VirtualFileSystemEntry child = createChild(shorty, id, delegate); // So we don't hold whole char[] buffer of a lengthy path + + insertChildAt(child, indexInReal, array, ignoreCase); + return child; + } } @NotNull - public VirtualFileSystemEntry createChild(@NotNull String name, int id) { - final VirtualFileSystemEntry child; - final NewVirtualFileSystem fs = getFileSystem(); + private static Comparer getComparer(@NotNull final String name, final boolean ignoreCase) { + return new Comparer() { + @Override + public int compareMyKeyTo(@NotNull VirtualFileSystemEntry file) { + return -file.compareNameTo(name, ignoreCase); + } + }; + } + + private synchronized VirtualFileSystemEntry[] getArraySafely() { + return myChildren; + } + + @NotNull + public VirtualFileSystemEntry createChild(@NotNull String name, int id, @NotNull NewVirtualFileSystem delegate) { + VirtualFileSystemEntry child; final int attributes = ourPersistence.getFileAttributes(id); if (PersistentFS.isDirectory(attributes)) { - child = new VirtualDirectoryImpl(name, this, fs, id, attributes); + child = new VirtualDirectoryImpl(name, this, delegate, id, attributes); } else { child = new VirtualFileImpl(name, this, id, attributes); //noinspection TestOnlyProblems - assertAccessInTests(child); + assertAccessInTests(child, delegate); } - if (fs.markNewFilesAsDirty()) { + if (delegate.markNewFilesAsDirty()) { child.markDirty(); } @@ -210,15 +232,14 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry { } @TestOnly - private static void assertAccessInTests(VirtualFileSystemEntry child) { + private static void assertAccessInTests(@NotNull VirtualFileSystemEntry child, @NotNull NewVirtualFileSystem delegate) { final Application application = ApplicationManager.getApplication(); if (IS_UNDER_TEAMCITY && SHOULD_PERFORM_ACCESS_CHECK && application.isUnitTestMode() && application instanceof ApplicationImpl && ((ApplicationImpl)application).isComponentsCreated()) { - NewVirtualFileSystem fileSystem = child.getFileSystem(); - if (fileSystem != LocalFileSystem.getInstance() && fileSystem != JarFileSystem.getInstance()) { + if (delegate != LocalFileSystem.getInstance() && delegate != JarFileSystem.getInstance()) { return; } // root' children are loaded always @@ -229,7 +250,7 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry { if (!isUnder) { for (String root : allowed) { String childPath = child.getPath(); - if (child.getFileSystem() == JarFileSystem.getInstance()) { + if (delegate == JarFileSystem.getInstance()) { VirtualFile local = JarFileSystem.getInstance().getVirtualFileForJar(child); assert local != null : child; childPath = local.getPath(); @@ -256,6 +277,7 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry { // null means we were unable to get roots, so do not check access @Nullable + @TestOnly private static Set allowedRoots() { if (insideGettingRoots) return null; @@ -299,7 +321,8 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry { private static boolean insideGettingRoots; - private static VirtualFile[] getAllRoots(Project project) { + @TestOnly + private static VirtualFile[] getAllRoots(@NotNull Project project) { insideGettingRoots = true; final Set roots = new THashSet(); @@ -312,19 +335,14 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry { } @Nullable - private VirtualFileSystemEntry createAndFindChildWithEventFire(@NotNull String name) { - final NewVirtualFileSystem delegate = getFileSystem(); + private VirtualFileSystemEntry createAndFindChildWithEventFire(@NotNull String name, @NotNull NewVirtualFileSystem delegate) { final VirtualFile fake = new FakeVirtualFile(this, name); final FileAttributes attributes = delegate.getAttributes(fake); - if (attributes != null) { - final String realName = delegate.getCanonicallyCasedName(fake); - final VFileCreateEvent event = new VFileCreateEvent(null, this, realName, attributes.isDirectory(), true); - RefreshQueue.getInstance().processSingleEvent(event); - return findChild(realName); - } - else { - return null; - } + if (attributes == null) return null; + final String realName = delegate.getCanonicallyCasedName(fake); + final VFileCreateEvent event = new VFileCreateEvent(null, this, realName, attributes.isDirectory(), true); + RefreshQueue.getInstance().processSingleEvent(event); + return findChild(realName); } @Override @@ -333,95 +351,172 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry { return findChild(name, true, true, getFileSystem()); } + private static int findIndexInOneHalf(final VirtualFileSystemEntry[] array, + int start, + int end, + final boolean isSuspicious, + @NotNull final Comparer comparer) { + return binSearch(array, start, end, new Comparer() { + @Override + public int compareMyKeyTo(@NotNull VirtualFileSystemEntry file) { + if (isSuspicious && !isSuspiciousName(file)) return 1; + if (!isSuspicious && isSuspiciousName(file)) return -1; + return comparer.compareMyKeyTo(file); + } + }); + } + + // returns two int indices packed into one long. left index is for the real file array half, right is for the suspicious name array + private static long findIndexInBoth(@NotNull VirtualFileSystemEntry[] array, @NotNull Comparer comparer) { + int high = array.length - 1; + if (high == -1) { + return pack(-1, -1); + } + int low = 0; + boolean startInSuspicious = isSuspiciousName(array[low]); + boolean endInSuspicious = isSuspiciousName(array[high]); + if (startInSuspicious == endInSuspicious) { + int index = findIndexInOneHalf(array, low, high + 1, startInSuspicious, comparer); + int otherIndex = startInSuspicious ? -1 : -array.length - 1; + return startInSuspicious ? pack(otherIndex, index) : pack(index, otherIndex); + } + boolean suspicious = false; + int cmp = -1; + int mid = -1; + int foundIndex = -1; + while (low <= high) { + mid = low + high >>> 1; + VirtualFileSystemEntry file = array[mid]; + cmp = comparer.compareMyKeyTo(file); + suspicious = isSuspiciousName(file); + if (cmp == 0) { + foundIndex = mid; + break; + } + if ((suspicious || cmp <= 0) && (!suspicious || cmp >= 0)) { + int indexInSuspicious = findIndexInOneHalf(array, mid + 1, high + 1, true, comparer); + int indexInReal = findIndexInOneHalf(array, low, mid, false, comparer); + return pack(indexInReal, indexInSuspicious); + } + + if (cmp > 0) { + low = mid + 1; + } + else { + high = mid - 1; + } + } + + // key not found. + if (cmp != 0) foundIndex = -low-1; + int newStart = suspicious ? low : mid + 1; + int newEnd = suspicious ? mid + 1 : high + 1; + int theOtherHalfIndex = newStart < newEnd ? findIndexInOneHalf(array, newStart, newEnd, !suspicious, comparer) : -newStart-1; + return suspicious ? pack(theOtherHalfIndex, foundIndex) : pack(foundIndex, theOtherHalfIndex); + } + + private static long pack(int indexInReal, int indexInSuspicious) { + return (long)indexInReal << 32 | (indexInSuspicious & 0xffffffffL); + } + @Override @Nullable public synchronized NewVirtualFile findChildIfCached(@NotNull String name) { - final VirtualFileSystemEntry[] a = asArray(); - if (a != null) { - final boolean ignoreCase = !getFileSystem().isCaseSensitive(); - for (VirtualFileSystemEntry file : a) { - if (file.nameMatches(name, ignoreCase)) return file; - } - - return null; - } - - final Map map = asMap(); - if (map != null) { - final VirtualFileSystemEntry file = map.get(name); - return file != NULL_VIRTUAL_FILE ? file : null; - } - - return null; + final boolean ignoreCase = !getFileSystem().isCaseSensitive(); + Comparer comparer = getComparer(name, ignoreCase); + VirtualFileSystemEntry found = doFindChildInArray(comparer); + return found == NULL_VIRTUAL_FILE ? null : found; } @Override @NotNull public Iterable iterInDbChildren() { - return ContainerUtil.iterate(getInDbChildren(), new Condition() { - @Override - public boolean value(VirtualFile file) { - return file != NULL_VIRTUAL_FILE; - } - }); - } - - @NotNull - private synchronized Collection getInDbChildren() { - VirtualFileSystemEntry[] children = asArray(); - if (children != null) { - return Arrays.asList((VirtualFile[])children); - } - if (!ourPersistence.wereChildrenAccessed(this)) { return Collections.emptyList(); } - if (ourPersistence.areChildrenLoaded(this)) { - return Arrays.asList(getChildren()); + if (!ourPersistence.areChildrenLoaded(this)) { + final String[] names = ourPersistence.listPersisted(this); + final NewVirtualFileSystem delegate = PersistentFS.replaceWithNativeFS(getFileSystem()); + for (String name : names) { + findChild(name, false, false, delegate); + } } - - final String[] names = ourPersistence.listPersisted(this); - final NewVirtualFileSystem delegate = PersistentFS.replaceWithNativeFS(getFileSystem()); - for (String name : names) { - findChild(name, false, false, delegate); - } - - // important: should return a copy here for safe iterations - return new ArrayList(ensureAsMap().values()); + return getCachedChildren(); } @Override @NotNull public synchronized VirtualFile[] getChildren() { - VirtualFileSystemEntry[] children = asArray(); - if (children != null) { - return children; + VirtualFileSystemEntry[] children = myChildren; + NewVirtualFileSystem delegate = getFileSystem(); + final boolean ignoreCase = !delegate.isCaseSensitive(); + if (allChildrenLoaded()) { + assertConsistency(children, ignoreCase); + int sas = getSuspiciousArrayStart(); + return sas == children.length ? children : Arrays.copyOf(children, sas); } - Pair pair = ourPersistence.listAll(this); - final int[] childrenIds = pair.second; + FSRecords.NameId[] childrenIds = ourPersistence.listAll(this); + VirtualFileSystemEntry[] result; if (childrenIds.length == 0) { - children = EMPTY_ARRAY; + result = EMPTY_ARRAY; } else { - children = new VirtualFileSystemEntry[childrenIds.length]; - String[] names = pair.first; - final Map map = asMap(); - for (int i = 0; i < children.length; i++) { - final int childId = childrenIds[i]; - final String name = names[i]; - VirtualFileSystemEntry child = map != null ? map.get(name) : null; + Arrays.sort(childrenIds, new Comparator() { + @Override + public int compare(FSRecords.NameId o1, FSRecords.NameId o2) { + String name1 = o1.name; + String name2 = o2.name; + return compareNames(name1, name2, ignoreCase); + } + }); + result = new VirtualFileSystemEntry[childrenIds.length]; + int delegateI = 0; + int cachedI = 0; - children[i] = child != null && child != NULL_VIRTUAL_FILE ? child : createChild(name, childId); + int cachedEnd = getSuspiciousArrayStart(); + while (delegateI < childrenIds.length) { + FSRecords.NameId nameId = childrenIds[delegateI]; + while (cachedI < cachedEnd && children[cachedI].compareNameTo(nameId.name, ignoreCase) < 0) cachedI++; + + VirtualFileSystemEntry resultFile; + if (cachedI < cachedEnd && children[cachedI].compareNameTo(nameId.name, ignoreCase) == 0) { + resultFile = children[cachedI++]; + } + else { + resultFile = createChild(nameId.name, nameId.id, delegate); + } + result[delegateI++] = resultFile; } + + assertConsistency(result, ignoreCase); } if (getId() > 0) { - myChildren = children; + myChildren = result; + setChildrenLoaded(); } - return children; + return result; + } + + private static void assertConsistency(@NotNull VirtualFileSystemEntry[] array, boolean ignoreCase) { + for (int i = 0; i < array.length; i++) { + VirtualFileSystemEntry file = array[i]; + if (isSuspiciousName(file) && i != array.length - 1 ) { + assert isSuspiciousName(array[i + 1]); + } + if (i != 0) { + String prevName = array[i - 1].getName(); + int cmp = file.compareNameTo(prevName, ignoreCase); + assert cmp != 0 : prevName + " equals to "+ file+"; children: "+Arrays.toString(array); + + if (isSuspiciousName(file) == isSuspiciousName(array[i - 1])) { + assert cmp > 0 : "Not sorted"; + } + } + } } @Override @@ -430,127 +525,135 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry { return findChild(name, false, true, getFileSystem()); } - @Override - @Nullable - public NewVirtualFile findChildById(int id) { - final NewVirtualFile loaded = findChildByIdIfCached(id); - if (loaded != null) { - return loaded; + public VirtualFileSystemEntry findChildById(int id, boolean cachedOnly) { + VirtualFile[] array = getArraySafely(); + VirtualFileSystemEntry result = null; + for (VirtualFile file : array) { + VirtualFileSystemEntry withId = (VirtualFileSystemEntry)file; + if (withId.getId() == id) { + result = withId; + break; + } } + if (result != null) return result; + if (cachedOnly) return null; String name = ourPersistence.getName(id); return findChild(name, false, false, getFileSystem()); } - @Override - public NewVirtualFile findChildByIdIfCached(int id) { - final VirtualFile[] a; - synchronized (this) { - a = asArray(); - } - if (a != null) { - for (VirtualFile file : a) { - NewVirtualFile withId = (NewVirtualFile)file; - if (withId.getId() == id) return withId; - } - - return null; - } - synchronized (this) { - final Map map = asMap(); - if (map != null) { - for (Map.Entry entry : map.entrySet()) { - VirtualFile file = entry.getValue(); - if (file == NULL_VIRTUAL_FILE) continue; - NewVirtualFile withId = (NewVirtualFile)file; - if (withId.getId() == id) return withId; - } - } - } - return null; - } - @NotNull @Override public byte[] contentsToByteArray() throws IOException { throw new IOException("Cannot get content of directory: " + this); } - // MUST BE CALLED UNDER this LOCK - @Nullable - private VirtualFileSystemEntry[] asArray() { - Object children = myChildren; - if (children instanceof VirtualFileSystemEntry[]) return (VirtualFileSystemEntry[])children; - return null; + public synchronized void addChild(@NotNull VirtualFileSystemEntry child) { + VirtualFileSystemEntry[] array = myChildren; + final String childName = child.getName(); + final boolean ignoreCase = !getFileSystem().isCaseSensitive(); + long r = findIndexInBoth(array, getComparer(childName, ignoreCase)); + int indexInReal = (int)(r >> 32); + int indexInSuspicious = (int)r; + + if (indexInSuspicious >= 0) { + // remove suspicious first + myChildren = array = ArrayUtil.remove(array, indexInSuspicious, new ArrayFactory() { + @Override + public VirtualFileSystemEntry[] create(int count) { + return new VirtualFileSystemEntry[count]; + } + }); + assertConsistency(myChildren, ignoreCase); + } + if (indexInReal >= 0) return; // already stored + + insertChildAt(child, indexInReal, array, ignoreCase); } - // MUST BE CALLED UNDER this LOCK - @Nullable - private Map asMap() { - Object children = myChildren; - if (children instanceof Map) { - @SuppressWarnings({"unchecked"}) - final Map map = (Map)children; - return map; - } - return null; - } - - @NotNull - private Map ensureAsMap() { - Map map; - if (myChildren == null) { - map = createMap(); - myChildren = map; - } - else { - @SuppressWarnings({"unchecked"}) - final Map aMap = (Map)myChildren; - map = aMap; - } - - return map; - } - - public synchronized void addChild(@NotNull VirtualFileSystemEntry file) { - final VirtualFileSystemEntry[] a = asArray(); - if (a != null) { - myChildren = ArrayUtil.append(a, file); - } - else { - ensureAsMap().put(file.getName(), file); - } + private void insertChildAt(@NotNull VirtualFileSystemEntry file, int negativeIndex, @NotNull VirtualFileSystemEntry[] array, boolean ignoreCase) { + VirtualFileSystemEntry[] appended = new VirtualFileSystemEntry[array.length + 1]; + int i = -negativeIndex -1; + System.arraycopy(array, 0, appended, 0, i); + appended[i] = file; + System.arraycopy(array, i, appended, i+1, array.length - i); + assertConsistency(appended, ignoreCase); + myChildren = appended; } public synchronized void removeChild(@NotNull VirtualFile file) { - final VirtualFileSystemEntry[] a = asArray(); - if (a != null) { - myChildren = ArrayUtil.remove(a, file); - } - else { - ensureAsMap().put(file.getName(), NULL_VIRTUAL_FILE); - } + boolean ignoreCase = !getFileSystem().isCaseSensitive(); + String name = file.getName(); + + myChildren = ArrayUtil.remove(myChildren, (VirtualFileSystemEntry)file, new ArrayFactory() { + @Override + public VirtualFileSystemEntry[] create(int count) { + return new VirtualFileSystemEntry[count]; + } + }); + addToSuspiciousNames(name, ignoreCase); + assertConsistency(myChildren, ignoreCase); } + private static final int CHILDREN_CACHED = 0x08; public synchronized boolean allChildrenLoaded() { - return asArray() != null; + return getFlag(CHILDREN_CACHED); + } + private void setChildrenLoaded() { + setFlag(CHILDREN_CACHED, true); } @NotNull public synchronized List getSuspiciousNames() { - final Map map = asMap(); - if (map == null) return Collections.emptyList(); + List suspicious = new SubList(myChildren, getSuspiciousArrayStart(), myChildren.length); + return ContainerUtil.map2List(suspicious, new Function() { + @Override + public String fun(VirtualFile file) { + return file.getName(); + } + }); + } - List names = null; + private int getSuspiciousArrayStart() { + int index = binSearch(myChildren, 0, myChildren.length, new Comparer() { + @Override + public int compareMyKeyTo(@NotNull VirtualFileSystemEntry v) { + return isSuspiciousName(v) ? -1 : 1; + } + }); + return -index - 1; + } - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() == NULL_VIRTUAL_FILE) { - if (names == null) names = new SmartList(); - names.add(entry.getKey()); + private static boolean isSuspiciousName(@NotNull VirtualFileSystemEntry v) { + return v.getParent() == NULL_VIRTUAL_FILE; + } + + interface Comparer { + int compareMyKeyTo(@NotNull VirtualFileSystemEntry file); + } + + private static int binSearch(@NotNull VirtualFileSystemEntry[] array, + int start, + int end, + @NotNull Comparer comparer) { + int low = start; + int high = end - 1; + assert low >= 0 && low <= array.length; + + while (low <= high) { + int mid = low + high >>> 1; + int cmp = comparer.compareMyKeyTo(array[mid]); + if (cmp > 0) { + low = mid + 1; + } + else if (cmp < 0) { + high = mid - 1; + } + else { + return mid; // key found } } - - return names == null ? Collections.emptyList() : names; + return -(low + 1); // key not found. } @Override @@ -560,40 +663,8 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry { @Override @NotNull - public synchronized Collection getCachedChildren() { - final Map map = asMap(); - if (map != null) { - Set files = new THashSet(map.values()); - files.remove(NULL_VIRTUAL_FILE); - return files; - } - - final VirtualFile[] a = asArray(); - if (a != null) return Arrays.asList(a); - - return Collections.emptyList(); - } - - @NotNull - private Map createMap() { - return getFileSystem().isCaseSensitive() - ? new THashMap() - : new THashMap(CaseInsensitiveStringHashingStrategy.INSTANCE); - } - - @TestOnly - public synchronized void cleanupCachedChildren(@NotNull Set survivors) { - assert ApplicationManager.getApplication().isUnitTestMode(); - if (survivors.contains(this)) { - for (VirtualFile file : getCachedChildren()) { - if (file instanceof VirtualDirectoryImpl) { - ((VirtualDirectoryImpl)file).cleanupCachedChildren(survivors); - } - } - } - else { - myChildren = null; - } + public synchronized List getCachedChildren() { + return new SubList(myChildren, 0, getSuspiciousArrayStart()); } @Override @@ -606,4 +677,21 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry { public OutputStream getOutputStream(final Object requestor, final long newModificationStamp, final long newTimeStamp) throws IOException { throw new IOException("getOutputStream() must not be called against a directory: " + getUrl()); } + + @Override + public void markDirtyRecursively() { + markDirty(); + markDirtyRecursivelyInternal(); + } + + // optimisation: do not travel up unnecessary + private void markDirtyRecursivelyInternal() { + for (VirtualFileSystemEntry child : getArraySafely()) { + if (isSuspiciousName(child)) break; + child.markDirtyInternal(); + if (child instanceof VirtualDirectoryImpl) { + ((VirtualDirectoryImpl)child).markDirtyRecursivelyInternal(); + } + } + } } diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileImpl.java index 0ff55ade14b6..f89e53226bd8 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileImpl.java @@ -78,16 +78,6 @@ public class VirtualFileImpl extends VirtualFileSystemEntry { return null; } - @Override - public NewVirtualFile findChildById(final int id) { - return null; - } - - @Override - public NewVirtualFile findChildByIdIfCached(final int id) { - return null; - } - @Override public VirtualFile[] getChildren() { return EMPTY_ARRAY; diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileSystemEntry.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileSystemEntry.java index ff4e5f5a041e..e5a63f2fd33f 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileSystemEntry.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileSystemEntry.java @@ -23,6 +23,7 @@ import com.intellij.openapi.util.ThrowableComputable; import com.intellij.openapi.util.io.FileTooBigException; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsBundle; import com.intellij.openapi.vfs.VirtualFile; @@ -31,6 +32,7 @@ import com.intellij.openapi.vfs.encoding.EncodingRegistry; import com.intellij.openapi.vfs.newvfs.NewVirtualFile; import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS; import com.intellij.psi.SingleRootFileViewProvider; +import com.intellij.util.ArrayUtil; import com.intellij.util.io.IOUtil; import com.intellij.util.text.StringFactory; import org.jetbrains.annotations.NonNls; @@ -57,13 +59,27 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile { private static final int IS_SPECIAL_FLAG = 0x0800; private static final int INT_FLAGS_MASK = 0xff00; - @NonNls private static final String EMPTY = ""; - @NonNls private static final String[] WELL_KNOWN_SUFFIXES = {"$1.class", "$2.class", ".class", ".java", ".html", ".txt", ".xml"}; + private static final String EMPTY = ""; + @NonNls private static final String[] WELL_KNOWN_SUFFIXES = {"$1.class", "$2.class","Test.java","List.java","tion.java", ".class", ".java", ".html", ".txt", ".xml",".php",".gif",".svn",".css",".js"}; + private static final byte[][] WELL_KNOWN_SUFFIXES_BYTES; + private static final int[] WELL_KNOWN_SUFFIXES_LENGTH; + private static final int SUFFIX_BITS = 4; + static { + WELL_KNOWN_SUFFIXES_BYTES = new byte[WELL_KNOWN_SUFFIXES.length][]; + WELL_KNOWN_SUFFIXES_LENGTH = new int[WELL_KNOWN_SUFFIXES.length]; + for (int i = 0; i < WELL_KNOWN_SUFFIXES.length; i++) { + String suffix = WELL_KNOWN_SUFFIXES[i]; + WELL_KNOWN_SUFFIXES_BYTES[i] = suffix.getBytes(CharsetToolkit.UTF8_CHARSET); + WELL_KNOWN_SUFFIXES_LENGTH[i] = suffix.length(); + } + + assert 1 << SUFFIX_BITS == WELL_KNOWN_SUFFIXES.length + 1; + } /** Either a String or byte[]. Possibly should be concatenated with one of the entries in the {@link #WELL_KNOWN_SUFFIXES}. */ private volatile Object myName; private volatile VirtualDirectoryImpl myParent; - /** Also, high three bits are used as an index into the {@link #WELL_KNOWN_SUFFIXES} array. */ + /** Also, high four bits are used as an index into the {@link #WELL_KNOWN_SUFFIXES} array. */ private volatile short myFlags = 0; private volatile int myId; @@ -73,7 +89,7 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile { storeName(name); - if (parent != null) { + if (parent != null && parent != VirtualDirectoryImpl.NULL_VIRTUAL_FILE) { setFlagInt(IS_SYMLINK_FLAG, PersistentFS.isSymLink(attributes)); setFlagInt(IS_SPECIAL_FLAG, PersistentFS.isSpecialFile(attributes)); updateLinkStatus(); @@ -81,12 +97,12 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile { } private void storeName(@NotNull String name) { - myFlags &= 0x1fff; + myFlags &= 0xffff >>> SUFFIX_BITS; for (int i = 0; i < WELL_KNOWN_SUFFIXES.length; i++) { String suffix = WELL_KNOWN_SUFFIXES[i]; if (name.endsWith(suffix)) { name = StringUtil.trimEnd(name, suffix); - int mask = (i+1) << 13; + int mask = (i+1) << 16-SUFFIX_BITS; myFlags |= mask; break; } @@ -104,27 +120,39 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile { setFlagInt(HAS_SYMLINK_FLAG, isSymLink || ((VirtualFileSystemEntry)myParent).getFlagInt(HAS_SYMLINK_FLAG)); } + @NotNull private static Object encodeName(@NotNull String name) { int length = name.length(); - if (length == 0) return EMPTY; - - if (!IOUtil.isAscii(name)) { - return name; - } + if (length == 0) return ArrayUtil.EMPTY_BYTE_ARRAY; byte[] bytes = new byte[length]; for (int i = 0; i < length; i++) { - bytes[i] = (byte)name.charAt(i); + char c = name.charAt(i); + if (!IOUtil.isAscii(c)) { + return name; + } + bytes[i] = (byte)c; } return bytes; } @NotNull private String getEncodedSuffix() { - int index = (myFlags >> 13) & 0x07; + int index = (myFlags >> 16-SUFFIX_BITS) & ((1<> 16-SUFFIX_BITS) & ((1<> 16-SUFFIX_BITS) & ((1< listAll(int parentId) { + public static class NameId { + public static final NameId[] EMPTY_ARRAY = new NameId[0]; + public final int id; + public final String name; + + public NameId(int id, @NotNull String name) { + this.id = id; + this.name = name; + } + } + + @NotNull + public static NameId[] listAll(int parentId) { try { r.lock(); try { final DataInputStream input = readAttribute(parentId, CHILDREN_ATT); - if (input == null) return Pair.create(ArrayUtil.EMPTY_STRING_ARRAY, ArrayUtil.EMPTY_INT_ARRAY); + if (input == null) return NameId.EMPTY_ARRAY; - final int count = DataInputOutputUtil.readINT(input); - final int[] ids = ArrayUtil.newIntArray(count); - final String[] names = ArrayUtil.newStringArray(count); + int count = DataInputOutputUtil.readINT(input); + NameId[] result = count == 0 ? NameId.EMPTY_ARRAY : new NameId[count]; for (int i = 0; i < count; i++) { int id = DataInputOutputUtil.readINT(input); id = id >= 0 ? id + parentId : -id; - ids[i] = id; - names[i] = getName(id); + result[i] = new NameId(id, getName(id)); } input.close(); - return Pair.create(names, ids); + return result; } finally { r.unlock(); diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFS.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFS.java index 456da7aff059..ec2c892a635b 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFS.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFS.java @@ -15,7 +15,6 @@ */ package com.intellij.openapi.vfs.newvfs.persistent; -import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; @@ -56,7 +55,7 @@ public abstract class PersistentFS extends ManagingFS { public abstract String[] listPersisted(@NotNull VirtualFile parent); @NotNull - public abstract Pair listAll(@NotNull VirtualFile parent); + public abstract FSRecords.NameId[] listAll(@NotNull VirtualFile parent); public abstract int getId(@NotNull VirtualFile parent, @NotNull String childName, @NotNull NewVirtualFileSystem delegate); diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFSImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFSImpl.java index 1d6533ecb405..ae03a6ced1c5 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFSImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFSImpl.java @@ -19,28 +19,23 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ex.ApplicationEx; import com.intellij.openapi.components.ApplicationComponent; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.ShutDownTracker; import com.intellij.openapi.util.io.*; import com.intellij.openapi.vfs.*; -import com.intellij.openapi.vfs.newvfs.BulkFileListener; -import com.intellij.openapi.vfs.newvfs.FileAttribute; -import com.intellij.openapi.vfs.newvfs.NewVirtualFile; -import com.intellij.openapi.vfs.newvfs.NewVirtualFileSystem; +import com.intellij.openapi.vfs.newvfs.*; import com.intellij.openapi.vfs.newvfs.events.*; import com.intellij.openapi.vfs.newvfs.impl.FakeVirtualFile; import com.intellij.openapi.vfs.newvfs.impl.VirtualDirectoryImpl; import com.intellij.openapi.vfs.newvfs.impl.VirtualFileSystemEntry; import com.intellij.util.ArrayUtil; +import com.intellij.util.Function; +import com.intellij.util.IncorrectOperationException; import com.intellij.util.containers.ConcurrentIntObjectMap; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.StripedLockIntObjectConcurrentHashMap; import com.intellij.util.io.ReplicatorInputStream; import com.intellij.util.messages.MessageBus; -import gnu.trove.THashMap; -import gnu.trove.TIntArrayList; -import gnu.trove.TIntHashSet; -import gnu.trove.TIntObjectHashMap; +import gnu.trove.*; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -64,6 +59,7 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone private final Map myRoots = new THashMap(FileUtil.PATH_HASHING_STRATEGY); private final TIntObjectHashMap myRootsById = new TIntObjectHashMap(); @Nullable private VirtualFileSystemEntry myFakeRoot; + @NotNull private final ConcurrentIntObjectMap myIdToDirCache = new StripedLockIntObjectConcurrentHashMap(); private final Object myInputLock = new Object(); @@ -127,13 +123,17 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone @NotNull public String[] list(@NotNull final VirtualFile file) { int id = getFileId(file); - int[] childrenIds = FSRecords.list(id); - String[] names = listPersisted(childrenIds); - if (areChildrenLoaded(id)) { - return names; + + FSRecords.NameId[] nameIds = FSRecords.listAll(id); + if (!areChildrenLoaded(id)) { + nameIds = persistAllChildren(file, id, nameIds); } - Pair pair = persistAllChildren(file, id, Pair.create(names, childrenIds)); - return pair.first; + return ContainerUtil.map2Array(nameIds, String.class, new Function() { + @Override + public String fun(FSRecords.NameId id) { + return id.name; + } + }); } @Override @@ -152,85 +152,72 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone } @NotNull - private static Pair persistAllChildren(@NotNull VirtualFile file, int id, @NotNull Pair current) { - String[] currentNames = current.first; - int[] currentIds = current.second; - - NewVirtualFileSystem fs = replaceWithNativeFS(getDelegate(file)); + private static FSRecords.NameId[] persistAllChildren(@NotNull final VirtualFile file, final int id, @NotNull FSRecords.NameId[] current) { + final NewVirtualFileSystem fs = replaceWithNativeFS(getDelegate(file)); String[] delegateNames = VfsUtil.filterNames(fs.list(file)); - if (delegateNames.length == 0 && currentNames.length > 0) { + if (delegateNames.length == 0 && current.length > 0) { return current; } - String[] names; - if (currentNames.length == 0) { - names = delegateNames; + THashMap result = new THashMap(); + if (current.length == 0) { + for (String name : delegateNames) { + result.put(name, new FSRecords.NameId(-1, name)); + } } else { - Set allNamesSet = new LinkedHashSet((currentNames.length + delegateNames.length) * 2); - ContainerUtil.addAll(allNamesSet, currentNames); - ContainerUtil.addAll(allNamesSet, delegateNames); - names = ArrayUtil.toStringArray(allNamesSet); - } - - int[] childrenIds = ArrayUtil.newIntArray(names.length); - - int filter = 0; - for (int i = 0; i < names.length; i++) { - final String name = names[i]; - int idx = ArrayUtil.indexOf(currentNames, name); - if (idx >= 0) { - childrenIds[i] = currentIds[idx]; + for (FSRecords.NameId nameId : current) { + result.put(nameId.name, nameId); } - else { - final FakeVirtualFile child = new FakeVirtualFile(file, name); - final FileAttributes attributes = fs.getAttributes(child); - if (attributes != null) { - final int childId = createAndFillRecord(fs, child, id, attributes); - childrenIds[i] = childId; - } - else { - childrenIds[i] = -1; - ++filter; - LOG.warn("fs=" + fs + " dir=" + file + " name=" + name + " curr=" + currentNames.length); + for (String name : delegateNames) { + if (!result.containsKey(name)) { + result.put(name, new FSRecords.NameId(-1, name)); } } } - if (filter != 0) { - final int newLength = names.length - filter; - final String[] newNames = ArrayUtil.newStringArray(newLength); - final int[] newIds = ArrayUtil.newIntArray(newLength); - for (int i = 0, k = 0; i < names.length; i++) { - if (childrenIds[i] >= 0) { - newNames[k] = names[i]; - newIds[k] = childrenIds[i]; - //noinspection AssignmentToForLoopParameter - ++k; + final TIntArrayList childrenIds = new TIntArrayList(); + result.transformValues(new TObjectFunction() { + @Override + public FSRecords.NameId execute(FSRecords.NameId nameId) { + if (nameId.id < 0) { + FakeVirtualFile child = new FakeVirtualFile(file, nameId.name); + FileAttributes attributes = fs.getAttributes(child); + if (attributes != null) { + int childId = createAndFillRecord(fs, child, id, attributes); + nameId = new FSRecords.NameId(childId, nameId.name); + } } + if (nameId.id > 0) { + childrenIds.add(nameId.id); + } + return nameId; } - names = newNames; - childrenIds = newIds; - } + }); - FSRecords.updateList(id, childrenIds); + FSRecords.updateList(id, childrenIds.toNativeArray()); + setChildrenCached(id); + + Collection nameIds = result.values(); + return nameIds.toArray(new FSRecords.NameId[nameIds.size()]); + } + + public static void setChildrenCached(int id) { int flags = FSRecords.getFlags(id); FSRecords.setFlags(id, flags | CHILDREN_CACHED_FLAG, true); - - return Pair.create(names, childrenIds); } @Override @NotNull - public Pair listAll(@NotNull VirtualFile parent) { + public FSRecords.NameId[] listAll(@NotNull VirtualFile parent) { final int parentId = getFileId(parent); - Pair pair = FSRecords.listAll(parentId); + FSRecords.NameId[] nameIds = FSRecords.listAll(parentId); if (!areChildrenLoaded(parentId)) { - return persistAllChildren(parent, parentId, pair); + return persistAllChildren(parent, parentId, nameIds); } - return pair; + return nameIds; } private static boolean areChildrenLoaded(final int parentId) { @@ -709,7 +696,7 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone }; @NotNull - private static List validateEvents(@NotNull List events) { + private static List validateEvents(@NotNull List events) { final List deletionEvents = ContainerUtil.newArrayList(); for (int i = 0, size = events.size(); i < size; i++) { final VFileEvent event = events.get(i); @@ -747,11 +734,10 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone return filtered; } - @Override - public void processEvents(@NotNull List events) { + public void processEvents(@NotNull List events) { ApplicationManager.getApplication().assertWriteAccessAllowed(); - List validated = validateEvents(events); + List validated = validateEvents(events); BulkFileListener publisher = myEventsBus.syncPublisher(VirtualFileManager.VFS_CHANGES); publisher.before(validated); @@ -764,11 +750,12 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone @Override @Nullable public VirtualFileSystemEntry findRoot(@NotNull String basePath, @NotNull NewVirtualFileSystem fs) { - final String rootUrl = fs.getProtocol() + "://" + basePath; + String rootUrl = fs.getProtocol() + "://" + VfsImplUtil.normalize(fs, basePath); + VirtualFileSystemEntry root; myRootsLock.readLock().lock(); try { - final VirtualFileSystemEntry root = basePath.isEmpty() ? myFakeRoot : myRoots.get(rootUrl); + root = basePath.isEmpty() ? myFakeRoot : myRoots.get(rootUrl); if (root != null) return root; } finally { @@ -777,10 +764,12 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone myRootsLock.writeLock().lock(); try { - VirtualFileSystemEntry root = basePath.isEmpty() ? myFakeRoot : myRoots.get(rootUrl); + root = basePath.isEmpty() ? myFakeRoot : myRoots.get(rootUrl); if (root != null) return root; - final int rootId = FSRecords.findRootRecord(rootUrl); + int rootId = FSRecords.findRootRecord(rootUrl); + root = myRootsById.get(rootId); + if (root != null) return root; if (basePath.isEmpty()) { // fake super-root @@ -802,8 +791,15 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone else if (fs instanceof JarFileSystem) { // optimization: for jar roots do not store base path in the myName field, use local FS file's getPath() String parentPath = basePath.substring(0, basePath.indexOf(JarFileSystem.JAR_SEPARATOR)); - final VirtualFile parentLocalFile = LocalFileSystem.getInstance().findFileByPath(parentPath); + VirtualFile parentLocalFile = LocalFileSystem.getInstance().findFileByPath(parentPath); if (parentLocalFile == null) return null; + + // check one more time since the findFileByPath could have created the root (by reentering the findRoot) + root = myRoots.get(rootUrl); + if (root != null) return root; + root = myRootsById.get(rootId); + if (root != null) return root; + root = new JarRoot(fs, rootId, parentLocalFile); } else { @@ -827,21 +823,18 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone } else { myRoots.put(rootUrl, root); - myRootsById.put(root.getId(), root); + myRootsById.put(rootId, root); + + if (rootId != root.getId()) throw new AssertionError(); } return root; } - catch (IOException e) { - throw new RuntimeException(e); - } finally { myRootsLock.writeLock().unlock(); } } - @NotNull private final ConcurrentIntObjectMap myIdToDirCache = new StripedLockIntObjectConcurrentHashMap(); - @Override public void clearIdCache() { myIdToDirCache.clear(); @@ -852,35 +845,34 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone @Override @Nullable public NewVirtualFile findFileById(final int id) { - return _findFileById(id, false, new TIntArrayList(DEPTH_LIMIT), 0); + return findFileById(id, false, null, 0); } @Override public NewVirtualFile findFileByIdIfCached(final int id) { - return _findFileById(id, true, new TIntArrayList(DEPTH_LIMIT), 0); + return findFileById(id, true, null, 0); } @Nullable - private NewVirtualFile _findFileById(int id, boolean cachedOnly, TIntArrayList visited, int mask) { - NewVirtualFile cached = myIdToDirCache.get(id); - if (cached != null) { - return cached; - } + private VirtualFileSystemEntry findFileById(int id, boolean cachedOnly, TIntArrayList visited, int mask) { + VirtualFileSystemEntry cached = myIdToDirCache.get(id); + if (cached != null) return cached; - if (visited.size() >= DEPTH_LIMIT || (mask & id) == id && visited.contains(id)) { - StringBuilder sb = new StringBuilder("Dead loop detected in persistent FS (id=" + id + " cached-only=" + cachedOnly + "):"); + if (visited != null && (visited.size() >= DEPTH_LIMIT || (mask & id) == id && visited.contains(id))) { + @NonNls String sb = "Dead loop detected in persistent FS (id=" + id + " cached-only=" + cachedOnly + "):"; for (int i = 0; i < visited.size(); i++) { int _id = visited.get(i); - sb.append("\n ").append(_id).append(" '").append(getName(_id)).append("' ") - .append(String.format("%02x", getFileAttributes(_id))).append(' ').append(myIdToDirCache.containsKey(_id)); + sb += "\n " + _id + " '" + getName(_id) + "' " + + String.format("%02x", getFileAttributes(_id)) + ' ' + myIdToDirCache.containsKey(_id); } - LOG.error(sb.toString()); + LOG.error(sb); return null; } + if (visited == null) visited = new TIntArrayList(DEPTH_LIMIT); visited.add(id); int parentId = getParent(id); - NewVirtualFile result; + VirtualFileSystemEntry result; if (parentId == 0) { myRootsLock.readLock().lock(); try { @@ -891,17 +883,17 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone } } else { - NewVirtualFile parentFile = _findFileById(parentId, cachedOnly, visited, mask |= id); - if (parentFile == null) { - result = null; + VirtualFileSystemEntry parentFile = findFileById(parentId, cachedOnly, visited, mask |= id); + if (parentFile instanceof VirtualDirectoryImpl) { + result = ((VirtualDirectoryImpl)parentFile).findChildById(id, cachedOnly); } else { - result = cachedOnly ? parentFile.findChildByIdIfCached(id) : parentFile.findChildById(id); + result = null; } } if (result != null && result.isDirectory()) { - NewVirtualFile old = myIdToDirCache.put(id, result); + VirtualFileSystemEntry old = myIdToDirCache.put(id, result); if (old != null) result = old; } return result; @@ -960,11 +952,11 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone return VfsUtilCore.toVirtualFileArray(roots); } - private void applyEvent(@NotNull VFileEvent event) { + private VirtualFileSystemEntry applyEvent(@NotNull VFileEvent event) { try { if (event instanceof VFileCreateEvent) { final VFileCreateEvent createEvent = (VFileCreateEvent)event; - executeCreateChild(createEvent.getParent(), createEvent.getChildName()); + return executeCreateChild(createEvent.getParent(), createEvent.getChildName()); } else if (event instanceof VFileDeleteEvent) { final VFileDeleteEvent deleteEvent = (VFileDeleteEvent)event; @@ -976,7 +968,7 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone } else if (event instanceof VFileCopyEvent) { final VFileCopyEvent copyEvent = (VFileCopyEvent)event; - executeCreateChild(copyEvent.getNewParent(), copyEvent.getNewChildName()); + return executeCreateChild(copyEvent.getNewParent(), copyEvent.getNewChildName()); } else if (event instanceof VFileMoveEvent) { final VFileMoveEvent moveEvent = (VFileMoveEvent)event; @@ -996,6 +988,7 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone // Exception applying single event should not prevent other events from applying. LOG.error(e); } + return null; } @NotNull @@ -1004,7 +997,7 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone return "PersistentFS"; } - private static void executeCreateChild(@NotNull VirtualFile parent, @NotNull String name) { + private static VirtualFileSystemEntry executeCreateChild(@NotNull VirtualFile parent, @NotNull String name) { final NewVirtualFileSystem delegate = getDelegate(parent); final VirtualFile fake = new FakeVirtualFile(parent, name); final FileAttributes attributes = delegate.getAttributes(fake); @@ -1014,8 +1007,11 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone appendIdToParentList(parentId, childId); assert parent instanceof VirtualDirectoryImpl : parent; final VirtualDirectoryImpl dir = (VirtualDirectoryImpl)parent; - dir.addChild(dir.createChild(name, childId)); + VirtualFileSystemEntry child = dir.createChild(name, childId, dir.getFileSystem()); + dir.addChild(child); + return child; } + return null; } private static int createAndFillRecord(@NotNull NewVirtualFileSystem delegateSystem, @@ -1036,41 +1032,36 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone private void executeDelete(@NotNull VirtualFile file) { if (!file.exists()) { LOG.error("Deleting a file, which does not exist: " + file.getPath()); + return; + } + clearIdCache(); + + int id = getFileId(file); + FSRecords.deleteRecordRecursively(id); + + final VirtualFile parent = file.getParent(); + final int parentId = parent == null ? 0 : getFileId(parent); + + if (parentId == 0) { + myRootsLock.writeLock().lock(); + try { + String rootUrl = file.getUrl(); + VirtualFileSystemEntry root = myRoots.remove(rootUrl); + myRootsById.remove(id); + FSRecords.deleteRootRecord(id); + } + finally { + myRootsLock.writeLock().unlock(); + } } else { - clearIdCache(); - - final int id = getFileId(file); - FSRecords.deleteRecordRecursively(id); - - final VirtualFile parent = file.getParent(); - final int parentId = parent != null ? getFileId(parent) : 0; - - if (parentId != 0) { - removeIdFromParentList(parentId, id, parent, file); - VirtualDirectoryImpl directory = (VirtualDirectoryImpl)file.getParent(); - assert directory != null : file; - directory.removeChild(file); - } - else { - myRootsLock.writeLock().lock(); - try { - myRoots.remove(file.getUrl()); - myRootsById.remove(id); - try { - FSRecords.deleteRootRecord(id); - } - catch (IOException e) { - throw new RuntimeException(e); - } - } - finally { - myRootsLock.writeLock().unlock(); - } - } - - invalidateSubtree(file); + removeIdFromParentList(parentId, id, parent, file); + VirtualDirectoryImpl directory = (VirtualDirectoryImpl)file.getParent(); + assert directory != null : file; + directory.removeChild(file); } + + invalidateSubtree(file); } private static void invalidateSubtree(@NotNull VirtualFile file) { @@ -1149,7 +1140,7 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone } @Override - public String getName(final int id) { + public String getName(int id) { assert id > 0; return FSRecords.getName(id); } @@ -1182,7 +1173,7 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone private static class JarRoot extends VirtualDirectoryImpl { private final VirtualFile myParentLocalFile; - public JarRoot(@NotNull NewVirtualFileSystem fs, int rootId, @NotNull VirtualFile parentLocalFile) { + private JarRoot(@NotNull NewVirtualFileSystem fs, int rootId, @NotNull VirtualFile parentLocalFile) { super("", null, fs, rootId, 0); myParentLocalFile = parentLocalFile; } @@ -1194,8 +1185,13 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone } @Override - protected Object rawName() { + protected String rawName() { return myParentLocalFile.getPath() + JarFileSystem.JAR_SEPARATOR; } + + @Override + public void setParent(@NotNull VirtualFile newParent) { + throw new IncorrectOperationException(); + } } } diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/VfsUtilTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/VfsUtilTest.java index 2c61d00b9237..9bf7bbdbd50a 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/VfsUtilTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/VfsUtilTest.java @@ -19,16 +19,20 @@ import com.intellij.concurrency.JobLauncher; import com.intellij.openapi.application.Result; import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.application.ex.PathManagerEx; +import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.io.IoTestUtil; +import com.intellij.openapi.vfs.newvfs.ManagingFS; +import com.intellij.openapi.vfs.newvfs.NewVirtualFile; import com.intellij.testFramework.PlatformLangTestCase; import com.intellij.testFramework.PlatformTestUtil; import com.intellij.testFramework.UsefulTestCase; import com.intellij.testFramework.vcs.DirectoryData; import com.intellij.util.Processor; +import com.intellij.util.ThrowableRunnable; import com.intellij.util.ui.UIUtil; -import junit.framework.Assert; import org.jetbrains.annotations.NotNull; import java.io.File; @@ -37,13 +41,11 @@ import java.net.URI; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import static com.intellij.openapi.util.io.IoTestUtil.assertTimestampsEqual; -import static com.intellij.openapi.util.io.IoTestUtil.assertTimestampsNotEqual; - public class VfsUtilTest extends PlatformLangTestCase { @Override protected void runBareRunnable(Runnable runnable) throws Throwable { @@ -81,12 +83,11 @@ public class VfsUtilTest extends PlatformLangTestCase { } public void testFindFileByUrl() throws Exception { - VirtualFile file0; File file1 = new File(PathManagerEx.getTestDataPath()); file1 = new File(file1, "vfs"); file1 = new File(file1, "findFileByUrl"); - file0 = VfsUtil.findFileByURL(file1.toURI().toURL()); + VirtualFile file0 = VfsUtil.findFileByURL(file1.toURI().toURL()); assertNotNull(file0); assertTrue(file0.isDirectory()); final VirtualFile[] children = file0.getChildren(); @@ -154,7 +155,7 @@ public class VfsUtilTest extends PlatformLangTestCase { VfsUtilCore.visitChildrenRecursively(data.getBase(), new VirtualFileVisitor() { @Override public boolean visitFile(@NotNull VirtualFile file) { - Assert.assertTrue(!VfsUtilCore.isAncestor(vSubDir, file, true)); + assertTrue(!VfsUtilCore.isAncestor(vSubDir, file, true)); return !vSubDir.equals(file); } }); @@ -215,7 +216,7 @@ public class VfsUtilTest extends PlatformLangTestCase { assertEquals(timestamp[i], file.lastModified()); VirtualFile child = fs.findFileByIoFile(file); assertNotNull(child); - assertTimestampsEqual(timestamp[i], child.getTimeStamp()); + IoTestUtil.assertTimestampsEqual(timestamp[i], child.getTimeStamp()); } for (int i = 0; i < N; i++) { @@ -225,7 +226,7 @@ public class VfsUtilTest extends PlatformLangTestCase { long modified = file.lastModified(); assertTrue("File:" + file.getPath() + "; time:" + modified, timestamp[i] != modified); timestamp[i] = modified; - assertTimestampsNotEqual(children[i].getTimeStamp(), modified); + IoTestUtil.assertTimestampsNotEqual(children[i].getTimeStamp(), modified); } final CountDownLatch latch = new CountDownLatch(N); @@ -245,7 +246,7 @@ public class VfsUtilTest extends PlatformLangTestCase { for (int i = 0; i < N; i++) { VirtualFile child = children[i]; - assertTimestampsEqual(timestamp[i], child.getTimeStamp()); + IoTestUtil.assertTimestampsEqual(timestamp[i], child.getTimeStamp()); } } @@ -348,4 +349,91 @@ public class VfsUtilTest extends PlatformLangTestCase { assertEquals("http://developer.android.com/guide/developing/tools/avd.html?f=23r2ewd", uri.toString()); assertEquals("f=23r2ewd", uri.getQuery()); } + + public void testFindChildByNamePerformance() throws IOException { + File tempDir = new WriteAction() { + @Override + protected void run(Result result) throws Throwable { + File res = createTempDirectory(); + result.setResult(res); + } + }.execute().getResultObject(); + final VirtualFile vDir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(tempDir); + assertNotNull(vDir); + assertTrue(vDir.isDirectory()); + + for (int i=0; i<10000; i++) { + final String name = i + ".txt"; + new WriteCommandAction.Simple(getProject()) { + @Override + protected void run() throws Throwable { + vDir.createChildData(vDir, name); + } + }.execute().throwException(); + } + final VirtualFile theChild = vDir.getChildren()[5111]; + + PlatformTestUtil.startPerformanceTest("find child is slow", 450, new ThrowableRunnable() { + @Override + public void run() throws Throwable { + for (int i = 0; i < 1000000; i++) { + VirtualFile child = vDir.findChild("5111.txt"); + assertSame(theChild, child); + } + } + }).assertTiming(); + } + + public void testFindRootWithDenormalizedPath() { + File tempDir = new WriteAction() { + @Override + protected void run(Result result) throws Throwable { + File res = createTempDirectory(); + new File(res, "x.jar").createNewFile(); + result.setResult(res); + } + }.execute().getResultObject(); + VirtualFile vDir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(tempDir); + VirtualFile jar = vDir.findChild("x.jar"); + assertNotNull(jar); + + NewVirtualFile root1 = ManagingFS.getInstance().findRoot(jar.getPath()+"!/", JarFileSystem.getInstance()); + NewVirtualFile root2 = ManagingFS.getInstance().findRoot(jar.getParent().getPath() + "//"+ jar.getName()+"!/", JarFileSystem.getInstance()); + assertNotNull(root1); + assertSame(root1, root2); + } + + public void testFindRootPerformance() { + File tempDir = new WriteAction() { + @Override + protected void run(Result result) throws Throwable { + File res = createTempDirectory(); + new File(res, "x.jar").createNewFile(); + result.setResult(res); + } + }.execute().getResultObject(); + final VirtualFile vDir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(tempDir); + final VirtualFile jar = vDir.findChild("x.jar"); + assertNotNull(jar); + + final NewVirtualFile root = ManagingFS.getInstance().findRoot(jar.getPath()+"!/", JarFileSystem.getInstance()); + PlatformTestUtil.startPerformanceTest("find root is slow", 500, new ThrowableRunnable() { + @Override + public void run() throws Throwable { + final String path = jar.getPath() + "!/"; + final JarFileSystem fileSystem = JarFileSystem.getInstance(); + JobLauncher.getInstance().invokeConcurrentlyUnderProgress(Collections.nCopies(500, null), null, false, new Processor() { + @Override + public boolean process(Object o) { + for (int i = 0; i < 1000; i++) { + NewVirtualFile rootJar = ManagingFS.getInstance().findRoot(path, fileSystem); + assertNotNull(rootJar); + assertSame(root, rootJar); + } + return true; + } + }); + } + }).assertTiming(); + } } 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 37aedb8b677f..87971737bf45 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 @@ -16,6 +16,7 @@ package com.intellij.openapi.roots.impl; import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.fileTypes.FileTypeRegistry; @@ -28,13 +29,13 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectBundle; import com.intellij.openapi.roots.*; import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.VfsUtilCore; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileVisitor; -import com.intellij.openapi.vfs.VirtualFileWithId; -import com.intellij.util.ArrayUtil; +import com.intellij.openapi.vfs.*; +import com.intellij.util.*; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import com.intellij.util.containers.Stack; import gnu.trove.*; @@ -44,15 +45,17 @@ import org.jetbrains.annotations.TestOnly; import java.util.*; -public abstract class DirectoryIndexImpl extends DirectoryIndex { +public class DirectoryIndexImpl extends DirectoryIndex { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.DirectoryIndexImpl"); + private static final boolean CHECK = ApplicationManager.getApplication().isUnitTestMode(); protected final Project myProject; protected final DirectoryIndexExcludePolicy[] myExcludePolicies; protected volatile IndexState myState; - private boolean myInitialized = false; - private boolean myDisposed = false; + private volatile boolean myInitialized = false; + private volatile boolean myDisposed = false; + private final PackageSink mySink = new PackageSink(); public DirectoryIndexImpl(@NotNull Project project) { myProject = project; @@ -66,6 +69,63 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { }); } + private class PackageSink extends QueryFactory>> { + private final Condition IS_VALID = new Condition() { + @Override + public boolean value(final VirtualFile virtualFile) { + return virtualFile.isValid(); + } + }; + + private PackageSink() { + registerExecutor(new QueryExecutor>>() { + @Override + public boolean execute(@NotNull final Pair> stateAndDirs, + @NotNull final Processor consumer) { + for (VirtualFile dir : stateAndDirs.second) { + DirectoryInfo info = stateAndDirs.first.myDirToInfoMap.get(getId(dir)); + assert info != null; + + if (!info.isInLibrarySource() || info.isInModuleSource() || info.hasLibraryClassRoot()) { + if (!consumer.process(dir)) return false; + } + } + return true; + } + }); + } + + public Query search(@NotNull String packageName, boolean includeLibrarySources) { + checkAvailability(); + dispatchPendingEvents(); + + IndexState state = myState; + int[] allDirs = state.getDirsForPackage(packageName); + if (allDirs == null) allDirs = ArrayUtil.EMPTY_INT_ARRAY; + + List files = new ArrayList(allDirs.length); + for (int dir : allDirs) { + VirtualFile file = findFileById(dir); + if (file != null) { + files.add(file); + } + } + + Query query = includeLibrarySources ? new CollectionQuery(files) : createQuery(Pair.create(state, files)); + return new FilteredQuery(query, IS_VALID); + } + } + + @Override + @NotNull + public Query getDirectoriesByPackageName(@NotNull String packageName, boolean includeLibrarySources) { + return mySink.search(packageName, includeLibrarySources); + } + + private static class FileSystemPersistenceHolder { + private static final FileSystemPersistence persistence = ApplicationManager.getApplication().getComponents(FileSystemPersistence.class)[0]; + } + @Override @TestOnly public void checkConsistency() { @@ -73,21 +133,27 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { doCheckConsistency(true); } + @TestOnly + public void assertAncestorConsistent() { + myState.assertAncestorsConsistent(); + } + @TestOnly private void doCheckConsistency(boolean reverseAllSets) { assert myInitialized; assert !myDisposed; final IndexState oldState = myState; - myState = myState.copy(); + myState.assertAncestorsConsistent(); + myState = myState.copy(null); myState.doInitialize(reverseAllSets); int[] keySet = myState.myDirToInfoMap.keys(); assert keySet.length == oldState.myDirToInfoMap.keys().length; for (int file : keySet) { - DirectoryInfo info1 = myState.myDirToInfoMap.get(file); - DirectoryInfo info2 = oldState.myDirToInfoMap.get(file); + DirectoryInfo info1 = myState.getInfo(file); + DirectoryInfo info2 = oldState.getInfo(file); assert info1.equals(info2); info1.assertConsistency(); } @@ -99,10 +165,8 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { int[] dirs = oldState.getDirsForPackage(packageName); int[] dirs1 = myState.getDirsForPackage(packageName); - TIntHashSet set1 = new TIntHashSet(); - set1.addAll(dirs); - TIntHashSet set2 = new TIntHashSet(); - set2.addAll(dirs1); + TIntHashSet set1 = new TIntHashSet(dirs); + TIntHashSet set2 = new TIntHashSet(dirs1); assert set1.equals(set2); return true; } @@ -164,7 +228,7 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { dispatchPendingEvents(); if (!(dir instanceof VirtualFileWithId)) return null; - return myState.myDirToInfoMap.get(getId(dir)); + return myState.getInfo(getId(dir)); } @Override @@ -173,6 +237,10 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { return dir instanceof VirtualFileWithId && myState.myProjectExcludeRoots.contains(getId(dir)); } + private static VirtualFile findFileById(int dir) { + return FileSystemPersistenceHolder.persistence.findFileById(dir); + } + @Override public String getPackageName(@NotNull VirtualFile dir) { checkAvailability(); @@ -183,10 +251,11 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { protected void dispatchPendingEvents() { } - protected void checkAvailability() { + private void checkAvailability() { if (!myInitialized) { LOG.error("Directory index is not initialized yet for " + myProject); } + if (myDisposed) { LOG.error("Directory index is already disposed for " + myProject); } @@ -198,7 +267,7 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { return parentPackageName.isEmpty() ? subdirName : parentPackageName + "." + subdirName; } - protected class IndexState { + class IndexState { final TIntObjectHashMap> myExcludeRootsMap = new TIntObjectHashMap>(); final TIntHashSet myProjectExcludeRoots = new TIntHashSet(); final TIntObjectHashMap myDirToInfoMap = new TIntObjectHashMap(); @@ -206,18 +275,18 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { final List multiDirPackages = new ArrayList(Arrays.asList(new int[]{-1})); final TIntObjectHashMap myDirToPackageName = new TIntObjectHashMap(); - public IndexState() { } + private IndexState() { + } @Nullable - public int[] getDirsForPackage(String packageName) { + private int[] getDirsForPackage(@NotNull String packageName) { int i = myPackageNameToDirsMap.get(packageName); return i == 0 ? null : i > 0 ? new int[]{i} : multiDirPackages.get(-i); } private void removeDirFromPackage(@NotNull String packageName, int dirId) { int i = myPackageNameToDirsMap.get(packageName); - assert i != 0; - int[] oldPackageDirs = i > 0 ? new int[]{i} : multiDirPackages.get(-i); + int[] oldPackageDirs = i == 0 ? null : i > 0 ? new int[]{i} : multiDirPackages.get(-i); int index = ArrayUtil.find(oldPackageDirs, dirId); assert index != -1; oldPackageDirs = ArrayUtil.remove(oldPackageDirs, index); @@ -229,7 +298,7 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { } } else { - assert i < 0; + assert i < 0 : i; multiDirPackages.set(-i, oldPackageDirs); } } @@ -257,17 +326,56 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { @NotNull private DirectoryInfo getOrCreateDirInfo(int dirId) { - DirectoryInfo info = myDirToInfoMap.get(dirId); + DirectoryInfo info = getInfo(dirId); if (info == null) { - info = new DirectoryInfo(null, null,null,null,(byte)0,null); - myDirToInfoMap.put(dirId, info); + info = DirectoryInfo.createNew(); + storeInfo(info, dirId); } return info; } - private DirectoryInfo storeInfo(DirectoryInfo info, int dirId) { - myDirToInfoMap.put(dirId, info); - return info; + @Nullable + DirectoryInfo getInfo(int fileId) { + return myDirToInfoMap.get(fileId); + } + + + private void storeInfo(@NotNull DirectoryInfo info, int id) { + if (CHECK) { + VirtualFile file = findFileById(id); + VirtualFile contentRoot = info.getContentRoot(); + if (file != null && contentRoot != null) { + assert VfsUtilCore.isAncestor(contentRoot, file, false) : "File: "+file+"; Content root: "+contentRoot; + } + } + myDirToInfoMap.put(id, info); + } + + void assertAncestorsConsistent() { + if (CHECK) { + myDirToInfoMap.forEachEntry(new TIntObjectProcedure() { + @Override + public boolean execute(int id, DirectoryInfo info) { + VirtualFile file = findFileById(id); + if (file == null) { + return true; + } + VirtualFile contentRoot = info.getContentRoot(); + if (contentRoot != null) { + assertAncestor(info, contentRoot, id); + } + VirtualFile sourceRoot = info.getSourceRoot(); + if (sourceRoot != null) { + assertAncestor(info, sourceRoot, id); + + if (contentRoot != null) { + assert VfsUtilCore.isAncestor(contentRoot, sourceRoot, false) : contentRoot + ";" + sourceRoot; + } + } + return true; + } + }); + } } void fillMapWithModuleContent(@NotNull VirtualFile root, @@ -275,6 +383,9 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { final VirtualFile contentRoot, @Nullable final ProgressIndicator progress) { final int contentRootId = contentRoot == null ? 0 : getId(contentRoot); + if (contentRoot != null) { + assert VfsUtilCore.isAncestor(contentRoot, root, false) : "Root: "+root+"; contentRoot: "+contentRoot; + } VfsUtilCore.visitChildrenRecursively(root, new DirectoryVisitor() { @Override protected DirectoryInfo updateInfo(@NotNull VirtualFile file) { @@ -288,7 +399,7 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { if (info.getModule() != null) { // module contents overlap VirtualFile dir = file.getParent(); - DirectoryInfo parentInfo = dir == null ? null : myDirToInfoMap.get(getId(dir)); + DirectoryInfo parentInfo = dir == null ? null : getInfo(getId(dir)); if (parentInfo == null || !info.getModule().equals(parentInfo.getModule())) return null; } @@ -297,12 +408,34 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { @Override protected void afterChildrenVisited(@NotNull VirtualFile file, @NotNull DirectoryInfo info) { - info = info.withModule(module).withContentRoot(contentRoot); - storeInfo(info, getId(file)); + with(getId(file), info, module, contentRoot, null, null, 0, null); } }); } + @NotNull + private DirectoryInfo with(int id, + @NotNull DirectoryInfo info, + Module module, + VirtualFile contentRoot, + VirtualFile sourceRoot, + VirtualFile libraryClassRoot, + @DirectoryInfo.SourceFlag int sourceFlag, + OrderEntry[] orderEntries) { + if (contentRoot != null) { + assertAncestor(info, contentRoot, id); + } + if (sourceRoot != null) { + VirtualFile root = contentRoot == null ? info.getContentRoot() : contentRoot; + if (root != null) { + assertAncestor(info, root, getId(sourceRoot)); + } + } + DirectoryInfo newInfo = info.with(module, contentRoot, sourceRoot, libraryClassRoot, (byte)sourceFlag, orderEntries); + storeInfo(newInfo, id); + return newInfo; + } + private abstract class DirectoryVisitor extends VirtualFileVisitor { private final Stack myDirectoryInfoStack = new Stack(); @@ -360,6 +493,7 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { } for (ContentEntry contentEntry : contentEntries) { + VirtualFile contentRoot = contentEntry.getFile(); SourceFolder[] sourceFolders = contentEntry.getSourceFolders(); if (reverseAllSets) { sourceFolders = ArrayUtil.reverseArray(sourceFolders); @@ -367,18 +501,20 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { for (SourceFolder sourceFolder : sourceFolders) { VirtualFile dir = sourceFolder.getFile(); if (dir != null) { - fillMapWithModuleSource(dir, module, sourceFolder.getPackagePrefix(), dir, sourceFolder.isTestSource(), progress); + fillMapWithModuleSource(module, contentRoot, dir, sourceFolder.getPackagePrefix(), dir, sourceFolder.isTestSource(), progress); } } } } - protected void fillMapWithModuleSource(@NotNull final VirtualFile dir, - @NotNull final Module module, + protected void fillMapWithModuleSource(@NotNull final Module module, + @NotNull final VirtualFile contentRoot, + @NotNull final VirtualFile dir, @NotNull final String packageName, @NotNull final VirtualFile sourceRoot, final boolean isTestSource, @Nullable final ProgressIndicator progress) { + assert VfsUtilCore.isAncestor(sourceRoot, dir, false) : "SourceRoot: "+sourceRoot+" ("+sourceRoot.getFileSystem()+"); dir: "+dir+" ("+dir.getFileSystem()+")"; VfsUtilCore.visitChildrenRecursively(dir, new DirectoryVisitor() { private final Stack myPackages = new Stack(); @@ -388,19 +524,22 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { progress.checkCanceled(); } int id = getId(file); - DirectoryInfo info = myDirToInfoMap.get(id); + DirectoryInfo info = getInfo(id); if (info == null) return null; if (!module.equals(info.getModule())) return null; + if (!contentRoot.equals(info.getContentRoot())) return null; if (info.isInModuleSource()) { // module sources overlap String definedPackage = myDirToPackageName.get(id); if (definedPackage != null && definedPackage.isEmpty()) return null; // another source root starts here } - info = info.withInModuleSource(true) - .withTestSource(isTestSource) - .withSourceRoot(sourceRoot); - storeInfo(info, id); + + assert VfsUtilCore.isAncestor(dir, file, false) : "dir: "+dir+" ("+dir.getFileSystem()+"); file: "+file+" ("+file.getFileSystem()+")"; + + int flag = info.getSourceFlag() | DirectoryInfo.MODULE_SOURCE_FLAG; + flag = BitUtil.set(flag, DirectoryInfo.TEST_SOURCE_FLAG, isTestSource); + info = with(id, info, null, null, sourceRoot, null, (byte)flag, null); String currentPackage = myPackages.isEmpty() ? packageName : getPackageNameForSubdir(myPackages.peek(), file.getName()); myPackages.push(currentPackage); @@ -449,9 +588,8 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { if (definedPackage != null && definedPackage.isEmpty()) return false; // another library source root starts here } - info = info.withInLibrarySource(true) - .withSourceRoot(sourceRoot); - storeInfo(info, dirId); + int flag = info.getSourceFlag() | DirectoryInfo.LIBRARY_SOURCE_FLAG; + with(dirId, info, null, null, sourceRoot, null, (byte)flag, null); final String packageName = getCurrentValue(); final String newPackageName = Comparing.equal(file, dir) ? packageName : getPackageNameForSubdir(packageName, file.getName()); @@ -497,8 +635,7 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { if (definedPackage != null && definedPackage.isEmpty()) return false; // another library root starts here } - info = info.withLibraryClassRoot(classRoot); - storeInfo(info, dirId); + info = with(dirId, info, null, null, null, classRoot, 0, null); final String packageName = getCurrentValue(); final String childPackageName = Comparing.equal(file, dir) ? packageName : getPackageNameForSubdir(packageName, file.getName()); @@ -578,20 +715,20 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { } } - protected void setPackageName(int dirId, String newPackageName) { + protected void setPackageName(int dirId, @Nullable String newPackageName) { String oldPackageName = myDirToPackageName.get(dirId); if (oldPackageName != null) { removeDirFromPackage(oldPackageName, dirId); } - if (newPackageName != null) { + if (newPackageName == null) { + myDirToPackageName.remove(dirId); + } + else { addDirToPackage(newPackageName, dirId); myDirToPackageName.put(dirId, newPackageName); } - else { - myDirToPackageName.remove(dirId); - } } // orderEntries must be sorted BY_OWNER_MODULE @@ -613,7 +750,7 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { if (isIgnored(dir)) return null; int dirId = getId(dir); - DirectoryInfo info = myDirToInfoMap.get(dirId); // do not create it here! + DirectoryInfo info = getInfo(dirId); // do not create it here! if (info == null) return null; if (module != null) { @@ -633,8 +770,10 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { OrderEntry[] oldParentEntries = myEntries.isEmpty() ? null : myEntries.peek(); OrderEntry[] oldEntries = info.getOrderEntries(); myEntries.push(oldEntries); - info = info.withOrderEntries(orderEntries, parentInfo, oldParentEntries); - storeInfo(info, dirId); + + OrderEntry[] newOrderEntries = info.calcNewOrderEntries(orderEntries, parentInfo, oldParentEntries); + info = with(dirId, info, null, null, null, null, 0, newOrderEntries); + return info; } @@ -646,6 +785,7 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { } protected void doInitialize(boolean reverseAllSets/* for testing order independence*/) { + assertAncestorsConsistent(); ProgressIndicator progress = ProgressIndicatorProvider.getGlobalProgressIndicator(); if (progress == null) progress = new EmptyProgressIndicator(); @@ -674,6 +814,7 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { progress.checkCanceled(); progress.setText2(""); + assertAncestorsConsistent(); MultiMap depEntries = new MultiMap(); MultiMap libClassRootEntries = new MultiMap(); MultiMap libSourceRootEntries = new MultiMap(); @@ -699,6 +840,7 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { } }); + assertAncestorsConsistent(); myDirToInfoMap.transformValues(new TObjectFunction() { @Override public DirectoryInfo execute(DirectoryInfo info) { @@ -717,9 +859,10 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { return interned; } }); + assertAncestorsConsistent(); } - private void initExcludedDirMap(Module[] modules, ProgressIndicator progress) { + private void initExcludedDirMap(@NotNull Module[] modules, ProgressIndicator progress) { progress.checkCanceled(); progress.setText2(ProjectBundle.message("project.index.building.exclude.roots.progress")); @@ -734,11 +877,13 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { ExcludeFolder[] excludeRoots = contentEntry.getExcludeFolders(); for (ExcludeFolder excludeRoot : excludeRoots) { // Output paths should be excluded (if marked as such) regardless if they're under corresponding module's content root - if (excludeRoot.getFile() != null) { + VirtualFile excludeRootFile = excludeRoot.getFile(); + if (excludeRootFile != null) { if (!FileUtil.startsWith(contentRoot.getUrl(), excludeRoot.getUrl())) { - if (isExcludeRootForModule(module, excludeRoot.getFile())) { - putForFileAndAllAncestors(excludeRoot.getFile(), excludeRoot.getUrl()); + if (isExcludeRootForModule(module, excludeRootFile)) { + putForFileAndAllAncestors(excludeRootFile, excludeRoot.getUrl()); } + myProjectExcludeRoots.add(getId(excludeRootFile)); } } @@ -771,13 +916,15 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { } @NotNull - public IndexState copy() { + IndexState copy(@Nullable final TIntProcedure idFilter) { final IndexState copy = new IndexState(); myExcludeRootsMap.forEachEntry(new TIntObjectProcedure>() { @Override public boolean execute(int id, Set urls) { - copy.myExcludeRootsMap.put(id, new THashSet(urls)); + if (idFilter == null || idFilter.execute(id)) { + copy.myExcludeRootsMap.put(id, new THashSet(urls)); + } return true; } }); @@ -786,26 +933,46 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { myDirToInfoMap.forEachEntry(new TIntObjectProcedure() { @Override public boolean execute(int id, DirectoryInfo info) { - copy.myDirToInfoMap.put(id, info); + if (idFilter == null || idFilter.execute(id)) { + copy.storeInfo(info, id); + } return true; } }); + copy.multiDirPackages.clear(); + for (int[] dirs : multiDirPackages) { + int[] filtered = ContainerUtil.filter(dirs, new TIntProcedure() { + @Override + public boolean execute(int id) { + return id == -1 || copy.getInfo(id) != null && (idFilter == null || idFilter.execute(id)); + } + }); + copy.multiDirPackages.add(filtered); + } myPackageNameToDirsMap.forEachEntry(new TObjectIntProcedure() { @Override public boolean execute(String name, int id) { - copy.myPackageNameToDirsMap.put(name, id); + if (id > 0) { + if (copy.getInfo(id) == null) id = 0; + } + else if (id < 0) { + if (copy.multiDirPackages.get(-id).length == 0) id = 0; + } + if (id != 0 && (idFilter == null || idFilter.execute(id))) { + copy.myPackageNameToDirsMap.put(name, id); + } return true; } }); - copy.multiDirPackages.clear(); - copy.multiDirPackages.addAll(multiDirPackages); myDirToPackageName.forEachEntry(new TIntObjectProcedure() { @Override public boolean execute(int id, String name) { - copy.myDirToPackageName.put(id, name); + if (idFilter == null || idFilter.execute(id)) { + copy.myDirToPackageName.put(id, name); + } return true; } }); @@ -824,7 +991,19 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex { return result; } - static int getId(@NotNull VirtualFile classRoot) { - return ((VirtualFileWithId)classRoot).getId(); + static int getId(@NotNull VirtualFile file) { + return ((VirtualFileWithId)file).getId(); + } + + static void assertAncestor(@NotNull DirectoryInfo info, @NotNull VirtualFile root, int myId) { + VirtualFile myFile = findFileById(myId); + assert myFile.getFileSystem() == root.getFileSystem() : myFile.getFileSystem() +", "+ root.getFileSystem() +"; my file: "+myFile+"; root: "+root + "; "+ + myFile.getParent().getPath().equals(root.getPath()); + assert VfsUtilCore.isAncestor(root, myFile, false) : "my file: "+myFile+" ("+getId(myFile)+")" + myFile.getClass() + " - " +System.identityHashCode(myFile) + + "; root: "+root +" ("+getId(root)+")" + root.getClass() + " - " +System.identityHashCode(root) + + "; equalsToParent:"+ (myFile.getParent() == null ? "" : myFile.getParent().getPath()).equals(root.getPath()) + + "; equalsToRoot:"+ myFile.equals(root) + + "; equalsToRootPath:"+ myFile.getPath().equals(root.getPath()) + + "; my contentRoot: "+info.getContentRoot()+"; my sourceRoot: "+info.getSourceRoot()+"; my classRoot: "+info.getLibraryClassRoot(); } } \ No newline at end of file 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 030a268dd90e..04108ad92c8f 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 @@ -20,6 +20,7 @@ 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; @@ -41,11 +42,14 @@ public final class DirectoryInfo { 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) + public static final byte TEST_SOURCE_FLAG = 1; // (makes sense only if MODULE_SOURCE_FLAG is set) + public static final byte LIBRARY_SOURCE_FLAG = 2; // set if it's a directory with sources of some library + public 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}) + public @interface SourceFlag {} + + @SourceFlag private final byte sourceFlag; /** @@ -54,12 +58,15 @@ public final class DirectoryInfo { */ private final OrderEntry[] orderEntries; - 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) { + static DirectoryInfo createNew() { + return new DirectoryInfo(null, null, null, null, (byte)0, null); + } + private DirectoryInfo(Module module, + VirtualFile contentRoot, + VirtualFile sourceRoot, + VirtualFile libraryClassRoot, + @SourceFlag byte sourceFlag, + OrderEntry[] orderEntries) { this.module = module; this.libraryClassRoot = libraryClassRoot; this.contentRoot = contentRoot; @@ -75,14 +82,12 @@ public final class DirectoryInfo { DirectoryInfo info = (DirectoryInfo)o; - if (sourceFlag != info.sourceFlag) 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; + return sourceFlag == info.sourceFlag && + Comparing.equal(contentRoot, info.contentRoot) && + Comparing.equal(libraryClassRoot, info.libraryClassRoot) && + Comparing.equal(module, info.module) && + Arrays.equals(orderEntries, info.orderEntries) && + Comparing.equal(sourceRoot, info.sourceRoot); } @Override @@ -209,10 +214,7 @@ public final class DirectoryInfo { } // orderEntries must be sorted BY_OWNER_MODULE - @NotNull - public DirectoryInfo withOrderEntries(@NotNull OrderEntry[] orderEntries, - @Nullable final DirectoryInfo parentInfo, - @Nullable final OrderEntry[] oldParentEntries) { + OrderEntry[] calcNewOrderEntries(@NotNull OrderEntry[] orderEntries, @Nullable DirectoryInfo parentInfo, @Nullable OrderEntry[] oldParentEntries) { OrderEntry[] newOrderEntries; if (orderEntries.length == 0) { newOrderEntries = null; @@ -226,8 +228,7 @@ public final class DirectoryInfo { else { newOrderEntries = mergeWith(orderEntries); } - - return new DirectoryInfo(module, contentRoot, sourceRoot, libraryClassRoot, sourceFlag, newOrderEntries); + return newOrderEntries; } // entries must be sorted BY_OWNER_MODULE @@ -293,54 +294,32 @@ public final class DirectoryInfo { return BitUtil.isSet(sourceFlag, MODULE_SOURCE_FLAG); } - @NotNull - public 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); } - @NotNull - public 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); } - @NotNull - public 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; } - @NotNull - public DirectoryInfo withModule(Module module) { - return new DirectoryInfo(module, contentRoot, sourceRoot, libraryClassRoot, sourceFlag, orderEntries); + private static T iff(T value, T defaultValue) { + return value == null ? defaultValue : value; } @NotNull - public DirectoryInfo withLibraryClassRoot(@NotNull VirtualFile libraryClassRoot) { - return new DirectoryInfo(module, contentRoot, sourceRoot, libraryClassRoot, sourceFlag, orderEntries); - } - - @NotNull - public DirectoryInfo withContentRoot(VirtualFile contentRoot) { - return new DirectoryInfo(module, contentRoot, sourceRoot, libraryClassRoot, sourceFlag, orderEntries); - } - - @NotNull - public DirectoryInfo withSourceRoot(@NotNull VirtualFile sourceRoot) { - return new DirectoryInfo(module, contentRoot, sourceRoot, libraryClassRoot, sourceFlag, orderEntries); + public DirectoryInfo with(Module module, + VirtualFile contentRoot, + VirtualFile sourceRoot, + VirtualFile libraryClassRoot, + @SourceFlag byte sourceFlag, + OrderEntry[] orderEntries) { + return new DirectoryInfo(iff(module, this.module), iff(contentRoot, this.contentRoot), iff(sourceRoot, this.sourceRoot), + iff(libraryClassRoot, this.libraryClassRoot), sourceFlag == 0 ? this.sourceFlag : sourceFlag, + iff(orderEntries, this.orderEntries)); } @NotNull @@ -355,4 +334,9 @@ public final class DirectoryInfo { assert BY_OWNER_MODULE.compare(entries[i-1], entries[i]) <= 0; } } + + @SourceFlag + int getSourceFlag() { + return sourceFlag; + } } diff --git a/platform/testFramework/src/com/intellij/testFramework/LightPlatformTestCase.java b/platform/testFramework/src/com/intellij/testFramework/LightPlatformTestCase.java index 2d640755b2c2..d774b1081859 100644 --- a/platform/testFramework/src/com/intellij/testFramework/LightPlatformTestCase.java +++ b/platform/testFramework/src/com/intellij/testFramework/LightPlatformTestCase.java @@ -60,6 +60,8 @@ import com.intellij.openapi.project.impl.ProjectImpl; import com.intellij.openapi.project.impl.ProjectManagerImpl; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.*; +import com.intellij.openapi.roots.impl.DirectoryIndex; +import com.intellij.openapi.roots.impl.DirectoryIndexImpl; import com.intellij.openapi.startup.StartupManager; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Disposer; @@ -547,6 +549,8 @@ public abstract class LightPlatformTestCase extends UsefulTestCase implements Da if (manager instanceof FileDocumentManagerImpl) { ((FileDocumentManagerImpl)manager).dropAllUnsavedDocuments(); } + + ((DirectoryIndexImpl)DirectoryIndex.getInstance(project)).assertAncestorConsistent(); } }.execute().throwException(); diff --git a/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/HeavyIdeaTestFixtureImpl.java b/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/HeavyIdeaTestFixtureImpl.java index 9d4c5e8e801a..2be2a8ab10ae 100644 --- a/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/HeavyIdeaTestFixtureImpl.java +++ b/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/HeavyIdeaTestFixtureImpl.java @@ -34,6 +34,8 @@ import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ex.ProjectManagerEx; import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.roots.impl.DirectoryIndex; +import com.intellij.openapi.roots.impl.DirectoryIndexImpl; import com.intellij.openapi.startup.StartupManager; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Disposer; @@ -43,8 +45,10 @@ import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.encoding.EncodingManager; -import com.intellij.psi.*; -import com.intellij.psi.impl.PsiDocumentManagerImpl; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; import com.intellij.psi.impl.source.tree.injected.InjectedLanguageManagerImpl; import com.intellij.testFramework.EditorListenerTracker; import com.intellij.testFramework.LightPlatformTestCase; @@ -111,6 +115,7 @@ class HeavyIdeaTestFixtureImpl extends BaseFixture implements HeavyIdeaTestFixtu for (ModuleFixtureBuilder moduleFixtureBuilder : myModuleFixtureBuilders) { moduleFixtureBuilder.getFixture().tearDown(); } + ((DirectoryIndexImpl)DirectoryIndex.getInstance(getProject())).assertAncestorConsistent(); UIUtil.invokeAndWaitIfNeeded(new Runnable() { @Override diff --git a/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/LightIdeaTestFixtureImpl.java b/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/LightIdeaTestFixtureImpl.java index e9b6552652d1..046ebf59e1f6 100644 --- a/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/LightIdeaTestFixtureImpl.java +++ b/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/LightIdeaTestFixtureImpl.java @@ -21,7 +21,8 @@ import com.intellij.codeInspection.ex.InspectionTool; import com.intellij.idea.IdeaTestApplication; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS; +import com.intellij.openapi.roots.impl.DirectoryIndex; +import com.intellij.openapi.roots.impl.DirectoryIndexImpl; import com.intellij.psi.codeStyle.CodeStyleSchemes; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; @@ -58,11 +59,11 @@ class LightIdeaTestFixtureImpl extends BaseFixture implements LightIdeaTestFixtu Project project = getProject(); CodeStyleSettingsManager.getInstance(project).dropTemporarySettings(); checkForSettingsDamage(); - PersistentFS.getInstance().clearIdCache(); LightPlatformTestCase.doTearDown(project, LightPlatformTestCase.getApplication(), true); super.tearDown(); InjectedLanguageManagerImpl.checkInjectorsAreDisposed(project); + ((DirectoryIndexImpl)DirectoryIndex.getInstance(project)).assertAncestorConsistent(); } diff --git a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java index cf9fd8785fe9..2bf2eaf0dd4b 100644 --- a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java +++ b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java @@ -2203,9 +2203,9 @@ public class StringUtil extends StringUtilRt { else { // similar logic to charsMatch() below if (ch1 != ch2) { - final int diff1 = Character.toUpperCase(ch1) - Character.toUpperCase(ch2); + final int diff1 = StringUtilRt.toUpperCase(ch1) - StringUtilRt.toUpperCase(ch2); if (diff1 != 0) { - final int diff2 = Character.toLowerCase(ch1) - Character.toLowerCase(ch2); + final int diff2 = StringUtilRt.toLowerCase(ch1) - StringUtilRt.toLowerCase(ch2); if (diff2 != 0) { return diff2; } @@ -2281,30 +2281,31 @@ public class StringUtil extends StringUtilRt { return true; } - public static boolean charsMatch(char c1, char c2, boolean ignoreCase) { + public static int compare(char c1, char c2, boolean ignoreCase) { // duplicating String.equalsIgnoreCase logic - if (c1 == c2) { - return true; + int d = c1 - c2; + if (d == 0 || !ignoreCase) { + return d; } - if (ignoreCase) { - // If characters don't match but case may be ignored, - // try converting both characters to uppercase. - // If the results match, then the comparison scan should - // continue. - char u1 = Character.toUpperCase(c1); - char u2 = Character.toUpperCase(c2); - if (u1 == u2) { - return true; - } + // If characters don't match but case may be ignored, + // try converting both characters to uppercase. + // If the results match, then the comparison scan should + // continue. + char u1 = StringUtilRt.toUpperCase(c1); + char u2 = StringUtilRt.toUpperCase(c2); + d = u1 - u2; + if (d != 0) { // Unfortunately, conversion to uppercase does not work properly // for the Georgian alphabet, which has strange rules about case // conversion. So we need to make one last check before // exiting. - if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) { - return true; - } + d = StringUtilRt.toLowerCase(u1) - StringUtilRt.toLowerCase(u2); } - return false; + return d; + } + + public static boolean charsMatch(char c1, char c2, boolean ignoreCase) { + return compare(c1,c2,ignoreCase) == 0; } public static String formatLinks(String message) {