jdk/src/share/classes/java/nio/file/FileTreeWalker.java
changeset 2057 3acf8e5e2ca0
child 2071 5e6af6d106cb
equal deleted inserted replaced
2056:115e09b7a004 2057:3acf8e5e2ca0
       
     1 /*
       
     2  * Copyright 2007-2009 Sun Microsystems, Inc.  All Rights Reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Sun designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Sun in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
       
    22  * CA 95054 USA or visit www.sun.com if you need additional information or
       
    23  * have any questions.
       
    24  */
       
    25 
       
    26 package java.nio.file;
       
    27 
       
    28 import java.nio.file.attribute.*;
       
    29 import java.io.IOException;
       
    30 import java.util.*;
       
    31 
       
    32 /**
       
    33  * Simple file tree walker that works in a similar manner to nftw(3C).
       
    34  *
       
    35  * @see Files#walkFileTree
       
    36  */
       
    37 
       
    38 class FileTreeWalker {
       
    39     private final boolean followLinks;
       
    40     private final boolean detectCycles;
       
    41     private final LinkOption[] linkOptions;
       
    42     private final FileVisitor<? super Path> visitor;
       
    43 
       
    44     FileTreeWalker(Set<FileVisitOption> options, FileVisitor<? super Path> visitor) {
       
    45         boolean fl = false;
       
    46         boolean dc = false;
       
    47         for (FileVisitOption option: options) {
       
    48             switch (option) {
       
    49                 case FOLLOW_LINKS  : fl = true; break;
       
    50                 case DETECT_CYCLES : dc = true; break;
       
    51                 default:
       
    52                     if (option == null)
       
    53                         throw new NullPointerException("Visit options contains 'null'");
       
    54                     throw new AssertionError("Should not get here");
       
    55             }
       
    56         }
       
    57         this.followLinks = fl;
       
    58         this.detectCycles = fl | dc;
       
    59         this.linkOptions = (fl) ? new LinkOption[0] :
       
    60             new LinkOption[] { LinkOption.NOFOLLOW_LINKS };
       
    61         this.visitor = visitor;
       
    62     }
       
    63 
       
    64     /**
       
    65      * Walk file tree starting at the given file
       
    66      */
       
    67     void walk(Path start, int maxDepth) {
       
    68         FileVisitResult result = walk(start,
       
    69                                       maxDepth,
       
    70                                       new ArrayList<AncestorDirectory>());
       
    71         if (result == null) {
       
    72             throw new NullPointerException("Visitor returned 'null'");
       
    73         }
       
    74     }
       
    75 
       
    76     /**
       
    77      * @param   file
       
    78      *          The directory to visit
       
    79      * @param   path
       
    80      *          list of directories that is relative path from starting file
       
    81      * @param   depth
       
    82      *          Depth remaining
       
    83      * @param   ancestors
       
    84      *          use when cycle detection is enabled
       
    85      */
       
    86     private FileVisitResult walk(Path file,
       
    87                                  int depth,
       
    88                                  List<AncestorDirectory> ancestors)
       
    89     {
       
    90         // depth check
       
    91         if (depth-- < 0)
       
    92             return FileVisitResult.CONTINUE;
       
    93 
       
    94         BasicFileAttributes attrs = null;
       
    95         IOException exc = null;
       
    96 
       
    97         // attempt to get attributes of file. If fails and we are following
       
    98         // links then a link target might not exist so get attributes of link
       
    99         try {
       
   100             try {
       
   101                 attrs = Attributes.readBasicFileAttributes(file, linkOptions);
       
   102             } catch (IOException x1) {
       
   103                 if (followLinks) {
       
   104                     try {
       
   105                         attrs = Attributes
       
   106                             .readBasicFileAttributes(file, LinkOption.NOFOLLOW_LINKS);
       
   107                     } catch (IOException x2) {
       
   108                         exc = x2;
       
   109                     }
       
   110                 } else {
       
   111                     exc = x1;
       
   112                 }
       
   113             }
       
   114         } catch (SecurityException x) {
       
   115             return FileVisitResult.CONTINUE;
       
   116         }
       
   117 
       
   118         // unable to get attributes of file
       
   119         if (exc != null) {
       
   120             return visitor.visitFileFailed(file, exc);
       
   121         }
       
   122 
       
   123         // file is not a directory so invoke visitFile method
       
   124         if (!attrs.isDirectory()) {
       
   125             return visitor.visitFile(file, attrs);
       
   126         }
       
   127 
       
   128         // check for cycles
       
   129         if (detectCycles) {
       
   130             Object key = attrs.fileKey();
       
   131 
       
   132             // if this directory and ancestor has a file key then we compare
       
   133             // them; otherwise we use less efficient isSameFile test.
       
   134             for (AncestorDirectory ancestor: ancestors) {
       
   135                 Object ancestorKey = ancestor.fileKey();
       
   136                 if (key != null && ancestorKey != null) {
       
   137                     if (key.equals(ancestorKey)) {
       
   138                         // cycle detected
       
   139                         return visitor.visitFile(file, attrs);
       
   140                     }
       
   141                 } else {
       
   142                     try {
       
   143                         if (file.isSameFile(ancestor.file())) {
       
   144                             // cycle detected
       
   145                             return visitor.visitFile(file, attrs);
       
   146                         }
       
   147                     } catch (IOException x) {
       
   148                         // ignore
       
   149                     } catch (SecurityException x) {
       
   150                         // ignore
       
   151                     }
       
   152                 }
       
   153             }
       
   154 
       
   155             ancestors.add(new AncestorDirectory(file, key));
       
   156         }
       
   157 
       
   158         // visit directory
       
   159         try {
       
   160             DirectoryStream<Path> stream = null;
       
   161             FileVisitResult result;
       
   162 
       
   163             // open the directory
       
   164             try {
       
   165                 stream = file.newDirectoryStream();
       
   166             } catch (IOException x) {
       
   167                 return visitor.preVisitDirectoryFailed(file, x);
       
   168             } catch (SecurityException x) {
       
   169                 // ignore, as per spec
       
   170                 return FileVisitResult.CONTINUE;
       
   171             }
       
   172 
       
   173             // the exception notified to the postVisitDirectory method
       
   174             IOException ioe = null;
       
   175 
       
   176             // invoke preVisitDirectory and then visit each entry
       
   177             try {
       
   178                 result = visitor.preVisitDirectory(file);
       
   179                 if (result != FileVisitResult.CONTINUE) {
       
   180                     return result;
       
   181                 }
       
   182 
       
   183                 // if an I/O occurs during iteration then a CME is thrown. We
       
   184                 // need to distinguish this from a CME thrown by the visitor.
       
   185                 boolean inAction = false;
       
   186 
       
   187                 try {
       
   188                     for (Path entry: stream) {
       
   189                         inAction = true;
       
   190                         result = walk(entry, depth, ancestors);
       
   191                         inAction = false;
       
   192 
       
   193                         // returning null will cause NPE to be thrown
       
   194                         if (result == null || result == FileVisitResult.TERMINATE)
       
   195                             return result;
       
   196 
       
   197                         // skip remaining siblings in this directory
       
   198                         if (result == FileVisitResult.SKIP_SIBLINGS)
       
   199                             break;
       
   200                     }
       
   201                 } catch (ConcurrentModificationException x) {
       
   202                     // if CME thrown because the iteration failed then remember
       
   203                     // the IOException so that it is notified to postVisitDirectory
       
   204                     if (!inAction) {
       
   205                         // iteration failed
       
   206                         Throwable t = x.getCause();
       
   207                         if (t instanceof IOException)
       
   208                             ioe = (IOException)t;
       
   209                     }
       
   210                     if (ioe == null)
       
   211                         throw x;
       
   212                 }
       
   213             } finally {
       
   214                 try {
       
   215                     stream.close();
       
   216                 } catch (IOException x) { }
       
   217             }
       
   218 
       
   219             // invoke postVisitDirectory last
       
   220             return visitor.postVisitDirectory(file, ioe);
       
   221 
       
   222         } finally {
       
   223             // remove key from trail if doing cycle detection
       
   224             if (detectCycles) {
       
   225                 ancestors.remove(ancestors.size()-1);
       
   226             }
       
   227         }
       
   228     }
       
   229 
       
   230     private static class AncestorDirectory {
       
   231         private final FileRef dir;
       
   232         private final Object key;
       
   233         AncestorDirectory(FileRef dir, Object key) {
       
   234             this.dir = dir;
       
   235             this.key = key;
       
   236         }
       
   237         FileRef file() {
       
   238             return dir;
       
   239         }
       
   240         Object fileKey() {
       
   241             return key;
       
   242         }
       
   243     }
       
   244 }