From 7c310d870e07a6ac771a54fd87fa554184199dd0 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Tue, 21 Aug 2012 13:26:39 +0400 Subject: [PATCH] VFS visitor extended --- .../com/intellij/openapi/vfs/VfsUtilCore.java | 74 ++-- .../openapi/vfs/VirtualFileVisitor.java | 170 ++++++++- .../FileDocumentManagerImplTest.java | 2 - .../openapi/vfs/VirtualFileVisitorTest.java | 348 ++++++++++++++++++ .../vfs/local/SymlinkHandlingTest.java | 2 +- .../openapi/roots/impl/FileIndexImplUtil.java | 30 +- .../com/intellij/mock/MockVirtualFile.java | 25 +- .../util/containers/ContainerUtilRt.java | 15 + .../com/intellij/util/containers/Stack.java | 12 +- .../util/containers/ContainerUtil.java | 15 + .../openapi/vcs/impl/VcsRootIterator.java | 37 +- 11 files changed, 628 insertions(+), 102 deletions(-) create mode 100644 platform/platform-tests/testSrc/com/intellij/openapi/vfs/VirtualFileVisitorTest.java rename platform/{util => util-rt}/src/com/intellij/util/containers/Stack.java (87%) diff --git a/platform/core-api/src/com/intellij/openapi/vfs/VfsUtilCore.java b/platform/core-api/src/com/intellij/openapi/vfs/VfsUtilCore.java index e13b66a8bcbd..33b32ac457cb 100644 --- a/platform/core-api/src/com/intellij/openapi/vfs/VfsUtilCore.java +++ b/platform/core-api/src/com/intellij/openapi/vfs/VfsUtilCore.java @@ -15,6 +15,7 @@ */ package com.intellij.openapi.vfs; +import com.intellij.openapi.roots.ContentIterator; import com.intellij.openapi.util.io.BufferExposingByteArrayInputStream; import com.intellij.openapi.util.io.FileUtil; import com.intellij.util.Function; @@ -26,9 +27,7 @@ import org.jetbrains.annotations.Nullable; import java.io.*; import java.util.Collection; -import java.util.HashSet; import java.util.List; -import java.util.Set; public class VfsUtilCore { /** @@ -170,38 +169,63 @@ public class VfsUtilCore { return stream; } - public static void visitChildrenRecursively(@NotNull VirtualFile file, @NotNull VirtualFileVisitor visitor) { - visitChildrenRecursively(file, visitor, null); + public static boolean iterateChildrenRecursively(@NotNull final VirtualFile root, + @Nullable final VirtualFileFilter filter, + @NotNull final ContentIterator iterator) { + final VirtualFileVisitor.Result result = visitChildrenRecursively(root, new VirtualFileVisitor() { + @NotNull + @Override + public Result visitFileEx(@NotNull VirtualFile file) { + if (filter != null && !filter.accept(file)) return SKIP_CHILDREN; + if (!iterator.processFile(file)) return skipTo(root); + return CONTINUE; + } + }); + return result.skipToParent != root; } - private static void visitChildrenRecursively(@NotNull VirtualFile file, - @NotNull VirtualFileVisitor visitor, - @Nullable Set visitedSymLinks) { - if (!file.isValid()) return; - if (!visitor.visitFile(file)) return; + public static VirtualFileVisitor.Result visitChildrenRecursively(@NotNull VirtualFile file, @NotNull VirtualFileVisitor visitor) { + if (!file.isValid()) return VirtualFileVisitor.CONTINUE; - boolean visitChildren = true; - if (file.isSymLink()) { - if (!visitor.followSymLinks()) return; - if (visitedSymLinks == null) { - visitedSymLinks = new HashSet(); + visitor.pushFrame(); + try { + final boolean visited = visitor.allowVisitFile(file); + if (visited) { + VirtualFileVisitor.Result result = visitor.visitFileEx(file); + if (result.skipChildren) return result; } - if (!visitedSymLinks.add(file) || isInvalidLink(file)) { - visitChildren = false; + + if (!visitor.allowVisitChildren(file)) return VirtualFileVisitor.CONTINUE; + + if (!visitor.depthLimitReached()) { + final Iterable iterable = visitor.getChildrenIterable(file); + if (iterable != null) { + for (VirtualFile child : iterable) { + VirtualFileVisitor.Result result = visitChildrenRecursively(child, visitor); + if (result.skipToParent != null && result.skipToParent != child) return result; + } + } + else { + @SuppressWarnings("UnsafeVfsRecursion") VirtualFile[] children = file.getChildren(); + for (VirtualFile child : children) { + VirtualFileVisitor.Result result = visitChildrenRecursively(child, visitor); + if (result.skipToParent != null && result.skipToParent != child) return result; + } + } } + + if (visited) { + visitor.afterChildrenVisited(file); + } + + return VirtualFileVisitor.CONTINUE; } - - if (visitChildren) { - @SuppressWarnings("UnsafeVfsRecursion") VirtualFile[] children = file.getChildren(); - for (VirtualFile child : children) { - visitChildrenRecursively(child, visitor, visitedSymLinks); - } + finally { + visitor.popFrame(); } - - visitor.afterChildrenVisited(file); } - private static boolean isInvalidLink(@NotNull VirtualFile link) { + public static boolean isInvalidLink(@NotNull VirtualFile link) { final VirtualFile target = link.getCanonicalFile(); return target == null || target == link || isAncestor(target, link, true); } diff --git a/platform/core-api/src/com/intellij/openapi/vfs/VirtualFileVisitor.java b/platform/core-api/src/com/intellij/openapi/vfs/VirtualFileVisitor.java index 6cd27cf611d3..b41afb1b6cac 100644 --- a/platform/core-api/src/com/intellij/openapi/vfs/VirtualFileVisitor.java +++ b/platform/core-api/src/com/intellij/openapi/vfs/VirtualFileVisitor.java @@ -15,28 +15,182 @@ */ package com.intellij.openapi.vfs; +import com.intellij.openapi.util.Key; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.HashMap; +import com.intellij.util.containers.Stack; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; /** * @author Dmitry Avdeev * @since 31.10.2011 */ public abstract class VirtualFileVisitor { - private final boolean myFollowSymLinks; + public static class Option { + private Option() { } - protected VirtualFileVisitor() { - this(true); + private static class LimitOption extends Option { + private final int limit; + + private LimitOption(int limit) { + this.limit = limit; + } + } } - protected VirtualFileVisitor(boolean followSymLinks) { - myFollowSymLinks = followSymLinks; + public static final Option NO_FOLLOW_SYMLINKS = new Option(); + public static final Option SKIP_ROOT = new Option(); + public static final Option ONE_LEVEL_DEEP = new Option.LimitOption(1); + + public static Option limit(int maxDepth) { + return new Option.LimitOption(maxDepth); } - public boolean followSymLinks() { - return myFollowSymLinks; + + public static class Result { + public final boolean skipChildren; + public final VirtualFile skipToParent; + + private Result(boolean skipChildren, @Nullable VirtualFile skipToParent) { + this.skipChildren = skipChildren; + this.skipToParent = skipToParent; + } + + @Override + public String toString() { + return "(" + (skipChildren ? "skip," + skipToParent : "continue") + ")"; + } } - public abstract boolean visitFile(@NotNull VirtualFile file); + public static final Result CONTINUE = new Result(false, null); + public static final Result SKIP_CHILDREN = new Result(true, null); + public static Result skipTo(@NotNull VirtualFile parentToSkipTo) { + return new Result(true, parentToSkipTo); + } + + + private boolean myFollowSymLinks = true; + private boolean mySkipRoot = false; + private int myDepthLimit = -1; + + private int myLevel = -1; + private Stack> myParameters = null; + + protected VirtualFileVisitor(Option... options) { + for (Option option : options) { + if (option == NO_FOLLOW_SYMLINKS) myFollowSymLinks = false; + else if (option == SKIP_ROOT) mySkipRoot = true; + else if (option instanceof Option.LimitOption) myDepthLimit = ((Option.LimitOption)option).limit; + } + } + + + /** + * Simple visiting method. + * On returning {@code true} a visitor will proceed to file's children, on {@code false} - to file's next sibling. + * + * @param file a file to visit. + * @return {@code true} to proceed to file's children, {@code false} to skip to file's next sibling. + */ + public boolean visitFile(@NotNull VirtualFile file) { + return true; + } + + /** + * Extended visiting method. + * + * @param file a file to visit. + * @return {@linkplain #CONTINUE} to proceed to file's children,
+ * {@linkplain #SKIP_CHILDREN} to skip to file's next sibling,
+ * result of {@linkplain #skipTo(VirtualFile)} to skip to given file's next sibling. + */ + @NotNull + public Result visitFileEx(@NotNull VirtualFile file) { + return visitFile(file) ? CONTINUE : SKIP_CHILDREN; + } + + /** + * This method is only called if visiting wasn't interrupted (by returning skip-requesting result + * from {@linkplain #visitFile(VirtualFile)} or {@linkplain #visitFileEx(VirtualFile)} methods). + * + * @param file a file whose children were successfully visited. + */ public void afterChildrenVisited(@NotNull VirtualFile file) { } + + /** + * By default, visitor uses ({@linkplain com.intellij.openapi.vfs.VirtualFile#getChildren()}) to iterate over file's children. + * You can override this method to implement another mechanism. + * + * @param file a virtual file to get children from. + * @return children iterable, or null to use {@linkplain com.intellij.openapi.vfs.VirtualFile#getChildren()}. + */ + @Nullable + public Iterable getChildrenIterable(@NotNull VirtualFile file) { + return null; + } + + + public final void set(@NotNull Key parameter, @Nullable T value) { + if (myParameters == null) { + myParameters = ContainerUtil.newStack(); + } + + final Map frame; + if (myParameters.isEmpty()) { + myParameters.push(frame = ContainerUtil.newHashMap()); + } + else { + frame = myParameters.peek(); + } + + frame.put(parameter, value); + } + + @Nullable + public final T get(@NotNull Key parameter) { + if (myParameters == null || myParameters.isEmpty()) { + return null; + } + @SuppressWarnings({"unchecked", "UnnecessaryLocalVariable"}) final T value = (T)myParameters.peek().get(parameter); + return value; + } + + + final boolean allowVisitFile(@SuppressWarnings("UnusedParameters") @NotNull VirtualFile file) { + return myLevel > 0 || !mySkipRoot; + } + + final boolean allowVisitChildren(@NotNull VirtualFile file) { + return !file.isSymLink() || (myFollowSymLinks && !VfsUtilCore.isInvalidLink(file)); + } + + final boolean depthLimitReached() { + return myDepthLimit >= 0 && myLevel >= myDepthLimit; + } + + final void pushFrame() { + ++myLevel; + + if (myParameters != null && !myParameters.isEmpty()) { + final Map lastFrame = myParameters.peek(); + myParameters.push(new HashMap() { + @Override + public Object get(Object key) { + return containsKey(key) ? super.get(key) : lastFrame.get(key); + } + }); + } + } + + final void popFrame() { + --myLevel; + + if (myParameters != null && !myParameters.isEmpty()) { + myParameters.pop(); + } + } } diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/fileEditor/FileDocumentManagerImplTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/fileEditor/FileDocumentManagerImplTest.java index 886a2f195f40..11461413020b 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/fileEditor/FileDocumentManagerImplTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/fileEditor/FileDocumentManagerImplTest.java @@ -443,7 +443,6 @@ public class FileDocumentManagerImplTest extends PlatformLiteFixture { myDocumentManager.myReloadFromDisk = Boolean.TRUE; try { - file.setActualTimeStamp(file.getTimeStamp() + 1); myDocumentManager.saveAllDocuments(); long fileStamp = file.getModificationStamp(); @@ -476,7 +475,6 @@ public class FileDocumentManagerImplTest extends PlatformLiteFixture { file.setContent(null, "xxx", false); - file.setActualTimeStamp(file.getTimeStamp() + 1); myDocumentManager.saveAllDocuments(); assertEquals("zzztest", document.getText()); diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/VirtualFileVisitorTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/VirtualFileVisitorTest.java new file mode 100644 index 000000000000..1eb1a1e68d23 --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/VirtualFileVisitorTest.java @@ -0,0 +1,348 @@ +/* + * Copyright 2000-2012 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 com.intellij.mock.MockVirtualFile; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.Function; +import com.intellij.util.NullableFunction; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.Collections; + +import static org.junit.Assert.assertEquals; + +public class VirtualFileVisitorTest { + private static VirtualFile myRoot; + + @BeforeClass + public static void setUp() throws Exception { + myRoot = + dir("/", + dir("d1", + dir("d11", + file("f11.1"), + file("f11.2")), + file("f1.1"), + dir("d12", + file("f12.1"), + file("f12.2"))), + dir("d2", + file("f2.1"), + file("f2.2"))); + } + + @AfterClass + public static void tearDown() throws Exception { + myRoot = null; + } + + @Test + public void visitAll() { + doTest( + null, null, + "-> /\n" + + " -> d1\n" + + " -> d11\n" + + " -> f11.1\n" + + " <- f11.1\n" + + " -> f11.2\n" + + " <- f11.2\n" + + " <- d11\n" + + " -> f1.1\n" + + " <- f1.1\n" + + " -> d12\n" + + " -> f12.1\n" + + " <- f12.1\n" + + " -> f12.2\n" + + " <- f12.2\n" + + " <- d12\n" + + " <- d1\n" + + " -> d2\n" + + " -> f2.1\n" + + " <- f2.1\n" + + " -> f2.2\n" + + " <- f2.2\n" + + " <- d2\n" + + "<- /\n"); + } + + @Test + public void skipChildrenForDirectory() { + doTest( + new Function() { + @Override + public Object fun(VirtualFile file) { + return "d11".equals(file.getName()) ? VirtualFileVisitor.SKIP_CHILDREN : VirtualFileVisitor.CONTINUE; + } + }, + null, + "-> /\n" + + " -> d1\n" + + " -> d11\n" + + " -> f1.1\n" + + " <- f1.1\n" + + " -> d12\n" + + " -> f12.1\n" + + " <- f12.1\n" + + " -> f12.2\n" + + " <- f12.2\n" + + " <- d12\n" + + " <- d1\n" + + " -> d2\n" + + " -> f2.1\n" + + " <- f2.1\n" + + " -> f2.2\n" + + " <- f2.2\n" + + " <- d2\n" + + " <- /\n"); + } + + @Test + public void skipChildrenForFiles() { + doTest( + new Function() { + @Override + public Object fun(VirtualFile file) { + return file.isDirectory() ? VirtualFileVisitor.CONTINUE : VirtualFileVisitor.SKIP_CHILDREN; + } + }, + null, + "-> /\n" + + " -> d1\n" + + " -> d11\n" + + " -> f11.1\n" + + " -> f11.2\n" + + " <- d11\n" + + " -> f1.1\n" + + " -> d12\n" + + " -> f12.1\n" + + " -> f12.2\n" + + " <- d12\n" + + " <- d1\n" + + " -> d2\n" + + " -> f2.1\n" + + " -> f2.2\n" + + " <- d2\n" + + " <- /\n"); + } + + @Test + public void skipToParent() { + final Ref skip = Ref.create(); + doTest( + new Function() { + @Override + public Object fun(VirtualFile file) { + if ("d1".equals(file.getName())) skip.set(file); + return "f11.1".equals(file.getName()) ? skip.get() : VirtualFileVisitor.CONTINUE; + } + }, + null, + "-> /\n" + + " -> d1\n" + + " -> d11\n" + + " -> f11.1\n" + + " -> d2\n" + + " -> f2.1\n" + + " <- f2.1\n" + + " -> f2.2\n" + + " <- f2.2\n" + + " <- d2\n" + + " <- /\n"); + } + + @Test + public void skipToRoot() { + doTest( + new Function() { + @Override + public Object fun(VirtualFile file) { + return "f11.1".equals(file.getName()) ? myRoot : VirtualFileVisitor.CONTINUE; + } + }, + null, + "-> /\n" + + " -> d1\n" + + " -> d11\n" + + " -> f11.1\n"); + } + + @Test + public void abort() { + doTest( + new Function() { + @Override + public Object fun(VirtualFile file) { + if ("f11.1".equals(file.getName())) { + throw new AbortException(); + } + return VirtualFileVisitor.CONTINUE; + } + }, + null, + "-> /\n" + + " -> d1\n" + + " -> d11\n" + + " -> f11.1\n"); + } + + @Test + public void parameters() { + final Key KEY = Key.create("vfs.visitor.test.parameter"); + + VfsUtilCore.visitChildrenRecursively(myRoot, new VirtualFileVisitor() { + { + set(KEY, myRoot.getPath()); + } + + @Override + public boolean visitFile(@NotNull VirtualFile file) { + String expected = file == myRoot ? myRoot.getPath() : file.getParent().getPath(); + assertEquals(expected, get(KEY)); + + if (file.isDirectory()) { + set(KEY, file.getPath()); + } + + return true; + } + }); + } + + @Test + public void depthLimit() { + doTest( + null, null, + "-> /\n" + + "<- /\n", + VirtualFileVisitor.limit(0) + ); + + doTest( + null, null, + "-> /\n" + + " -> d1\n" + + " <- d1\n" + + " -> d2\n" + + " <- d2\n" + + "<- /\n", + VirtualFileVisitor.ONE_LEVEL_DEEP + ); + + doTest( + null, null, + "-> d1\n" + + "<- d1\n" + + "-> d2\n" + + "<- d2\n", + VirtualFileVisitor.SKIP_ROOT, VirtualFileVisitor.ONE_LEVEL_DEEP); + } + + @Test + public void customIterable() { + doTest( + null, + new NullableFunction>() { + @Override + public Iterable fun(VirtualFile file) { + return "d12".equals(file.getName()) ? Collections.singletonList(file.getChildren()[1]) : null; + } + }, + "-> /\n" + + " -> d1\n" + + " -> d11\n" + + " -> f11.1\n" + + " <- f11.1\n" + + " -> f11.2\n" + + " <- f11.2\n" + + " <- d11\n" + + " -> f1.1\n" + + " <- f1.1\n" + + " -> d12\n" + + " -> f12.2\n" + + " <- f12.2\n" + + " <- d12\n" + + " <- d1\n" + + " -> d2\n" + + " -> f2.1\n" + + " <- f2.1\n" + + " -> f2.2\n" + + " <- f2.2\n" + + " <- d2\n" + + "<- /\n"); + } + + private static class AbortException extends RuntimeException { } + + private static void doTest(@Nullable final Function condition, + @Nullable final Function> iterable, + @NotNull String expected, + VirtualFileVisitor.Option... options) { + final StringBuilder sb = new StringBuilder(); + + try { + VfsUtilCore.visitChildrenRecursively(myRoot, new VirtualFileVisitor(options) { + private int level = 0; + + @NotNull + @Override + public Result visitFileEx(@NotNull VirtualFile file) { + sb.append(StringUtil.repeat(" ", level++)).append("-> ").append(file.getName()).append('\n'); + + if (condition != null) { + Object result = condition.fun(file); + if (result instanceof Result) return (Result)result; + if (result instanceof VirtualFile) return skipTo((VirtualFile)result); + } + return CONTINUE; + } + + @Override + public void afterChildrenVisited(@NotNull VirtualFile file) { + sb.append(StringUtil.repeat(" ", --level)).append("<- ").append(file.getName()).append('\n'); + } + + @Nullable + @Override + public Iterable getChildrenIterable(@NotNull VirtualFile file) { + return iterable != null ? iterable.fun(file) : super.getChildrenIterable(file); + } + }); + } + catch (AbortException ignore) { } + + assertEquals(expected, sb.toString()); + } + + private static MockVirtualFile dir(@NotNull String name, MockVirtualFile... children) { + final MockVirtualFile root = new MockVirtualFile(true, name); + for (MockVirtualFile child : children) { + root.addChild(child); + } + return root; + } + + private static MockVirtualFile file(@NotNull String name) { + return new MockVirtualFile(name); + } +} diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/SymlinkHandlingTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/SymlinkHandlingTest.java index 9f52f4192daf..eae60f9a094c 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/SymlinkHandlingTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/SymlinkHandlingTest.java @@ -389,7 +389,7 @@ public class SymlinkHandlingTest extends LightPlatformLangTestCase { })); final Set actualSet = new HashSet(); - VfsUtilCore.visitChildrenRecursively(vDir, new VirtualFileVisitor(true) { + VfsUtilCore.visitChildrenRecursively(vDir, new VirtualFileVisitor() { @Override public boolean visitFile(@NotNull VirtualFile file) { actualSet.add(file.getPath()); diff --git a/platform/projectModel-api/src/com/intellij/openapi/roots/impl/FileIndexImplUtil.java b/platform/projectModel-api/src/com/intellij/openapi/roots/impl/FileIndexImplUtil.java index 769e51d5c7bb..37f29516e495 100644 --- a/platform/projectModel-api/src/com/intellij/openapi/roots/impl/FileIndexImplUtil.java +++ b/platform/projectModel-api/src/com/intellij/openapi/roots/impl/FileIndexImplUtil.java @@ -13,38 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.intellij.openapi.roots.impl; import com.intellij.openapi.roots.ContentIterator; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileFilter; -import com.intellij.openapi.vfs.VirtualFileVisitor; import org.jetbrains.annotations.NotNull; public class FileIndexImplUtil { - private FileIndexImplUtil() { + private FileIndexImplUtil() { } + + public static boolean iterateRecursively(@NotNull final VirtualFile root, + @NotNull final VirtualFileFilter filter, + @NotNull final ContentIterator iterator) { + return VfsUtilCore.iterateChildrenRecursively(root, filter, iterator); } - - public static boolean iterateRecursively(@NotNull final VirtualFile root, @NotNull final VirtualFileFilter filter, @NotNull final ContentIterator iterator){ - - try { - VfsUtilCore.visitChildrenRecursively(root, new VirtualFileVisitor() { - @Override - public boolean visitFile(@NotNull VirtualFile file) { - if (!file.isValid() || !filter.accept(file)) return false; - - if (!iterator.processFile(file)) throw new StopItException(); - return true; - } - }); - return true; - } - catch (StopItException e) { - return false; - } - } - - private static class StopItException extends RuntimeException {} } diff --git a/platform/testFramework/src/com/intellij/mock/MockVirtualFile.java b/platform/testFramework/src/com/intellij/mock/MockVirtualFile.java index 56956f385bf8..8e6478204749 100644 --- a/platform/testFramework/src/com/intellij/mock/MockVirtualFile.java +++ b/platform/testFramework/src/com/intellij/mock/MockVirtualFile.java @@ -38,6 +38,7 @@ public class MockVirtualFile extends VirtualFile { private final List myChildren = new SmartList(); private String myText; private final MockVirtualFileSystem myFileSystem = new MockVirtualFileSystem(); + private boolean myIsWritable = true; private long myModStamp = LocalTimeCounter.currentTime(); public MockVirtualFile(final String name) { @@ -73,11 +74,15 @@ public class MockVirtualFile extends VirtualFile { @Override public VirtualFile createChildData(final Object requestor, @NotNull @NonNls final String name) { final MockVirtualFile file = new MockVirtualFile(name); - file.setParent(this); - myChildren.add(file); + addChild(file); return file; } + public void addChild(@NotNull final MockVirtualFile child) { + child.setParent(this); + myChildren.add(child); + } + @Override @NotNull public VirtualFileSystem getFileSystem() { @@ -90,11 +95,11 @@ public class MockVirtualFile extends VirtualFile { return prefix + "/" + myName; } - private boolean myIsWritable = true; @Override public boolean isWritable() { return myIsWritable; } + public void setWritable(boolean b) { myIsWritable = b; } @@ -117,7 +122,7 @@ public class MockVirtualFile extends VirtualFile { @Override public VirtualFile[] getChildren() { - return VfsUtil.toVirtualFileArray(myChildren); + return VfsUtilCore.toVirtualFileArray(myChildren); } @Override @@ -161,16 +166,6 @@ public class MockVirtualFile extends VirtualFile { @Override public void refresh(boolean asynchronous, boolean recursive, Runnable postRunnable) { - - } - - private long myActualTimeStamp = myTimeStamp; - public void setActualTimeStamp(long actualTimeStamp) { - myActualTimeStamp = actualTimeStamp; - } - - public long getActualTimeStamp() { - return myActualTimeStamp; } @Override @@ -191,6 +186,4 @@ public class MockVirtualFile extends VirtualFile { myListener.contentsChanged(new VirtualFileEvent(requestor, this, null, oldStamp, myModStamp)); } } - - } diff --git a/platform/util-rt/src/com/intellij/util/containers/ContainerUtilRt.java b/platform/util-rt/src/com/intellij/util/containers/ContainerUtilRt.java index 50531dcc4b13..39fe03605908 100644 --- a/platform/util-rt/src/com/intellij/util/containers/ContainerUtilRt.java +++ b/platform/util-rt/src/com/intellij/util/containers/ContainerUtilRt.java @@ -127,6 +127,21 @@ public class ContainerUtilRt { return 5 + size + size / 5; } + @NotNull + public static Stack newStack() { + return new Stack(); + } + + @NotNull + public static Stack newStack(Collection initial) { + return new Stack(initial); + } + + @NotNull + public static Stack newStack(T... initial) { + return new Stack(Arrays.asList(initial)); + } + /** * Optimized toArray() as opposed to the {@link java.util.Collections#emptyList()}. */ diff --git a/platform/util/src/com/intellij/util/containers/Stack.java b/platform/util-rt/src/com/intellij/util/containers/Stack.java similarity index 87% rename from platform/util/src/com/intellij/util/containers/Stack.java rename to platform/util-rt/src/com/intellij/util/containers/Stack.java index 5b9044602807..da0e9d8a99ac 100644 --- a/platform/util/src/com/intellij/util/containers/Stack.java +++ b/platform/util-rt/src/com/intellij/util/containers/Stack.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2012 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. @@ -13,14 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -/* - * @author max - */ package com.intellij.util.containers; import java.util.*; +/** + * A drop-in replacement for {@link java.util.Stack} based on {@link java.util.ArrayList} (instead of {@link java.util.Vector}) + * and therefore is (1) not synchronized and (2) faster. + * + * @author max + */ public class Stack extends ArrayList { public Stack(int initialCapacity) { super(initialCapacity); diff --git a/platform/util/src/com/intellij/util/containers/ContainerUtil.java b/platform/util/src/com/intellij/util/containers/ContainerUtil.java index b8111fe6d6dc..4425f6c2f416 100644 --- a/platform/util/src/com/intellij/util/containers/ContainerUtil.java +++ b/platform/util/src/com/intellij/util/containers/ContainerUtil.java @@ -1490,6 +1490,21 @@ public class ContainerUtil extends ContainerUtilRt { return list; } + @NotNull + public static Stack newStack() { + return ContainerUtilRt.newStack(); + } + + @NotNull + public static Stack newStack(Collection initial) { + return ContainerUtilRt.newStack(initial); + } + + @NotNull + public static Stack newStack(T... initial) { + return ContainerUtilRt.newStack(initial); + } + @NotNull public static List emptyList() { return ContainerUtilRt.emptyList(); diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/VcsRootIterator.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/VcsRootIterator.java index 72e242ce52dc..8caa6f2bc981 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/VcsRootIterator.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/VcsRootIterator.java @@ -165,29 +165,24 @@ public class VcsRootIterator { } public void iterate() { - class StopIterationException extends RuntimeException { } - - try { - VfsUtilCore.visitChildrenRecursively(myRoot, new VirtualFileVisitor(false) { - @Override - public void afterChildrenVisited(@NotNull VirtualFile file) { - if (myDirectoryFilter != null) { - myDirectoryFilter.afterChildrenVisited(file); - } + VfsUtilCore.visitChildrenRecursively(myRoot, new VirtualFileVisitor(VirtualFileVisitor.NO_FOLLOW_SYMLINKS) { + @Override + public void afterChildrenVisited(@NotNull VirtualFile file) { + if (myDirectoryFilter != null) { + myDirectoryFilter.afterChildrenVisited(file); } + } - @Override - public boolean visitFile(@NotNull VirtualFile file) { - if (isExcluded(myExcludedFileIndex, file)) return false; - if (myRootPresentFilter != null && ! myRootPresentFilter.accept(file)) return false; - if (myProject.isDisposed() || ! process(file)) throw new StopIterationException(); - if (myDirectoryFilter != null && file.isDirectory() && ! myDirectoryFilter.shouldGoIntoDirectory(file)) return false; - return true; - } - }); - } catch (StopIterationException e) { - // - } + @NotNull + @Override + public Result visitFileEx(@NotNull VirtualFile file) { + if (isExcluded(myExcludedFileIndex, file)) return SKIP_CHILDREN; + if (myRootPresentFilter != null && ! myRootPresentFilter.accept(file)) return SKIP_CHILDREN; + if (myProject.isDisposed() || ! process(file)) return skipTo(myRoot); + if (myDirectoryFilter != null && file.isDirectory() && ! myDirectoryFilter.shouldGoIntoDirectory(file)) return SKIP_CHILDREN; + return CONTINUE; + } + }); } private boolean process(VirtualFile current) {