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