src/share/classes/java/nio/file/Files.java

Print this page
rev 7023 : 8006884: (fs) Add Files.list, lines and find
Reviewed-by:
Contributed-by: alan.bateman@oracle.com, henry.jen@oracle.com

*** 27,46 **** --- 27,53 ---- 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; import java.io.Writer; import java.io.BufferedReader; 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; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder;
*** 2585,2597 **** Set<FileVisitOption> options, int maxDepth, FileVisitor<? super Path> visitor) throws IOException { - if (maxDepth < 0) - throw new IllegalArgumentException("'maxDepth' is negative"); - /** * Create a FileTreeWalker to walk the file tree, invoking the visitor * for each event. */ try (FileTreeWalker walker = new FileTreeWalker(options, maxDepth)) { --- 2592,2601 ----
*** 3175,3180 **** --- 3179,3514 ---- writer.newLine(); } } return path; } + + // -- Stream APIs -- + + /** + * Implementation of CloseableStream + */ + private static class DelegatingCloseableStream<T> extends DelegatingStream<T> + implements CloseableStream<T> + { + private final Closeable closeable; + + DelegatingCloseableStream(Closeable c, Stream<T> 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. + * + * <p> 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. + * + * <p> The stream is <i>weakly consistent</i>. 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. + * + * <p> 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. + * + * <p> 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 <i>(optional specific exception)</i> + * @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<Path> list(Path dir) throws IOException { + DirectoryStream<Path> ds = Files.newDirectoryStream(dir); + final Iterator<Path> delegate = ds.iterator(); + + // Re-wrap DirectoryIteratorException to UncheckedIOException + Iterator<Path> it = new Iterator<Path>() { + public boolean hasNext() { + try { + return delegate.hasNext(); + } catch (DirectoryIteratorException 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 <em>depth-first</em>, 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}. + * + * <p> 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 <em>descendants</em> 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 + * <em>sibling</em> of the directory. + * + * <p> The stream is <i>weakly consistent</i>. 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. + * + * <p> 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. + * + * <p> 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}. + * + * <p> 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. + * + * <p> 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. + * + * <p> 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. + * + * <p> 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<Path> 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 <em>depth-first</em>, 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}. + * + * <p> This method works as if invoking it were equivalent to evaluating the + * expression: + * <blockquote><pre> + * walk(start, Integer.MAX_VALUE, options) + * </pre></blockquote> + * 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<Path> 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. + * + * <p>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 is more efficient by avoiding + * redundant retrieval of the {@code BasicFileAttributes}. + * + * <p>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<Path> find(Path start, + int maxDepth, + BiPredicate<Path, BasicFileAttributes> 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. + * + * <p> Bytes from the file are decoded into characters using the specified + * charset and the same line terminators as specified by {@code + * readAllLines} are supported. + * + * <p> After this method returns, then any subsequent I/O exception that + * occurs while reading from the file or when a malformed or unmappable byte + * sequence is read, is wrapped in an {@link UncheckedIOException} that will + * be thrown form the + * {@link java.util.stream.Stream} method that caused the read to take + * place. In case an {@code IOException} is thrown when closing the file, + * it is also wrapped as an {@code UncheckedIOException}. + * + * <p> 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<String> lines(Path path, Charset cs) + throws IOException + { + BufferedReader br = Files.newBufferedReader(path, cs); + return new DelegatingCloseableStream<>(br, br.lines()); + } }