@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, @NotNull Comparator comparator) { if (name.isEmpty()) { return null; } VirtualFileSystemEntry found = doFindChildInArray(name, comparator); if (found != null) return found; if (allChildrenLoaded()) { return NULL_VIRTUAL_FILE; } if (ensureCanonicalName) { VirtualFile fake = new FakeVirtualFile(this, name); name = delegate.getCanonicallyCasedName(fake); if (name.isEmpty()) return null; } synchronized (this) { // maybe another doFindChild() sneaked in the middle VirtualFileSystemEntry[] array = myChildren; long r = findIndexInBoth(array, name, comparator); int indexInReal = (int) (r >> 32); int indexInAdopted = (int) r; if (indexInAdopted >= 0) return NULL_VIRTUAL_FILE; // double check if (indexInReal >= 0) { return array[indexInReal]; } // 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) { return null; } VirtualFileSystemEntry child = createChild(FileNameCache.storeName(name), id, delegate); VirtualFileSystemEntry[] after = myChildren; if (after != array) { // in tests when we call assertAccessInTests it can load a huge number of files which lead // to children modification // so fall back to slow path addChild(child); } else { insertChildAt(child, indexInReal); assertConsistency(myChildren, !delegate.isCaseSensitive(), name); } return child; } }
@NotNull 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 && current.length > 0) { return current; } Set<String> toAdd = ContainerUtil.newHashSet(delegateNames); for (FSRecords.NameId nameId : current) { toAdd.remove(nameId.name.toString()); } final TIntArrayList childrenIds = new TIntArrayList(current.length + toAdd.size()); final List<FSRecords.NameId> nameIds = ContainerUtil.newArrayListWithCapacity(current.length + toAdd.size()); for (FSRecords.NameId nameId : current) { childrenIds.add(nameId.id); nameIds.add(nameId); } for (String newName : toAdd) { FakeVirtualFile child = new FakeVirtualFile(file, newName); FileAttributes attributes = fs.getAttributes(child); if (attributes != null) { int childId = createAndFillRecord(fs, child, id, attributes); childrenIds.add(childId); nameIds.add(new FSRecords.NameId(childId, FileNameCache.storeName(newName), newName)); } } FSRecords.updateList(id, childrenIds.toNativeArray()); setChildrenCached(id); return nameIds.toArray(new FSRecords.NameId[nameIds.size()]); }
/** @author max */ public class VirtualDirectoryImpl extends VirtualFileSystemEntry { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.newvfs.impl.VirtualDirectoryImpl"); public static boolean CHECK = ApplicationManager.getApplication().isUnitTestMode(); static final VirtualDirectoryImpl NULL_VIRTUAL_FILE = new VirtualDirectoryImpl( FileNameCache.storeName("*?;%NULL"), null, LocalFileSystem.getInstance(), -42, 0) { public String toString() { return "NULL"; } }; private final NewVirtualFileSystem myFS; /** * The array is logically divided into the two parts: - left subarray for storing real child files * - right subarray for storing "adopted children" files. "Adopted children" are fake files which * are used for storing names which were accessed via findFileByName() or similar calls. We have * to store these unsuccessful find attempts to be able to correctly refresh in the future. See * usages of {@link #getSuspiciousNames()} in the {@link * com.intellij.openapi.vfs.newvfs.persistent.RefreshWorker} * * <p>Guarded by this, files in each subarray are sorted according to the compareNameTo() * comparator TODO: revise the whole adopted scheme */ private VirtualFileSystemEntry[] myChildren = EMPTY_ARRAY; public VirtualDirectoryImpl( @NonNls final int nameId, @Nullable final VirtualDirectoryImpl parent, @NotNull final NewVirtualFileSystem fs, final int id, @PersistentFS.Attributes final int attributes) { super(nameId, parent, id, attributes); myFS = fs; } @Override @NotNull public NewVirtualFileSystem getFileSystem() { return myFS; } @Nullable private VirtualFileSystemEntry findChild( @NotNull String name, boolean doRefresh, boolean ensureCanonicalName, @NotNull NewVirtualFileSystem delegate) { boolean ignoreCase = !delegate.isCaseSensitive(); Comparator comparator = getComparator(ignoreCase); VirtualFileSystemEntry result = doFindChild(name, ensureCanonicalName, delegate, comparator); if (result == NULL_VIRTUAL_FILE) { result = doRefresh ? createAndFindChildWithEventFire(name, delegate) : null; } else if (result != null && doRefresh && delegate.isDirectory(result) != result.isDirectory()) { RefreshQueue.getInstance().refresh(false, false, null, result); result = findChild(name, false, ensureCanonicalName, delegate); } if (result == null) { addToAdoptedChildren(!delegate.isCaseSensitive(), name, comparator); } return result; } private synchronized void addToAdoptedChildren( final boolean ignoreCase, @NotNull final String name, @NotNull Comparator comparator) { long r = findIndexInBoth(myChildren, name, comparator); int indexInReal = (int) (r >> 32); int indexInAdopted = (int) r; if (indexInAdopted >= 0) return; // already added if (!allChildrenLoaded()) { insertChildAt(new AdoptedChild(name), indexInAdopted); } if (indexInReal >= 0) { // there suddenly can be that we ask to add name to adopted whereas it already contains in the // real part // in this case we should remove it from there removeFromArray(indexInReal); } assertConsistency(myChildren, ignoreCase, name); } private static class AdoptedChild extends VirtualFileImpl { private final String myName; private AdoptedChild(String name) { super(-1, NULL_VIRTUAL_FILE, -42, -1); myName = name; } @NotNull @Override public String getName() { return myName; } @Override public void setNewName(@NotNull String newName) { throw new IncorrectOperationException(); } @Override public int compareNameTo(@NotNull String name, boolean ignoreCase) { return compareNames(myName, name, ignoreCase); } @Override protected char[] appendPathOnFileSystem(int accumulatedPathLength, int[] positionRef) { char[] chars = getParent() .appendPathOnFileSystem(accumulatedPathLength + 1 + myName.length(), positionRef); if (positionRef[0] > 0 && chars[positionRef[0] - 1] != '/') { chars[positionRef[0]++] = '/'; } positionRef[0] = VirtualFileSystemEntry.copyString(chars, positionRef[0], myName); return chars; } } @Nullable // null if there can't be a child with this name, NULL_VIRTUAL_FILE private synchronized VirtualFileSystemEntry doFindChildInArray( @NotNull String name, @NotNull Comparator comparator) { VirtualFileSystemEntry[] array = myChildren; long r = findIndexInBoth(array, name, comparator); int indexInReal = (int) (r >> 32); int indexInAdopted = (int) r; if (indexInAdopted >= 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, @NotNull Comparator comparator) { if (name.isEmpty()) { return null; } VirtualFileSystemEntry found = doFindChildInArray(name, comparator); if (found != null) return found; if (allChildrenLoaded()) { return NULL_VIRTUAL_FILE; } if (ensureCanonicalName) { VirtualFile fake = new FakeVirtualFile(this, name); name = delegate.getCanonicallyCasedName(fake); if (name.isEmpty()) return null; } synchronized (this) { // maybe another doFindChild() sneaked in the middle VirtualFileSystemEntry[] array = myChildren; long r = findIndexInBoth(array, name, comparator); int indexInReal = (int) (r >> 32); int indexInAdopted = (int) r; if (indexInAdopted >= 0) return NULL_VIRTUAL_FILE; // double check if (indexInReal >= 0) { return array[indexInReal]; } // 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) { return null; } VirtualFileSystemEntry child = createChild(FileNameCache.storeName(name), id, delegate); VirtualFileSystemEntry[] after = myChildren; if (after != array) { // in tests when we call assertAccessInTests it can load a huge number of files which lead // to children modification // so fall back to slow path addChild(child); } else { insertChildAt(child, indexInReal); assertConsistency(myChildren, !delegate.isCaseSensitive(), name); } return child; } } private static final Comparator CASE_SENSITIVE = new Comparator() { @Override public int compareFileNameTo(@NotNull String myName, @NotNull VirtualFileSystemEntry file) { return -file.compareNameTo(myName, false); } }; private static final Comparator CASE_INSENSITIVE = new Comparator() { @Override public int compareFileNameTo(@NotNull String myName, @NotNull VirtualFileSystemEntry file) { return -file.compareNameTo(myName, true); } }; @NotNull private static Comparator getComparator(final boolean ignoreCase) { return ignoreCase ? CASE_INSENSITIVE : CASE_SENSITIVE; } private synchronized VirtualFileSystemEntry[] getArraySafely() { return myChildren; } @NotNull public VirtualFileSystemEntry createChild( String name, int id, @NotNull NewVirtualFileSystem delegate) { return createChild(FileNameCache.storeName(name), id, delegate); } @NotNull private VirtualFileSystemEntry createChild( int nameId, int id, @NotNull NewVirtualFileSystem delegate) { VirtualFileSystemEntry child; final int attributes = ourPersistence.getFileAttributes(id); if (PersistentFS.isDirectory(attributes)) { child = new VirtualDirectoryImpl(nameId, this, delegate, id, attributes); } else { child = new VirtualFileImpl(nameId, this, id, attributes); //noinspection TestOnlyProblems assertAccessInTests(child, delegate); } if (delegate.markNewFilesAsDirty()) { child.markDirty(); } return child; } private static final boolean IS_UNDER_TEAMCITY = System.getProperty("bootstrap.testcases") != null; private static final boolean SHOULD_PERFORM_ACCESS_CHECK = System.getenv("NO_FS_ROOTS_ACCESS_CHECK") == null; private static final Collection<String> ourAdditionalRoots = new THashSet<String>(); @TestOnly public static void allowRootAccess(@NotNull String... roots) { for (String root : roots) { ourAdditionalRoots.add(FileUtil.toSystemIndependentName(root)); } } @TestOnly public static void disallowRootAccess(@NotNull String... roots) { for (String root : roots) { ourAdditionalRoots.remove(FileUtil.toSystemIndependentName(root)); } } @TestOnly 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()) { if (delegate != LocalFileSystem.getInstance() && delegate != JarFileSystem.getInstance()) { return; } // root' children are loaded always if (child.getParent() == null || child.getParent().getParent() == null) return; Set<String> allowed = allowedRoots(); boolean isUnder = allowed == null; if (!isUnder) { String childPath = child.getPath(); if (delegate == JarFileSystem.getInstance()) { VirtualFile local = JarFileSystem.getInstance().getVirtualFileForJar(child); assert local != null : child; childPath = local.getPath(); } for (String root : allowed) { if (FileUtil.startsWith(childPath, root)) { isUnder = true; break; } if (root.startsWith(JarFileSystem.PROTOCOL_PREFIX)) { String rootLocalPath = FileUtil.toSystemIndependentName(PathUtil.toPresentableUrl(root)); isUnder = FileUtil.startsWith(childPath, rootLocalPath); if (isUnder) break; } } } assert isUnder || allowed.isEmpty() : "File accessed outside allowed roots: " + child + ";\nAllowed roots: " + new ArrayList<String>(allowed); } } // null means we were unable to get roots, so do not check access @Nullable @TestOnly private static Set<String> allowedRoots() { if (insideGettingRoots) return null; Project[] openProjects = ProjectManager.getInstance().getOpenProjects(); if (openProjects.length == 0) return null; final Set<String> allowed = new THashSet<String>(); allowed.add(FileUtil.toSystemIndependentName(PathManager.getHomePath())); try { URL outUrl = Application.class.getResource("/"); String output = new File(outUrl.toURI()).getParentFile().getParentFile().getPath(); allowed.add(FileUtil.toSystemIndependentName(output)); } catch (URISyntaxException ignored) { } allowed.add(FileUtil.toSystemIndependentName(SystemProperties.getJavaHome())); allowed.add( FileUtil.toSystemIndependentName(new File(FileUtil.getTempDirectory()).getParent())); allowed.add(FileUtil.toSystemIndependentName(System.getProperty("java.io.tmpdir"))); allowed.add(FileUtil.toSystemIndependentName(SystemProperties.getUserHome())); for (Project project : openProjects) { if (!project.isInitialized()) { return null; // all is allowed } for (VirtualFile root : ProjectRootManager.getInstance(project).getContentRoots()) { allowed.add(root.getPath()); } for (VirtualFile root : getAllRoots(project)) { allowed.add(StringUtil.trimEnd(root.getPath(), JarFileSystem.JAR_SEPARATOR)); } String location = project.getBasePath(); assert location != null : project; allowed.add(FileUtil.toSystemIndependentName(location)); } allowed.addAll(ourAdditionalRoots); return allowed; } private static boolean insideGettingRoots; @TestOnly private static VirtualFile[] getAllRoots(@NotNull Project project) { insideGettingRoots = true; final Set<VirtualFile> roots = new THashSet<VirtualFile>(); final OrderEnumerator enumerator = ProjectRootManager.getInstance(project).orderEntries(); ContainerUtil.addAll(roots, enumerator.getClassesRoots()); ContainerUtil.addAll(roots, enumerator.getSourceRoots()); insideGettingRoots = false; return VfsUtilCore.toVirtualFileArray(roots); } @Nullable 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) 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 @Nullable public NewVirtualFile refreshAndFindChild(@NotNull String name) { return findChild(name, true, true, getFileSystem()); } private static int findIndexInOneHalf( final VirtualFileSystemEntry[] array, int start, int end, final boolean isAdopted, @NotNull String name, @NotNull final Comparator comparator) { return binSearch( array, start, end, name, new Comparator() { @Override public int compareFileNameTo( @NotNull String myName, @NotNull VirtualFileSystemEntry file) { if (isAdopted && !isAdoptedChild(file)) return 1; if (!isAdopted && isAdoptedChild(file)) return -1; return comparator.compareFileNameTo(myName, file); } }); } // returns two int indices packed into one long. left index is for the real file array half, right // is for the adopted children name array private static long findIndexInBoth( @NotNull VirtualFileSystemEntry[] array, @NotNull String name, @NotNull Comparator comparator) { int high = array.length - 1; if (high == -1) { return pack(-1, -1); } int low = 0; boolean startInAdopted = isAdoptedChild(array[low]); boolean endInAdopted = isAdoptedChild(array[high]); if (startInAdopted == endInAdopted) { int index = findIndexInOneHalf(array, low, high + 1, startInAdopted, name, comparator); int otherIndex = startInAdopted ? -1 : -array.length - 1; return startInAdopted ? pack(otherIndex, index) : pack(index, otherIndex); } boolean adopted = false; int cmp = -1; int mid = -1; int foundIndex = -1; while (low <= high) { mid = low + high >>> 1; VirtualFileSystemEntry file = array[mid]; cmp = comparator.compareFileNameTo(name, file); adopted = isAdoptedChild(file); if (cmp == 0) { foundIndex = mid; break; } if ((adopted || cmp <= 0) && (!adopted || cmp >= 0)) { int indexInAdopted = findIndexInOneHalf(array, mid + 1, high + 1, true, name, comparator); int indexInReal = findIndexInOneHalf(array, low, mid, false, name, comparator); return pack(indexInReal, indexInAdopted); } if (cmp > 0) { low = mid + 1; } else { high = mid - 1; } } // key not found. if (cmp != 0) foundIndex = -low - 1; int newStart = adopted ? low : mid + 1; int newEnd = adopted ? mid + 1 : high + 1; int theOtherHalfIndex = newStart < newEnd ? findIndexInOneHalf(array, newStart, newEnd, !adopted, name, comparator) : -newStart - 1; return adopted ? pack(theOtherHalfIndex, foundIndex) : pack(foundIndex, theOtherHalfIndex); } private static long pack(int indexInReal, int indexInAdopted) { return (long) indexInReal << 32 | (indexInAdopted & 0xffffffffL); } @Override @Nullable public synchronized NewVirtualFile findChildIfCached(@NotNull String name) { final boolean ignoreCase = !getFileSystem().isCaseSensitive(); Comparator comparator = getComparator(ignoreCase); VirtualFileSystemEntry found = doFindChildInArray(name, comparator); return found == NULL_VIRTUAL_FILE ? null : found; } @Override @NotNull public Iterable<VirtualFile> iterInDbChildren() { if (!ourPersistence.wereChildrenAccessed(this)) { return Collections.emptyList(); } 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); } } return getCachedChildren(); } @Override @NotNull public synchronized VirtualFile[] getChildren() { VirtualFileSystemEntry[] children = myChildren; NewVirtualFileSystem delegate = getFileSystem(); final boolean ignoreCase = !delegate.isCaseSensitive(); if (allChildrenLoaded()) { assertConsistency(children, ignoreCase); return children; } final boolean wasChildrenLoaded = ourPersistence.areChildrenLoaded(this); final FSRecords.NameId[] childrenIds = ourPersistence.listAll(this); VirtualFileSystemEntry[] result; if (childrenIds.length == 0) { result = EMPTY_ARRAY; } else { Arrays.sort( childrenIds, new java.util.Comparator<FSRecords.NameId>() { @Override public int compare(FSRecords.NameId o1, FSRecords.NameId o2) { String name1 = o1.name; String name2 = o2.name; int cmp = compareNames(name1, name2, ignoreCase); if (cmp == 0 && name1 != name2) { LOG.error( ourPersistence + " returned duplicate file names(" + name1 + "," + name2 + ")" + " ignoreCase: " + ignoreCase + " SystemInfo.isFileSystemCaseSensitive: " + SystemInfo.isFileSystemCaseSensitive + " SystemInfo.OS: " + SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION + " wasChildrenLoaded: " + wasChildrenLoaded + " in the dir: " + VirtualDirectoryImpl.this + ";" + " children: " + Arrays.toString(childrenIds)); } return cmp; } }); result = new VirtualFileSystemEntry[childrenIds.length]; int delegateI = 0; int i = 0; int cachedEnd = getAdoptedChildrenStart(); // merge (sorted) children[0..cachedEnd) and childrenIds into the result array. // file that is already in children array must be copied into the result as is // for the file name that is new in childrenIds the file must be created and copied into // result while (delegateI < childrenIds.length) { FSRecords.NameId nameId = childrenIds[delegateI]; while (i < cachedEnd && children[i].compareNameTo(nameId.name, ignoreCase) < 0) i++; // skip files that are not in childrenIds VirtualFileSystemEntry resultFile; if (i < cachedEnd && children[i].compareNameTo(nameId.name, ignoreCase) == 0) { resultFile = children[i++]; } else { resultFile = createChild(nameId.nameId, nameId.id, delegate); } result[delegateI++] = resultFile; } assertConsistency(result, ignoreCase, children, cachedEnd, childrenIds); } if (getId() > 0) { myChildren = result; setChildrenLoaded(); } return result; } private void assertConsistency( @NotNull VirtualFileSystemEntry[] array, boolean ignoreCase, @NotNull Object... details) { if (!CHECK) return; boolean allChildrenLoaded = allChildrenLoaded(); for (int i = 0; i < array.length; i++) { VirtualFileSystemEntry file = array[i]; boolean isAdopted = isAdoptedChild(file); assert !isAdopted || !allChildrenLoaded; if (isAdopted && i != array.length - 1) { assert isAdoptedChild(array[i + 1]); } if (i != 0) { VirtualFileSystemEntry prev = array[i - 1]; String prevName = prev.getName(); int cmp = file.compareNameTo(prevName, ignoreCase); if (cmp == 0) { error( verboseToString.fun(prev) + " equals to " + verboseToString.fun(file), array, details); } if (isAdopted == isAdoptedChild(prev)) { if (cmp <= 0) { error( "Not sorted: " + verboseToString.fun(prev) + " is not less than " + verboseToString.fun(file), array, details); } } } } } private static final Function<VirtualFileSystemEntry, String> verboseToString = new Function<VirtualFileSystemEntry, String>() { @Override public String fun(VirtualFileSystemEntry file) { //noinspection HardCodedStringLiteral return file + " (name: '" + file.getName() + "', " + file.getClass() + ", parent: " + file.getParent() + "; id: " + file.getId() + "; FS: " + file.getFileSystem() + "; delegate.attrs: " + file.getFileSystem().getAttributes(file) + "; caseSensitive: " + file.getFileSystem().isCaseSensitive() + "; canonical: " + file.getFileSystem().getCanonicallyCasedName(file) + ") "; } }; private static void error( @NonNls String message, VirtualFileSystemEntry[] array, Object... details) { String children = StringUtil.join(array, verboseToString, ","); throw new AssertionError( message + "; children: " + children + "\nDetails: " + ContainerUtil.map( details, new Function<Object, Object>() { @Override public Object fun(Object o) { return o instanceof Object[] ? Arrays.toString((Object[]) o) : o; } })); } @Override @Nullable public VirtualFileSystemEntry findChild(@NotNull final String name) { return findChild(name, false, true, getFileSystem()); } 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()); } @NotNull @Override public byte[] contentsToByteArray() throws IOException { throw new IOException("Cannot get content of directory: " + this); } public synchronized void addChild(@NotNull VirtualFileSystemEntry child) { VirtualFileSystemEntry[] array = myChildren; final String childName = child.getName(); final boolean ignoreCase = !getFileSystem().isCaseSensitive(); long r = findIndexInBoth(array, childName, getComparator(ignoreCase)); int indexInReal = (int) (r >> 32); int indexInAdopted = (int) r; if (indexInAdopted >= 0) { // remove Adopted first removeFromArray(indexInAdopted); } if (indexInReal < 0) { insertChildAt(child, indexInReal); } // else already stored assertConsistency(myChildren, ignoreCase, child); } private void insertChildAt(@NotNull VirtualFileSystemEntry file, int negativeIndex) { @NotNull VirtualFileSystemEntry[] array = myChildren; 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); myChildren = appended; } public synchronized void removeChild(@NotNull VirtualFile file) { boolean ignoreCase = !getFileSystem().isCaseSensitive(); String name = file.getName(); addToAdoptedChildren(ignoreCase, name, getComparator(ignoreCase)); assertConsistency(myChildren, ignoreCase, file); } private void removeFromArray(int index) { myChildren = ArrayUtil.remove( myChildren, index, new ArrayFactory<VirtualFileSystemEntry>() { @NotNull @Override public VirtualFileSystemEntry[] create(int count) { return new VirtualFileSystemEntry[count]; } }); } public boolean allChildrenLoaded() { return getFlagInt(CHILDREN_CACHED); } private void setChildrenLoaded() { setFlagInt(CHILDREN_CACHED, true); } @NotNull public synchronized List<String> getSuspiciousNames() { List<VirtualFile> suspicious = new SubList<VirtualFile>(myChildren, getAdoptedChildrenStart(), myChildren.length); return ContainerUtil.map2List( suspicious, new Function<VirtualFile, String>() { @Override public String fun(VirtualFile file) { return file.getName(); } }); } private int getAdoptedChildrenStart() { int index = binSearch( myChildren, 0, myChildren.length, "", new Comparator() { @Override public int compareFileNameTo( @NotNull String myName, @NotNull VirtualFileSystemEntry v) { return isAdoptedChild(v) ? -1 : 1; } }); return -index - 1; } private static boolean isAdoptedChild(@NotNull VirtualFileSystemEntry v) { return v.getParent() == NULL_VIRTUAL_FILE; } private interface Comparator { int compareFileNameTo(@NotNull String myName, @NotNull VirtualFileSystemEntry file); } private static int binSearch( @NotNull VirtualFileSystemEntry[] array, int start, int end, @NotNull String name, @NotNull Comparator comparator) { int low = start; int high = end - 1; assert low >= 0 && low <= array.length; while (low <= high) { int mid = low + high >>> 1; int cmp = comparator.compareFileNameTo(name, array[mid]); if (cmp > 0) { low = mid + 1; } else if (cmp < 0) { high = mid - 1; } else { return mid; // key found } } return -(low + 1); // key not found. } @Override public boolean isDirectory() { return true; } @Override @NotNull public synchronized List<VirtualFile> getCachedChildren() { return new SubList<VirtualFile>(myChildren, 0, getAdoptedChildrenStart()); } @Override public InputStream getInputStream() throws IOException { throw new IOException("getInputStream() must not be called against a directory: " + getUrl()); } @Override @NotNull 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 (isAdoptedChild(child)) break; child.markDirtyInternal(); if (child instanceof VirtualDirectoryImpl) { ((VirtualDirectoryImpl) child).markDirtyRecursivelyInternal(); } } } }
@NotNull public VirtualFileSystemEntry createChild( String name, int id, @NotNull NewVirtualFileSystem delegate) { return createChild(FileNameCache.storeName(name), id, delegate); }