/* * Copyright 2000-2015 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.ContainerUtil; import com.intellij.util.containers.Stack; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.Map; /** * @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 Map<VirtualFile, List<VirtualFile>> myVisitedTargets; 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; } } if (myFollowSymLinks) { myVisitedTargets = ContainerUtil.newHashMap(); } } /** * 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 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 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<>(); } } public final T getCurrentValue() { return myValue; } final boolean allowVisitFile(@SuppressWarnings("UnusedParameters") @NotNull VirtualFile file) { return myLevel > 0 || !mySkipRoot; } final boolean allowVisitChildren(@NotNull VirtualFile file) { if (!file.is(VFileProperty.SYMLINK)) { return true; } if (!myFollowSymLinks || VfsUtilCore.isInvalidLink(file)) { return false; } VirtualFile target = file.getCanonicalFile(); List<VirtualFile> links = myVisitedTargets.get(target); if (links == null) { myVisitedTargets.put(target, ContainerUtil.newSmartList(file)); return true; } boolean hasLoop = false; for (VirtualFile link : links) { if (VfsUtilCore.isAncestor(link, file, true)) { hasLoop = true; break; } } links.add(file); return !hasLoop; } 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(); } } }