changeset 8404:92f1d0575ea4

8006884: (fs) Add Files.list, lines and find Summary: adapt latest FileTreeWalker Contributed-by: alan.bateman@oracle.com
author henryjen
date Thu, 25 Apr 2013 23:29:30 -0700
parents 507c7f5e073d
children 56778384450d
files src/share/classes/java/nio/file/FileTreeIterator.java src/share/classes/java/nio/file/FileTreeWalker.java src/share/classes/java/nio/file/Files.java
diffstat 3 files changed, 115 insertions(+), 431 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/java/nio/file/FileTreeIterator.java	Thu Apr 25 22:33:41 2013 -0700
+++ b/src/share/classes/java/nio/file/FileTreeIterator.java	Thu Apr 25 23:29:30 2013 -0700
@@ -28,416 +28,97 @@
 import java.io.Closeable;
 import java.io.IOException;
 import java.io.UncheckedIOException;
-import java.util.ArrayDeque;
+import java.util.Arrays;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 import java.util.Objects;
-import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.FileTreeWalker.Event;
 
-class FileTreeIterator implements Iterator<FileTreeIterator.Entry>, Closeable {
+/**
+ * An {@code Iterator to iterate over the nodes of a file tree.
+ *
+ * <pre>{@code
+ *     try (FileTreeIterator iterator = new FileTreeIterator(start, maxDepth, options)) {
+ *         while (iterator.hasNext()) {
+ *             Event ev = iterator.next();
+ *             Path path = ev.file();
+ *             BasicFileAttributes attrs = ev.attributes();
+ *         }
+ *     }
+ * }</pre>
+ */
+
+class FileTreeIterator implements Iterator<Event>, Closeable {
+    private final FileTreeWalker walker;
+    private Event next;
 
     /**
-     * A pair of {@code Path} and its {@code BasicFileAttributes}.
+     * Creates a new iterator to walk the file tree starting at the given file.
+     *
+     * @throws  IllegalArgumentException
+     *          if {@code maxDepth} is negative
+     * @throws  IOException
+     *          if an I/O errors occurs opening the starting file
+     * @throws  SecurityException
+     *          if the security manager denies access to the starting file
+     * @throws  NullPointerException
+     *          if {@code start} or {@code options} is {@ocde null} or
+     *          the options array contains a {@code null} element
      */
-    static class Entry {
-        private final Path file;
-        private final BasicFileAttributes attrs;
-        // Latched exception thrown when tried to read the BasicFileAttributes
-        private RuntimeException latched_ex;
+    FileTreeIterator(Path start, int maxDepth, FileVisitOption... options)
+        throws IOException
+    {
+        this.walker = new FileTreeWalker(Arrays.asList(options), maxDepth);
+        this.next = walker.walk(start);
+        assert next.type() == FileTreeWalker.EventType.ENTRY ||
+               next.type() == FileTreeWalker.EventType.START_DIRECTORY;
 
-        Entry(Path file, BasicFileAttributes attrs) {
-            this.file = Objects.requireNonNull(file);
-            this.attrs = attrs;
-            latched_ex = null;
-        }
+        // IOException if there a problem accessing the starting file
+        IOException ioe = next.ioeException();
+        if (ioe != null)
+            throw ioe;
+    }
 
-        Entry(Path file, RuntimeException ex) {
-            this.file = Objects.requireNonNull(file);
-            this.latched_ex = Objects.requireNonNull(ex);
-            attrs = null;
-        }
+    private void fetchNextIfNeeded() {
+        if (next == null) {
+            FileTreeWalker.Event ev = walker.next();
+            while (ev != null) {
+                IOException ioe = ev.ioeException();
+                if (ioe != null)
+                    throw new UncheckedIOException(ioe);
 
-        static Entry make(Path file, boolean followLinks) {
-            Objects.requireNonNull(file);
-            BasicFileAttributes attrs;
-            try {
-                if (followLinks) {
-                    try {
-                        attrs = Files.readAttributes(file, BasicFileAttributes.class);
-                        return new Entry(file, attrs);
-                    } catch (IOException notcare) {
-                        // ignore, try not to follow link
-                    }
+                // END_DIRECTORY events are ignored
+                if (ev.type() != FileTreeWalker.EventType.END_DIRECTORY) {
+                    next = ev;
+                    return;
                 }
-                attrs = Files.readAttributes(file, BasicFileAttributes.class,
-                                             LinkOption.NOFOLLOW_LINKS);
-                return new Entry(file, attrs);
-            } catch (IOException ioe) {
-                return new Entry(file, new UncheckedIOException(ioe));
-            } catch (RuntimeException ex) {
-                return new Entry(file, ex);
-            }
-        }
-
-        public Entry ignoreException() {
-            latched_ex = null;
-            return this;
-        }
-
-        public Path getPath() {
-            return file;
-        }
-
-        /**
-         * Could return null if ignoreException
-         */
-        public BasicFileAttributes getFileAttributes() {
-            if (latched_ex != null) {
-                throw latched_ex;
-            }
-            return attrs;
-        }
-
-        public void checkException() throws IOException {
-            if (latched_ex != null) {
-                if (latched_ex instanceof UncheckedIOException) {
-                    throw ((UncheckedIOException) latched_ex).getCause();
-                } else {
-                    throw latched_ex;
-                }
+                ev = walker.next();
             }
         }
     }
 
-    private static class Context {
-        final Path file;
-        final BasicFileAttributes attrs;
-        final DirectoryStream<Path> ds;
-        final Iterator<Path> itor;
-
-        Context(Path file, BasicFileAttributes attrs, DirectoryStream<Path> ds, Iterator<Path> itor) {
-            this.file = file;
-            this.attrs = attrs;
-            this.ds = ds;
-            this.itor = itor;
-        }
+    @Override
+    public boolean hasNext() {
+        if (!walker.isOpen())
+            throw new IllegalStateException();
+        fetchNextIfNeeded();
+        return next != null;
     }
 
-    private static class VisitorException extends RuntimeException {
-        VisitorException(IOException ioe) {
-            super(ioe);
-        }
-
-        @Override
-        public IOException getCause() {
-            return (IOException) super.getCause();
-        }
+    @Override
+    public Event next() {
+        if (!walker.isOpen())
+            throw new IllegalStateException();
+        fetchNextIfNeeded();
+        if (next == null)
+            throw new NoSuchElementException();
+        Event result = next;
+        next = null;
+        return result;
     }
 
-    private final boolean followLinks;
-    private final int maxDepth;
-    private final ArrayDeque<Context> stack = new ArrayDeque<>();
-
-    private FileVisitor<Path> visitorProxy;
-    private Entry next;
-
-    private FileTreeIterator(int maxDepth,
-                             FileVisitOption... options) {
-        this.maxDepth = maxDepth;
-
-        boolean follow = false;
-        for (FileVisitOption opt : options) {
-            switch(opt) {
-                case FOLLOW_LINKS:
-                    follow = true;
-                    break;
-                default:
-                    // nothing should be here
-                    break;
-            }
-        }
-        this.followLinks = follow;
+    @Override
+    public void close() {
+        walker.close();
     }
-
-    private FileTreeIterator init(Path start, FileVisitor<? super Path> visitor) throws IOException {
-        next = Entry.make(start, followLinks);
-        try {
-            next.checkException();
-        } catch (SecurityException se) {
-            // First level, re-throw it.
-            throw se;
-        } catch (IOException ioe) {
-            if (visitor != null) {
-                visitor.visitFileFailed(start, ioe);
-            } else {
-                throw ioe;
-            }
-        }
-
-        // Wrap IOException in VisitorException so we can throw from hasNext()
-        // and distinguish them for re-throw later.
-        // For non-proxy mode, exception come in should be re-thrown so the caller know
-        // it is not processed and deal with it accordingly.
-        visitorProxy = new FileVisitor<Path>() {
-            public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes attrs) {
-                if (visitor != null) {
-                    try {
-                        return visitor.preVisitDirectory(path, attrs);
-                    } catch (IOException ex) {
-                        throw new VisitorException(ex);
-                    }
-                }
-                return FileVisitResult.CONTINUE;
-            }
-
-            public FileVisitResult postVisitDirectory(Path path, IOException exc) throws IOException {
-                if (visitor != null) {
-                    try {
-                        return visitor.postVisitDirectory(path, exc);
-                    } catch (IOException ex) {
-                        throw new VisitorException(ex);
-                    }
-                } else if (exc != null) {
-                    throw exc;
-                }
-                return FileVisitResult.CONTINUE;
-            }
-
-            public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) {
-                if (visitor != null) {
-                    try {
-                        return visitor.visitFile(path, attrs);
-                    } catch (IOException ex) {
-                        throw new VisitorException(ex);
-                    }
-                }
-                return FileVisitResult.CONTINUE;
-            }
-
-            public FileVisitResult visitFileFailed(Path path, IOException exc) throws IOException {
-                if (visitor != null) {
-                    try {
-                        return visitor.visitFileFailed(path, exc);
-                    } catch (IOException ex) {
-                        throw new VisitorException(ex);
-                    }
-                } else if (exc != null) {
-                    throw exc;
-                }
-                return FileVisitResult.CONTINUE;
-            }
-        };
-
-        // Setup first visit for directory
-        visitNext();
-
-        return this;
-    }
-
-    public static FileTreeIterator iterator(Path start, int maxDepth,
-                                            FileVisitOption... options) throws IOException {
-        return new FileTreeIterator(maxDepth, options).init(start, null);
-    }
-
-    public static void walkThrough(Path start, int maxDepth,
-                                   FileVisitor visitor,
-                                   FileVisitOption... options) throws IOException {
-        Objects.requireNonNull(visitor);
-        FileTreeIterator itor = new FileTreeIterator(maxDepth, options).init(start, visitor);
-        try {
-            while (itor.hasNext()) {
-                itor.next();
-            }
-        } catch (VisitorException ex) {
-            // Only VisitorException is processed here as others should be
-            // handled by FileVisitor already.
-            throw ex.getCause();
-        }
-    }
-
-    private boolean detectLoop(Path dir, BasicFileAttributes attrs) {
-        Object key = attrs.fileKey();
-        for (Context ctx : stack) {
-            Object ancestorKey = ctx.attrs.fileKey();
-            if (key != null && ancestorKey != null) {
-                if (key.equals(ancestorKey)) {
-                    return true;
-                }
-            } else {
-                boolean isSameFile = false;
-                try {
-                    isSameFile = Files.isSameFile(dir, ctx.file);
-                } catch (IOException x) {
-                    // ignore
-                } catch (SecurityException x) {
-                    // ignore
-                }
-                if (isSameFile) {
-                    return true;
-                }
-            }
-        }
-
-        return false;
-    }
-
-    private void evalVisitorResult(FileVisitResult result) {
-        Objects.requireNonNull(result);
-        switch (result) {
-            case TERMINATE:
-                try {
-                    close();
-                } catch (IOException ioe) {
-                    // ignore
-                }
-                break;
-            case SKIP_SIBLINGS:
-            case SKIP_SUBTREE:
-                // stop iterate in the containing folder
-                if (! stack.isEmpty()) {
-                    exitDirectory(null);
-                }
-                break;
-            case CONTINUE:
-                break;
-        }
-    }
-
-    private void enteringDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
-        // Detect loop when follow links
-        if (followLinks && detectLoop(dir, attrs)) {
-            // Loop detected
-            throw new FileSystemLoopException(dir.toString());
-            // ?? skip is better ??
-            // return;
-        }
-
-        DirectoryStream<Path> ds = Files.newDirectoryStream(dir);
-        stack.push(new Context(dir, attrs, ds, ds.iterator()));
-    }
-
-    private void exitDirectory(DirectoryIteratorException die) {
-        Context ctx = stack.pop();
-        IOException failure = (die == null) ? null : die.getCause();
-
-        try {
-            ctx.ds.close();
-        } catch (IOException ioe) {
-            if (failure != null) {
-                failure = ioe;
-            }
-        }
-
-        try {
-            evalVisitorResult(visitorProxy.postVisitDirectory(ctx.file, failure));
-        } catch (IOException ex) {
-            throw new UncheckedIOException(ex);
-            // retain DirectoryIteratorException information ?
-            // throw (die == null) ? new UncheckedIOException(ex) : die;
-        }
-    }
-
-    private void visitNext() {
-        Path p = next.file;
-        try {
-            BasicFileAttributes attrs;
-            try {
-                attrs = next.getFileAttributes();
-            } catch (UncheckedIOException uioe) {
-                throw uioe.getCause();
-            }
-            if (attrs.isDirectory() && stack.size() < maxDepth) {
-                enteringDirectory(p, attrs);
-                FileVisitResult result = visitorProxy.preVisitDirectory(p, attrs);
-                // Simply undo enter, not calling postVisitDirectory
-                if (FileVisitResult.CONTINUE != result) {
-                    Context ctx = stack.pop();
-                    try {
-                        ctx.ds.close();
-                    } catch (IOException ioe) {
-                        // ignore
-                    }
-                }
-                // deal result from containing folder
-                evalVisitorResult(result);
-            } else {
-                evalVisitorResult(visitorProxy.visitFile(p, attrs));
-            }
-        } catch (IOException ioe) {
-            try {
-                evalVisitorResult(visitorProxy.visitFileFailed(p, ioe));
-            } catch (IOException ioe2) {
-                throw new UncheckedIOException(ioe2);
-            }
-        }
-    }
-
-    /**
-     * When there is an exception occurred, we will try to resume the iteration
-     * to next element. So the exception is thrown, and next call to hasNext()
-     * will continue the iteration.
-     */
-    public boolean hasNext() {
-        // next was read-ahead, not yet fetched.
-        if (next != null) {
-            return true;
-        }
-
-        // Check if iterator had been closed.
-        if (stack.isEmpty()) {
-            return false;
-        }
-
-        Iterator<Path> itor = stack.peek().itor;
-        try {
-            Path p = itor.next();
-            next = Entry.make(p, followLinks);
-            visitNext();
-        } catch (SecurityException se) {
-            // ignore and skip this file
-            next = null;
-            return hasNext();
-        } catch (DirectoryIteratorException die) {
-            // try to resume from level above
-            exitDirectory(die);
-        } catch (NoSuchElementException nsee) {
-            // nothing left at this level
-            exitDirectory(null);
-        }
-        return stack.isEmpty() ? false : hasNext();
-    }
-
-    public Entry next() {
-        if (next != null || hasNext()) {
-            try {
-                return next;
-            } finally {
-                next = null;
-            }
-        } else {
-            throw new NoSuchElementException();
-        }
-    }
-
-    public void close() throws IOException {
-        IOException ioe = null;
-
-        for (Context ctx : stack) {
-            try {
-                ctx.ds.close();
-            } catch (IOException ex) {
-                // ignore so we try to close all DirectoryStream
-                // keep the last exception to throw later
-                ioe = ex;
-            }
-        }
-
-        next = null;
-        stack.clear();
-
-        if (ioe != null) {
-            // Throw at least one if there is any
-            throw ioe;
-        }
-    }
-}
\ No newline at end of file
+}
--- a/src/share/classes/java/nio/file/FileTreeWalker.java	Thu Apr 25 22:33:41 2013 -0700
+++ b/src/share/classes/java/nio/file/FileTreeWalker.java	Thu Apr 25 23:29:30 2013 -0700
@@ -29,8 +29,8 @@
 import java.io.Closeable;
 import java.io.IOException;
 import java.util.ArrayDeque;
+import java.util.Collection;
 import java.util.Iterator;
-import java.util.Set;
 import sun.nio.fs.BasicFileAttributesHolder;
 
 /**
@@ -164,8 +164,17 @@
 
     /**
      * Creates a {@code FileTreeWalker}.
+     *
+     * @throws  IllegalArgumentException
+     *          if {@code maxDepth} is negative
+     * @throws  ClassCastException
+     *          if (@code options} contains an element that is not a
+     *          {@code FileVisitOption}
+     * @throws  NullPointerException
+     *          if {@code options} is {@ocde null} or the options
+     *          array contains a {@code null} element
      */
-    FileTreeWalker(Set<FileVisitOption> options, int maxDepth) {
+    FileTreeWalker(Collection<FileVisitOption> options, int maxDepth) {
         boolean fl = false;
         for (FileVisitOption option: options) {
             // will throw NPE if options contains null
@@ -175,6 +184,9 @@
                     throw new AssertionError("Should not get here");
             }
         }
+        if (maxDepth < 0)
+            throw new IllegalArgumentException("'maxDepth' is negative");
+
         this.followLinks = fl;
         this.linkOptions = (fl) ? new LinkOption[0] :
             new LinkOption[] { LinkOption.NOFOLLOW_LINKS };
--- a/src/share/classes/java/nio/file/Files.java	Thu Apr 25 22:33:41 2013 -0700
+++ b/src/share/classes/java/nio/file/Files.java	Thu Apr 25 23:29:30 2013 -0700
@@ -2594,9 +2594,6 @@
                                     FileVisitor<? super Path> visitor)
         throws IOException
     {
-        if (maxDepth < 0)
-            throw new IllegalArgumentException("'maxDepth' is negative");
-
         /**
          * Create a FileTreeWalker to walk the file tree, invoking the visitor
          * for each event.
@@ -3186,10 +3183,11 @@
     }
 
     // -- Stream APIs --
+
     /**
      * Implementation of CloseableStream
      */
-    static class DelegatingCloseableStream<T> extends DelegatingStream<T>
+    private static class DelegatingCloseableStream<T> extends DelegatingStream<T>
         implements CloseableStream<T>
     {
         private final Closeable closeable;
@@ -3229,13 +3227,14 @@
      * directory.
      *
      * <p> If an {@link IOException} is thrown when accessing the directory
-     * after returned from this method, it is wrapped in an {@link
+     * after this method has returned, it is wrapped in an {@link
      * UncheckedIOException} which will be thrown from the method that caused
      * the access to take place.
      *
      * @param   dir  The path to the directory
-     * @return       The {@link CloseableStream} describing the content of the
-     *               directory
+     *
+     * @return  The {@code CloseableStream} describing the content of the
+     *          directory
      *
      * @throws  NotDirectoryException
      *          if the file could not otherwise be opened because it is not
@@ -3254,21 +3253,21 @@
     public static CloseableStream<Path> list(Path dir) throws IOException {
         DirectoryStream<Path> ds = Files.newDirectoryStream(dir);
         final Iterator<Path> delegate = ds.iterator();
+
         // Re-wrap DirectoryIteratorException to UncheckedIOException
         Iterator<Path> it = new Iterator<Path>() {
             public boolean hasNext() {
                 try {
                     return delegate.hasNext();
-                } catch (DirectoryIteratorException die) {
-                    throw new UncheckedIOException(die.getCause());
+                } catch (DirectoryIteratorException e) {
+                    throw new UncheckedIOException(e.getCause());
                 }
             }
-
             public Path next() {
                 try {
                     return delegate.next();
-                } catch (DirectoryIteratorException die) {
-                    throw new UncheckedIOException(die.getCause());
+                } catch (DirectoryIteratorException e) {
+                    throw new UncheckedIOException(e.getCause());
                 }
             }
         };
@@ -3332,7 +3331,7 @@
      * directory.
      *
      * <p> If an {@link IOException} is thrown when accessing the directory
-     * after returned from this method, it is wrapped in an {@link
+     * after this method has returned, it is wrapped in an {@link
      * UncheckedIOException} which will be thrown from the method that caused
      * the access to take place.
      *
@@ -3360,14 +3359,10 @@
                                              FileVisitOption... options)
         throws IOException
     {
-        if (maxDepth < 0) {
-            throw new IllegalArgumentException("'maxDepth' is negative");
-        }
-
-        FileTreeIterator itor = FileTreeIterator.iterator(start, maxDepth, options);
-        return new DelegatingCloseableStream<>(itor,
-            StreamSupport.stream(Spliterators.spliteratorUnknownSize(itor, Spliterator.DISTINCT))
-                   .map(entry -> entry.getPath()));
+        FileTreeIterator iterator = new FileTreeIterator(start, maxDepth, options);
+        return new DelegatingCloseableStream<>(iterator,
+            StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.DISTINCT))
+                   .map(entry -> entry.file()));
     }
 
     /**
@@ -3458,31 +3453,27 @@
                                              FileVisitOption... options)
         throws IOException
     {
-        if (maxDepth < 0) {
-            throw new IllegalArgumentException("'maxDepth' is negative");
-        }
-        FileTreeIterator itor = FileTreeIterator.iterator(start, maxDepth, options);
-        return new DelegatingCloseableStream<>(itor,
-            StreamSupport.stream(Spliterators.spliteratorUnknownSize(itor, Spliterator.DISTINCT))
-                   .filter(entry -> matcher.test(entry.getPath(), entry.getFileAttributes()))
-                   .map(entry -> entry.getPath()));
+        FileTreeIterator iterator = new FileTreeIterator(start, maxDepth, options);
+        return new DelegatingCloseableStream<>(iterator,
+            StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.DISTINCT))
+                   .filter(entry -> matcher.test(entry.file(), entry.attributes()))
+                   .map(entry -> entry.file()));
     }
 
     /**
      * Read all lines from a file as a {@code CloseableStream}.  Unlike {@link
      * #readAllLines(Path, Charset) readAllLines}, this method does not read
-     * all lines into a {@code List}, but populate lazily as the stream is
-     * consumed.
+     * all lines into a {@code List}, but instead populates lazily as the stream
+     * is consumed.
      *
      * <p> Bytes from the file are decoded into characters using the specified
      * charset and the same line terminators as specified by {@code
      * readAllLines} are supported.
      *
-     * This method would throw an {@link java.io.IOException} if an error
-     * occurs when opening the file. After returned from this method, if an
-     * I/O error occurs while reading from the file or a malformed or
-     * unmappable byte sequence is read, the {@code IOException} is wrapped in
-     * an {@link java.io.UncheckedIOException} which will be thrown from the
+     * <p> After this method returns, then any subsequent I/O exception that
+     * occurs while reading from the file or when a malformed or unmappable byte
+     * sequence is read, is wrapped in an {@link UncheckedIOException} that will
+     * be thrown form the
      * {@link java.util.stream.Stream} method that caused the read to take
      * place. In case an {@code IOException} is thrown when closing the file,
      * it is also wrapped as an {@code UncheckedIOException}.