/* * 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 com.intellij.util.containers.Stack; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * @author Dmitry Avdeev * @since 31.10.2011 */ public abstract class VirtualFileVisitor<T> { public static class Option { private Option() { } private static class LimitOption extends Option { private final int limit; private LimitOption(int limit) { this.limit = limit; } } } public static final Option NO_FOLLOW_SYMLINKS = new Option(); public static final Option SKIP_ROOT = new Option(); public static final Option ONE_LEVEL_DEEP = limit(1); public static Option limit(int maxDepth) { return new Option.LimitOption(maxDepth); } 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; } @NonNls @Override public String toString() { return "(" + (skipChildren ? "skip," + skipToParent : "continue") + ")"; } } 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); } protected static class VisitorException extends RuntimeException { public VisitorException(Throwable cause) { super(cause); } } private boolean myFollowSymLinks = true; private boolean mySkipRoot = false; private int myDepthLimit = -1; private int myLevel = 0; private Stack<T> myValueStack = null; private T myValue = null; protected VirtualFileVisitor(@NotNull 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,<br/> * {@linkplain #SKIP_CHILDREN} to skip to file's next sibling,<br/> * 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<VirtualFile> getChildrenIterable(@NotNull VirtualFile file) { return null; } /** * Stores the {@code value} to this visitor. The stored value can be retrieved later by calling the {@link #getCurrentValue()}. * The visitor maintains the stack of stored values. I.e: * This value is held here only during the visiting the current file and all its children. As soon as the visitor finished with * the current file and all its subtree and returns to the level up, the value is cleared * and the {@link #getCurrentValue()} returns the previous value which was stored here before the {@link #setValueForChildren} call. */ public final void setValueForChildren(@Nullable T value) { myValue = value; if (myValueStack == null) { myValueStack = new Stack<T>(); } } public final T getCurrentValue() { return myValue; } final boolean allowVisitFile(@SuppressWarnings("UnusedParameters") @NotNull VirtualFile file) { return myLevel > 0 || !mySkipRoot; } final boolean allowVisitChildren(@NotNull VirtualFile file) { return !file.is(VFileProperty.SYMLINK) || myFollowSymLinks && !VfsUtilCore.isInvalidLink(file); } final boolean depthLimitReached() { return myDepthLimit >= 0 && myLevel >= myDepthLimit; } final void saveValue() { ++myLevel; if (myValueStack != null) { myValueStack.push(myValue); } } final void restoreValue(boolean pushed) { if (pushed) { --myLevel; if (myValueStack != null && !myValueStack.isEmpty()) { myValueStack.pop(); } } if (myValueStack != null) { myValue = myValueStack.isEmpty() ? null : myValueStack.peek(); } } }