diff --git a/jdk/src/share/classes/java/nio/file/FileTreeIterator.java b/jdk/src/share/classes/java/nio/file/FileTreeIterator.java new file mode 100644 index 00000000000..63b4dc767d8 --- /dev/null +++ b/jdk/src/share/classes/java/nio/file/FileTreeIterator.java @@ -0,0 +1,124 @@ +/* + * 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.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.nio.file.FileTreeWalker.Event; + +/** + * An {@code Iterator to iterate over the nodes of a file tree. + * + *
{@code
+ *     try (FileTreeIterator iterator = new FileTreeIterator(start, maxDepth, options)) {
+ *         while (iterator.hasNext()) {
+ *             Event ev = iterator.next();
+ *             Path path = ev.file();
+ *             BasicFileAttributes attrs = ev.attributes();
+ *         }
+ *     }
+ * }
+ */ + +class FileTreeIterator implements Iterator, Closeable { + private final FileTreeWalker walker; + private Event next; + + /** + * Creates a new iterator to walk the file tree starting at the given file. + * + * @throws IllegalArgumentException + * if {@code maxDepth} is negative + * @throws IOException + * if an I/O errors occurs opening the starting file + * @throws SecurityException + * if the security manager denies access to the starting file + * @throws NullPointerException + * if {@code start} or {@code options} is {@ocde null} or + * the options array contains a {@code null} element + */ + FileTreeIterator(Path start, int maxDepth, FileVisitOption... options) + throws IOException + { + this.walker = new FileTreeWalker(Arrays.asList(options), maxDepth); + this.next = walker.walk(start); + assert next.type() == FileTreeWalker.EventType.ENTRY || + next.type() == FileTreeWalker.EventType.START_DIRECTORY; + + // IOException if there a problem accessing the starting file + IOException ioe = next.ioeException(); + if (ioe != null) + throw ioe; + } + + private void fetchNextIfNeeded() { + if (next == null) { + FileTreeWalker.Event ev = walker.next(); + while (ev != null) { + IOException ioe = ev.ioeException(); + if (ioe != null) + throw new UncheckedIOException(ioe); + + // END_DIRECTORY events are ignored + if (ev.type() != FileTreeWalker.EventType.END_DIRECTORY) { + next = ev; + return; + } + ev = walker.next(); + } + } + } + + @Override + public boolean hasNext() { + if (!walker.isOpen()) + throw new IllegalStateException(); + fetchNextIfNeeded(); + return next != null; + } + + @Override + public Event next() { + if (!walker.isOpen()) + throw new IllegalStateException(); + fetchNextIfNeeded(); + if (next == null) + throw new NoSuchElementException(); + Event result = next; + next = null; + return result; + } + + @Override + public void close() { + walker.close(); + } +} diff --git a/jdk/src/share/classes/java/nio/file/FileTreeWalker.java b/jdk/src/share/classes/java/nio/file/FileTreeWalker.java index 8ce95bc8240..85086e1e5a8 100644 --- a/jdk/src/share/classes/java/nio/file/FileTreeWalker.java +++ b/jdk/src/share/classes/java/nio/file/FileTreeWalker.java @@ -29,8 +29,8 @@ import java.nio.file.attribute.BasicFileAttributes; import java.io.Closeable; import java.io.IOException; import java.util.ArrayDeque; +import java.util.Collection; import java.util.Iterator; -import java.util.Set; import sun.nio.fs.BasicFileAttributesHolder; /** @@ -164,8 +164,17 @@ class FileTreeWalker implements Closeable { /** * Creates a {@code FileTreeWalker}. + * + * @throws IllegalArgumentException + * if {@code maxDepth} is negative + * @throws ClassCastException + * if (@code options} contains an element that is not a + * {@code FileVisitOption} + * @throws NullPointerException + * if {@code options} is {@ocde null} or the options + * array contains a {@code null} element */ - FileTreeWalker(Set options, int maxDepth) { + FileTreeWalker(Collection options, int maxDepth) { boolean fl = false; for (FileVisitOption option: options) { // will throw NPE if options contains null @@ -175,6 +184,9 @@ class FileTreeWalker implements Closeable { throw new AssertionError("Should not get here"); } } + if (maxDepth < 0) + throw new IllegalArgumentException("'maxDepth' is negative"); + this.followLinks = fl; this.linkOptions = (fl) ? new LinkOption[0] : new LinkOption[] { LinkOption.NOFOLLOW_LINKS }; diff --git a/jdk/src/share/classes/java/nio/file/Files.java b/jdk/src/share/classes/java/nio/file/Files.java index a935a58001e..d816ad9fdf5 100644 --- a/jdk/src/share/classes/java/nio/file/Files.java +++ b/jdk/src/share/classes/java/nio/file/Files.java @@ -29,6 +29,7 @@ import java.nio.file.attribute.*; 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,13 @@ import java.io.BufferedWriter; 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.stream.CloseableStream; +import java.util.stream.DelegatingStream; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; import java.security.AccessController; import java.security.PrivilegedAction; import java.nio.charset.Charset; @@ -2587,9 +2594,6 @@ public final class Files { FileVisitor visitor) throws IOException { - if (maxDepth < 0) - throw new IllegalArgumentException("'maxDepth' is negative"); - /** * Create a FileTreeWalker to walk the file tree, invoking the visitor * for each event. @@ -3177,4 +3181,336 @@ public final class Files { } return path; } + + // -- Stream APIs -- + + /** + * Implementation of CloseableStream + */ + private static class DelegatingCloseableStream extends DelegatingStream + implements CloseableStream + { + private final Closeable closeable; + + DelegatingCloseableStream(Closeable c, Stream delegate) { + super(delegate); + this.closeable = c; + } + + public void close() { + try { + closeable.close(); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + } + + /** + * 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 returning from this + * method. + * + *

When not using the try-with-resources construct, then the stream's + * {@link CloseableStream#close close} method should be invoked after the + * operation is completed so as to free any resources held for the open + * directory. Operating on a closed stream behaves as if the end of stream + * has been reached. Due to read-ahead, one or more elements may be + * returned after the stream has been closed. + * + *

If an {@link IOException} is thrown when accessing the directory + * after this method has returned, it is wrapped in an {@link + * UncheckedIOException} which will be thrown from the method that caused + * the access to take place. + * + * @param dir The path to the directory + * + * @return The {@code CloseableStream} describing the content of the + * directory + * + * @throws NotDirectoryException + * if the file could not otherwise be opened because it is not + * 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) + * @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 e) { + throw new UncheckedIOException(e.getCause()); + } + } + public Path next() { + try { + return delegate.next(); + } catch (DirectoryIteratorException e) { + throw new UncheckedIOException(e.getCause()); + } + } + }; + + return new DelegatingCloseableStream<>(ds, + StreamSupport.stream(Spliterators.spliteratorUnknownSize(it, + Spliterator.DISTINCT))); + } + + /** + * Return a {@code CloseableStream} that is lazily populated with {@code + * Path} by walking the file tree rooted at a given starting file. The + * file tree is traversed depth-first, the elements in the stream + * are {@link Path} objects that are obtained as if by {@link + * Path#resolve(Path) resolving} the relative path against {@code start}. + * + *

The {@code stream} walks the file tree as elements are consumed. + * The {@code 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 the stream's + * {@link CloseableStream#close close} method should be invoked after the + * operation is completed so as to free any resources held for the open + * directory. Operate the stream after it is closed will throw an + * {@link java.lang.IllegalStateException}. + * + *

If an {@link IOException} is thrown when accessing the directory + * after this method has returned, it is wrapped in an {@link + * UncheckedIOException} which will be thrown from the method that caused + * the access to take place. + * + * @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 + { + FileTreeIterator iterator = new FileTreeIterator(start, maxDepth, options); + return new DelegatingCloseableStream<>(iterator, + StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.DISTINCT)) + .map(entry -> entry.file())); + } + + /** + * Return a {@code CloseableStream} that is lazily populated with {@code + * Path} by walking the file tree rooted at a given starting file. The + * file tree is traversed depth-first, the elements in the stream + * are {@link Path} objects that are obtained as if by {@link + * Path#resolve(Path) resolving} the relative path against {@code start}. + * + *

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. Compare to calling {@link + * java.util.stream.Stream#filter filter} on the {@code Stream} + * returned by {@code walk} method, this method may be more efficient by + * avoiding redundant retrieval of the {@code BasicFileAttributes}. + * + *

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 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 + { + FileTreeIterator iterator = new FileTreeIterator(start, maxDepth, options); + return new DelegatingCloseableStream<>(iterator, + StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.DISTINCT)) + .filter(entry -> matcher.test(entry.file(), entry.attributes())) + .map(entry -> entry.file())); + } + + /** + * Read all lines from a file as a {@code CloseableStream}. Unlike {@link + * #readAllLines(Path, Charset) readAllLines}, this method does not read + * all lines into a {@code List}, but instead populates 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. + * + *

After this method returns, then any subsequent I/O exception that + * occurs while reading from the file or when a malformed or unmappable byte + * sequence is read, is wrapped in an {@link UncheckedIOException} that will + * be thrown form the + * {@link java.util.stream.Stream} method that caused the read to take + * place. In case an {@code IOException} is thrown when closing the file, + * it is also wrapped as an {@code UncheckedIOException}. + * + *

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()); + } } diff --git a/jdk/src/share/classes/java/util/Spliterator.java b/jdk/src/share/classes/java/util/Spliterator.java index 588b8176d47..5ed0b1243de 100644 --- a/jdk/src/share/classes/java/util/Spliterator.java +++ b/jdk/src/share/classes/java/util/Spliterator.java @@ -140,32 +140,32 @@ import java.util.function.LongConsumer; * (in approximate order of decreasing desirability): *

    *
  • The source cannot be structurally interfered with. - *
    For example, an instance of + *
    For example, an instance of * {@link java.util.concurrent.CopyOnWriteArrayList} is an immutable source. * A Spliterator created from the source reports a characteristic of * {@code IMMUTABLE}.
  • *
  • The source manages concurrent modifications. - *
    For example, a key set of a {@link java.util.concurrent.ConcurrentHashMap} + *
    For example, a key set of a {@link java.util.concurrent.ConcurrentHashMap} * is a concurrent source. A Spliterator created from the source reports a * characteristic of {@code CONCURRENT}.
  • *
  • The mutable source provides a late-binding and fail-fast Spliterator. - *
    Late binding narrows the window during which interference can affect + *
    Late binding narrows the window during which interference can affect * the calculation; fail-fast detects, on a best-effort basis, that structural * interference has occurred after traversal has commenced and throws * {@link ConcurrentModificationException}. For example, {@link ArrayList}, * and many other non-concurrent {@code Collection} classes in the JDK, provide * a late-binding, fail-fast spliterator.
  • *
  • The mutable source provides a non-late-binding but fail-fast Spliterator. - *
    The source increases the likelihood of throwing + *
    The source increases the likelihood of throwing * {@code ConcurrentModificationException} since the window of potential * interference is larger.
  • *
  • The mutable source provides a late-binding and non-fail-fast Spliterator. - *
    The source risks arbitrary, non-deterministic behavior after traversal + *
    The source risks arbitrary, non-deterministic behavior after traversal * has commenced since interference is not detected. *
  • *
  • The mutable source provides a non-late-binding and non-fail-fast * Spliterator. - *
    The source increases the risk of arbitrary, non-deterministic behavior + *
    The source increases the risk of arbitrary, non-deterministic behavior * since non-detected interference may occur after construction. *
  • *
@@ -284,6 +284,8 @@ import java.util.function.LongConsumer; * is set to {@code true} then diagnostic warnings are reported if boxing of * primitive values occur when operating on primitive subtype specializations. * + * @param the type of elements returned by this Spliterator + * * @see Collection * @since 1.8 */ @@ -333,9 +335,8 @@ public interface Spliterator { * Upon non-null return: *