persistent fs fixes:

- fixed multiple instances of JarRoot exception with the same id
- fixed multiple instances of the root which differ in the case only
- removed clearing id cache in tests since it may lead to multiplication of same-id instances
- VirtualFile.findChildByName() performance
This commit is contained in:
Alexey Kudravtsev
2013-02-15 13:12:47 +04:00
parent 44d26db9a7
commit fa409e4f87
21 changed files with 1183 additions and 732 deletions
@@ -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);
}
@@ -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<VirtualFile, Pair<IndexState, List<VirtualFile>>> {
private final Condition<VirtualFile> IS_VALID = new Condition<VirtualFile>() {
@Override
public boolean value(final VirtualFile virtualFile) {
return virtualFile.isValid();
}
};
private PackageSink() {
registerExecutor(new QueryExecutor<VirtualFile, Pair<IndexState, List<VirtualFile>>>() {
@Override
public boolean execute(@NotNull final Pair<IndexState, List<VirtualFile>> stateAndDirs,
@NotNull final Processor<VirtualFile> 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<VirtualFile> 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<VirtualFile> files = new ArrayList<VirtualFile>(allDirs.length);
for (int dir : allDirs) {
VirtualFile file = myManagingFS.findFileById(dir);
if (file != null) {
files.add(file);
}
}
Query<VirtualFile> query = includeLibrarySources ? new CollectionQuery<VirtualFile>(files) : createQuery(Pair.create(state, files));
return new FilteredQuery<VirtualFile>(query, IS_VALID);
}
}
@Override
@@ -114,11 +60,6 @@ public class DirectoryIndexComponent extends DirectoryIndexImpl {
markContentRootsForRefresh();
}
@Override
@NotNull
public Query<VirtualFile> 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<List<VirtualFile>> 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<int[]> 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<VirtualFile> list = new ArrayList<VirtualFile>();
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<VirtualFile> 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<VirtualFile> 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();
}
}
}
@@ -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<? extends VFileEvent> 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);
}
@@ -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) {
@@ -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();
}
@@ -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();
}
}
@@ -122,7 +122,7 @@ public class VfsImplUtil {
@Nullable
private static Pair<NewVirtualFile, Iterable<String>> 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<String> 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);
}
}
@@ -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<E> extends AbstractList<E> 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> T[] toArray(@NotNull T[] a) {
int size = size();
if (a.length < size) {
return Arrays.copyOfRange(this.a, start, end, (Class<? extends T[]>)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;
}
}
@@ -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<String, VFile> 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<String, VirtualFileSystemEntry> 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<String, VirtualFileSystemEntry> 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<String> 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<VirtualFile> roots = new THashSet<VirtualFile>();
@@ -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<String, VirtualFileSystemEntry> 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<VirtualFile> iterInDbChildren() {
return ContainerUtil.iterate(getInDbChildren(), new Condition<VirtualFile>() {
@Override
public boolean value(VirtualFile file) {
return file != NULL_VIRTUAL_FILE;
}
});
}
@NotNull
private synchronized Collection<VirtualFile> 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<VirtualFile>(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<String[], int[]> 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<String, VirtualFileSystemEntry> 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<FSRecords.NameId>() {
@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<String, VirtualFileSystemEntry> map = asMap();
if (map != null) {
for (Map.Entry<String, VirtualFileSystemEntry> 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<VirtualFileSystemEntry>() {
@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<String, VirtualFileSystemEntry> asMap() {
Object children = myChildren;
if (children instanceof Map) {
@SuppressWarnings({"unchecked"})
final Map<String, VirtualFileSystemEntry> map = (Map<String, VirtualFileSystemEntry>)children;
return map;
}
return null;
}
@NotNull
private Map<String, VirtualFileSystemEntry> ensureAsMap() {
Map<String, VirtualFileSystemEntry> map;
if (myChildren == null) {
map = createMap();
myChildren = map;
}
else {
@SuppressWarnings({"unchecked"})
final Map<String, VirtualFileSystemEntry> aMap = (Map<String, VirtualFileSystemEntry>)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<VirtualFileSystemEntry>() {
@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<String> getSuspiciousNames() {
final Map<String, VirtualFileSystemEntry> map = asMap();
if (map == null) return Collections.emptyList();
List<VirtualFile> suspicious = new SubList<VirtualFile>(myChildren, getSuspiciousArrayStart(), myChildren.length);
return ContainerUtil.map2List(suspicious, new Function<VirtualFile, String>() {
@Override
public String fun(VirtualFile file) {
return file.getName();
}
});
}
List<String> 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<String, VirtualFileSystemEntry> entry : map.entrySet()) {
if (entry.getValue() == NULL_VIRTUAL_FILE) {
if (names == null) names = new SmartList<String>();
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.<String>emptyList() : names;
return -(low + 1); // key not found.
}
@Override
@@ -560,40 +663,8 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
@Override
@NotNull
public synchronized Collection<VirtualFile> getCachedChildren() {
final Map<String, VirtualFileSystemEntry> map = asMap();
if (map != null) {
Set<VirtualFile> files = new THashSet<VirtualFile>(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<String, VirtualFileSystemEntry> createMap() {
return getFileSystem().isCaseSensitive()
? new THashMap<String, VirtualFileSystemEntry>()
: new THashMap<String, VirtualFileSystemEntry>(CaseInsensitiveStringHashingStrategy.INSTANCE);
}
@TestOnly
public synchronized void cleanupCachedChildren(@NotNull Set<VirtualFile> 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<VirtualFile> getCachedChildren() {
return new SubList<VirtualFile>(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();
}
}
}
}
@@ -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;
@@ -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<<SUFFIX_BITS)-1);
if (index == 0) return EMPTY;
return WELL_KNOWN_SUFFIXES[index-1];
}
@NotNull
private byte[] getEncodedSuffixBytes() {
int index = (myFlags >> 16-SUFFIX_BITS) & ((1<<SUFFIX_BITS)-1);
if (index == 0) return ArrayUtil.EMPTY_BYTE_ARRAY;
return WELL_KNOWN_SUFFIXES_BYTES[index-1];
}
private int getEncodedSuffixLength() {
int index = (myFlags >> 16-SUFFIX_BITS) & ((1<<SUFFIX_BITS)-1);
if (index == 0) return 0;
return WELL_KNOWN_SUFFIXES_LENGTH[index-1];
}
@Override
@NotNull
@@ -146,29 +174,45 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile {
return StringFactory.createShared(chars);
}
boolean nameMatches(@NotNull String pattern, boolean ignoreCase) {
Object name = rawName();
String suffix = getEncodedSuffix();
if (name instanceof String) {
final String nameStr = (String)name;
return pattern.length() == nameStr.length() + suffix.length() &&
pattern.regionMatches(ignoreCase, 0, nameStr, 0, nameStr.length()) &&
pattern.regionMatches(ignoreCase, nameStr.length(), suffix, 0, suffix.length());
int compareNameTo(@NotNull String name, boolean ignoreCase) {
Object rawName = rawName();
if (rawName instanceof String) {
String thisName = getName();
return compareNames(thisName, name, ignoreCase);
}
byte[] bytes = (byte[])name;
int length = bytes.length;
if (length + suffix.length() != pattern.length()) {
return false;
}
byte[] bytes = (byte[])rawName;
int suffixLength = getEncodedSuffixLength();
int bytesLength = bytes.length;
int d = bytesLength + suffixLength - name.length();
if (d != 0) return d;
d = compare(bytes, 0, name, 0, bytesLength, ignoreCase);
if (d != 0) return d;
byte[] suffix = getEncodedSuffixBytes();
d = compare(suffix, 0, name, bytesLength, suffixLength, ignoreCase);
return d;
}
for (int i = 0; i < length; i++) {
if (!StringUtil.charsMatch((char)bytes[i], pattern.charAt(i), ignoreCase)) {
return false;
}
static int compareNames(@NotNull String name1, @NotNull String name2, boolean ignoreCase) {
int d = name1.length() - name2.length();
if (d != 0) return d;
for (int i=0; i<name1.length(); i++) {
// com.intellij.openapi.util.text.StringUtil.compare(String,String,boolean) inconsistent
d = StringUtil.compare(name1.charAt(i), name2.charAt(i), ignoreCase);
if (d != 0) return d;
}
return 0;
}
return pattern.regionMatches(ignoreCase, length, suffix, 0, suffix.length());
private static int compare(@NotNull byte[] name1, int offset1, @NotNull String name2, int offset2, int len, boolean ignoreCase) {
for (int i1 = offset1, i2=offset2; i1 < offset1 + len; i1++, i2++) {
char c1 = (char)name1[i1];
char c2 = name2.charAt(i2);
int d = StringUtil.compare(c1, c2, ignoreCase);
if (d != 0) return d;
}
return 0;
}
protected Object rawName() {
@@ -218,11 +262,16 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile {
@Override
public void markDirty() {
if (!isDirty()) {
setFlagInt(DIRTY_FLAG, true);
if (myParent != null) myParent.markDirty();
markDirtyInternal();
VirtualDirectoryImpl parent = myParent;
if (parent != null) parent.markDirty();
}
}
protected void markDirtyInternal() {
setFlagInt(DIRTY_FLAG, true);
}
@Override
public void markDirtyRecursively() {
markDirty();
@@ -467,7 +516,7 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile {
content = contentsToByteArray();
}
catch (FileNotFoundException e) {
// file has already been deleted from disk
// file has already been deleted on disk
return super.getCharset();
}
charset = LoadTextUtil.detectCharsetAndSetBOM(this, content);
@@ -25,7 +25,6 @@ import com.intellij.openapi.application.ApplicationNamesInfo;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream;
import com.intellij.openapi.util.io.ByteSequence;
import com.intellij.openapi.util.io.FileUtil;
@@ -654,7 +653,7 @@ public class FSRecords implements Forceable {
return DbConnection.isDirty();
}
public static int findRootRecord(String rootUrl) throws IOException {
public static int findRootRecord(@NotNull String rootUrl) {
try {
try {
w.lock();
@@ -713,7 +712,7 @@ public class FSRecords implements Forceable {
}
}
public static void deleteRootRecord(int id) throws IOException {
public static void deleteRootRecord(int id) {
try {
try {
w.lock();
@@ -790,24 +789,34 @@ public class FSRecords implements Forceable {
}
}
public static Pair<String[],int[]> 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();
@@ -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<String[],int[]> 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);
@@ -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<String, VirtualFileSystemEntry> myRoots = new THashMap<String, VirtualFileSystemEntry>(FileUtil.PATH_HASHING_STRATEGY);
private final TIntObjectHashMap<VirtualFileSystemEntry> myRootsById = new TIntObjectHashMap<VirtualFileSystemEntry>();
@Nullable private VirtualFileSystemEntry myFakeRoot;
@NotNull private final ConcurrentIntObjectMap<VirtualFileSystemEntry> myIdToDirCache = new StripedLockIntObjectConcurrentHashMap<VirtualFileSystemEntry>();
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<String[], int[]> pair = persistAllChildren(file, id, Pair.create(names, childrenIds));
return pair.first;
return ContainerUtil.map2Array(nameIds, String.class, new Function<FSRecords.NameId, String>() {
@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<String[], int[]> persistAllChildren(@NotNull VirtualFile file, int id, @NotNull Pair<String[], int[]> 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<String, FSRecords.NameId> result = new THashMap<String, FSRecords.NameId>();
if (current.length == 0) {
for (String name : delegateNames) {
result.put(name, new FSRecords.NameId(-1, name));
}
}
else {
Set<String> allNamesSet = new LinkedHashSet<String>((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<FSRecords.NameId, FSRecords.NameId>() {
@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<FSRecords.NameId> 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<String[],int[]> listAll(@NotNull VirtualFile parent) {
public FSRecords.NameId[] listAll(@NotNull VirtualFile parent) {
final int parentId = getFileId(parent);
Pair<String[], int[]> 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<? extends VFileEvent> validateEvents(@NotNull List<? extends VFileEvent> events) {
private static List<VFileEvent> validateEvents(@NotNull List<VFileEvent> events) {
final List<EventWrapper> 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<? extends VFileEvent> events) {
public void processEvents(@NotNull List<VFileEvent> events) {
ApplicationManager.getApplication().assertWriteAccessAllowed();
List<? extends VFileEvent> validated = validateEvents(events);
List<VFileEvent> 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<NewVirtualFile> myIdToDirCache = new StripedLockIntObjectConcurrentHashMap<NewVirtualFile>();
@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();
}
}
}
@@ -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<File>() {
@Override
protected void run(Result<File> 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<File>() {
@Override
protected void run(Result<File> 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<File>() {
@Override
protected void run(Result<File> 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<Object>() {
@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();
}
}
@@ -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<VirtualFile, Pair<IndexState, List<VirtualFile>>> {
private final Condition<VirtualFile> IS_VALID = new Condition<VirtualFile>() {
@Override
public boolean value(final VirtualFile virtualFile) {
return virtualFile.isValid();
}
};
private PackageSink() {
registerExecutor(new QueryExecutor<VirtualFile, Pair<IndexState, List<VirtualFile>>>() {
@Override
public boolean execute(@NotNull final Pair<IndexState, List<VirtualFile>> stateAndDirs,
@NotNull final Processor<VirtualFile> 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<VirtualFile> 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<VirtualFile> files = new ArrayList<VirtualFile>(allDirs.length);
for (int dir : allDirs) {
VirtualFile file = findFileById(dir);
if (file != null) {
files.add(file);
}
}
Query<VirtualFile> query = includeLibrarySources ? new CollectionQuery<VirtualFile>(files) : createQuery(Pair.create(state, files));
return new FilteredQuery<VirtualFile>(query, IS_VALID);
}
}
@Override
@NotNull
public Query<VirtualFile> 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<Set<String>> myExcludeRootsMap = new TIntObjectHashMap<Set<String>>();
final TIntHashSet myProjectExcludeRoots = new TIntHashSet();
final TIntObjectHashMap<DirectoryInfo> myDirToInfoMap = new TIntObjectHashMap<DirectoryInfo>();
@@ -206,18 +275,18 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex {
final List<int[]> multiDirPackages = new ArrayList<int[]>(Arrays.asList(new int[]{-1}));
final TIntObjectHashMap<String> myDirToPackageName = new TIntObjectHashMap<String>();
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<DirectoryInfo>() {
@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<DirectoryInfo> myDirectoryInfoStack = new Stack<DirectoryInfo>();
@@ -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<String> myPackages = new Stack<String>();
@@ -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<VirtualFile, OrderEntry> depEntries = new MultiMap<VirtualFile, OrderEntry>();
MultiMap<VirtualFile, OrderEntry> libClassRootEntries = new MultiMap<VirtualFile, OrderEntry>();
MultiMap<VirtualFile, OrderEntry> libSourceRootEntries = new MultiMap<VirtualFile, OrderEntry>();
@@ -699,6 +840,7 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex {
}
});
assertAncestorsConsistent();
myDirToInfoMap.transformValues(new TObjectFunction<DirectoryInfo, DirectoryInfo>() {
@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<Set<String>>() {
@Override
public boolean execute(int id, Set<String> urls) {
copy.myExcludeRootsMap.put(id, new THashSet<String>(urls));
if (idFilter == null || idFilter.execute(id)) {
copy.myExcludeRootsMap.put(id, new THashSet<String>(urls));
}
return true;
}
});
@@ -786,26 +933,46 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex {
myDirToInfoMap.forEachEntry(new TIntObjectProcedure<DirectoryInfo>() {
@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<String>() {
@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<String>() {
@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();
}
}
@@ -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> 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;
}
}
@@ -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();
@@ -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
@@ -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();
}
@@ -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) {