FileTreeWalker.java revision 907
893N/A/*
893N/A * Copyright 2007-2009 Sun Microsystems, Inc. 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
893N/A * published by the Free Software Foundation. Sun designates this
893N/A * particular file as subject to the "Classpath" exception as provided
893N/A * by Sun 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 *
893N/A * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
893N/A * CA 95054 USA or visit www.sun.com if you need additional information or
893N/A * have any 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 boolean detectCycles;
893N/A private final LinkOption[] linkOptions;
893N/A private final FileVisitor<? super Path> visitor;
893N/A
893N/A FileTreeWalker(Set<FileVisitOption> options, FileVisitor<? super Path> visitor) {
893N/A boolean fl = false;
893N/A boolean dc = false;
893N/A for (FileVisitOption option: options) {
893N/A switch (option) {
893N/A case FOLLOW_LINKS : fl = true; break;
893N/A case DETECT_CYCLES : dc = true; break;
893N/A default:
893N/A if (option == null)
893N/A throw new NullPointerException("Visit options contains 'null'");
893N/A throw new AssertionError("Should not get here");
893N/A }
893N/A }
893N/A this.followLinks = fl;
893N/A this.detectCycles = fl | dc;
893N/A this.linkOptions = (fl) ? new LinkOption[0] :
893N/A new LinkOption[] { LinkOption.NOFOLLOW_LINKS };
893N/A this.visitor = visitor;
893N/A }
893N/A
893N/A /**
893N/A * Walk file tree starting at the given file
893N/A */
893N/A void walk(Path start, int maxDepth) {
907N/A // don't use attributes of starting file as they may be stale
907N/A if (start instanceof BasicFileAttributesHolder) {
907N/A ((BasicFileAttributesHolder)start).invalidate();
907N/A }
893N/A FileVisitResult result = walk(start,
893N/A maxDepth,
893N/A new ArrayList<AncestorDirectory>());
893N/A if (result == null) {
893N/A throw new NullPointerException("Visitor returned 'null'");
893N/A }
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)
893N/A {
893N/A // depth check
893N/A if (depth-- < 0)
893N/A return FileVisitResult.CONTINUE;
893N/A
907N/A // if attributes are cached then use them if possible
893N/A BasicFileAttributes attrs = null;
907N/A if (file instanceof BasicFileAttributesHolder) {
907N/A BasicFileAttributes cached = ((BasicFileAttributesHolder)file).get();
907N/A if (!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 {
907N/A attrs = Attributes.readBasicFileAttributes(file, linkOptions);
907N/A } catch (IOException x1) {
907N/A if (followLinks) {
907N/A try {
907N/A attrs = Attributes
907N/A .readBasicFileAttributes(file, 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) {
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
893N/A // file is not a directory so invoke visitFile method
893N/A if (!attrs.isDirectory()) {
893N/A return visitor.visitFile(file, attrs);
893N/A }
893N/A
893N/A // check for cycles
893N/A if (detectCycles) {
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
893N/A return visitor.visitFile(file, attrs);
893N/A }
893N/A } else {
893N/A try {
893N/A if (file.isSameFile(ancestor.file())) {
893N/A // cycle detected
893N/A return visitor.visitFile(file, attrs);
893N/A }
893N/A } catch (IOException x) {
893N/A // ignore
893N/A } catch (SecurityException x) {
893N/A // ignore
893N/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 {
893N/A stream = file.newDirectoryStream();
893N/A } catch (IOException x) {
893N/A return visitor.preVisitDirectoryFailed(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 {
893N/A result = visitor.preVisitDirectory(file);
893N/A if (result != FileVisitResult.CONTINUE) {
893N/A return result;
893N/A }
893N/A
893N/A // if an I/O occurs during iteration then a CME is thrown. We
893N/A // need to distinguish this from a CME thrown by the visitor.
893N/A boolean inAction = false;
893N/A
893N/A try {
893N/A for (Path entry: stream) {
893N/A inAction = true;
893N/A result = walk(entry, depth, ancestors);
893N/A inAction = false;
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 }
893N/A } catch (ConcurrentModificationException x) {
893N/A // if CME thrown because the iteration failed then remember
893N/A // the IOException so that it is notified to postVisitDirectory
893N/A if (!inAction) {
893N/A // iteration failed
893N/A Throwable t = x.getCause();
893N/A if (t instanceof IOException)
893N/A ioe = (IOException)t;
893N/A }
893N/A if (ioe == null)
893N/A throw x;
893N/A }
893N/A } finally {
893N/A try {
893N/A stream.close();
893N/A } catch (IOException x) { }
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
893N/A if (detectCycles) {
893N/A ancestors.remove(ancestors.size()-1);
893N/A }
893N/A }
893N/A }
893N/A
893N/A private static class AncestorDirectory {
893N/A private final FileRef dir;
893N/A private final Object key;
893N/A AncestorDirectory(FileRef dir, Object key) {
893N/A this.dir = dir;
893N/A this.key = key;
893N/A }
893N/A FileRef file() {
893N/A return dir;
893N/A }
893N/A Object fileKey() {
893N/A return key;
893N/A }
893N/A }
893N/A}