--- old/src/java.base/share/classes/java/nio/file/FileTreeIterator.java 2015-07-02 15:16:17.613612918 +0200 +++ new/src/java.base/share/classes/java/nio/file/FileTreeIterator.java 2015-07-02 15:16:17.543612465 +0200 @@ -33,6 +33,7 @@ import java.util.NoSuchElementException; import java.util.Objects; import java.nio.file.FileTreeWalker.Event; +import java.util.function.Consumer; /** * An {@code Iterator to iterate over the nodes of a file tree. @@ -49,6 +50,7 @@ */ class FileTreeIterator implements Iterator, Closeable { + private final Consumer ioExceptionHandler; private final FileTreeWalker walker; private Event next; @@ -57,26 +59,38 @@ * * @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 + * if {@code start} or {@code options} or {@code ioExceptionHandler} + * is {@code null} or the options array contains a {@code null} element */ - FileTreeIterator(Path start, int maxDepth, FileVisitOption... options) - throws IOException + FileTreeIterator(Path start, int maxDepth, + Consumer ioExceptionHandler, + FileVisitOption... options) { + this.ioExceptionHandler = Objects.requireNonNull(ioExceptionHandler); 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; - // IOException if there a problem accessing the starting file + // handle IOException if there a problem accessing the starting file IOException ioe = next.ioeException(); - if (ioe != null) - throw ioe; + if (ioe != null) { + next = null; + try { + ioExceptionHandler.accept(ioe); + } catch (Throwable t) { + // clean-up when constructors throws + try { + close(); + } catch (Throwable ct) { + t.addSuppressed(ct); + } + throw t; + } + } } private void fetchNextIfNeeded() { @@ -84,11 +98,11 @@ FileTreeWalker.Event ev = walker.next(); while (ev != null) { IOException ioe = ev.ioeException(); - if (ioe != null) - throw new UncheckedIOException(ioe); - + if (ioe != null) { + ioExceptionHandler.accept(ioe); + } // END_DIRECTORY events are ignored - if (ev.type() != FileTreeWalker.EventType.END_DIRECTORY) { + else if (ev.type() != FileTreeWalker.EventType.END_DIRECTORY) { next = ev; return; } --- old/src/java.base/share/classes/java/nio/file/Files.java 2015-07-02 15:16:17.824614283 +0200 +++ new/src/java.base/share/classes/java/nio/file/Files.java 2015-07-02 15:16:17.753613823 +0200 @@ -74,6 +74,7 @@ import java.util.Spliterator; import java.util.Spliterators; import java.util.function.BiPredicate; +import java.util.function.Consumer; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -3570,7 +3571,122 @@ *

The returned stream contains references to one or more open directories. * The directories are closed by closing the stream. * - *

If an {@link IOException} is thrown when accessing the directory + *

If an {@link IOException} is thrown when accessing a file or directory, + * it is passed to given {@code ioExceptionHandler}. + * If this handler throws an unchecked exception it will be thrown from the + * method that caused the access to take place. If the handler doesn't throw + * any exception then the elements of the stream that caused the IOException(s) + * by accessing them are absent from the stream. This may include entire subtrees + * rooted at directories that caused IOException(s) by accessing them. + * The 1st access to starting file takes place in this method. + * + * @apiNote + * This method must be used within a try-with-resources statement or similar + * control structure to ensure that the stream's open directories are closed + * promptly after the stream's operations have completed. + * + * @param start + * the starting file + * @param maxDepth + * the maximum number of directory levels to visit + * @param ioExceptionHandler + * the consumer used to handle any IOException(s) that are thrown + * while walking the file tree + * @param options + * options to configure the traversal + * + * @return the {@link Stream} of {@link Path} + * + * @throws IllegalArgumentException + * if the {@code maxDepth} parameter is negative + * @throws NullPointerException + * if {@code start} or {@code ioExceptionHandler} + * or {@code options} is {@code null} or the options array contains + * a {@code null} element + * @throws SecurityException + * If the security manager denies access to the starting file. + * In the case of the default provider, the {@link + * SecurityManager#checkRead(String) checkRead} method is invoked + * to check read access to the directory. + * @since 1.9 + * @see #walk(Path, int, FileVisitOption...) + */ + public static Stream walk(Path start, + int maxDepth, + Consumer ioExceptionHandler, + FileVisitOption... options) + { + FileTreeIterator iterator = new FileTreeIterator(start, maxDepth, + ioExceptionHandler, + options); + try { + Spliterator spliterator = + Spliterators.spliteratorUnknownSize(iterator, Spliterator.DISTINCT); + return StreamSupport.stream(spliterator, false) + .onClose(iterator::close) + .map(entry -> entry.file()); + } catch (Throwable t) { + try { + iterator.close(); + } catch (Throwable ct) { + t.addSuppressed(ct); + } + throw t; + } + } + + /** + * Return a {@code Stream} that is lazily populated with {@code + * Path} by walking the file tree rooted at a given starting file. The + * file tree is traversed depth-first, the elements in the stream + * are {@link Path} objects that are obtained as if by {@link + * Path#resolve(Path) resolving} the relative path against {@code start}. + * + *

The {@code stream} walks the file tree as elements are consumed. + * The {@code Stream} returned is guaranteed to have at least one + * element, the starting file itself. For each file visited, the stream + * attempts to read its {@link BasicFileAttributes}. If the file is a + * directory and can be opened successfully, entries in the directory, and + * their descendants will follow the directory in the stream as + * they are encountered. When all entries have been visited, then the + * directory is closed. The file tree walk then continues at the next + * sibling of the directory. + * + *

The stream is weakly consistent. It does not freeze the + * file tree while iterating, so it may (or may not) reflect updates to + * the file tree that occur after returned from this method. + * + *

By default, symbolic links are not automatically followed by this + * method. If the {@code options} parameter contains the {@link + * FileVisitOption#FOLLOW_LINKS FOLLOW_LINKS} option then symbolic links are + * followed. When following links, and the attributes of the target cannot + * be read, then this method attempts to get the {@code BasicFileAttributes} + * of the link. + * + *

If the {@code options} parameter contains the {@link + * FileVisitOption#FOLLOW_LINKS FOLLOW_LINKS} option then the stream keeps + * track of directories visited so that cycles can be detected. A cycle + * arises when there is an entry in a directory that is an ancestor of the + * directory. Cycle detection is done by recording the {@link + * java.nio.file.attribute.BasicFileAttributes#fileKey file-key} of directories, + * or if file keys are not available, by invoking the {@link #isSameFile + * isSameFile} method to test if a directory is the same file as an + * ancestor. When a cycle is detected it is treated as an I/O error with + * an instance of {@link FileSystemLoopException}. + * + *

The {@code maxDepth} parameter is the maximum number of levels of + * directories to visit. A value of {@code 0} means that only the starting + * file is visited, unless denied by the security manager. A value of + * {@link Integer#MAX_VALUE MAX_VALUE} may be used to indicate that all + * levels should be visited. + * + *

When a security manager is installed and it denies access to a file + * (or directory), then it is ignored and not included in the stream. + * + *

The returned stream contains references to one or more open directories. + * The directories are closed by closing the stream. + * + *

If an {@link IOException} is thrown when accessing a file or directory * 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. @@ -3591,6 +3707,10 @@ * * @throws IllegalArgumentException * if the {@code maxDepth} parameter is negative + * @throws NullPointerException + * if {@code start} or {@code options} + * is {@code null} or the options array contains + * a {@code null} element * @throws SecurityException * If the security manager denies access to the starting file. * In the case of the default provider, the {@link @@ -3605,16 +3725,12 @@ FileVisitOption... options) throws IOException { - FileTreeIterator iterator = new FileTreeIterator(start, maxDepth, options); try { - Spliterator spliterator = - Spliterators.spliteratorUnknownSize(iterator, Spliterator.DISTINCT); - return StreamSupport.stream(spliterator, false) - .onClose(iterator::close) - .map(entry -> entry.file()); - } catch (Error|RuntimeException e) { - iterator.close(); - throw e; + return walk(start, maxDepth, + ioe -> { throw new UncheckedIOException(ioe); }, + options); + } catch (UncheckedIOException uioe) { + throw uioe.getCause(); } } @@ -3647,6 +3763,10 @@ * * @return the {@link Stream} of {@link Path} * + * @throws NullPointerException + * if {@code start} or {@code options} + * is {@code null} or the options array contains + * a {@code null} element * @throws SecurityException * If the security manager denies access to the starting file. * In the case of the default provider, the {@link @@ -3681,7 +3801,96 @@ *

The returned stream contains references to one or more open directories. * The directories are closed by closing the stream. * - *

If an {@link IOException} is thrown when accessing the directory + *

If an {@link IOException} is thrown when accessing a file or directory, + * it is passed to given {@code ioExceptionHandler}. + * If this handler throws an unchecked exception it will be thrown from the + * method that caused the access to take place. If the handler doesn't throw + * any exception then the elements of the stream that caused the IOException(s) + * by accessing them are absent from the stream. This may include entire subtrees + * rooted at directories that caused IOException(s) by accessing them. + * The 1st access to starting file takes place in this method. + * + * @apiNote + * This method must be used within a try-with-resources statement or similar + * control structure to ensure that the stream's open directories are closed + * promptly after the stream's operations have completed. + * + * @param start + * the starting file + * @param maxDepth + * the maximum number of directory levels to search + * @param matcher + * the function used to decide whether a file should be included + * in the returned stream + * @param ioExceptionHandler + * the consumer used to handle any IOException(s) that are thrown + * while walking the file tree + * @param options + * options to configure the traversal + * + * @return the {@link Stream} of {@link Path} + * + * @throws IllegalArgumentException + * if the {@code maxDepth} parameter is negative + * @throws NullPointerException + * if {@code start} or {@code matcher} or {@code ioExceptionHandler} + * or {@code options} is {@code null} or the options array contains + * a {@code null} element + * @throws SecurityException + * If the security manager denies access to the starting file. + * In the case of the default provider, the {@link + * SecurityManager#checkRead(String) checkRead} method is invoked + * to check read access to the directory. + * + * @see #find(Path, int, BiPredicate, FileVisitOption...) + * @see #walk(Path, int, FileVisitOption...) + * @since 1.9 + */ + public static Stream find(Path start, + int maxDepth, + BiPredicate matcher, + Consumer ioExceptionHandler, + FileVisitOption... options) + { + Objects.requireNonNull(ioExceptionHandler); + FileTreeIterator iterator = new FileTreeIterator(start, maxDepth, ioExceptionHandler, options); + try { + Spliterator spliterator = + Spliterators.spliteratorUnknownSize(iterator, Spliterator.DISTINCT); + return StreamSupport.stream(spliterator, false) + .onClose(iterator::close) + .filter(entry -> matcher.test(entry.file(), entry.attributes())) + .map(FileTreeWalker.Event::file); + } catch (Throwable t) { + try { + iterator.close(); + } catch (Throwable ct) { + t.addSuppressed(ct); + } + throw t; + } + } + + /** + * Return a {@code Stream} that is lazily populated with {@code + * Path} by searching for files in a file tree rooted at a given starting + * file. + * + *

This method walks the file tree in exactly the manner specified by + * the {@link #walk walk} method. For each file encountered, the given + * {@link BiPredicate} is invoked with its {@link Path} and {@link + * BasicFileAttributes}. The {@code Path} object is obtained as if by + * {@link Path#resolve(Path) resolving} the relative path against {@code + * start} and is only included in the returned {@link Stream} if + * the {@code BiPredicate} returns true. Compare to calling {@link + * java.util.stream.Stream#filter filter} on the {@code Stream} + * returned by {@code walk} method, this method may be more efficient by + * avoiding redundant retrieval of the {@code BasicFileAttributes}. + * + *

The returned stream contains references to one or more open directories. + * The directories are closed by closing the stream. + * + *

If an {@link IOException} is thrown when accessing a file or directory * after returned from this method, it is wrapped in an {@link * UncheckedIOException} which will be thrown from the method that caused * the access to take place. @@ -3705,6 +3914,10 @@ * * @throws IllegalArgumentException * if the {@code maxDepth} parameter is negative + * @throws NullPointerException + * if {@code start} or {@code matcher} or {@code options} + * is {@code null} or the options array contains + * a {@code null} element * @throws SecurityException * If the security manager denies access to the starting file. * In the case of the default provider, the {@link @@ -3713,6 +3926,7 @@ * @throws IOException * if an I/O error is thrown when accessing the starting file. * + * @see #find(Path, int, BiPredicate, Consumer, FileVisitOption...) * @see #walk(Path, int, FileVisitOption...) * @since 1.8 */ @@ -3722,17 +3936,12 @@ FileVisitOption... options) throws IOException { - FileTreeIterator iterator = new FileTreeIterator(start, maxDepth, options); try { - Spliterator spliterator = - Spliterators.spliteratorUnknownSize(iterator, Spliterator.DISTINCT); - return StreamSupport.stream(spliterator, false) - .onClose(iterator::close) - .filter(entry -> matcher.test(entry.file(), entry.attributes())) - .map(entry -> entry.file()); - } catch (Error|RuntimeException e) { - iterator.close(); - throw e; + return find(start, maxDepth, matcher, + ioe -> { throw new UncheckedIOException(ioe); }, + options); + } catch (UncheckedIOException uioe) { + throw uioe.getCause(); } }