893N/A/*
5021N/A * Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
893N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
893N/A *
893N/A * This code is free software; you can redistribute it and/or modify it
893N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
893N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
893N/A *
893N/A * This code is distributed in the hope that it will be useful, but WITHOUT
893N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
893N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
893N/A * version 2 for more details (a copy is included in the LICENSE file that
893N/A * accompanied this code).
893N/A *
893N/A * You should have received a copy of the GNU General Public License version
893N/A * 2 along with this work; if not, write to the Free Software Foundation,
893N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
893N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
893N/A */
893N/A
893N/Apackage java.nio.file;
893N/A
893N/Aimport java.nio.file.attribute.*;
893N/Aimport java.io.IOException;
893N/Aimport java.util.*;
907N/Aimport sun.nio.fs.BasicFileAttributesHolder;
893N/A
893N/A/**
893N/A * Simple file tree walker that works in a similar manner to nftw(3C).
893N/A *
893N/A * @see Files#walkFileTree
893N/A */
893N/A
893N/Aclass FileTreeWalker {
893N/A private final boolean followLinks;
893N/A private final LinkOption[] linkOptions;
893N/A private final FileVisitor<? super Path> visitor;
1658N/A private final int maxDepth;
893N/A
1658N/A FileTreeWalker(Set<FileVisitOption> options,
1658N/A FileVisitor<? super Path> visitor,
1658N/A int maxDepth)
1658N/A {
893N/A boolean fl = false;
893N/A for (FileVisitOption option: options) {
2826N/A // will throw NPE if options contains null
893N/A switch (option) {
2826N/A case FOLLOW_LINKS : fl = true; break;
893N/A default:
893N/A throw new AssertionError("Should not get here");
893N/A }
893N/A }
893N/A this.followLinks = fl;
893N/A this.linkOptions = (fl) ? new LinkOption[0] :
893N/A new LinkOption[] { LinkOption.NOFOLLOW_LINKS };
893N/A this.visitor = visitor;
1658N/A this.maxDepth = maxDepth;
893N/A }
893N/A
893N/A /**
893N/A * Walk file tree starting at the given file
893N/A */
2826N/A void walk(Path start) throws IOException {
893N/A FileVisitResult result = walk(start,
1658N/A 0,
893N/A new ArrayList<AncestorDirectory>());
3479N/A Objects.requireNonNull(result, "FileVisitor returned null");
893N/A }
893N/A
893N/A /**
893N/A * @param file
907N/A * the directory to visit
893N/A * @param depth
907N/A * depth remaining
893N/A * @param ancestors
893N/A * use when cycle detection is enabled
893N/A */
893N/A private FileVisitResult walk(Path file,
893N/A int depth,
893N/A List<AncestorDirectory> ancestors)
2826N/A throws IOException
893N/A {
907N/A // if attributes are cached then use them if possible
893N/A BasicFileAttributes attrs = null;
1658N/A if ((depth > 0) &&
1658N/A (file instanceof BasicFileAttributesHolder) &&
1658N/A (System.getSecurityManager() == null))
1658N/A {
907N/A BasicFileAttributes cached = ((BasicFileAttributesHolder)file).get();
5021N/A if (cached != null && (!followLinks || !cached.isSymbolicLink()))
907N/A attrs = cached;
907N/A }
893N/A IOException exc = null;
893N/A
893N/A // attempt to get attributes of file. If fails and we are following
893N/A // links then a link target might not exist so get attributes of link
907N/A if (attrs == null) {
893N/A try {
907N/A try {
3471N/A attrs = Files.readAttributes(file, BasicFileAttributes.class, linkOptions);
907N/A } catch (IOException x1) {
907N/A if (followLinks) {
907N/A try {
3471N/A attrs = Files.readAttributes(file,
3471N/A BasicFileAttributes.class,
3471N/A LinkOption.NOFOLLOW_LINKS);
907N/A } catch (IOException x2) {
907N/A exc = x2;
907N/A }
907N/A } else {
907N/A exc = x1;
893N/A }
893N/A }
907N/A } catch (SecurityException x) {
1658N/A // If access to starting file is denied then SecurityException
1658N/A // is thrown, otherwise the file is ignored.
1658N/A if (depth == 0)
1658N/A throw x;
907N/A return FileVisitResult.CONTINUE;
893N/A }
893N/A }
893N/A
893N/A // unable to get attributes of file
893N/A if (exc != null) {
893N/A return visitor.visitFileFailed(file, exc);
893N/A }
893N/A
2826N/A // at maximum depth or file is not a directory
2826N/A if (depth >= maxDepth || !attrs.isDirectory()) {
893N/A return visitor.visitFile(file, attrs);
893N/A }
893N/A
2826N/A // check for cycles when following links
2826N/A if (followLinks) {
893N/A Object key = attrs.fileKey();
893N/A
893N/A // if this directory and ancestor has a file key then we compare
893N/A // them; otherwise we use less efficient isSameFile test.
893N/A for (AncestorDirectory ancestor: ancestors) {
893N/A Object ancestorKey = ancestor.fileKey();
893N/A if (key != null && ancestorKey != null) {
893N/A if (key.equals(ancestorKey)) {
893N/A // cycle detected
2826N/A return visitor.visitFileFailed(file,
2826N/A new FileSystemLoopException(file.toString()));
893N/A }
893N/A } else {
2826N/A boolean isSameFile = false;
893N/A try {
3471N/A isSameFile = Files.isSameFile(file, ancestor.file());
893N/A } catch (IOException x) {
893N/A // ignore
893N/A } catch (SecurityException x) {
893N/A // ignore
893N/A }
2826N/A if (isSameFile) {
2826N/A // cycle detected
2826N/A return visitor.visitFileFailed(file,
2826N/A new FileSystemLoopException(file.toString()));
2826N/A }
893N/A }
893N/A }
893N/A
893N/A ancestors.add(new AncestorDirectory(file, key));
893N/A }
893N/A
893N/A // visit directory
893N/A try {
893N/A DirectoryStream<Path> stream = null;
893N/A FileVisitResult result;
893N/A
893N/A // open the directory
893N/A try {
3471N/A stream = Files.newDirectoryStream(file);
893N/A } catch (IOException x) {
2826N/A return visitor.visitFileFailed(file, x);
893N/A } catch (SecurityException x) {
893N/A // ignore, as per spec
893N/A return FileVisitResult.CONTINUE;
893N/A }
893N/A
893N/A // the exception notified to the postVisitDirectory method
893N/A IOException ioe = null;
893N/A
893N/A // invoke preVisitDirectory and then visit each entry
893N/A try {
2826N/A result = visitor.preVisitDirectory(file, attrs);
893N/A if (result != FileVisitResult.CONTINUE) {
893N/A return result;
893N/A }
893N/A
893N/A try {
893N/A for (Path entry: stream) {
1658N/A result = walk(entry, depth+1, ancestors);
893N/A
893N/A // returning null will cause NPE to be thrown
893N/A if (result == null || result == FileVisitResult.TERMINATE)
893N/A return result;
893N/A
893N/A // skip remaining siblings in this directory
893N/A if (result == FileVisitResult.SKIP_SIBLINGS)
893N/A break;
893N/A }
2826N/A } catch (DirectoryIteratorException e) {
2826N/A // IOException will be notified to postVisitDirectory
2826N/A ioe = e.getCause();
893N/A }
893N/A } finally {
893N/A try {
893N/A stream.close();
3471N/A } catch (IOException e) {
3471N/A // IOException will be notified to postVisitDirectory
3471N/A if (ioe == null)
3471N/A ioe = e;
3471N/A }
893N/A }
893N/A
893N/A // invoke postVisitDirectory last
893N/A return visitor.postVisitDirectory(file, ioe);
893N/A
893N/A } finally {
893N/A // remove key from trail if doing cycle detection
2826N/A if (followLinks) {
893N/A ancestors.remove(ancestors.size()-1);
893N/A }
893N/A }
893N/A }
893N/A
893N/A private static class AncestorDirectory {
1319N/A private final Path dir;
893N/A private final Object key;
1319N/A AncestorDirectory(Path dir, Object key) {
893N/A this.dir = dir;
893N/A this.key = key;
893N/A }
1319N/A Path file() {
893N/A return dir;
893N/A }
893N/A Object fileKey() {
893N/A return key;
893N/A }
893N/A }
893N/A}