--- 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 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() {
+ 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 super T> predicate) {
+ return delegate.filter(predicate);
+ }
+
+ public Stream map(Function super T, ? extends R> mapper) {
+ return delegate.map(mapper);
+ }
+
+ public IntStream map(ToIntFunction super T> mapper) {
+ return delegate.map(mapper);
+ }
+
+ public LongStream map(ToLongFunction super T> mapper) {
+ return delegate.map(mapper);
+ }
+
+ public DoubleStream map(ToDoubleFunction super T> 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 super T> comparator) {
+ return delegate.sorted(comparator);
+ }
+
+ public void forEach(Consumer super T> consumer) {
+ delegate.forEach(consumer);
+ }
+
+ public void forEachUntil(Consumer super T> consumer, BooleanSupplier until) {
+ delegate.forEachUntil(consumer, until);
+ }
+
+ public Stream peek(Consumer super T> 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 super T, R> collector) {
+ return delegate.collect(collector);
+ }
+
+ public R collectUnordered(Collector super T, R> collector) {
+ return delegate.collectUnordered(collector);
+ }
+
+ public boolean anyMatch(Predicate super T> predicate) {
+ return delegate.anyMatch(predicate);
+ }
+
+ public boolean allMatch(Predicate super T> predicate) {
+ return delegate.allMatch(predicate);
+ }
+
+ public boolean noneMatch(Predicate super T> 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