--- old/src/share/classes/java/io/BufferedReader.java 2013-01-25 23:38:50.891458171 -0800 +++ new/src/share/classes/java/io/BufferedReader.java 2013-01-25 23:38:50.707458174 -0800 @@ -529,19 +529,27 @@ } /** - * Returns a {@code Stream} describing lines read from this {@code - * BufferedReader}. The {@link Stream} is lazily populated via calls to - * {@link #readLine()}. - *

- * If an {@link IOException} is thrown when accessing the underlying {@code - * BufferedReader}, it is wrapped in an {@link UncheckedIOException} which - * will be thrown from the {@code Stream} method that caused the read to - * take place. + * Returns a {@code Stream}, the elemtns of which are lines read from this + * {@code BufferedReader}. The {@link Stream} is lazily populated via + * calls to {@link #readLine()}. * - * @since 1.8 + *

Each element consumed by the {@code Stream} caused a line to be + * read from this {@code BufferedReader}. Since the {@code Stream} does + * not necessary consume all lines, it is possible to mix and use + * different read methods on a {@code BufferedReader}. Each method will + * simply pick up from where it was left on last read. + * + *

If an {@link IOException} is thrown when accessing the underlying + * {@code BufferedReader}, it is wrapped in an {@link + * UncheckedIOException} which will be thrown from the {@code Stream} + * method that caused the read to take place. For example, when trying to + * read from the {@code Stream} after the {@code BufferedReader} is + * closed, will throw an {@code UnchecheckedIOException}. * - * @return a {@code Stream<String>} containing the lines of text + * @return a {@code Stream} containing the lines of text * described by this {@code BufferedReader} + * + * @since 1.8 */ public Stream lines() { Iterator iter = new Iterator() { --- old/src/share/classes/java/io/UncheckedIOException.java 2013-01-25 23:38:51.683458157 -0800 +++ new/src/share/classes/java/io/UncheckedIOException.java 2013-01-25 23:38:51.507458160 -0800 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,6 +24,8 @@ */ package java.io; +import java.util.Objects; + /** * Wraps an {@link IOException} with an unchecked exception * @@ -31,16 +33,59 @@ * @since JDK1.8 */ public class UncheckedIOException extends RuntimeException { + private static final long serialVersionUID = -8134305061645241065L; + + /** + * Constructs an instance of this class. + * + * @param message + * the detail message + * @param cause + * the {@code IOException} + * + * @throws NullPointerException + * if the cause is {@code null} + */ public UncheckedIOException(String message, IOException cause) { - super(message, cause); + super(message, Objects.requireNonNull(cause)); } + /** + * Constructs an instance of this class. + * + * @param cause + * the {@code IOException} + * + * @throws NullPointerException + * if the cause is {@code null} + */ public UncheckedIOException(IOException cause) { - super(cause); + super(Objects.requireNonNull(cause)); } + /** + * Returns the cause of this exception. + * + * @return the cause + */ @Override public IOException getCause() { return (IOException) super.getCause(); } + + /** + * Called to read the object from a stream. + * + * @throws InvalidObjectException + * if the object is invalid or has a cause that is not + * an {@code IOException} + */ + private void readObject(ObjectInputStream s) + throws IOException, ClassNotFoundException + { + s.defaultReadObject(); + Throwable cause = super.getCause(); + if (!(cause instanceof IOException)) + throw new InvalidObjectException("Cause must be an IOException"); + } } --- old/src/share/classes/java/nio/file/DirectoryStream.java 2013-01-25 23:38:52.535458142 -0800 +++ new/src/share/classes/java/nio/file/DirectoryStream.java 2013-01-25 23:38:52.351458145 -0800 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,16 +28,19 @@ import java.util.Iterator; import java.io.Closeable; import java.io.IOException; +import java.util.stream.Stream; +import java.util.stream.Streams; /** * An object to iterate over the entries in a directory. A directory stream - * allows for the convenient use of the for-each construct to iterate over a - * directory. + * allows for the convenient use of the for-each construct or the {@link + * Stream} API to iterate over a directory. * *

While {@code DirectoryStream} extends {@code Iterable}, it is not a - * general-purpose {@code Iterable} as it supports only a single {@code - * Iterator}; invoking the {@link #iterator iterator} method to obtain a second - * or subsequent iterator throws {@code IllegalStateException}. + * general-purpose {@code Iterable}. A {@code DirectoryStream} supports only a + * single iteration via either the {@link #iterator iterator} or the {@link + * #entries entries} method. Invoking either method to do a second or + * subsequent iteration throws {@code IllegalStateException}. * *

An important property of the directory stream's {@code Iterator} is that * its {@link Iterator#hasNext() hasNext} method is guaranteed to read-ahead by @@ -61,13 +64,13 @@ * } * * - *

Once a directory stream is closed, then further access to the directory, - * using the {@code Iterator}, behaves as if the end of stream has been reached. - * Due to read-ahead, the {@code Iterator} may return one or more elements - * after the directory stream has been closed. Once these buffered elements - * have been read, then subsequent calls to the {@code hasNext} method returns - * {@code false}, and subsequent calls to the {@code next} method will throw - * {@code NoSuchElementException}. + *

Once a directory stream is closed, then further access to the + * directory, using the {@code Iterator} or {@code Stream}, behaves as if the + * end of stream has been reached. Due to read-ahead, one or more elements may + * be returned after the directory stream has been closed. Once these buffered + * elements have been read, then subsequent calls to the {@code hasNext} + * method returns {@code false}, and subsequent calls to the {@code next} + * method will throw {@code NoSuchElementException}. * *

A directory stream is not required to be asynchronously closeable. * If a thread is blocked on the directory stream's iterator reading from the @@ -75,13 +78,14 @@ * second thread may block until the read operation is complete. * *

If an I/O error is encountered when accessing the directory then it - * causes the {@code Iterator}'s {@code hasNext} or {@code next} methods to - * throw {@link DirectoryIteratorException} with the {@link IOException} as the - * cause. As stated above, the {@code hasNext} method is guaranteed to - * read-ahead by at least one element. This means that if {@code hasNext} method - * returns {@code true}, and is followed by a call to the {@code next} method, - * then it is guaranteed that the {@code next} method will not fail with a - * {@code DirectoryIteratorException}. + * causes the methods to throw {@link DirectoryIteratorException} with the + * {@link IOException} as the cause. This could be the {@code Iterator}'s + * {@code hasNext} or {@code next} method or one of the {@code Stream} methods. + * As stated above, the {@code hasNext} method is guaranteed to read-ahead by + * at least one element. This means that if {@code hasNext} method returns + * {@code true}, and is followed by a call to the {@code next} method, then it + * is guaranteed that the {@code next} method will not fail with a {@code + * DirectoryIteratorException}. * *

The elements returned by the iterator are in no specific order. Some file * systems maintain special links to the directory itself and the directory's @@ -151,9 +155,24 @@ * @return the iterator associated with this {@code DirectoryStream} * * @throws IllegalStateException - * if this directory stream is closed or the iterator has already - * been returned + * if this directory stream is closed or the iterator or stream + * has already been returned */ @Override Iterator iterator(); + + /** + * Returns the stream associated with this {@code DirectoryStream}. + * + * @return the stream associated with this {@code DirectoryStream} + * + * @throws IllegalStateException + * if this directory stream is closed or the iterator or stream + * has already been returned + * @since 1.8 + */ + default Stream entries() { + return Streams.stream(Streams.spliteratorUnknownSize(iterator()), + Streams.STREAM_IS_DISTINCT); + } } --- old/src/share/classes/java/nio/file/Files.java 2013-01-25 23:38:53.431458126 -0800 +++ new/src/share/classes/java/nio/file/Files.java 2013-01-25 23:38:53.251458129 -0800 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,6 +29,7 @@ import java.nio.file.spi.FileSystemProvider; import java.nio.file.spi.FileTypeDetector; import java.nio.channels.SeekableByteChannel; +import java.io.Closeable; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; @@ -38,7 +39,14 @@ import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.IOException; +import java.io.UncheckedIOException; import java.util.*; +import java.util.function.BiPredicate; +import java.util.function.Supplier; +import java.util.stream.CloseableStream; +import java.util.stream.DelegatingStream; +import java.util.stream.Stream; +import java.util.stream.Streams; import java.security.AccessController; import java.security.PrivilegedAction; import java.nio.charset.Charset; @@ -2588,7 +2596,8 @@ { if (maxDepth < 0) throw new IllegalArgumentException("'maxDepth' is negative"); - new FileTreeWalker(options, visitor, maxDepth).walk(start); + //new FileTreeWalker(options, visitor, maxDepth).walk(start); + FileTreeIterator.walkThrough(start, maxDepth, visitor, options.toArray(new FileVisitOption[0])); return start; } @@ -3123,4 +3132,333 @@ } return path; } + + // -- Stream APIs -- + /** + * Implementation of CloseableStream + */ + static class DelegatingCloseableStream extends DelegatingStream + implements CloseableStream + { + private final Closeable closeable; + + DelegatingCloseableStream(Closeable c, Stream delegate) { + super(delegate); + this.closeable = c; + } + + public void close() throws IOException { + closeable.close(); + } + } + + /** + * Return a lazily populated {@code CloseableStream}, the elements of + * which are the entries in the directory. The listing is not recursive. + * + *

The elements of the stream are {@link Path} objects that are obtained + * as if by {@link Path#resolve(Path) resolving} the name of the directory + * entry against {@code dir}. Some file systems maintain special links to + * the directory itself and the directory's parent directory. Entries + * representing these links are not included. + * + *

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

When not using the try-with-resources construct, then stream's + * {@link CloseableStream#close close} method should be invoked after + * operation is completed so as to free any resources held for the open + * directory. + * + *

If an {@link IOException} is thrown when accessing the directory + * after returned from this method, it is wrapped in an {@link + * UncheckedIOException} which will be thrown from the {@link Stream} + * 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 + * + * @throws NotDirectoryException + * if the file could not otherwise be opened because it is not + * a directory (optional specific exception) + * @throws IOException + * if an I/O error occurs when opening the directory + * @throws SecurityException + * In the case of the default provider, and a security manager is + * installed, the {@link SecurityManager#checkRead(String) checkRead} + * method is invoked to check read access to the directory. + * + * @see #newDirectoryStream(Path) + * @see DirectoryStream#entries() + * @since 1.8 + */ + public static CloseableStream list(Path dir) throws IOException { + DirectoryStream ds = Files.newDirectoryStream(dir); + final Iterator delegate = ds.iterator(); + // Re-wrap DirectoryIteratorException to UncheckedIOException + Iterator it = new Iterator() { + public boolean hasNext() { + try { + return delegate.hasNext(); + } catch (DirectoryIteratorException die) { + throw new UncheckedIOException(die.getCause()); + } + } + + public Path next() { + try { + return delegate.next(); + } catch (DirectoryIteratorException die) { + throw new UncheckedIOException(die.getCause()); + } + } + }; + + return new DelegatingCloseableStream(ds, + Streams.stream(Streams.spliteratorUnknownSize(it), + Streams.STREAM_IS_DISTINCT)); + } + + /** + * Return a {@code CloseableStream} that is lazily populated with {@code + * Path} by walking the file tree rooted at a given staring 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 CloseableStream} 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. + * + *

When not using the try-with-resources construct, then stream's + * {@link CloseableStream#close close} method should be invoked after + * operation is completed so as to free any resources held for the open + * directory. + * + *

If an {@link IOException} is thrown when accessing the directory + * after returned from this method, it is wrapped in an {@link + * UncheckedIOException} which will be thrown from the {@code Stream} + * method that caused the access to take place. + * + * @param start + * the starting file + * @param maxDepth + * the maximum number of directory levels to visit + * @param options + * options to configure the traversal + * + * @return the {@link CloseableStream} of {@link Path} + * + * @throws IllegalArgumentException + * if the {@code maxDepth} parameter is negative + * @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. + * @throws IOException + * if an I/O error is thrown when accessing the starting file. + * @since 1.8 + */ + public static CloseableStream walk(Path start, int maxDepth, + FileVisitOption... options) + throws IOException + { + if (maxDepth < 0) { + throw new IllegalArgumentException("'maxDepth' is negative"); + } + + FileTreeIterator itor = FileTreeIterator.iterator(start, maxDepth, options); + return new DelegatingCloseableStream(itor, + Streams.stream(Streams.spliteratorUnknownSize(itor), + Streams.STREAM_IS_DISTINCT) + .map(entry -> entry.getPath())); + } + + /** + * Return a {@code CloseableStream} that is lazily populated with {@code + * Path} by walking the file tree rooted at a given staring 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}. + * + *

This method works as if invoking it were equivalent to evaluating the + * expression: + *

+     * walk(start, Integer.MAX_VALUE, options)
+     * 
+ * In other words, it visits all levels of the file tree. + * + * @param start + * the starting file + * @param options + * options to configure the traversal + * + * @return the {@link CloseableStream} of {@link Path} + * + * @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. + * @throws IOException + * if an I/O error is thrown when accessing the starting file. + * + * @see #walk(Path, int, FileVisitOption...) + * @since 1.8 + */ + public static CloseableStream walk(Path start, + FileVisitOption... options) + throws IOException + { + return walk(start, Integer.MAX_VALUE, options); + } + + /** + * Return a {@code CloseableStream} 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 CloseableStream} if + * the {@code BiPredicate} returns true. + * + *

If an {@link IOException} is thrown when accessing the directory + * after returned from this method, it is wrapped in an {@link + * UncheckedIOException} which will be thrown from the {@code Stream} + * method that caused the access to take place. + * + * @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 options + * options to configure the traversal + * + * @return the {@link CloseableStream} of {@link Path} + * + * @throws IllegalArgumentException + * if the {@code maxDepth} parameter is negative + * @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. + * @throws IOException + * if an I/O error is thrown when accessing the starting file. + * + * @see #walk(Path, int, FileVisitOption...) + * @since 1.8 + */ + public static CloseableStream find(Path start, + int maxDepth, + BiPredicate matcher, + FileVisitOption... options) + throws IOException + { + if (maxDepth < 0) { + throw new IllegalArgumentException("'maxDepth' is negative"); + } + FileTreeIterator itor = FileTreeIterator.iterator(start, maxDepth, options); + return new DelegatingCloseableStream(itor, + Streams.stream(Streams.spliteratorUnknownSize(itor), + Streams.STREAM_IS_DISTINCT) + .filter(entry -> matcher.test(entry.getPath(), entry.getFileAttributes())) + .map(entry -> entry.getPath())); + } + + /** + * 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. + * + *

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 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 {@link + * java.util.stream.Stream} method that caused the read to take place. + * + *

When not using the try-with-resources construct, then stream's + * {@link CloseableStream#close close} method should be invoked after + * operation is completed so as to free any resources held for the open + * file. + * + * @param path + * the path to the file + * @param cs + * the charset to use for decoding + * + * @return the lines from the file as a {@code CloseableStream} + * + * @throws IOException + * if an I/O error occurs opening the file + * @throws SecurityException + * In the case of the default provider, and a security manager is + * installed, the {@link SecurityManager#checkRead(String) checkRead} + * method is invoked to check read access to the file. + * + * @see #readAllLines(Path, Charset) + * @see #newBufferedReader(Path, Charset) + * @see java.io.BufferedReader#lines() + * @since 1.8 + */ + public static CloseableStream lines(Path path, Charset cs) + throws IOException + { + BufferedReader br = Files.newBufferedReader(path, cs); + return new DelegatingCloseableStream(br, br.lines()); + } } --- /dev/null 2013-01-19 23:30:52.572866804 -0800 +++ new/src/share/classes/java/nio/file/FileTreeIterator.java 2013-01-25 23:38:54.131458114 -0800 @@ -0,0 +1,438 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.nio.file; + +import java.io.Closeable; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.ArrayDeque; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.nio.file.attribute.BasicFileAttributes; + +class FileTreeIterator implements Iterator, Closeable { + + /** + * A pair of {@code Path} and its {@code BasicFileAttributes}. + */ + static class Entry { + private final Path file; + private final BasicFileAttributes attrs; + // Latched exception thrown when tried to read the BasicFileAttributes + private RuntimeException latched_ex; + + Entry(Path file, BasicFileAttributes attrs) { + this.file = Objects.requireNonNull(file); + this.attrs = attrs; + latched_ex = null; + } + + Entry(Path file, RuntimeException ex) { + this.file = Objects.requireNonNull(file); + this.latched_ex = Objects.requireNonNull(ex); + attrs = null; + } + + 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 + } + } + 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; + } + } + } + } + + private static class Context { + final Path file; + final BasicFileAttributes attrs; + final DirectoryStream ds; + final Iterator itor; + + Context(Path file, BasicFileAttributes attrs, DirectoryStream ds, Iterator itor) { + this.file = file; + this.attrs = attrs; + this.ds = ds; + this.itor = itor; + } + } + + private static class VisitorException extends RuntimeException { + VisitorException(IOException ioe) { + super(ioe); + } + + @Override + public IOException getCause() { + return (IOException) super.getCause(); + } + } + + private final boolean followLinks; + private final int maxDepth; + private final ArrayDeque stack = new ArrayDeque<>(); + + private FileVisitor 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; + } + + private FileTreeIterator init(Path start, FileVisitor 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() { + 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 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 = next.getFileAttributes(); + 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 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 --- /dev/null 2013-01-19 23:30:52.572866804 -0800 +++ new/src/share/classes/java/util/stream/CloseableStream.java 2013-01-25 23:38:54.935458099 -0800 @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util.stream; + +import java.io.Closeable; + +/** + * A {@code CloseableStream} is a {@code Stream} that can be closed. + * The close method is invoked to release resources that the object is + * holding (such as open files). + * + * @since 1.8 + */ +public interface CloseableStream extends Stream, Closeable { +} --- /dev/null 2013-01-19 23:30:52.572866804 -0800 +++ new/src/share/classes/java/util/stream/DelegatingStream.java 2013-01-25 23:38:55.715458085 -0800 @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util.stream; + +import java.util.Comparator; +import java.util.Objects; +import java.util.Optional; +import java.util.Spliterator; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.BinaryOperator; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.function.ToDoubleFunction; +import java.util.function.ToIntFunction; +import java.util.function.ToLongFunction; + +/** + * A {@code Stream} implementation that delegate operations to another {@code + * Stream}. + * + * @since 1.8 + */ +public class DelegatingStream implements Stream { + final private Stream delegate; + + /** + * Construct a {@code Stream} that delegates operations to another {@code + * Stream}. + * + * @param delegate The {@link Stream} that actually performs the operation + * @throws NullPointerException if the delegate is null + */ + public DelegatingStream(Stream delegate) { + this.delegate = Objects.requireNonNull(delegate); + } + + // -- BaseStream methods -- + + public Spliterator spliterator() { + return delegate.spliterator(); + } + + public boolean isParallel() { + return delegate.isParallel(); + } + + public int getStreamFlags() { + return delegate.getStreamFlags(); + } + + // -- Stream methods -- + + public Stream filter(Predicate predicate) { + return delegate.filter(predicate); + } + + public Stream map(Function mapper) { + return delegate.map(mapper); + } + + public IntStream map(ToIntFunction mapper) { + return delegate.map(mapper); + } + + public LongStream map(ToLongFunction mapper) { + return delegate.map(mapper); + } + + public DoubleStream map(ToDoubleFunction mapper) { + return delegate.map(mapper); + } + + public Stream explode(BiConsumer, ? super T> exploder) { + return delegate.explode(exploder); + } + + public Stream distinct() { + return delegate.distinct(); + } + + public Stream sorted() { + return delegate.sorted(); + } + + public Stream sorted(Comparator comparator) { + return delegate.sorted(comparator); + } + + public void forEach(Consumer consumer) { + delegate.forEach(consumer); + } + + public void forEachUntil(Consumer consumer, BooleanSupplier until) { + delegate.forEachUntil(consumer, until); + } + + public Stream peek(Consumer consumer) { + return delegate.peek(consumer); + } + + public Stream limit(long maxSize) { + return delegate.limit(maxSize); + } + + public Stream substream(long startingOffset) { + return delegate.substream(startingOffset); + } + + public Stream substream(long startingOffset, long endingOffset) { + return delegate.substream(startingOffset, endingOffset); + } + + public A[] toArray(IntFunction generator) { + return delegate.toArray(generator); + } + + public T reduce(T identity, BinaryOperator reducer) { + return delegate.reduce(identity, reducer); + } + + public Optional reduce(BinaryOperator reducer) { + return delegate.reduce(reducer); + } + + public U reduce(U identity, BiFunction accumulator, + BinaryOperator reducer) { + return delegate.reduce(identity, accumulator, reducer); + } + + public R collect(Supplier resultFactory, + BiConsumer accumulator, + BiConsumer reducer) { + return delegate.collect(resultFactory, accumulator, reducer); + } + + public R collect(Collector collector) { + return delegate.collect(collector); + } + + public R collectUnordered(Collector collector) { + return delegate.collectUnordered(collector); + } + + public boolean anyMatch(Predicate predicate) { + return delegate.anyMatch(predicate); + } + + public boolean allMatch(Predicate predicate) { + return delegate.allMatch(predicate); + } + + public boolean noneMatch(Predicate predicate) { + return delegate.noneMatch(predicate); + } + + public Optional findFirst() { + return delegate.findFirst(); + } + + public Optional findAny() { + return delegate.findAny(); + } + + public Stream sequential() { + return delegate.sequential(); + } + + public Stream parallel() { + return delegate.parallel(); + } +} \ No newline at end of file --- /dev/null 2013-01-19 23:30:52.572866804 -0800 +++ new/test/java/nio/file/DirectoryStream/Entries.java 2013-01-25 23:38:56.511458071 -0800 @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* @test + * @bug 8006884 + * @summary Unit test for java.nio.file.DirectoyStream + * @library .. + * @run testng Entries + */ + +import java.nio.file.DirectoryStream; +import java.nio.file.DirectoryIteratorException; +import java.nio.file.NotDirectoryException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.io.IOException; +import java.util.TreeSet; +import java.util.Comparators; +import java.util.stream.Stream; +import java.util.Iterator; +import org.testng.annotations.Test; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.AfterClass; +import static org.testng.Assert.*; + +@Test(groups = "unit") +public class Entries { + /** + * Default test folder + * testFolder - empty + * - file + * - dir - d1 + * - f1 + * - lnDir2 (../dir2) + * - dir2 + * - linkDir (./dir) + * - linkFile(./file) + */ + static Path testFolder; + static boolean supportsLinks; + static Path[] level1; + static Path[] level2; + + @BeforeClass + void setupTestFolder() throws IOException { + testFolder = TestUtil.createTemporaryDirectory(); + supportsLinks = TestUtil.supportsLinks(testFolder); + TreeSet set = new TreeSet<>(); + + // Level 1 + Path empty = testFolder.resolve("empty"); + Path file = testFolder.resolve("file"); + Path dir = testFolder.resolve("dir"); + Path dir2 = testFolder.resolve("dir2"); + Files.createDirectory(empty); + Files.createFile(file); + Files.createDirectory(dir); + Files.createDirectory(dir2); + set.add(empty); + set.add(file); + set.add(dir); + set.add(dir2); + if (supportsLinks) { + Path tmp = testFolder.resolve("linkDir"); + Files.createSymbolicLink(tmp, dir); + set.add(tmp); + tmp = testFolder.resolve("linkFile"); + Files.createSymbolicLink(tmp, file); + set.add(tmp); + } + level1 = set.toArray(new Path[0]); + + set.clear(); + // Level 2 + Path tmp = dir.resolve("d1"); + Files.createDirectory(tmp); + set.add(tmp); + tmp = dir.resolve("f1"); + Files.createFile(tmp); + set.add(tmp); + if (supportsLinks) { + tmp = dir.resolve("lnDir2"); + Files.createSymbolicLink(tmp, dir2); + set.add(tmp); + } + level2 = set.toArray(new Path[0]); + } + + @AfterClass + void cleanupTestFolder() throws IOException { + TestUtil.removeAll(testFolder); + } + + public void testEntriesWithoutFilter() throws IOException { + try (DirectoryStream ds = Files.newDirectoryStream(testFolder)) { + Path[] actual = ds.entries().sorted(Comparators.naturalOrder()).toArray(Path[]::new); + assertEquals(actual, level1); + } + + // link to a directory + if (supportsLinks) { + try (DirectoryStream ds = Files.newDirectoryStream(testFolder.resolve("linkDir"))) { + Path[] actual = ds.entries().sorted(Comparators.naturalOrder()).toArray(Path[]::new); + assertEquals(actual.length, level2.length); + for (int i = 0; i < actual.length; i++) { + assertEquals(actual[i].getFileName(), level2[i].getFileName()); + } + } + } + } + + public void testEntriesWithFilter() throws IOException { + try (DirectoryStream ds = Files.newDirectoryStream(testFolder, "f*")) { + Path[] actual = ds.entries().toArray(Path[]::new); + assertEquals(actual.length, 1); + assertEquals(actual[0], testFolder.resolve("file")); + } + + try (DirectoryStream ds = Files.newDirectoryStream(testFolder, "z*")) { + int count = ds.entries().map(p -> 1).reduce(0, Integer::sum); + assertEquals(count, 0, "Expect empty stream."); + } + } + + public void testDirectoryIteratorException() throws IOException { + // check that an IOException thrown by a filter is propagated + DirectoryStream.Filter filter = new DirectoryStream.Filter() { + public boolean accept(Path file) throws IOException { + throw new java.util.zip.ZipException(); + } + }; + + try (DirectoryStream ds = Files.newDirectoryStream(testFolder, filter)) { + Path[] actual = ds.entries().toArray(Path[]::new); + throw new RuntimeException("DirectoryIteratorException expected"); + } catch (DirectoryIteratorException x) { + IOException cause = x.getCause(); + if (!(cause instanceof java.util.zip.ZipException)) + throw new RuntimeException("Expected IOException not propagated"); + } + } + + public void testEmptyFolder() throws IOException { + try (DirectoryStream ds = Files.newDirectoryStream(testFolder.resolve("empty"))) { + int count = ds.entries().map(p -> 1).reduce(0, Integer::sum); + assertEquals(count, 0, "Expect empty stream."); + } + } + + public void testIllegalStateException() throws IOException { + try (DirectoryStream ds = Files.newDirectoryStream(testFolder)) { + Stream s = ds.entries(); + try { + ds.iterator(); + fail("Expect IllegalStateException from iterator() call."); + } catch (IllegalStateException ise1) {} + } + + try (DirectoryStream ds = Files.newDirectoryStream(testFolder)) { + Iterator it = ds.iterator(); + try { + ds.entries(); + fail("Expect IllegalStateException from entries() call."); + } catch (IllegalStateException ise2) {} + } + } + + public void testNotDirectoryException() throws IOException { + try { + Files.newDirectoryStream(testFolder.resolve("file")); + throw new RuntimeException("NotDirectoryException not thrown"); + } catch (NotDirectoryException x) { + } + } +} \ No newline at end of file --- /dev/null 2013-01-19 23:30:52.572866804 -0800 +++ new/test/java/nio/file/Files/StreamTest.java 2013-01-25 23:38:57.283458058 -0800 @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* @test + * @bug 8006884 + * @summary Unit test for java.nio.file.Files + * @library .. + * @run testng StreamTest + */ + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.Charset; +import java.nio.charset.MalformedInputException; +import java.nio.file.Files; +import java.nio.file.FileSystemLoopException; +import java.nio.file.FileVisitOption; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Comparators; +import java.util.List; +import java.util.stream.CloseableStream; +import java.util.stream.Collectors; +import java.util.TreeSet; + +import org.testng.annotations.Test; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.AfterClass; +import static org.testng.Assert.*; + +@Test(groups = "unit") +public class StreamTest { + /** + * Default test folder + * testFolder - empty + * - file + * - dir - d1 + * - f1 + * - lnDir2 (../dir2) + * - dir2 + * - linkDir (./dir) + * - linkFile(./file) + */ + static Path testFolder; + static boolean supportsLinks; + static Path[] level1; + static Path[] all; + static Path[] all_folowLinks; + + @BeforeClass + void setupTestFolder() throws IOException { + testFolder = TestUtil.createTemporaryDirectory(); + supportsLinks = TestUtil.supportsLinks(testFolder); + TreeSet set = new TreeSet<>(); + + // Level 1 + Path empty = testFolder.resolve("empty"); + Path file = testFolder.resolve("file"); + Path dir = testFolder.resolve("dir"); + Path dir2 = testFolder.resolve("dir2"); + Files.createDirectory(empty); + Files.createFile(file); + Files.createDirectory(dir); + Files.createDirectory(dir2); + set.add(empty); + set.add(file); + set.add(dir); + set.add(dir2); + if (supportsLinks) { + Path tmp = testFolder.resolve("linkDir"); + Files.createSymbolicLink(tmp, dir); + set.add(tmp); + tmp = testFolder.resolve("linkFile"); + Files.createSymbolicLink(tmp, file); + set.add(tmp); + } + level1 = set.toArray(new Path[0]); + + // Level 2 + Path tmp = dir.resolve("d1"); + Files.createDirectory(tmp); + set.add(tmp); + tmp = dir.resolve("f1"); + Files.createFile(tmp); + set.add(tmp); + if (supportsLinks) { + tmp = dir.resolve("lnDir2"); + Files.createSymbolicLink(tmp, dir2); + set.add(tmp); + } + // walk include starting folder + set.add(testFolder); + all = set.toArray(new Path[0]); + + // Follow links + if (supportsLinks) { + tmp = testFolder.resolve("linkDir"); + set.add(tmp.resolve("d1")); + set.add(tmp.resolve("f1")); + tmp = tmp.resolve("lnDir2"); + set.add(tmp); + } + all_folowLinks = set.toArray(new Path[0]); + } + + @AfterClass + void cleanupTestFolder() throws IOException { + TestUtil.removeAll(testFolder); + } + + public void testBasic() { + try(CloseableStream s = Files.list(testFolder)) { + Object[] actual = s.sorted(Comparators.naturalOrder()).toArray(); + assertEquals(actual, level1); + } catch (IOException ioe) { + fail("Unexpected IOException"); + } + + try(CloseableStream s = Files.list(testFolder.resolve("empty"))) { + int count = s.map(p -> 1).reduce(0, Integer::sum); + assertEquals(count, 0, "Expect empty stream."); + } catch (IOException ioe) { + fail("Unexpected IOException"); + } + } + + public void testWalk() { + try(CloseableStream s = Files.walk(testFolder)) { + Object[] actual = s.sorted(Comparators.naturalOrder()).toArray(); + assertEquals(actual, all); + } catch (IOException ioe) { + fail("Unexpected IOException"); + } + } + + public void testWalkFollowLink() { + // If link is not supported, the directory structure won't have link. + // We still want to test the behavior with FOLLOW_LINKS option. + try(CloseableStream s = Files.walk(testFolder, FileVisitOption.FOLLOW_LINKS)) { + Object[] actual = s.sorted(Comparators.naturalOrder()).toArray(); + assertEquals(actual, all_folowLinks); + } catch (IOException ioe) { + fail("Unexpected IOException"); + } + } + + private void validateFileSystemLoopException(Path start, Path... causes) { + try(CloseableStream s = Files.walk(start, FileVisitOption.FOLLOW_LINKS)) { + try { + int count = s.map(p -> 1).reduce(0, Integer::sum); + fail("Should got FileSystemLoopException, but got " + count + "elements."); + } catch (UncheckedIOException uioe) { + IOException ioe = uioe.getCause(); + if (ioe instanceof FileSystemLoopException) { + FileSystemLoopException fsle = (FileSystemLoopException) ioe; + boolean match = false; + for (Path cause: causes) { + if (fsle.getFile().equals(cause.toString())) { + match = true; + break; + } + } + assertTrue(match); + } else { + fail("Unexpected UncheckedIOException cause " + ioe.toString()); + } + } + } catch(IOException ex) { + fail("Unexpected IOException " + ex); + } + } + + public void testWalkFollowLinkLoop() { + if (!supportsLinks) { + return; + } + + // Loops. + try { + Path dir = testFolder.resolve("dir"); + Path linkdir = testFolder.resolve("linkDir"); + Path d1 = dir.resolve("d1"); + Path cause = d1.resolve("lnSelf"); + Files.createSymbolicLink(cause, d1); + + // loop in descendant. + validateFileSystemLoopException(dir, cause); + // loop in self + validateFileSystemLoopException(d1, cause); + // start from other place via link + validateFileSystemLoopException(linkdir, + linkdir.resolve(Paths.get("d1", "lnSelf"))); + Files.delete(cause); + + // loop to parent. + cause = d1.resolve("lnParent"); + Files.createSymbolicLink(cause, dir); + + // loop should be detected at test/dir/d1/lnParent/d1 + validateFileSystemLoopException(d1, cause.resolve("d1")); + // loop should be detected at link + validateFileSystemLoopException(dir, cause); + // loop should be detected at test/linkdir/d1/lnParent + // which is test/dir we have visited via test/linkdir + validateFileSystemLoopException(linkdir, + linkdir.resolve(Paths.get("d1", "lnParent"))); + Files.delete(cause); + + // cross loop + Path dir2 = testFolder.resolve("dir2"); + cause = dir2.resolve("lnDir"); + Files.createSymbolicLink(cause, dir); + validateFileSystemLoopException(dir, + dir.resolve(Paths.get("lnDir2", "lnDir"))); + validateFileSystemLoopException(dir2, + dir2.resolve(Paths.get("lnDir", "lnDir2"))); + validateFileSystemLoopException(linkdir, + linkdir.resolve(Paths.get("lnDir2", "lnDir"))); + } catch(IOException ioe) { + fail("Unexpected IOException " + ioe); + } + } + + public void testLines() throws IOException { + final Charset US_ASCII = Charset.forName("US-ASCII"); + Path tmpfile = Files.createTempFile("blah", "txt"); + + try { + // zero lines + assertTrue(Files.size(tmpfile) == 0, "File should be empty"); + try (CloseableStream s = Files.lines(tmpfile, US_ASCII)) { + assertEquals(s.map(l -> 1).reduce(0, Integer::sum), 0, "No line expected"); + } + + // one line + byte[] hi = { (byte)'h', (byte)'i' }; + Files.write(tmpfile, hi); + try (CloseableStream s = Files.lines(tmpfile, US_ASCII)) { + List lines = s.collect(Collectors.toList()); + assertTrue(lines.size() == 1, "One line expected"); + assertTrue(lines.get(0).equals("hi"), "'Hi' expected"); + } + + // two lines using platform's line separator + List expected = Arrays.asList("hi", "there"); + Files.write(tmpfile, expected, US_ASCII); + assertTrue(Files.size(tmpfile) > 0, "File is empty"); + try (CloseableStream s = Files.lines(tmpfile, US_ASCII)) { + List lines = s.collect(Collectors.toList()); + assertTrue(lines.equals(expected), "Unexpected lines"); + } + + // MalformedInputException + byte[] bad = { (byte)0xff, (byte)0xff }; + Files.write(tmpfile, bad); + try (CloseableStream s = Files.lines(tmpfile, US_ASCII)) { + try { + List lines = s.collect(Collectors.toList()); + throw new RuntimeException("UncheckedIOException expected"); + } catch (UncheckedIOException ex) { + assertTrue(ex.getCause() instanceof MalformedInputException, + "MalformedInputException expected"); + } + } + + // NullPointerException + try { + Files.lines(null, US_ASCII); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException ignore) { } + try { + Files.lines(tmpfile, null); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException ignore) { } + + } finally { + Files.delete(tmpfile); + } + } +} \ No newline at end of file