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.project.Project;
import com.intellij.openapi.roots.*; import com.intellij.openapi.roots.*;
import com.intellij.openapi.startup.StartupManager; import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.*; import com.intellij.openapi.vfs.*;
import com.intellij.openapi.vfs.impl.BulkVirtualFileListenerAdapter; import com.intellij.openapi.vfs.impl.BulkVirtualFileListenerAdapter;
import com.intellij.openapi.vfs.newvfs.ManagingFS; import com.intellij.openapi.vfs.newvfs.ManagingFS;
import com.intellij.openapi.vfs.newvfs.NewVirtualFile; import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
import com.intellij.util.*; import com.intellij.util.ArrayUtil;
import com.intellij.util.messages.MessageBusConnection; import com.intellij.util.messages.MessageBusConnection;
import gnu.trove.TIntArrayList;
import gnu.trove.TIntProcedure;
import org.jetbrains.annotations.NotNull; 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. * Descendant of DirectoryIndex which can update itself automatically based on VFS changes.
*/ */
public class DirectoryIndexComponent extends DirectoryIndexImpl { public class DirectoryIndexComponent extends DirectoryIndexImpl {
private final MessageBusConnection myConnection; private final MessageBusConnection myConnection;
private final ManagingFS myManagingFS;
private final PackageSink mySink;
public DirectoryIndexComponent(@NotNull Project project, @NotNull StartupManager startupManager, @NotNull ManagingFS managingFS) { public DirectoryIndexComponent(@NotNull Project project, @NotNull StartupManager startupManager, @NotNull ManagingFS managingFS) {
super(project); super(project);
@@ -56,55 +51,6 @@ public class DirectoryIndexComponent extends DirectoryIndexImpl {
initialize(); 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 @Override
@@ -114,11 +60,6 @@ public class DirectoryIndexComponent extends DirectoryIndexImpl {
markContentRootsForRefresh(); markContentRootsForRefresh();
} }
@Override
@NotNull
public Query<VirtualFile> getDirectoriesByPackageName(@NotNull String packageName, boolean includeLibrarySources) {
return mySink.search(packageName, includeLibrarySources);
}
private void subscribeToFileChanges() { private void subscribeToFileChanges() {
myConnection.subscribe(FileTypeManager.TOPIC, new FileTypeListener.Adapter() { myConnection.subscribe(FileTypeManager.TOPIC, new FileTypeListener.Adapter() {
@@ -156,8 +97,6 @@ public class DirectoryIndexComponent extends DirectoryIndexImpl {
} }
private class MyVirtualFileListener extends VirtualFileAdapter { private class MyVirtualFileListener extends VirtualFileAdapter {
private final Key<List<VirtualFile>> FILES_TO_RELEASE_KEY = Key.create("DirectoryIndexImpl.MyVirtualFileListener.FILES_TO_RELEASE_KEY");
@Override @Override
public void fileCreated(VirtualFileEvent event) { public void fileCreated(VirtualFileEvent event) {
VirtualFile file = event.getFile(); VirtualFile file = event.getFile();
@@ -166,32 +105,38 @@ public class DirectoryIndexComponent extends DirectoryIndexImpl {
VirtualFile parent = file.getParent(); VirtualFile parent = file.getParent();
if (parent == null) return; if (parent == null) return;
DirectoryInfo existing = myState.getInfo(getId(file));
assert existing == null : file+" -> "+existing;
myState = updateStateWithNewFile(file, parent); myState = updateStateWithNewFile(file, parent);
} }
@NotNull @NotNull
private IndexState updateStateWithNewFile(@NotNull VirtualFile file, @NotNull VirtualFile parent) { private IndexState updateStateWithNewFile(@NotNull VirtualFile file, @NotNull VirtualFile parent) {
final IndexState originalState = myState; final IndexState originalState = myState;
IndexState state = originalState; IndexState state = originalState;
int parentId = getId(parent); 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 // fill info for all nested roots
String fileUrl = file.getUrl();
for (Module eachModule : ModuleManager.getInstance(myProject).getModules()) { for (Module eachModule : ModuleManager.getInstance(myProject).getModules()) {
for (ContentEntry eachRoot : getContentEntries(eachModule)) { for (ContentEntry contentRoot : getContentEntries(eachModule)) {
if (parentInfo != null) { if (parentInfo != null) {
VirtualFile contFile = eachRoot.getFile(); VirtualFile contFile = contentRoot.getFile();
if (contFile != null && contFile.equals(parentInfo.getContentRoot())) continue; if (contFile != null && contFile.equals(parentInfo.getContentRoot())) continue;
} }
String url = eachRoot.getUrl(); String contentRootUrl = contentRoot.getUrl();
if (FileUtil.startsWith(url, file.getUrl())) { if (FileUtil.startsWith(contentRootUrl, fileUrl)) {
String rel = FileUtil.getRelativePath(file.getUrl(), url, '/'); String rel = FileUtil.getRelativePath(fileUrl, contentRootUrl, '/');
if (rel != null) { if (rel != null) {
VirtualFile f = file.findFileByRelativePath(rel); VirtualFile f = file.findFileByRelativePath(rel);
if (f != null) { if (f != null) {
if (state == originalState) state = state.copy(); if (state == originalState) state = state.copy(null);
state.fillMapWithModuleContent(f, eachModule, f, null); state.fillMapWithModuleContent(f, eachModule, f, null);
} }
} }
@@ -207,15 +152,16 @@ public class DirectoryIndexComponent extends DirectoryIndexImpl {
if (policy.isExcludeRoot(file)) return state; if (policy.isExcludeRoot(file)) return state;
} }
if (state == originalState) state = state.copy(); if (state == originalState) state = state.copy(null);
state.fillMapWithModuleContent(file, module, parentInfo.getContentRoot(), null); VirtualFile parentContentRoot = parentInfo.getContentRoot();
state.fillMapWithModuleContent(file, module, parentContentRoot, null);
String parentPackage = state.myDirToPackageName.get(parentId); String parentPackage = state.myDirToPackageName.get(parentId);
if (module != null) { if (module != null) {
if (parentInfo.isInModuleSource()) { if (parentInfo.isInModuleSource()) {
String newDirPackageName = getPackageNameForSubdir(parentPackage, file.getName()); 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; return state;
} }
private final Key<int[]> FILES_TO_RELEASE_KEY = Key.create("DirectoryIndexImpl.MyVirtualFileListener.FILES_TO_RELEASE_KEY");
@Override @Override
public void beforeFileDeletion(VirtualFileEvent event) { public void beforeFileDeletion(VirtualFileEvent event) {
VirtualFile file = event.getFile(); VirtualFile file = event.getFile();
if (!file.isDirectory()) return; if (!file.isDirectory()) return;
if (!myState.myDirToInfoMap.containsKey(getId(file))) return; if (myState.getInfo(getId(file)) == null) return;
final IndexState state = myState.copy(); TIntArrayList list = new TIntArrayList();
addDirsRecursively(myState, list, file);
ArrayList<VirtualFile> list = new ArrayList<VirtualFile>(); file.putUserData(FILES_TO_RELEASE_KEY, list.toNativeArray());
addDirsRecursively(state, list, file);
file.putUserData(FILES_TO_RELEASE_KEY, list);
myState = state;
} }
private void addDirsRecursively(IndexState state, ArrayList<VirtualFile> list, VirtualFile dir) { private void addDirsRecursively(@NotNull IndexState state, @NotNull TIntArrayList list, @NotNull VirtualFile dir) {
if (!(dir instanceof NewVirtualFile) || !state.myDirToInfoMap.containsKey(getId(dir))) return; 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()) { for (VirtualFile child : ((NewVirtualFile)dir).getCachedChildren()) {
if (child.isDirectory()) { if (child.isDirectory()) {
@@ -265,14 +212,18 @@ public class DirectoryIndexComponent extends DirectoryIndexImpl {
@Override @Override
public void fileDeleted(VirtualFileEvent event) { public void fileDeleted(VirtualFileEvent event) {
VirtualFile file = event.getFile(); 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; if (list == null) return;
IndexState copy = null; IndexState copy = null;
for (VirtualFile dir : list) { for (int id : list) {
int id = getId(dir); if (myState.getInfo(id) != null) {
if (myState.myDirToInfoMap.containsKey(id)) { if (copy == null) copy = myState.copy(new TIntProcedure() {
if (copy == null) copy = myState.copy(); @Override
public boolean execute(int fid) {
return ArrayUtil.indexOf(list, fid) == -1;
}
});
copy.myDirToInfoMap.remove(id); copy.myDirToInfoMap.remove(id);
copy.setPackageName(id, null); copy.setPackageName(id, null);
@@ -282,6 +233,12 @@ public class DirectoryIndexComponent extends DirectoryIndexImpl {
if (copy != null) { if (copy != null) {
myState = copy; myState = copy;
} }
myState.assertAncestorsConsistent();
}
@Override
public void beforeFileMovement(VirtualFileMoveEvent event) {
super.beforeFileMovement(event);
} }
@Override @Override
@@ -290,6 +247,7 @@ public class DirectoryIndexComponent extends DirectoryIndexImpl {
if (file.isDirectory()) { if (file.isDirectory()) {
doInitialize(); doInitialize();
} }
myState.assertAncestorsConsistent();
} }
@Override @Override
@@ -300,6 +258,7 @@ public class DirectoryIndexComponent extends DirectoryIndexImpl {
doInitialize(); doInitialize();
} }
} }
myState.assertAncestorsConsistent();
} }
} }
} }
@@ -16,19 +16,18 @@
package com.intellij.openapi.vfs.newvfs; package com.intellij.openapi.vfs.newvfs;
import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.vfs.FileSystemPersistence;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.util.List;
/** /**
* @author max * @author max
*/ */
public abstract class ManagingFS implements FileSystemInterface { public abstract class ManagingFS implements FileSystemInterface, FileSystemPersistence {
private static class ManagingFSHolder { private static class ManagingFSHolder {
private static final ManagingFS ourInstance = ApplicationManager.getApplication().getComponent(ManagingFS.class); 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 boolean wereChildrenAccessed(@NotNull VirtualFile dir);
public abstract void processEvents(@NotNull List<? extends VFileEvent> events);
@Nullable @Nullable
public abstract NewVirtualFile findRoot(@NotNull String basePath, @NotNull NewVirtualFileSystem fs); public abstract NewVirtualFile findRoot(@NotNull String basePath, @NotNull NewVirtualFileSystem fs);
@@ -70,6 +67,7 @@ public abstract class ManagingFS implements FileSystemInterface {
@NotNull @NotNull
public abstract VirtualFile[] getLocalRoots(); public abstract VirtualFile[] getLocalRoots();
@Override
@Nullable @Nullable
public abstract VirtualFile findFileById(int id); public abstract VirtualFile findFileById(int id);
} }
@@ -73,11 +73,11 @@ public abstract class NewVirtualFile extends VirtualFile implements VirtualFileW
@Override @Override
public abstract int getId(); public abstract int getId();
@Nullable @Nullable @Deprecated
public abstract NewVirtualFile findChildById(int id); public NewVirtualFile findChildById(int id) {return null;}
@Nullable @Nullable @Deprecated
public abstract NewVirtualFile findChildByIdIfCached(int id); public NewVirtualFile findChildByIdIfCached(int id) {return null;}
@Override @Override
public void refresh(final boolean asynchronous, final boolean recursive, final Runnable postRunnable) { 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.ManagingFS;
import com.intellij.openapi.vfs.newvfs.NewVirtualFile; import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
import com.intellij.openapi.vfs.newvfs.RefreshQueue; 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.openapi.vfs.newvfs.persistent.PersistentFS;
import com.intellij.util.Consumer; import com.intellij.util.Consumer;
import com.intellij.util.TimeoutUtil; import com.intellij.util.TimeoutUtil;
@@ -151,12 +150,6 @@ public final class LocalFileSystemImpl extends LocalFileSystemBase implements Ap
}); });
PersistentFS.getInstance().clearIdCache(); PersistentFS.getInstance().clearIdCache();
for (VirtualFile root : myManagingFS.getRoots(this)) {
if (root instanceof VirtualDirectoryImpl) {
((VirtualDirectoryImpl)root).cleanupCachedChildren(survivors);
}
}
myRootsToWatch.clear(); 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.FileWatcher;
import com.intellij.openapi.vfs.impl.local.LocalFileSystemImpl; import com.intellij.openapi.vfs.impl.local.LocalFileSystemImpl;
import com.intellij.openapi.vfs.newvfs.events.VFileEvent; 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.openapi.vfs.newvfs.persistent.RefreshWorker;
import com.intellij.util.concurrency.Semaphore; import com.intellij.util.concurrency.Semaphore;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -188,7 +189,7 @@ public class RefreshSessionImpl extends RefreshSession {
manager.fireBeforeRefreshStart(myIsAsync); manager.fireBeforeRefreshStart(myIsAsync);
try { try {
while (!myWorkQueue.isEmpty() || !myEvents.isEmpty()) { while (!myWorkQueue.isEmpty() || !myEvents.isEmpty()) {
ManagingFS.getInstance().processEvents(mergeEventsAndReset()); ((PersistentFSImpl)ManagingFS.getInstance()).processEvents(mergeEventsAndReset());
scan(); scan();
} }
} }
@@ -122,7 +122,7 @@ public class VfsImplUtil {
@Nullable @Nullable
private static Pair<NewVirtualFile, Iterable<String>> prepare(@NotNull NewVirtualFileSystem vfs, @NotNull String path) { 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) { if (normalizedPath == null) {
return null; return null;
} }
@@ -141,4 +141,9 @@ public class VfsImplUtil {
Iterable<String> parts = StringUtil.tokenize(normalizedPath.substring(basePath.length()), FILE_SEPARATORS); Iterable<String> parts = StringUtil.tokenize(normalizedPath.substring(basePath.length()), FILE_SEPARATORS);
return Pair.create(root, parts); 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.project.ProjectManager;
import com.intellij.openapi.roots.OrderEnumerator; import com.intellij.openapi.roots.OrderEnumerator;
import com.intellij.openapi.roots.ProjectRootManager; 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.FileAttributes;
import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.JarFileSystem; import com.intellij.openapi.vfs.*;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.newvfs.NewVirtualFile; import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
import com.intellij.openapi.vfs.newvfs.NewVirtualFileSystem; import com.intellij.openapi.vfs.newvfs.NewVirtualFileSystem;
import com.intellij.openapi.vfs.newvfs.RefreshQueue; import com.intellij.openapi.vfs.newvfs.RefreshQueue;
import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent; 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.openapi.vfs.newvfs.persistent.PersistentFS;
import com.intellij.util.*; import com.intellij.util.*;
import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.text.CaseInsensitiveStringHashingStrategy;
import gnu.trove.THashMap;
import gnu.trove.THashSet; import gnu.trove.THashSet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly; import org.jetbrains.annotations.TestOnly;
@@ -58,16 +53,20 @@ import java.util.*;
* @author max * @author max
*/ */
public class VirtualDirectoryImpl extends VirtualFileSystemEntry { 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() { public String toString() {
return "NULL"; return "NULL";
} }
}; };
private final NewVirtualFileSystem myFS; 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, @Nullable final VirtualDirectoryImpl parent,
@NotNull final NewVirtualFileSystem fs, @NotNull final NewVirtualFileSystem fs,
final int id, final int id,
@@ -89,60 +88,63 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
@NotNull NewVirtualFileSystem delegate) { @NotNull NewVirtualFileSystem delegate) {
VirtualFileSystemEntry result = doFindChild(name, ensureCanonicalName, delegate); VirtualFileSystemEntry result = doFindChild(name, ensureCanonicalName, delegate);
if (result == NULL_VIRTUAL_FILE) { 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) { if (result == null) {
synchronized (this) { addToSuspiciousNames(name, !delegate.isCaseSensitive());
Map<String, VirtualFileSystemEntry> map = asMap();
if (map != null) {
map.put(name, NULL_VIRTUAL_FILE);
} }
}
}
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; return result;
} }
@Nullable private synchronized void addToSuspiciousNames(@NotNull final String name, final boolean ignoreCase) {
private VirtualFileSystemEntry doFindChild(@NotNull String name, if (allChildrenLoaded()) return;
boolean ensureCanonicalName, int index = binSearch(myChildren, 0, myChildren.length, new Comparer() {
@NotNull NewVirtualFileSystem delegate) { @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()) { if (name.isEmpty()) {
return null; return null;
} }
final VirtualFileSystemEntry[] array; final boolean ignoreCase = !delegate.isCaseSensitive();
final Map<String, VirtualFileSystemEntry> map; Comparer comparer = getComparer(name, ignoreCase);
final VirtualFileSystemEntry file; VirtualFileSystemEntry found = doFindChildInArray(comparer);
synchronized (this) { if (found != null) return found;
array = asArray();
if (array == null) { if (allChildrenLoaded()) {
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;
}
return NULL_VIRTUAL_FILE; return NULL_VIRTUAL_FILE;
} }
if (file != null) return file;
if (ensureCanonicalName) { if (ensureCanonicalName) {
VirtualFile fake = new FakeVirtualFile(this, name); VirtualFile fake = new FakeVirtualFile(this, name);
name = delegate.getCanonicallyCasedName(fake); name = delegate.getCanonicallyCasedName(fake);
@@ -152,37 +154,57 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
synchronized (this) { synchronized (this) {
// do not extract getId outside the synchronized block since it will cause a concurrency problem. // do not extract getId outside the synchronized block since it will cause a concurrency problem.
int id = ourPersistence.getId(this, name, delegate); int id = ourPersistence.getId(this, name, delegate);
if (id > 0) { if (id <= 0) {
return null;
}
// maybe another doFindChild() sneaked in the middle // maybe another doFindChild() sneaked in the middle
VirtualFileSystemEntry lastTry = map.get(name); VirtualFileSystemEntry[] array = myChildren;
if (lastTry != null) return lastTry; 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];
}
final String shorty = new String(name); String shorty = new String(name);
VirtualFileSystemEntry child = createChild(shorty, id); // So we don't hold whole char[] buffer of a lengthy path VirtualFileSystemEntry child = createChild(shorty, id, delegate); // So we don't hold whole char[] buffer of a lengthy path
map.put(shorty, child);
insertChildAt(child, indexInReal, array, ignoreCase);
return child; return child;
} }
} }
return null; @NotNull
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 @NotNull
public VirtualFileSystemEntry createChild(@NotNull String name, int id) { public VirtualFileSystemEntry createChild(@NotNull String name, int id, @NotNull NewVirtualFileSystem delegate) {
final VirtualFileSystemEntry child; VirtualFileSystemEntry child;
final NewVirtualFileSystem fs = getFileSystem();
final int attributes = ourPersistence.getFileAttributes(id); final int attributes = ourPersistence.getFileAttributes(id);
if (PersistentFS.isDirectory(attributes)) { if (PersistentFS.isDirectory(attributes)) {
child = new VirtualDirectoryImpl(name, this, fs, id, attributes); child = new VirtualDirectoryImpl(name, this, delegate, id, attributes);
} }
else { else {
child = new VirtualFileImpl(name, this, id, attributes); child = new VirtualFileImpl(name, this, id, attributes);
//noinspection TestOnlyProblems //noinspection TestOnlyProblems
assertAccessInTests(child); assertAccessInTests(child, delegate);
} }
if (fs.markNewFilesAsDirty()) { if (delegate.markNewFilesAsDirty()) {
child.markDirty(); child.markDirty();
} }
@@ -210,15 +232,14 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
} }
@TestOnly @TestOnly
private static void assertAccessInTests(VirtualFileSystemEntry child) { private static void assertAccessInTests(@NotNull VirtualFileSystemEntry child, @NotNull NewVirtualFileSystem delegate) {
final Application application = ApplicationManager.getApplication(); final Application application = ApplicationManager.getApplication();
if (IS_UNDER_TEAMCITY && if (IS_UNDER_TEAMCITY &&
SHOULD_PERFORM_ACCESS_CHECK && SHOULD_PERFORM_ACCESS_CHECK &&
application.isUnitTestMode() && application.isUnitTestMode() &&
application instanceof ApplicationImpl && application instanceof ApplicationImpl &&
((ApplicationImpl)application).isComponentsCreated()) { ((ApplicationImpl)application).isComponentsCreated()) {
NewVirtualFileSystem fileSystem = child.getFileSystem(); if (delegate != LocalFileSystem.getInstance() && delegate != JarFileSystem.getInstance()) {
if (fileSystem != LocalFileSystem.getInstance() && fileSystem != JarFileSystem.getInstance()) {
return; return;
} }
// root' children are loaded always // root' children are loaded always
@@ -229,7 +250,7 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
if (!isUnder) { if (!isUnder) {
for (String root : allowed) { for (String root : allowed) {
String childPath = child.getPath(); String childPath = child.getPath();
if (child.getFileSystem() == JarFileSystem.getInstance()) { if (delegate == JarFileSystem.getInstance()) {
VirtualFile local = JarFileSystem.getInstance().getVirtualFileForJar(child); VirtualFile local = JarFileSystem.getInstance().getVirtualFileForJar(child);
assert local != null : child; assert local != null : child;
childPath = local.getPath(); 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 // null means we were unable to get roots, so do not check access
@Nullable @Nullable
@TestOnly
private static Set<String> allowedRoots() { private static Set<String> allowedRoots() {
if (insideGettingRoots) return null; if (insideGettingRoots) return null;
@@ -299,7 +321,8 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
private static boolean insideGettingRoots; private static boolean insideGettingRoots;
private static VirtualFile[] getAllRoots(Project project) { @TestOnly
private static VirtualFile[] getAllRoots(@NotNull Project project) {
insideGettingRoots = true; insideGettingRoots = true;
final Set<VirtualFile> roots = new THashSet<VirtualFile>(); final Set<VirtualFile> roots = new THashSet<VirtualFile>();
@@ -312,20 +335,15 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
} }
@Nullable @Nullable
private VirtualFileSystemEntry createAndFindChildWithEventFire(@NotNull String name) { private VirtualFileSystemEntry createAndFindChildWithEventFire(@NotNull String name, @NotNull NewVirtualFileSystem delegate) {
final NewVirtualFileSystem delegate = getFileSystem();
final VirtualFile fake = new FakeVirtualFile(this, name); final VirtualFile fake = new FakeVirtualFile(this, name);
final FileAttributes attributes = delegate.getAttributes(fake); final FileAttributes attributes = delegate.getAttributes(fake);
if (attributes != null) { if (attributes == null) return null;
final String realName = delegate.getCanonicallyCasedName(fake); final String realName = delegate.getCanonicallyCasedName(fake);
final VFileCreateEvent event = new VFileCreateEvent(null, this, realName, attributes.isDirectory(), true); final VFileCreateEvent event = new VFileCreateEvent(null, this, realName, attributes.isDirectory(), true);
RefreshQueue.getInstance().processSingleEvent(event); RefreshQueue.getInstance().processSingleEvent(event);
return findChild(realName); return findChild(realName);
} }
else {
return null;
}
}
@Override @Override
@Nullable @Nullable
@@ -333,95 +351,172 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
return findChild(name, true, true, getFileSystem()); 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 @Override
@Nullable @Nullable
public synchronized NewVirtualFile findChildIfCached(@NotNull String name) { public synchronized NewVirtualFile findChildIfCached(@NotNull String name) {
final VirtualFileSystemEntry[] a = asArray();
if (a != null) {
final boolean ignoreCase = !getFileSystem().isCaseSensitive(); final boolean ignoreCase = !getFileSystem().isCaseSensitive();
for (VirtualFileSystemEntry file : a) { Comparer comparer = getComparer(name, ignoreCase);
if (file.nameMatches(name, ignoreCase)) return file; VirtualFileSystemEntry found = doFindChildInArray(comparer);
} return found == NULL_VIRTUAL_FILE ? null : found;
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;
} }
@Override @Override
@NotNull @NotNull
public Iterable<VirtualFile> iterInDbChildren() { 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)) { if (!ourPersistence.wereChildrenAccessed(this)) {
return Collections.emptyList(); return Collections.emptyList();
} }
if (ourPersistence.areChildrenLoaded(this)) { if (!ourPersistence.areChildrenLoaded(this)) {
return Arrays.asList(getChildren());
}
final String[] names = ourPersistence.listPersisted(this); final String[] names = ourPersistence.listPersisted(this);
final NewVirtualFileSystem delegate = PersistentFS.replaceWithNativeFS(getFileSystem()); final NewVirtualFileSystem delegate = PersistentFS.replaceWithNativeFS(getFileSystem());
for (String name : names) { for (String name : names) {
findChild(name, false, false, delegate); findChild(name, false, false, delegate);
} }
}
// important: should return a copy here for safe iterations return getCachedChildren();
return new ArrayList<VirtualFile>(ensureAsMap().values());
} }
@Override @Override
@NotNull @NotNull
public synchronized VirtualFile[] getChildren() { public synchronized VirtualFile[] getChildren() {
VirtualFileSystemEntry[] children = asArray(); VirtualFileSystemEntry[] children = myChildren;
if (children != null) { NewVirtualFileSystem delegate = getFileSystem();
return children; 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); FSRecords.NameId[] childrenIds = ourPersistence.listAll(this);
final int[] childrenIds = pair.second; VirtualFileSystemEntry[] result;
if (childrenIds.length == 0) { if (childrenIds.length == 0) {
children = EMPTY_ARRAY; result = EMPTY_ARRAY;
} }
else { else {
children = new VirtualFileSystemEntry[childrenIds.length]; Arrays.sort(childrenIds, new Comparator<FSRecords.NameId>() {
String[] names = pair.first; @Override
final Map<String, VirtualFileSystemEntry> map = asMap(); public int compare(FSRecords.NameId o1, FSRecords.NameId o2) {
for (int i = 0; i < children.length; i++) { String name1 = o1.name;
final int childId = childrenIds[i]; String name2 = o2.name;
final String name = names[i]; return compareNames(name1, name2, ignoreCase);
VirtualFileSystemEntry child = map != null ? map.get(name) : null;
children[i] = child != null && child != NULL_VIRTUAL_FILE ? child : createChild(name, childId);
} }
});
result = new VirtualFileSystemEntry[childrenIds.length];
int delegateI = 0;
int cachedI = 0;
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) { 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 @Override
@@ -430,127 +525,135 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
return findChild(name, false, true, getFileSystem()); return findChild(name, false, true, getFileSystem());
} }
@Override public VirtualFileSystemEntry findChildById(int id, boolean cachedOnly) {
@Nullable VirtualFile[] array = getArraySafely();
public NewVirtualFile findChildById(int id) { VirtualFileSystemEntry result = null;
final NewVirtualFile loaded = findChildByIdIfCached(id); for (VirtualFile file : array) {
if (loaded != null) { VirtualFileSystemEntry withId = (VirtualFileSystemEntry)file;
return loaded; if (withId.getId() == id) {
result = withId;
break;
} }
}
if (result != null) return result;
if (cachedOnly) return null;
String name = ourPersistence.getName(id); String name = ourPersistence.getName(id);
return findChild(name, false, false, getFileSystem()); 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 @NotNull
@Override @Override
public byte[] contentsToByteArray() throws IOException { public byte[] contentsToByteArray() throws IOException {
throw new IOException("Cannot get content of directory: " + this); throw new IOException("Cannot get content of directory: " + this);
} }
// MUST BE CALLED UNDER this LOCK public synchronized void addChild(@NotNull VirtualFileSystemEntry child) {
@Nullable VirtualFileSystemEntry[] array = myChildren;
private VirtualFileSystemEntry[] asArray() { final String childName = child.getName();
Object children = myChildren; final boolean ignoreCase = !getFileSystem().isCaseSensitive();
if (children instanceof VirtualFileSystemEntry[]) return (VirtualFileSystemEntry[])children; long r = findIndexInBoth(array, getComparer(childName, ignoreCase));
return null; 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 private void insertChildAt(@NotNull VirtualFileSystemEntry file, int negativeIndex, @NotNull VirtualFileSystemEntry[] array, boolean ignoreCase) {
@Nullable VirtualFileSystemEntry[] appended = new VirtualFileSystemEntry[array.length + 1];
private Map<String, VirtualFileSystemEntry> asMap() { int i = -negativeIndex -1;
Object children = myChildren; System.arraycopy(array, 0, appended, 0, i);
if (children instanceof Map) { appended[i] = file;
@SuppressWarnings({"unchecked"}) System.arraycopy(array, i, appended, i+1, array.length - i);
final Map<String, VirtualFileSystemEntry> map = (Map<String, VirtualFileSystemEntry>)children; assertConsistency(appended, ignoreCase);
return map; myChildren = appended;
}
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);
}
} }
public synchronized void removeChild(@NotNull VirtualFile file) { public synchronized void removeChild(@NotNull VirtualFile file) {
final VirtualFileSystemEntry[] a = asArray(); boolean ignoreCase = !getFileSystem().isCaseSensitive();
if (a != null) { String name = file.getName();
myChildren = ArrayUtil.remove(a, file);
} myChildren = ArrayUtil.remove(myChildren, (VirtualFileSystemEntry)file, new ArrayFactory<VirtualFileSystemEntry>() {
else { @Override
ensureAsMap().put(file.getName(), NULL_VIRTUAL_FILE); 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() { public synchronized boolean allChildrenLoaded() {
return asArray() != null; return getFlag(CHILDREN_CACHED);
}
private void setChildrenLoaded() {
setFlag(CHILDREN_CACHED, true);
} }
@NotNull @NotNull
public synchronized List<String> getSuspiciousNames() { public synchronized List<String> getSuspiciousNames() {
final Map<String, VirtualFileSystemEntry> map = asMap(); List<VirtualFile> suspicious = new SubList<VirtualFile>(myChildren, getSuspiciousArrayStart(), myChildren.length);
if (map == null) return Collections.emptyList(); return ContainerUtil.map2List(suspicious, new Function<VirtualFile, String>() {
@Override
List<String> names = null; public String fun(VirtualFile file) {
return file.getName();
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());
} }
});
} }
return names == null ? Collections.<String>emptyList() : names; 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;
}
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 -(low + 1); // key not found.
} }
@Override @Override
@@ -560,40 +663,8 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
@Override @Override
@NotNull @NotNull
public synchronized Collection<VirtualFile> getCachedChildren() { public synchronized List<VirtualFile> getCachedChildren() {
final Map<String, VirtualFileSystemEntry> map = asMap(); return new SubList<VirtualFile>(myChildren, 0, getSuspiciousArrayStart());
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;
}
} }
@Override @Override
@@ -606,4 +677,21 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
public OutputStream getOutputStream(final Object requestor, final long newModificationStamp, final long newTimeStamp) throws IOException { 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()); 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; return null;
} }
@Override
public NewVirtualFile findChildById(final int id) {
return null;
}
@Override
public NewVirtualFile findChildByIdIfCached(final int id) {
return null;
}
@Override @Override
public VirtualFile[] getChildren() { public VirtualFile[] getChildren() {
return EMPTY_ARRAY; 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.FileTooBigException;
import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsBundle; import com.intellij.openapi.vfs.VfsBundle;
import com.intellij.openapi.vfs.VirtualFile; 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.NewVirtualFile;
import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS; import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS;
import com.intellij.psi.SingleRootFileViewProvider; import com.intellij.psi.SingleRootFileViewProvider;
import com.intellij.util.ArrayUtil;
import com.intellij.util.io.IOUtil; import com.intellij.util.io.IOUtil;
import com.intellij.util.text.StringFactory; import com.intellij.util.text.StringFactory;
import org.jetbrains.annotations.NonNls; 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 IS_SPECIAL_FLAG = 0x0800;
private static final int INT_FLAGS_MASK = 0xff00; private static final int INT_FLAGS_MASK = 0xff00;
@NonNls private static final String EMPTY = ""; private static final String EMPTY = "";
@NonNls private static final String[] WELL_KNOWN_SUFFIXES = {"$1.class", "$2.class", ".class", ".java", ".html", ".txt", ".xml"}; @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}. */ /** 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 Object myName;
private volatile VirtualDirectoryImpl myParent; 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 short myFlags = 0;
private volatile int myId; private volatile int myId;
@@ -73,7 +89,7 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile {
storeName(name); storeName(name);
if (parent != null) { if (parent != null && parent != VirtualDirectoryImpl.NULL_VIRTUAL_FILE) {
setFlagInt(IS_SYMLINK_FLAG, PersistentFS.isSymLink(attributes)); setFlagInt(IS_SYMLINK_FLAG, PersistentFS.isSymLink(attributes));
setFlagInt(IS_SPECIAL_FLAG, PersistentFS.isSpecialFile(attributes)); setFlagInt(IS_SPECIAL_FLAG, PersistentFS.isSpecialFile(attributes));
updateLinkStatus(); updateLinkStatus();
@@ -81,12 +97,12 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile {
} }
private void storeName(@NotNull String name) { private void storeName(@NotNull String name) {
myFlags &= 0x1fff; myFlags &= 0xffff >>> SUFFIX_BITS;
for (int i = 0; i < WELL_KNOWN_SUFFIXES.length; i++) { for (int i = 0; i < WELL_KNOWN_SUFFIXES.length; i++) {
String suffix = WELL_KNOWN_SUFFIXES[i]; String suffix = WELL_KNOWN_SUFFIXES[i];
if (name.endsWith(suffix)) { if (name.endsWith(suffix)) {
name = StringUtil.trimEnd(name, suffix); name = StringUtil.trimEnd(name, suffix);
int mask = (i+1) << 13; int mask = (i+1) << 16-SUFFIX_BITS;
myFlags |= mask; myFlags |= mask;
break; break;
} }
@@ -104,27 +120,39 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile {
setFlagInt(HAS_SYMLINK_FLAG, isSymLink || ((VirtualFileSystemEntry)myParent).getFlagInt(HAS_SYMLINK_FLAG)); setFlagInt(HAS_SYMLINK_FLAG, isSymLink || ((VirtualFileSystemEntry)myParent).getFlagInt(HAS_SYMLINK_FLAG));
} }
@NotNull
private static Object encodeName(@NotNull String name) { private static Object encodeName(@NotNull String name) {
int length = name.length(); int length = name.length();
if (length == 0) return EMPTY; if (length == 0) return ArrayUtil.EMPTY_BYTE_ARRAY;
if (!IOUtil.isAscii(name)) {
return name;
}
byte[] bytes = new byte[length]; byte[] bytes = new byte[length];
for (int i = 0; i < length; i++) { 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; return bytes;
} }
@NotNull @NotNull
private String getEncodedSuffix() { private String getEncodedSuffix() {
int index = (myFlags >> 13) & 0x07; int index = (myFlags >> 16-SUFFIX_BITS) & ((1<<SUFFIX_BITS)-1);
if (index == 0) return EMPTY; if (index == 0) return EMPTY;
return WELL_KNOWN_SUFFIXES[index-1]; 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 @Override
@NotNull @NotNull
@@ -146,29 +174,45 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile {
return StringFactory.createShared(chars); return StringFactory.createShared(chars);
} }
boolean nameMatches(@NotNull String pattern, boolean ignoreCase) { int compareNameTo(@NotNull String name, boolean ignoreCase) {
Object name = rawName(); Object rawName = rawName();
String suffix = getEncodedSuffix(); if (rawName instanceof String) {
if (name instanceof String) { String thisName = getName();
final String nameStr = (String)name; return compareNames(thisName, name, ignoreCase);
return pattern.length() == nameStr.length() + suffix.length() &&
pattern.regionMatches(ignoreCase, 0, nameStr, 0, nameStr.length()) &&
pattern.regionMatches(ignoreCase, nameStr.length(), suffix, 0, suffix.length());
} }
byte[] bytes = (byte[])name; byte[] bytes = (byte[])rawName;
int length = bytes.length; int suffixLength = getEncodedSuffixLength();
if (length + suffix.length() != pattern.length()) { int bytesLength = bytes.length;
return false; 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++) { static int compareNames(@NotNull String name1, @NotNull String name2, boolean ignoreCase) {
if (!StringUtil.charsMatch((char)bytes[i], pattern.charAt(i), ignoreCase)) { int d = name1.length() - name2.length();
return false; 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() { protected Object rawName() {
@@ -218,11 +262,16 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile {
@Override @Override
public void markDirty() { public void markDirty() {
if (!isDirty()) { if (!isDirty()) {
setFlagInt(DIRTY_FLAG, true); markDirtyInternal();
if (myParent != null) myParent.markDirty(); VirtualDirectoryImpl parent = myParent;
if (parent != null) parent.markDirty();
} }
} }
protected void markDirtyInternal() {
setFlagInt(DIRTY_FLAG, true);
}
@Override @Override
public void markDirtyRecursively() { public void markDirtyRecursively() {
markDirty(); markDirty();
@@ -467,7 +516,7 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile {
content = contentsToByteArray(); content = contentsToByteArray();
} }
catch (FileNotFoundException e) { catch (FileNotFoundException e) {
// file has already been deleted from disk // file has already been deleted on disk
return super.getCharset(); return super.getCharset();
} }
charset = LoadTextUtil.detectCharsetAndSetBOM(this, content); 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.application.PathManager;
import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Disposer; 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.BufferExposingByteArrayOutputStream;
import com.intellij.openapi.util.io.ByteSequence; import com.intellij.openapi.util.io.ByteSequence;
import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.io.FileUtil;
@@ -654,7 +653,7 @@ public class FSRecords implements Forceable {
return DbConnection.isDirty(); return DbConnection.isDirty();
} }
public static int findRootRecord(String rootUrl) throws IOException { public static int findRootRecord(@NotNull String rootUrl) {
try { try {
try { try {
w.lock(); 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 {
try { try {
w.lock(); 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 { try {
r.lock(); r.lock();
try { try {
final DataInputStream input = readAttribute(parentId, CHILDREN_ATT); 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); int count = DataInputOutputUtil.readINT(input);
final int[] ids = ArrayUtil.newIntArray(count); NameId[] result = count == 0 ? NameId.EMPTY_ARRAY : new NameId[count];
final String[] names = ArrayUtil.newStringArray(count);
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
int id = DataInputOutputUtil.readINT(input); int id = DataInputOutputUtil.readINT(input);
id = id >= 0 ? id + parentId : -id; id = id >= 0 ? id + parentId : -id;
ids[i] = id; result[i] = new NameId(id, getName(id));
names[i] = getName(id);
} }
input.close(); input.close();
return Pair.create(names, ids); return result;
} }
finally { finally {
r.unlock(); r.unlock();
@@ -15,7 +15,6 @@
*/ */
package com.intellij.openapi.vfs.newvfs.persistent; package com.intellij.openapi.vfs.newvfs.persistent;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
@@ -56,7 +55,7 @@ public abstract class PersistentFS extends ManagingFS {
public abstract String[] listPersisted(@NotNull VirtualFile parent); public abstract String[] listPersisted(@NotNull VirtualFile parent);
@NotNull @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); 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.application.ex.ApplicationEx;
import com.intellij.openapi.components.ApplicationComponent; import com.intellij.openapi.components.ApplicationComponent;
import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.ShutDownTracker; import com.intellij.openapi.util.ShutDownTracker;
import com.intellij.openapi.util.io.*; import com.intellij.openapi.util.io.*;
import com.intellij.openapi.vfs.*; import com.intellij.openapi.vfs.*;
import com.intellij.openapi.vfs.newvfs.BulkFileListener; import com.intellij.openapi.vfs.newvfs.*;
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.events.*; import com.intellij.openapi.vfs.newvfs.events.*;
import com.intellij.openapi.vfs.newvfs.impl.FakeVirtualFile; import com.intellij.openapi.vfs.newvfs.impl.FakeVirtualFile;
import com.intellij.openapi.vfs.newvfs.impl.VirtualDirectoryImpl; import com.intellij.openapi.vfs.newvfs.impl.VirtualDirectoryImpl;
import com.intellij.openapi.vfs.newvfs.impl.VirtualFileSystemEntry; import com.intellij.openapi.vfs.newvfs.impl.VirtualFileSystemEntry;
import com.intellij.util.ArrayUtil; 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.ConcurrentIntObjectMap;
import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.StripedLockIntObjectConcurrentHashMap; import com.intellij.util.containers.StripedLockIntObjectConcurrentHashMap;
import com.intellij.util.io.ReplicatorInputStream; import com.intellij.util.io.ReplicatorInputStream;
import com.intellij.util.messages.MessageBus; import com.intellij.util.messages.MessageBus;
import gnu.trove.THashMap; import gnu.trove.*;
import gnu.trove.TIntArrayList;
import gnu.trove.TIntHashSet;
import gnu.trove.TIntObjectHashMap;
import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; 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 Map<String, VirtualFileSystemEntry> myRoots = new THashMap<String, VirtualFileSystemEntry>(FileUtil.PATH_HASHING_STRATEGY);
private final TIntObjectHashMap<VirtualFileSystemEntry> myRootsById = new TIntObjectHashMap<VirtualFileSystemEntry>(); private final TIntObjectHashMap<VirtualFileSystemEntry> myRootsById = new TIntObjectHashMap<VirtualFileSystemEntry>();
@Nullable private VirtualFileSystemEntry myFakeRoot; @Nullable private VirtualFileSystemEntry myFakeRoot;
@NotNull private final ConcurrentIntObjectMap<VirtualFileSystemEntry> myIdToDirCache = new StripedLockIntObjectConcurrentHashMap<VirtualFileSystemEntry>();
private final Object myInputLock = new Object(); private final Object myInputLock = new Object();
@@ -127,13 +123,17 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone
@NotNull @NotNull
public String[] list(@NotNull final VirtualFile file) { public String[] list(@NotNull final VirtualFile file) {
int id = getFileId(file); int id = getFileId(file);
int[] childrenIds = FSRecords.list(id);
String[] names = listPersisted(childrenIds); FSRecords.NameId[] nameIds = FSRecords.listAll(id);
if (areChildrenLoaded(id)) { if (!areChildrenLoaded(id)) {
return names; nameIds = persistAllChildren(file, id, nameIds);
} }
Pair<String[], int[]> pair = persistAllChildren(file, id, Pair.create(names, childrenIds)); return ContainerUtil.map2Array(nameIds, String.class, new Function<FSRecords.NameId, String>() {
return pair.first; @Override
public String fun(FSRecords.NameId id) {
return id.name;
}
});
} }
@Override @Override
@@ -152,85 +152,72 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone
} }
@NotNull @NotNull
private static Pair<String[], int[]> persistAllChildren(@NotNull VirtualFile file, int id, @NotNull Pair<String[], int[]> current) { private static FSRecords.NameId[] persistAllChildren(@NotNull final VirtualFile file, final int id, @NotNull FSRecords.NameId[] current) {
String[] currentNames = current.first; final NewVirtualFileSystem fs = replaceWithNativeFS(getDelegate(file));
int[] currentIds = current.second;
NewVirtualFileSystem fs = replaceWithNativeFS(getDelegate(file));
String[] delegateNames = VfsUtil.filterNames(fs.list(file)); String[] delegateNames = VfsUtil.filterNames(fs.list(file));
if (delegateNames.length == 0 && currentNames.length > 0) { if (delegateNames.length == 0 && current.length > 0) {
return current; return current;
} }
String[] names; THashMap<String, FSRecords.NameId> result = new THashMap<String, FSRecords.NameId>();
if (currentNames.length == 0) { if (current.length == 0) {
names = delegateNames; for (String name : delegateNames) {
result.put(name, new FSRecords.NameId(-1, name));
}
} }
else { else {
Set<String> allNamesSet = new LinkedHashSet<String>((currentNames.length + delegateNames.length) * 2); for (FSRecords.NameId nameId : current) {
ContainerUtil.addAll(allNamesSet, currentNames); result.put(nameId.name, nameId);
ContainerUtil.addAll(allNamesSet, delegateNames); }
names = ArrayUtil.toStringArray(allNamesSet); for (String name : delegateNames) {
if (!result.containsKey(name)) {
result.put(name, new FSRecords.NameId(-1, name));
}
}
} }
int[] childrenIds = ArrayUtil.newIntArray(names.length); final TIntArrayList childrenIds = new TIntArrayList();
result.transformValues(new TObjectFunction<FSRecords.NameId, FSRecords.NameId>() {
int filter = 0; @Override
for (int i = 0; i < names.length; i++) { public FSRecords.NameId execute(FSRecords.NameId nameId) {
final String name = names[i]; if (nameId.id < 0) {
int idx = ArrayUtil.indexOf(currentNames, name); FakeVirtualFile child = new FakeVirtualFile(file, nameId.name);
if (idx >= 0) { FileAttributes attributes = fs.getAttributes(child);
childrenIds[i] = currentIds[idx];
}
else {
final FakeVirtualFile child = new FakeVirtualFile(file, name);
final FileAttributes attributes = fs.getAttributes(child);
if (attributes != null) { if (attributes != null) {
final int childId = createAndFillRecord(fs, child, id, attributes); int childId = createAndFillRecord(fs, child, id, attributes);
childrenIds[i] = childId; nameId = new FSRecords.NameId(childId, nameId.name);
}
else {
childrenIds[i] = -1;
++filter;
LOG.warn("fs=" + fs + " dir=" + file + " name=" + name + " curr=" + currentNames.length);
} }
} }
if (nameId.id > 0) {
childrenIds.add(nameId.id);
}
return nameId;
}
});
FSRecords.updateList(id, childrenIds.toNativeArray());
setChildrenCached(id);
Collection<FSRecords.NameId> nameIds = result.values();
return nameIds.toArray(new FSRecords.NameId[nameIds.size()]);
} }
if (filter != 0) { public static void setChildrenCached(int id) {
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;
}
}
names = newNames;
childrenIds = newIds;
}
FSRecords.updateList(id, childrenIds);
int flags = FSRecords.getFlags(id); int flags = FSRecords.getFlags(id);
FSRecords.setFlags(id, flags | CHILDREN_CACHED_FLAG, true); FSRecords.setFlags(id, flags | CHILDREN_CACHED_FLAG, true);
return Pair.create(names, childrenIds);
} }
@Override @Override
@NotNull @NotNull
public Pair<String[],int[]> listAll(@NotNull VirtualFile parent) { public FSRecords.NameId[] listAll(@NotNull VirtualFile parent) {
final int parentId = getFileId(parent); final int parentId = getFileId(parent);
Pair<String[], int[]> pair = FSRecords.listAll(parentId); FSRecords.NameId[] nameIds = FSRecords.listAll(parentId);
if (!areChildrenLoaded(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) { private static boolean areChildrenLoaded(final int parentId) {
@@ -709,7 +696,7 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone
}; };
@NotNull @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(); final List<EventWrapper> deletionEvents = ContainerUtil.newArrayList();
for (int i = 0, size = events.size(); i < size; i++) { for (int i = 0, size = events.size(); i < size; i++) {
final VFileEvent event = events.get(i); final VFileEvent event = events.get(i);
@@ -747,11 +734,10 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone
return filtered; return filtered;
} }
@Override public void processEvents(@NotNull List<VFileEvent> events) {
public void processEvents(@NotNull List<? extends VFileEvent> events) {
ApplicationManager.getApplication().assertWriteAccessAllowed(); ApplicationManager.getApplication().assertWriteAccessAllowed();
List<? extends VFileEvent> validated = validateEvents(events); List<VFileEvent> validated = validateEvents(events);
BulkFileListener publisher = myEventsBus.syncPublisher(VirtualFileManager.VFS_CHANGES); BulkFileListener publisher = myEventsBus.syncPublisher(VirtualFileManager.VFS_CHANGES);
publisher.before(validated); publisher.before(validated);
@@ -764,11 +750,12 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone
@Override @Override
@Nullable @Nullable
public VirtualFileSystemEntry findRoot(@NotNull String basePath, @NotNull NewVirtualFileSystem fs) { 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(); myRootsLock.readLock().lock();
try { try {
final VirtualFileSystemEntry root = basePath.isEmpty() ? myFakeRoot : myRoots.get(rootUrl); root = basePath.isEmpty() ? myFakeRoot : myRoots.get(rootUrl);
if (root != null) return root; if (root != null) return root;
} }
finally { finally {
@@ -777,10 +764,12 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone
myRootsLock.writeLock().lock(); myRootsLock.writeLock().lock();
try { try {
VirtualFileSystemEntry root = basePath.isEmpty() ? myFakeRoot : myRoots.get(rootUrl); root = basePath.isEmpty() ? myFakeRoot : myRoots.get(rootUrl);
if (root != null) return root; 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()) { if (basePath.isEmpty()) {
// fake super-root // fake super-root
@@ -802,8 +791,15 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone
else if (fs instanceof JarFileSystem) { else if (fs instanceof JarFileSystem) {
// optimization: for jar roots do not store base path in the myName field, use local FS file's getPath() // 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)); 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; 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); root = new JarRoot(fs, rootId, parentLocalFile);
} }
else { else {
@@ -827,21 +823,18 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone
} }
else { else {
myRoots.put(rootUrl, root); myRoots.put(rootUrl, root);
myRootsById.put(root.getId(), root); myRootsById.put(rootId, root);
if (rootId != root.getId()) throw new AssertionError();
} }
return root; return root;
} }
catch (IOException e) {
throw new RuntimeException(e);
}
finally { finally {
myRootsLock.writeLock().unlock(); myRootsLock.writeLock().unlock();
} }
} }
@NotNull private final ConcurrentIntObjectMap<NewVirtualFile> myIdToDirCache = new StripedLockIntObjectConcurrentHashMap<NewVirtualFile>();
@Override @Override
public void clearIdCache() { public void clearIdCache() {
myIdToDirCache.clear(); myIdToDirCache.clear();
@@ -852,35 +845,34 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone
@Override @Override
@Nullable @Nullable
public NewVirtualFile findFileById(final int id) { public NewVirtualFile findFileById(final int id) {
return _findFileById(id, false, new TIntArrayList(DEPTH_LIMIT), 0); return findFileById(id, false, null, 0);
} }
@Override @Override
public NewVirtualFile findFileByIdIfCached(final int id) { public NewVirtualFile findFileByIdIfCached(final int id) {
return _findFileById(id, true, new TIntArrayList(DEPTH_LIMIT), 0); return findFileById(id, true, null, 0);
} }
@Nullable @Nullable
private NewVirtualFile _findFileById(int id, boolean cachedOnly, TIntArrayList visited, int mask) { private VirtualFileSystemEntry findFileById(int id, boolean cachedOnly, TIntArrayList visited, int mask) {
NewVirtualFile cached = myIdToDirCache.get(id); VirtualFileSystemEntry cached = myIdToDirCache.get(id);
if (cached != null) { if (cached != null) return cached;
return cached;
}
if (visited.size() >= DEPTH_LIMIT || (mask & id) == id && visited.contains(id)) { if (visited != null && (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 + "):"); @NonNls String sb = "Dead loop detected in persistent FS (id=" + id + " cached-only=" + cachedOnly + "):";
for (int i = 0; i < visited.size(); i++) { for (int i = 0; i < visited.size(); i++) {
int _id = visited.get(i); int _id = visited.get(i);
sb.append("\n ").append(_id).append(" '").append(getName(_id)).append("' ") sb += "\n " + _id + " '" + getName(_id) + "' " +
.append(String.format("%02x", getFileAttributes(_id))).append(' ').append(myIdToDirCache.containsKey(_id)); String.format("%02x", getFileAttributes(_id)) + ' ' + myIdToDirCache.containsKey(_id);
} }
LOG.error(sb.toString()); LOG.error(sb);
return null; return null;
} }
if (visited == null) visited = new TIntArrayList(DEPTH_LIMIT);
visited.add(id); visited.add(id);
int parentId = getParent(id); int parentId = getParent(id);
NewVirtualFile result; VirtualFileSystemEntry result;
if (parentId == 0) { if (parentId == 0) {
myRootsLock.readLock().lock(); myRootsLock.readLock().lock();
try { try {
@@ -891,17 +883,17 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone
} }
} }
else { else {
NewVirtualFile parentFile = _findFileById(parentId, cachedOnly, visited, mask |= id); VirtualFileSystemEntry parentFile = findFileById(parentId, cachedOnly, visited, mask |= id);
if (parentFile == null) { if (parentFile instanceof VirtualDirectoryImpl) {
result = null; result = ((VirtualDirectoryImpl)parentFile).findChildById(id, cachedOnly);
} }
else { else {
result = cachedOnly ? parentFile.findChildByIdIfCached(id) : parentFile.findChildById(id); result = null;
} }
} }
if (result != null && result.isDirectory()) { if (result != null && result.isDirectory()) {
NewVirtualFile old = myIdToDirCache.put(id, result); VirtualFileSystemEntry old = myIdToDirCache.put(id, result);
if (old != null) result = old; if (old != null) result = old;
} }
return result; return result;
@@ -960,11 +952,11 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone
return VfsUtilCore.toVirtualFileArray(roots); return VfsUtilCore.toVirtualFileArray(roots);
} }
private void applyEvent(@NotNull VFileEvent event) { private VirtualFileSystemEntry applyEvent(@NotNull VFileEvent event) {
try { try {
if (event instanceof VFileCreateEvent) { if (event instanceof VFileCreateEvent) {
final VFileCreateEvent createEvent = (VFileCreateEvent)event; final VFileCreateEvent createEvent = (VFileCreateEvent)event;
executeCreateChild(createEvent.getParent(), createEvent.getChildName()); return executeCreateChild(createEvent.getParent(), createEvent.getChildName());
} }
else if (event instanceof VFileDeleteEvent) { else if (event instanceof VFileDeleteEvent) {
final VFileDeleteEvent deleteEvent = (VFileDeleteEvent)event; final VFileDeleteEvent deleteEvent = (VFileDeleteEvent)event;
@@ -976,7 +968,7 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone
} }
else if (event instanceof VFileCopyEvent) { else if (event instanceof VFileCopyEvent) {
final VFileCopyEvent copyEvent = (VFileCopyEvent)event; final VFileCopyEvent copyEvent = (VFileCopyEvent)event;
executeCreateChild(copyEvent.getNewParent(), copyEvent.getNewChildName()); return executeCreateChild(copyEvent.getNewParent(), copyEvent.getNewChildName());
} }
else if (event instanceof VFileMoveEvent) { else if (event instanceof VFileMoveEvent) {
final VFileMoveEvent moveEvent = (VFileMoveEvent)event; 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. // Exception applying single event should not prevent other events from applying.
LOG.error(e); LOG.error(e);
} }
return null;
} }
@NotNull @NotNull
@@ -1004,7 +997,7 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone
return "PersistentFS"; 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 NewVirtualFileSystem delegate = getDelegate(parent);
final VirtualFile fake = new FakeVirtualFile(parent, name); final VirtualFile fake = new FakeVirtualFile(parent, name);
final FileAttributes attributes = delegate.getAttributes(fake); final FileAttributes attributes = delegate.getAttributes(fake);
@@ -1014,8 +1007,11 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone
appendIdToParentList(parentId, childId); appendIdToParentList(parentId, childId);
assert parent instanceof VirtualDirectoryImpl : parent; assert parent instanceof VirtualDirectoryImpl : parent;
final VirtualDirectoryImpl dir = (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, private static int createAndFillRecord(@NotNull NewVirtualFileSystem delegateSystem,
@@ -1036,42 +1032,37 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone
private void executeDelete(@NotNull VirtualFile file) { private void executeDelete(@NotNull VirtualFile file) {
if (!file.exists()) { if (!file.exists()) {
LOG.error("Deleting a file, which does not exist: " + file.getPath()); LOG.error("Deleting a file, which does not exist: " + file.getPath());
return;
} }
else {
clearIdCache(); clearIdCache();
final int id = getFileId(file); int id = getFileId(file);
FSRecords.deleteRecordRecursively(id); FSRecords.deleteRecordRecursively(id);
final VirtualFile parent = file.getParent(); final VirtualFile parent = file.getParent();
final int parentId = parent != null ? getFileId(parent) : 0; final int parentId = parent == null ? 0 : getFileId(parent);
if (parentId != 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(); myRootsLock.writeLock().lock();
try { try {
myRoots.remove(file.getUrl()); String rootUrl = file.getUrl();
VirtualFileSystemEntry root = myRoots.remove(rootUrl);
myRootsById.remove(id); myRootsById.remove(id);
try {
FSRecords.deleteRootRecord(id); FSRecords.deleteRootRecord(id);
} }
catch (IOException e) {
throw new RuntimeException(e);
}
}
finally { finally {
myRootsLock.writeLock().unlock(); myRootsLock.writeLock().unlock();
} }
} }
else {
removeIdFromParentList(parentId, id, parent, file);
VirtualDirectoryImpl directory = (VirtualDirectoryImpl)file.getParent();
assert directory != null : file;
directory.removeChild(file);
}
invalidateSubtree(file); invalidateSubtree(file);
} }
}
private static void invalidateSubtree(@NotNull VirtualFile file) { private static void invalidateSubtree(@NotNull VirtualFile file) {
final VirtualFileSystemEntry impl = (VirtualFileSystemEntry)file; final VirtualFileSystemEntry impl = (VirtualFileSystemEntry)file;
@@ -1149,7 +1140,7 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone
} }
@Override @Override
public String getName(final int id) { public String getName(int id) {
assert id > 0; assert id > 0;
return FSRecords.getName(id); return FSRecords.getName(id);
} }
@@ -1182,7 +1173,7 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone
private static class JarRoot extends VirtualDirectoryImpl { private static class JarRoot extends VirtualDirectoryImpl {
private final VirtualFile myParentLocalFile; 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); super("", null, fs, rootId, 0);
myParentLocalFile = parentLocalFile; myParentLocalFile = parentLocalFile;
} }
@@ -1194,8 +1185,13 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone
} }
@Override @Override
protected Object rawName() { protected String rawName() {
return myParentLocalFile.getPath() + JarFileSystem.JAR_SEPARATOR; 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.Result;
import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.application.ex.PathManagerEx; import com.intellij.openapi.application.ex.PathManagerEx;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil; 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.PlatformLangTestCase;
import com.intellij.testFramework.PlatformTestUtil; import com.intellij.testFramework.PlatformTestUtil;
import com.intellij.testFramework.UsefulTestCase; import com.intellij.testFramework.UsefulTestCase;
import com.intellij.testFramework.vcs.DirectoryData; import com.intellij.testFramework.vcs.DirectoryData;
import com.intellij.util.Processor; import com.intellij.util.Processor;
import com.intellij.util.ThrowableRunnable;
import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.UIUtil;
import junit.framework.Assert;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.File; import java.io.File;
@@ -37,13 +41,11 @@ import java.net.URI;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; 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 { public class VfsUtilTest extends PlatformLangTestCase {
@Override @Override
protected void runBareRunnable(Runnable runnable) throws Throwable { protected void runBareRunnable(Runnable runnable) throws Throwable {
@@ -81,12 +83,11 @@ public class VfsUtilTest extends PlatformLangTestCase {
} }
public void testFindFileByUrl() throws Exception { public void testFindFileByUrl() throws Exception {
VirtualFile file0;
File file1 = new File(PathManagerEx.getTestDataPath()); File file1 = new File(PathManagerEx.getTestDataPath());
file1 = new File(file1, "vfs"); file1 = new File(file1, "vfs");
file1 = new File(file1, "findFileByUrl"); file1 = new File(file1, "findFileByUrl");
file0 = VfsUtil.findFileByURL(file1.toURI().toURL()); VirtualFile file0 = VfsUtil.findFileByURL(file1.toURI().toURL());
assertNotNull(file0); assertNotNull(file0);
assertTrue(file0.isDirectory()); assertTrue(file0.isDirectory());
final VirtualFile[] children = file0.getChildren(); final VirtualFile[] children = file0.getChildren();
@@ -154,7 +155,7 @@ public class VfsUtilTest extends PlatformLangTestCase {
VfsUtilCore.visitChildrenRecursively(data.getBase(), new VirtualFileVisitor() { VfsUtilCore.visitChildrenRecursively(data.getBase(), new VirtualFileVisitor() {
@Override @Override
public boolean visitFile(@NotNull VirtualFile file) { public boolean visitFile(@NotNull VirtualFile file) {
Assert.assertTrue(!VfsUtilCore.isAncestor(vSubDir, file, true)); assertTrue(!VfsUtilCore.isAncestor(vSubDir, file, true));
return !vSubDir.equals(file); return !vSubDir.equals(file);
} }
}); });
@@ -215,7 +216,7 @@ public class VfsUtilTest extends PlatformLangTestCase {
assertEquals(timestamp[i], file.lastModified()); assertEquals(timestamp[i], file.lastModified());
VirtualFile child = fs.findFileByIoFile(file); VirtualFile child = fs.findFileByIoFile(file);
assertNotNull(child); assertNotNull(child);
assertTimestampsEqual(timestamp[i], child.getTimeStamp()); IoTestUtil.assertTimestampsEqual(timestamp[i], child.getTimeStamp());
} }
for (int i = 0; i < N; i++) { for (int i = 0; i < N; i++) {
@@ -225,7 +226,7 @@ public class VfsUtilTest extends PlatformLangTestCase {
long modified = file.lastModified(); long modified = file.lastModified();
assertTrue("File:" + file.getPath() + "; time:" + modified, timestamp[i] != modified); assertTrue("File:" + file.getPath() + "; time:" + modified, timestamp[i] != modified);
timestamp[i] = modified; timestamp[i] = modified;
assertTimestampsNotEqual(children[i].getTimeStamp(), modified); IoTestUtil.assertTimestampsNotEqual(children[i].getTimeStamp(), modified);
} }
final CountDownLatch latch = new CountDownLatch(N); final CountDownLatch latch = new CountDownLatch(N);
@@ -245,7 +246,7 @@ public class VfsUtilTest extends PlatformLangTestCase {
for (int i = 0; i < N; i++) { for (int i = 0; i < N; i++) {
VirtualFile child = children[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("http://developer.android.com/guide/developing/tools/avd.html?f=23r2ewd", uri.toString());
assertEquals("f=23r2ewd", uri.getQuery()); 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; package com.intellij.openapi.roots.impl;
import com.intellij.openapi.Disposable; import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.fileTypes.FileTypeRegistry; 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.project.ProjectBundle;
import com.intellij.openapi.roots.*; import com.intellij.openapi.roots.*;
import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.*;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.*;
import com.intellij.openapi.vfs.VirtualFileVisitor; import com.intellij.util.containers.ContainerUtil;
import com.intellij.openapi.vfs.VirtualFileWithId;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.MultiMap; import com.intellij.util.containers.MultiMap;
import com.intellij.util.containers.Stack; import com.intellij.util.containers.Stack;
import gnu.trove.*; import gnu.trove.*;
@@ -44,15 +45,17 @@ import org.jetbrains.annotations.TestOnly;
import java.util.*; 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 Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.DirectoryIndexImpl");
private static final boolean CHECK = ApplicationManager.getApplication().isUnitTestMode();
protected final Project myProject; protected final Project myProject;
protected final DirectoryIndexExcludePolicy[] myExcludePolicies; protected final DirectoryIndexExcludePolicy[] myExcludePolicies;
protected volatile IndexState myState; protected volatile IndexState myState;
private boolean myInitialized = false; private volatile boolean myInitialized = false;
private boolean myDisposed = false; private volatile boolean myDisposed = false;
private final PackageSink mySink = new PackageSink();
public DirectoryIndexImpl(@NotNull Project project) { public DirectoryIndexImpl(@NotNull Project project) {
myProject = 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 @Override
@TestOnly @TestOnly
public void checkConsistency() { public void checkConsistency() {
@@ -73,21 +133,27 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex {
doCheckConsistency(true); doCheckConsistency(true);
} }
@TestOnly
public void assertAncestorConsistent() {
myState.assertAncestorsConsistent();
}
@TestOnly @TestOnly
private void doCheckConsistency(boolean reverseAllSets) { private void doCheckConsistency(boolean reverseAllSets) {
assert myInitialized; assert myInitialized;
assert !myDisposed; assert !myDisposed;
final IndexState oldState = myState; final IndexState oldState = myState;
myState = myState.copy(); myState.assertAncestorsConsistent();
myState = myState.copy(null);
myState.doInitialize(reverseAllSets); myState.doInitialize(reverseAllSets);
int[] keySet = myState.myDirToInfoMap.keys(); int[] keySet = myState.myDirToInfoMap.keys();
assert keySet.length == oldState.myDirToInfoMap.keys().length; assert keySet.length == oldState.myDirToInfoMap.keys().length;
for (int file : keySet) { for (int file : keySet) {
DirectoryInfo info1 = myState.myDirToInfoMap.get(file); DirectoryInfo info1 = myState.getInfo(file);
DirectoryInfo info2 = oldState.myDirToInfoMap.get(file); DirectoryInfo info2 = oldState.getInfo(file);
assert info1.equals(info2); assert info1.equals(info2);
info1.assertConsistency(); info1.assertConsistency();
} }
@@ -99,10 +165,8 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex {
int[] dirs = oldState.getDirsForPackage(packageName); int[] dirs = oldState.getDirsForPackage(packageName);
int[] dirs1 = myState.getDirsForPackage(packageName); int[] dirs1 = myState.getDirsForPackage(packageName);
TIntHashSet set1 = new TIntHashSet(); TIntHashSet set1 = new TIntHashSet(dirs);
set1.addAll(dirs); TIntHashSet set2 = new TIntHashSet(dirs1);
TIntHashSet set2 = new TIntHashSet();
set2.addAll(dirs1);
assert set1.equals(set2); assert set1.equals(set2);
return true; return true;
} }
@@ -164,7 +228,7 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex {
dispatchPendingEvents(); dispatchPendingEvents();
if (!(dir instanceof VirtualFileWithId)) return null; if (!(dir instanceof VirtualFileWithId)) return null;
return myState.myDirToInfoMap.get(getId(dir)); return myState.getInfo(getId(dir));
} }
@Override @Override
@@ -173,6 +237,10 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex {
return dir instanceof VirtualFileWithId && myState.myProjectExcludeRoots.contains(getId(dir)); return dir instanceof VirtualFileWithId && myState.myProjectExcludeRoots.contains(getId(dir));
} }
private static VirtualFile findFileById(int dir) {
return FileSystemPersistenceHolder.persistence.findFileById(dir);
}
@Override @Override
public String getPackageName(@NotNull VirtualFile dir) { public String getPackageName(@NotNull VirtualFile dir) {
checkAvailability(); checkAvailability();
@@ -183,10 +251,11 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex {
protected void dispatchPendingEvents() { protected void dispatchPendingEvents() {
} }
protected void checkAvailability() { private void checkAvailability() {
if (!myInitialized) { if (!myInitialized) {
LOG.error("Directory index is not initialized yet for " + myProject); LOG.error("Directory index is not initialized yet for " + myProject);
} }
if (myDisposed) { if (myDisposed) {
LOG.error("Directory index is already disposed for " + myProject); 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; return parentPackageName.isEmpty() ? subdirName : parentPackageName + "." + subdirName;
} }
protected class IndexState { class IndexState {
final TIntObjectHashMap<Set<String>> myExcludeRootsMap = new TIntObjectHashMap<Set<String>>(); final TIntObjectHashMap<Set<String>> myExcludeRootsMap = new TIntObjectHashMap<Set<String>>();
final TIntHashSet myProjectExcludeRoots = new TIntHashSet(); final TIntHashSet myProjectExcludeRoots = new TIntHashSet();
final TIntObjectHashMap<DirectoryInfo> myDirToInfoMap = new TIntObjectHashMap<DirectoryInfo>(); 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 List<int[]> multiDirPackages = new ArrayList<int[]>(Arrays.asList(new int[]{-1}));
final TIntObjectHashMap<String> myDirToPackageName = new TIntObjectHashMap<String>(); final TIntObjectHashMap<String> myDirToPackageName = new TIntObjectHashMap<String>();
public IndexState() { } private IndexState() {
}
@Nullable @Nullable
public int[] getDirsForPackage(String packageName) { private int[] getDirsForPackage(@NotNull String packageName) {
int i = myPackageNameToDirsMap.get(packageName); int i = myPackageNameToDirsMap.get(packageName);
return i == 0 ? null : i > 0 ? new int[]{i} : multiDirPackages.get(-i); return i == 0 ? null : i > 0 ? new int[]{i} : multiDirPackages.get(-i);
} }
private void removeDirFromPackage(@NotNull String packageName, int dirId) { private void removeDirFromPackage(@NotNull String packageName, int dirId) {
int i = myPackageNameToDirsMap.get(packageName); int i = myPackageNameToDirsMap.get(packageName);
assert i != 0; int[] oldPackageDirs = i == 0 ? null : i > 0 ? new int[]{i} : multiDirPackages.get(-i);
int[] oldPackageDirs = i > 0 ? new int[]{i} : multiDirPackages.get(-i);
int index = ArrayUtil.find(oldPackageDirs, dirId); int index = ArrayUtil.find(oldPackageDirs, dirId);
assert index != -1; assert index != -1;
oldPackageDirs = ArrayUtil.remove(oldPackageDirs, index); oldPackageDirs = ArrayUtil.remove(oldPackageDirs, index);
@@ -229,7 +298,7 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex {
} }
} }
else { else {
assert i < 0; assert i < 0 : i;
multiDirPackages.set(-i, oldPackageDirs); multiDirPackages.set(-i, oldPackageDirs);
} }
} }
@@ -257,17 +326,56 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex {
@NotNull @NotNull
private DirectoryInfo getOrCreateDirInfo(int dirId) { private DirectoryInfo getOrCreateDirInfo(int dirId) {
DirectoryInfo info = myDirToInfoMap.get(dirId); DirectoryInfo info = getInfo(dirId);
if (info == null) { if (info == null) {
info = new DirectoryInfo(null, null,null,null,(byte)0,null); info = DirectoryInfo.createNew();
myDirToInfoMap.put(dirId, info); storeInfo(info, dirId);
} }
return info; return info;
} }
private DirectoryInfo storeInfo(DirectoryInfo info, int dirId) { @Nullable
myDirToInfoMap.put(dirId, info); DirectoryInfo getInfo(int fileId) {
return info; 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, void fillMapWithModuleContent(@NotNull VirtualFile root,
@@ -275,6 +383,9 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex {
final VirtualFile contentRoot, final VirtualFile contentRoot,
@Nullable final ProgressIndicator progress) { @Nullable final ProgressIndicator progress) {
final int contentRootId = contentRoot == null ? 0 : getId(contentRoot); 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() { VfsUtilCore.visitChildrenRecursively(root, new DirectoryVisitor() {
@Override @Override
protected DirectoryInfo updateInfo(@NotNull VirtualFile file) { protected DirectoryInfo updateInfo(@NotNull VirtualFile file) {
@@ -288,7 +399,7 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex {
if (info.getModule() != null) { // module contents overlap if (info.getModule() != null) { // module contents overlap
VirtualFile dir = file.getParent(); 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; if (parentInfo == null || !info.getModule().equals(parentInfo.getModule())) return null;
} }
@@ -297,12 +408,34 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex {
@Override @Override
protected void afterChildrenVisited(@NotNull VirtualFile file, @NotNull DirectoryInfo info) { protected void afterChildrenVisited(@NotNull VirtualFile file, @NotNull DirectoryInfo info) {
info = info.withModule(module).withContentRoot(contentRoot); with(getId(file), info, module, contentRoot, null, null, 0, null);
storeInfo(info, getId(file));
} }
}); });
} }
@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 abstract class DirectoryVisitor extends VirtualFileVisitor {
private final Stack<DirectoryInfo> myDirectoryInfoStack = new Stack<DirectoryInfo>(); private final Stack<DirectoryInfo> myDirectoryInfoStack = new Stack<DirectoryInfo>();
@@ -360,6 +493,7 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex {
} }
for (ContentEntry contentEntry : contentEntries) { for (ContentEntry contentEntry : contentEntries) {
VirtualFile contentRoot = contentEntry.getFile();
SourceFolder[] sourceFolders = contentEntry.getSourceFolders(); SourceFolder[] sourceFolders = contentEntry.getSourceFolders();
if (reverseAllSets) { if (reverseAllSets) {
sourceFolders = ArrayUtil.reverseArray(sourceFolders); sourceFolders = ArrayUtil.reverseArray(sourceFolders);
@@ -367,18 +501,20 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex {
for (SourceFolder sourceFolder : sourceFolders) { for (SourceFolder sourceFolder : sourceFolders) {
VirtualFile dir = sourceFolder.getFile(); VirtualFile dir = sourceFolder.getFile();
if (dir != null) { 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, protected void fillMapWithModuleSource(@NotNull final Module module,
@NotNull final Module module, @NotNull final VirtualFile contentRoot,
@NotNull final VirtualFile dir,
@NotNull final String packageName, @NotNull final String packageName,
@NotNull final VirtualFile sourceRoot, @NotNull final VirtualFile sourceRoot,
final boolean isTestSource, final boolean isTestSource,
@Nullable final ProgressIndicator progress) { @Nullable final ProgressIndicator progress) {
assert VfsUtilCore.isAncestor(sourceRoot, dir, false) : "SourceRoot: "+sourceRoot+" ("+sourceRoot.getFileSystem()+"); dir: "+dir+" ("+dir.getFileSystem()+")";
VfsUtilCore.visitChildrenRecursively(dir, new DirectoryVisitor() { VfsUtilCore.visitChildrenRecursively(dir, new DirectoryVisitor() {
private final Stack<String> myPackages = new Stack<String>(); private final Stack<String> myPackages = new Stack<String>();
@@ -388,19 +524,22 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex {
progress.checkCanceled(); progress.checkCanceled();
} }
int id = getId(file); int id = getId(file);
DirectoryInfo info = myDirToInfoMap.get(id); DirectoryInfo info = getInfo(id);
if (info == null) return null; if (info == null) return null;
if (!module.equals(info.getModule())) return null; if (!module.equals(info.getModule())) return null;
if (!contentRoot.equals(info.getContentRoot())) return null;
if (info.isInModuleSource()) { // module sources overlap if (info.isInModuleSource()) { // module sources overlap
String definedPackage = myDirToPackageName.get(id); String definedPackage = myDirToPackageName.get(id);
if (definedPackage != null && definedPackage.isEmpty()) return null; // another source root starts here if (definedPackage != null && definedPackage.isEmpty()) return null; // another source root starts here
} }
info = info.withInModuleSource(true)
.withTestSource(isTestSource) assert VfsUtilCore.isAncestor(dir, file, false) : "dir: "+dir+" ("+dir.getFileSystem()+"); file: "+file+" ("+file.getFileSystem()+")";
.withSourceRoot(sourceRoot);
storeInfo(info, id); 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()); String currentPackage = myPackages.isEmpty() ? packageName : getPackageNameForSubdir(myPackages.peek(), file.getName());
myPackages.push(currentPackage); 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 if (definedPackage != null && definedPackage.isEmpty()) return false; // another library source root starts here
} }
info = info.withInLibrarySource(true) int flag = info.getSourceFlag() | DirectoryInfo.LIBRARY_SOURCE_FLAG;
.withSourceRoot(sourceRoot); with(dirId, info, null, null, sourceRoot, null, (byte)flag, null);
storeInfo(info, dirId);
final String packageName = getCurrentValue(); final String packageName = getCurrentValue();
final String newPackageName = Comparing.equal(file, dir) ? packageName : getPackageNameForSubdir(packageName, file.getName()); 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 if (definedPackage != null && definedPackage.isEmpty()) return false; // another library root starts here
} }
info = info.withLibraryClassRoot(classRoot); info = with(dirId, info, null, null, null, classRoot, 0, null);
storeInfo(info, dirId);
final String packageName = getCurrentValue(); final String packageName = getCurrentValue();
final String childPackageName = Comparing.equal(file, dir) ? packageName : getPackageNameForSubdir(packageName, file.getName()); 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); String oldPackageName = myDirToPackageName.get(dirId);
if (oldPackageName != null) { if (oldPackageName != null) {
removeDirFromPackage(oldPackageName, dirId); removeDirFromPackage(oldPackageName, dirId);
} }
if (newPackageName != null) { if (newPackageName == null) {
myDirToPackageName.remove(dirId);
}
else {
addDirToPackage(newPackageName, dirId); addDirToPackage(newPackageName, dirId);
myDirToPackageName.put(dirId, newPackageName); myDirToPackageName.put(dirId, newPackageName);
} }
else {
myDirToPackageName.remove(dirId);
}
} }
// orderEntries must be sorted BY_OWNER_MODULE // orderEntries must be sorted BY_OWNER_MODULE
@@ -613,7 +750,7 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex {
if (isIgnored(dir)) return null; if (isIgnored(dir)) return null;
int dirId = getId(dir); 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 (info == null) return null;
if (module != null) { if (module != null) {
@@ -633,8 +770,10 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex {
OrderEntry[] oldParentEntries = myEntries.isEmpty() ? null : myEntries.peek(); OrderEntry[] oldParentEntries = myEntries.isEmpty() ? null : myEntries.peek();
OrderEntry[] oldEntries = info.getOrderEntries(); OrderEntry[] oldEntries = info.getOrderEntries();
myEntries.push(oldEntries); 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; return info;
} }
@@ -646,6 +785,7 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex {
} }
protected void doInitialize(boolean reverseAllSets/* for testing order independence*/) { protected void doInitialize(boolean reverseAllSets/* for testing order independence*/) {
assertAncestorsConsistent();
ProgressIndicator progress = ProgressIndicatorProvider.getGlobalProgressIndicator(); ProgressIndicator progress = ProgressIndicatorProvider.getGlobalProgressIndicator();
if (progress == null) progress = new EmptyProgressIndicator(); if (progress == null) progress = new EmptyProgressIndicator();
@@ -674,6 +814,7 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex {
progress.checkCanceled(); progress.checkCanceled();
progress.setText2(""); progress.setText2("");
assertAncestorsConsistent();
MultiMap<VirtualFile, OrderEntry> depEntries = new MultiMap<VirtualFile, OrderEntry>(); MultiMap<VirtualFile, OrderEntry> depEntries = new MultiMap<VirtualFile, OrderEntry>();
MultiMap<VirtualFile, OrderEntry> libClassRootEntries = new MultiMap<VirtualFile, OrderEntry>(); MultiMap<VirtualFile, OrderEntry> libClassRootEntries = new MultiMap<VirtualFile, OrderEntry>();
MultiMap<VirtualFile, OrderEntry> libSourceRootEntries = 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>() { myDirToInfoMap.transformValues(new TObjectFunction<DirectoryInfo, DirectoryInfo>() {
@Override @Override
public DirectoryInfo execute(DirectoryInfo info) { public DirectoryInfo execute(DirectoryInfo info) {
@@ -717,9 +859,10 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex {
return interned; return interned;
} }
}); });
assertAncestorsConsistent();
} }
private void initExcludedDirMap(Module[] modules, ProgressIndicator progress) { private void initExcludedDirMap(@NotNull Module[] modules, ProgressIndicator progress) {
progress.checkCanceled(); progress.checkCanceled();
progress.setText2(ProjectBundle.message("project.index.building.exclude.roots.progress")); progress.setText2(ProjectBundle.message("project.index.building.exclude.roots.progress"));
@@ -734,11 +877,13 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex {
ExcludeFolder[] excludeRoots = contentEntry.getExcludeFolders(); ExcludeFolder[] excludeRoots = contentEntry.getExcludeFolders();
for (ExcludeFolder excludeRoot : excludeRoots) { for (ExcludeFolder excludeRoot : excludeRoots) {
// Output paths should be excluded (if marked as such) regardless if they're under corresponding module's content root // 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 (!FileUtil.startsWith(contentRoot.getUrl(), excludeRoot.getUrl())) {
if (isExcludeRootForModule(module, excludeRoot.getFile())) { if (isExcludeRootForModule(module, excludeRootFile)) {
putForFileAndAllAncestors(excludeRoot.getFile(), excludeRoot.getUrl()); putForFileAndAllAncestors(excludeRootFile, excludeRoot.getUrl());
} }
myProjectExcludeRoots.add(getId(excludeRootFile));
} }
} }
@@ -771,13 +916,15 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex {
} }
@NotNull @NotNull
public IndexState copy() { IndexState copy(@Nullable final TIntProcedure idFilter) {
final IndexState copy = new IndexState(); final IndexState copy = new IndexState();
myExcludeRootsMap.forEachEntry(new TIntObjectProcedure<Set<String>>() { myExcludeRootsMap.forEachEntry(new TIntObjectProcedure<Set<String>>() {
@Override @Override
public boolean execute(int id, Set<String> urls) { public boolean execute(int id, Set<String> urls) {
if (idFilter == null || idFilter.execute(id)) {
copy.myExcludeRootsMap.put(id, new THashSet<String>(urls)); copy.myExcludeRootsMap.put(id, new THashSet<String>(urls));
}
return true; return true;
} }
}); });
@@ -786,26 +933,46 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex {
myDirToInfoMap.forEachEntry(new TIntObjectProcedure<DirectoryInfo>() { myDirToInfoMap.forEachEntry(new TIntObjectProcedure<DirectoryInfo>() {
@Override @Override
public boolean execute(int id, DirectoryInfo info) { public boolean execute(int id, DirectoryInfo info) {
copy.myDirToInfoMap.put(id, info); if (idFilter == null || idFilter.execute(id)) {
copy.storeInfo(info, id);
}
return true; 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>() { myPackageNameToDirsMap.forEachEntry(new TObjectIntProcedure<String>() {
@Override @Override
public boolean execute(String name, int id) { public boolean execute(String name, int 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); copy.myPackageNameToDirsMap.put(name, id);
}
return true; return true;
} }
}); });
copy.multiDirPackages.clear();
copy.multiDirPackages.addAll(multiDirPackages);
myDirToPackageName.forEachEntry(new TIntObjectProcedure<String>() { myDirToPackageName.forEachEntry(new TIntObjectProcedure<String>() {
@Override @Override
public boolean execute(int id, String name) { public boolean execute(int id, String name) {
if (idFilter == null || idFilter.execute(id)) {
copy.myDirToPackageName.put(id, name); copy.myDirToPackageName.put(id, name);
}
return true; return true;
} }
}); });
@@ -824,7 +991,19 @@ public abstract class DirectoryIndexImpl extends DirectoryIndex {
return result; return result;
} }
static int getId(@NotNull VirtualFile classRoot) { static int getId(@NotNull VirtualFile file) {
return ((VirtualFileWithId)classRoot).getId(); 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.OrderEntry;
import com.intellij.openapi.roots.OrderRootType; import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.roots.RootPolicy; import com.intellij.openapi.roots.RootPolicy;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ArrayFactory; import com.intellij.util.ArrayFactory;
import com.intellij.util.ArrayUtil; import com.intellij.util.ArrayUtil;
@@ -41,11 +42,14 @@ public final class DirectoryInfo {
private final VirtualFile contentRoot; private final VirtualFile contentRoot;
private final VirtualFile sourceRoot; private final VirtualFile sourceRoot;
private static final byte TEST_SOURCE_FLAG = 1; // (makes sense only if MODULE_SOURCE_FLAG is set) public 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 public 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 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}) @MagicConstant(flags = {TEST_SOURCE_FLAG, LIBRARY_SOURCE_FLAG, MODULE_SOURCE_FLAG})
public @interface SourceFlag {}
@SourceFlag
private final byte sourceFlag; private final byte sourceFlag;
/** /**
@@ -54,11 +58,14 @@ public final class DirectoryInfo {
*/ */
private final OrderEntry[] orderEntries; private final OrderEntry[] orderEntries;
public DirectoryInfo(Module module, static DirectoryInfo createNew() {
return new DirectoryInfo(null, null, null, null, (byte)0, null);
}
private DirectoryInfo(Module module,
VirtualFile contentRoot, VirtualFile contentRoot,
VirtualFile sourceRoot, VirtualFile sourceRoot,
VirtualFile libraryClassRoot, VirtualFile libraryClassRoot,
@MagicConstant(flags = {TEST_SOURCE_FLAG, LIBRARY_SOURCE_FLAG, MODULE_SOURCE_FLAG}) byte sourceFlag, @SourceFlag byte sourceFlag,
OrderEntry[] orderEntries) { OrderEntry[] orderEntries) {
this.module = module; this.module = module;
this.libraryClassRoot = libraryClassRoot; this.libraryClassRoot = libraryClassRoot;
@@ -75,14 +82,12 @@ public final class DirectoryInfo {
DirectoryInfo info = (DirectoryInfo)o; DirectoryInfo info = (DirectoryInfo)o;
if (sourceFlag != info.sourceFlag) return false; return sourceFlag == info.sourceFlag &&
if (contentRoot != null ? !contentRoot.equals(info.contentRoot) : info.contentRoot != null) return false; Comparing.equal(contentRoot, info.contentRoot) &&
if (libraryClassRoot != null ? !libraryClassRoot.equals(info.libraryClassRoot) : info.libraryClassRoot != null) return false; Comparing.equal(libraryClassRoot, info.libraryClassRoot) &&
if (module != null ? !module.equals(info.module) : info.module != null) return false; Comparing.equal(module, info.module) &&
if (!Arrays.equals(orderEntries, info.orderEntries)) return false; Arrays.equals(orderEntries, info.orderEntries) &&
if (sourceRoot != null ? !sourceRoot.equals(info.sourceRoot) : info.sourceRoot != null) return false; Comparing.equal(sourceRoot, info.sourceRoot);
return true;
} }
@Override @Override
@@ -209,10 +214,7 @@ public final class DirectoryInfo {
} }
// orderEntries must be sorted BY_OWNER_MODULE // orderEntries must be sorted BY_OWNER_MODULE
@NotNull OrderEntry[] calcNewOrderEntries(@NotNull OrderEntry[] orderEntries, @Nullable DirectoryInfo parentInfo, @Nullable OrderEntry[] oldParentEntries) {
public DirectoryInfo withOrderEntries(@NotNull OrderEntry[] orderEntries,
@Nullable final DirectoryInfo parentInfo,
@Nullable final OrderEntry[] oldParentEntries) {
OrderEntry[] newOrderEntries; OrderEntry[] newOrderEntries;
if (orderEntries.length == 0) { if (orderEntries.length == 0) {
newOrderEntries = null; newOrderEntries = null;
@@ -226,8 +228,7 @@ public final class DirectoryInfo {
else { else {
newOrderEntries = mergeWith(orderEntries); newOrderEntries = mergeWith(orderEntries);
} }
return newOrderEntries;
return new DirectoryInfo(module, contentRoot, sourceRoot, libraryClassRoot, sourceFlag, newOrderEntries);
} }
// entries must be sorted BY_OWNER_MODULE // entries must be sorted BY_OWNER_MODULE
@@ -293,54 +294,32 @@ public final class DirectoryInfo {
return BitUtil.isSet(sourceFlag, MODULE_SOURCE_FLAG); 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() { public boolean isTestSource() {
return BitUtil.isSet(sourceFlag, TEST_SOURCE_FLAG); 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() { public boolean isInLibrarySource() {
return BitUtil.isSet(sourceFlag, LIBRARY_SOURCE_FLAG); 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() { public Module getModule() {
return module; return module;
} }
@NotNull private static <T> T iff(T value, T defaultValue) {
public DirectoryInfo withModule(Module module) { return value == null ? defaultValue : value;
return new DirectoryInfo(module, contentRoot, sourceRoot, libraryClassRoot, sourceFlag, orderEntries);
} }
@NotNull @NotNull
public DirectoryInfo withLibraryClassRoot(@NotNull VirtualFile libraryClassRoot) { public DirectoryInfo with(Module module,
return new DirectoryInfo(module, contentRoot, sourceRoot, libraryClassRoot, sourceFlag, orderEntries); VirtualFile contentRoot,
} VirtualFile sourceRoot,
VirtualFile libraryClassRoot,
@NotNull @SourceFlag byte sourceFlag,
public DirectoryInfo withContentRoot(VirtualFile contentRoot) { OrderEntry[] orderEntries) {
return new DirectoryInfo(module, contentRoot, sourceRoot, libraryClassRoot, sourceFlag, 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
public DirectoryInfo withSourceRoot(@NotNull VirtualFile sourceRoot) {
return new DirectoryInfo(module, contentRoot, sourceRoot, libraryClassRoot, sourceFlag, orderEntries);
} }
@NotNull @NotNull
@@ -355,4 +334,9 @@ public final class DirectoryInfo {
assert BY_OWNER_MODULE.compare(entries[i-1], entries[i]) <= 0; 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.project.impl.ProjectManagerImpl;
import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.*; 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.startup.StartupManager;
import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Disposer;
@@ -547,6 +549,8 @@ public abstract class LightPlatformTestCase extends UsefulTestCase implements Da
if (manager instanceof FileDocumentManagerImpl) { if (manager instanceof FileDocumentManagerImpl) {
((FileDocumentManagerImpl)manager).dropAllUnsavedDocuments(); ((FileDocumentManagerImpl)manager).dropAllUnsavedDocuments();
} }
((DirectoryIndexImpl)DirectoryIndex.getInstance(project)).assertAncestorConsistent();
} }
}.execute().throwException(); }.execute().throwException();
@@ -34,6 +34,8 @@ import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ex.ProjectManagerEx; import com.intellij.openapi.project.ex.ProjectManagerEx;
import com.intellij.openapi.roots.ProjectRootManager; 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.startup.StartupManager;
import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Disposer; 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.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.encoding.EncodingManager; import com.intellij.openapi.vfs.encoding.EncodingManager;
import com.intellij.psi.*; import com.intellij.psi.PsiDirectory;
import com.intellij.psi.impl.PsiDocumentManagerImpl; 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.psi.impl.source.tree.injected.InjectedLanguageManagerImpl;
import com.intellij.testFramework.EditorListenerTracker; import com.intellij.testFramework.EditorListenerTracker;
import com.intellij.testFramework.LightPlatformTestCase; import com.intellij.testFramework.LightPlatformTestCase;
@@ -111,6 +115,7 @@ class HeavyIdeaTestFixtureImpl extends BaseFixture implements HeavyIdeaTestFixtu
for (ModuleFixtureBuilder moduleFixtureBuilder : myModuleFixtureBuilders) { for (ModuleFixtureBuilder moduleFixtureBuilder : myModuleFixtureBuilders) {
moduleFixtureBuilder.getFixture().tearDown(); moduleFixtureBuilder.getFixture().tearDown();
} }
((DirectoryIndexImpl)DirectoryIndex.getInstance(getProject())).assertAncestorConsistent();
UIUtil.invokeAndWaitIfNeeded(new Runnable() { UIUtil.invokeAndWaitIfNeeded(new Runnable() {
@Override @Override
@@ -21,7 +21,8 @@ import com.intellij.codeInspection.ex.InspectionTool;
import com.intellij.idea.IdeaTestApplication; import com.intellij.idea.IdeaTestApplication;
import com.intellij.openapi.module.Module; import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project; 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.CodeStyleSchemes;
import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
@@ -58,11 +59,11 @@ class LightIdeaTestFixtureImpl extends BaseFixture implements LightIdeaTestFixtu
Project project = getProject(); Project project = getProject();
CodeStyleSettingsManager.getInstance(project).dropTemporarySettings(); CodeStyleSettingsManager.getInstance(project).dropTemporarySettings();
checkForSettingsDamage(); checkForSettingsDamage();
PersistentFS.getInstance().clearIdCache();
LightPlatformTestCase.doTearDown(project, LightPlatformTestCase.getApplication(), true); LightPlatformTestCase.doTearDown(project, LightPlatformTestCase.getApplication(), true);
super.tearDown(); super.tearDown();
InjectedLanguageManagerImpl.checkInjectorsAreDisposed(project); InjectedLanguageManagerImpl.checkInjectorsAreDisposed(project);
((DirectoryIndexImpl)DirectoryIndex.getInstance(project)).assertAncestorConsistent();
} }
@@ -2203,9 +2203,9 @@ public class StringUtil extends StringUtilRt {
else { else {
// similar logic to charsMatch() below // similar logic to charsMatch() below
if (ch1 != ch2) { if (ch1 != ch2) {
final int diff1 = Character.toUpperCase(ch1) - Character.toUpperCase(ch2); final int diff1 = StringUtilRt.toUpperCase(ch1) - StringUtilRt.toUpperCase(ch2);
if (diff1 != 0) { if (diff1 != 0) {
final int diff2 = Character.toLowerCase(ch1) - Character.toLowerCase(ch2); final int diff2 = StringUtilRt.toLowerCase(ch1) - StringUtilRt.toLowerCase(ch2);
if (diff2 != 0) { if (diff2 != 0) {
return diff2; return diff2;
} }
@@ -2281,30 +2281,31 @@ public class StringUtil extends StringUtilRt {
return true; 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 // duplicating String.equalsIgnoreCase logic
if (c1 == c2) { int d = c1 - c2;
return true; if (d == 0 || !ignoreCase) {
return d;
} }
if (ignoreCase) {
// If characters don't match but case may be ignored, // If characters don't match but case may be ignored,
// try converting both characters to uppercase. // try converting both characters to uppercase.
// If the results match, then the comparison scan should // If the results match, then the comparison scan should
// continue. // continue.
char u1 = Character.toUpperCase(c1); char u1 = StringUtilRt.toUpperCase(c1);
char u2 = Character.toUpperCase(c2); char u2 = StringUtilRt.toUpperCase(c2);
if (u1 == u2) { d = u1 - u2;
return true; if (d != 0) {
}
// Unfortunately, conversion to uppercase does not work properly // Unfortunately, conversion to uppercase does not work properly
// for the Georgian alphabet, which has strange rules about case // for the Georgian alphabet, which has strange rules about case
// conversion. So we need to make one last check before // conversion. So we need to make one last check before
// exiting. // exiting.
if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) { d = StringUtilRt.toLowerCase(u1) - StringUtilRt.toLowerCase(u2);
return true;
} }
return d;
} }
return false;
public static boolean charsMatch(char c1, char c2, boolean ignoreCase) {
return compare(c1,c2,ignoreCase) == 0;
} }
public static String formatLinks(String message) { public static String formatLinks(String message) {