# HG changeset patch # User henryjen # Date 1368135896 25200 # Node ID 23a863fbb6c349945b43057d740a2417494f37cd # Parent 032254f2467b611e9940ec7cf0853d29e0beb3e2 8006884: (fs) Add Files.list, lines and find Reviewed-by: briangoetz, mduigou Contributed-by: alan.bateman@oracle.com, henry.jen@oracle.com diff -r 032254f2467b -r 23a863fbb6c3 jdk/src/share/classes/java/nio/file/FileTreeIterator.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/share/classes/java/nio/file/FileTreeIterator.java Thu May 09 14:44:56 2013 -0700 @@ -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 -r 032254f2467b -r 23a863fbb6c3 jdk/src/share/classes/java/nio/file/FileTreeWalker.java --- a/jdk/src/share/classes/java/nio/file/FileTreeWalker.java Fri May 17 10:36:04 2013 -0700 +++ b/jdk/src/share/classes/java/nio/file/FileTreeWalker.java Thu May 09 14:44:56 2013 -0700 @@ -29,8 +29,8 @@ 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 @@ /** * 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 @@ 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 -r 032254f2467b -r 23a863fbb6c3 jdk/src/share/classes/java/nio/file/Files.java --- a/jdk/src/share/classes/java/nio/file/Files.java Fri May 17 10:36:04 2013 -0700 +++ b/jdk/src/share/classes/java/nio/file/Files.java Thu May 09 14:44:56 2013 -0700 @@ -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,13 @@ 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 @@ 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 @@ } 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 -r 032254f2467b -r 23a863fbb6c3 jdk/test/java/nio/file/Files/FaultyFileSystem.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/java/nio/file/Files/FaultyFileSystem.java Thu May 09 14:44:56 2013 -0700 @@ -0,0 +1,499 @@ +/* + * 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. + */ + +import java.io.IOException; +import java.net.URI; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.AccessMode; +import java.nio.file.CopyOption; +import java.nio.file.DirectoryIteratorException; +import java.nio.file.DirectoryStream; +import java.nio.file.FileStore; +import java.nio.file.FileSystem; +import java.nio.file.FileSystemAlreadyExistsException; +import java.nio.file.FileSystemNotFoundException; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.NoSuchFileException; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.nio.file.WatchService; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.FileAttributeView; +import java.nio.file.attribute.UserPrincipalLookupService; +import java.nio.file.spi.FileSystemProvider; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.function.Supplier; + +/** + * A {@code FileSystem} that helps testing by trigger exception throwing based on filenames. + */ +class FaultyFileSystem extends FileSystem { + final Path root; + final boolean removeRootAfterClose; + final FileSystem delegate; + boolean isOpen; + + FaultyFileSystem(Path root) throws IOException { + if (root == null) { + root = Files.createTempDirectory("faultyFS"); + removeRootAfterClose = true; + } else { + if (! Files.isDirectory(root)) { + throw new IllegalArgumentException("must be a directory."); + } + removeRootAfterClose = false; + } + this.root = root; + delegate = root.getFileSystem(); + isOpen = true; + } + + private static Path unwrap(Path p) { + return PassThroughFileSystem.unwrap(p); + } + + Path getRoot() { + return new PassThroughFileSystem.PassThroughPath(this, root); + } + + @Override + public void close() throws IOException { + if (isOpen) { + if (removeRootAfterClose) { + TestUtil.removeAll(root); + } + isOpen = false; + } + } + + @Override + public FileSystemProvider provider() { + return FaultyFSProvider.getInstance(); + } + + @Override + public boolean isOpen() { + return isOpen; + } + + @Override + public boolean isReadOnly() { + return delegate.isReadOnly(); + } + + @Override + public String getSeparator() { + return delegate.getSeparator(); + } + + private Iterable SoleIterable(final T element) { + return new Iterable() { + @Override + public Iterator iterator() { + return new Iterator() { + private T soleElement = element; + + @Override + public boolean hasNext() { + return soleElement != null; + } + + @Override + public T next() { + try { + return soleElement; + } finally { + soleElement = null; + } + } + }; + } + }; + } + + @Override + public Iterable getRootDirectories() { + return SoleIterable(getRoot()); + } + + @Override + public Iterable getFileStores() { + FileStore store; + try { + store = Files.getFileStore(root); + } catch (IOException ioe) { + store = null; + } + return SoleIterable(store); + } + + @Override + public Set supportedFileAttributeViews() { + // assume that unwrapped objects aren't exposed + return delegate.supportedFileAttributeViews(); + } + + @Override + public Path getPath(String first, String... more) { + return new PassThroughFileSystem.PassThroughPath(this, delegate.getPath(first, more)); + } + + @Override + public PathMatcher getPathMatcher(String syntaxAndPattern) { + final PathMatcher matcher = delegate.getPathMatcher(syntaxAndPattern); + return new PathMatcher() { + @Override + public boolean matches(Path path) { + return matcher.matches(unwrap(path)); + } + }; + } + + @Override + public UserPrincipalLookupService getUserPrincipalLookupService() { + // assume that unwrapped objects aren't exposed + return delegate.getUserPrincipalLookupService(); + } + + @Override + public WatchService newWatchService() throws IOException { + // to keep it simple + throw new UnsupportedOperationException(); + } + + static class FaultyException extends IOException { + FaultyException() { + super("fault triggered."); + } + } + + static class FaultyFSProvider extends FileSystemProvider { + private static final String SCHEME = "faulty"; + private static volatile FaultyFileSystem delegate; + private static FaultyFSProvider INSTANCE = new FaultyFSProvider(); + private boolean enabled; + + private FaultyFSProvider() {} + + public static FaultyFSProvider getInstance() { + return INSTANCE; + } + + public void setFaultyMode(boolean enable) { + enabled = enable; + } + + private void triggerEx(String filename, String... names) throws IOException { + if (! enabled) { + return; + } + + if (filename.equals("SecurityException")) { + throw new SecurityException("FaultyFS", new FaultyException()); + } + + if (filename.equals("IOException")) { + throw new FaultyException(); + } + + for (String name: names) { + if (name.equals(filename)) { + throw new FaultyException(); + } + } + } + + private void triggerEx(Path path, String... names) throws IOException { + triggerEx(path.getFileName().toString(), names); + } + + @Override + public String getScheme() { + return SCHEME; + } + + private void checkScheme(URI uri) { + if (!uri.getScheme().equalsIgnoreCase(SCHEME)) + throw new IllegalArgumentException(); + } + + private void checkUri(URI uri) { + checkScheme(uri); + if (!uri.getSchemeSpecificPart().equals("///")) + throw new IllegalArgumentException(); + } + + @Override + public FileSystem newFileSystem(Path fakeRoot, Map env) + throws IOException + { + if (env != null && env.keySet().contains("IOException")) { + triggerEx("IOException"); + } + + synchronized (FaultyFSProvider.class) { + if (delegate != null && delegate.isOpen()) + throw new FileSystemAlreadyExistsException(); + FaultyFileSystem result = new FaultyFileSystem(fakeRoot); + delegate = result; + return result; + } + } + + @Override + public FileSystem newFileSystem(URI uri, Map env) + throws IOException + { + if (env != null && env.keySet().contains("IOException")) { + triggerEx("IOException"); + } + + checkUri(uri); + synchronized (FaultyFSProvider.class) { + if (delegate != null && delegate.isOpen()) + throw new FileSystemAlreadyExistsException(); + FaultyFileSystem result = new FaultyFileSystem(null); + delegate = result; + return result; + } + } + + @Override + public FileSystem getFileSystem(URI uri) { + checkUri(uri); + FileSystem result = delegate; + if (result == null) + throw new FileSystemNotFoundException(); + return result; + } + + @Override + public Path getPath(URI uri) { + checkScheme(uri); + if (delegate == null) + throw new FileSystemNotFoundException(); + + // only allow absolute path + String path = uri.getSchemeSpecificPart(); + if (! path.startsWith("///")) { + throw new IllegalArgumentException(); + } + return new PassThroughFileSystem.PassThroughPath(delegate, delegate.root.resolve(path.substring(3))); + } + + @Override + public void setAttribute(Path file, String attribute, Object value, LinkOption... options) + throws IOException + { + triggerEx(file, "setAttribute"); + Files.setAttribute(unwrap(file), attribute, value, options); + } + + @Override + public Map readAttributes(Path file, String attributes, LinkOption... options) + throws IOException + { + triggerEx(file, "readAttributes"); + return Files.readAttributes(unwrap(file), attributes, options); + } + + @Override + public V getFileAttributeView(Path file, + Class type, + LinkOption... options) + { + return Files.getFileAttributeView(unwrap(file), type, options); + } + + @Override + public A readAttributes(Path file, + Class type, + LinkOption... options) + throws IOException + { + triggerEx(file, "readAttributes"); + return Files.readAttributes(unwrap(file), type, options); + } + + @Override + public void delete(Path file) throws IOException { + triggerEx(file, "delete"); + Files.delete(unwrap(file)); + } + + @Override + public void createSymbolicLink(Path link, Path target, FileAttribute... attrs) + throws IOException + { + triggerEx(target, "createSymbolicLink"); + Files.createSymbolicLink(unwrap(link), unwrap(target), attrs); + } + + @Override + public void createLink(Path link, Path existing) throws IOException { + triggerEx(existing, "createLink"); + Files.createLink(unwrap(link), unwrap(existing)); + } + + @Override + public Path readSymbolicLink(Path link) throws IOException { + Path target = Files.readSymbolicLink(unwrap(link)); + triggerEx(target, "readSymbolicLink"); + return new PassThroughFileSystem.PassThroughPath(delegate, target); + } + + + @Override + public void copy(Path source, Path target, CopyOption... options) throws IOException { + triggerEx(source, "copy"); + Files.copy(unwrap(source), unwrap(target), options); + } + + @Override + public void move(Path source, Path target, CopyOption... options) throws IOException { + triggerEx(source, "move"); + Files.move(unwrap(source), unwrap(target), options); + } + + private DirectoryStream wrap(final DirectoryStream stream) { + return new DirectoryStream() { + @Override + public Iterator iterator() { + final Iterator itr = stream.iterator(); + return new Iterator() { + private Path next = null; + @Override + public boolean hasNext() { + if (next == null) { + if (itr.hasNext()) { + next = itr.next(); + } else { + return false; + } + } + if (next != null) { + try { + triggerEx(next, "DirectoryIteratorException"); + } catch (IOException ioe) { + throw new DirectoryIteratorException(ioe); + } catch (SecurityException se) { + // ??? Does DS throw SecurityException during iteration? + next = null; + return hasNext(); + } + } + return (next != null); + } + @Override + public Path next() { + try { + if (next != null || hasNext()) { + return new PassThroughFileSystem.PassThroughPath(delegate, next); + } else { + throw new NoSuchElementException(); + } + } finally { + next = null; + } + } + + @Override + public void remove() { + itr.remove(); + } + }; + } + @Override + public void close() throws IOException { + stream.close(); + } + }; + } + + @Override + public DirectoryStream newDirectoryStream(Path dir, DirectoryStream.Filter filter) + throws IOException + { + triggerEx(dir, "newDirectoryStream"); + return wrap(Files.newDirectoryStream(unwrap(dir), filter)); + } + + @Override + public void createDirectory(Path dir, FileAttribute... attrs) + throws IOException + { + triggerEx(dir, "createDirectory"); + Files.createDirectory(unwrap(dir), attrs); + } + + @Override + public SeekableByteChannel newByteChannel(Path file, + Set options, + FileAttribute... attrs) + throws IOException + { + triggerEx(file, "newByteChannel"); + return Files.newByteChannel(unwrap(file), options, attrs); + } + + + @Override + public boolean isHidden(Path file) throws IOException { + triggerEx(file, "isHidden"); + return Files.isHidden(unwrap(file)); + } + + @Override + public FileStore getFileStore(Path file) throws IOException { + triggerEx(file, "getFileStore"); + return Files.getFileStore(unwrap(file)); + } + + @Override + public boolean isSameFile(Path file, Path other) throws IOException { + triggerEx(file, "isSameFile"); + return Files.isSameFile(unwrap(file), unwrap(other)); + } + + @Override + public void checkAccess(Path file, AccessMode... modes) + throws IOException + { + triggerEx(file, "checkAccess"); + // hack + if (modes.length == 0) { + if (Files.exists(unwrap(file))) + return; + else + throw new NoSuchFileException(file.toString()); + } + throw new RuntimeException("not implemented yet"); + } + } +} diff -r 032254f2467b -r 23a863fbb6c3 jdk/test/java/nio/file/Files/PassThroughFileSystem.java --- a/jdk/test/java/nio/file/Files/PassThroughFileSystem.java Fri May 17 10:36:04 2013 -0700 +++ b/jdk/test/java/nio/file/Files/PassThroughFileSystem.java Thu May 09 14:44:56 2013 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 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 @@ -304,7 +304,7 @@ public DirectoryStream newDirectoryStream(Path dir, DirectoryStream.Filter filter) throws IOException { - return wrap(Files.newDirectoryStream(dir, filter)); + return wrap(Files.newDirectoryStream(unwrap(dir), filter)); } @Override diff -r 032254f2467b -r 23a863fbb6c3 jdk/test/java/nio/file/Files/StreamTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/java/nio/file/Files/StreamTest.java Thu May 09 14:44:56 2013 -0700 @@ -0,0 +1,621 @@ +/* + * 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 .. + * @build PassThroughFileSystem FaultyFileSystem + * @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.DirectoryIteratorException; +import java.nio.file.DirectoryStream; +import java.nio.file.FileSystemLoopException; +import java.nio.file.FileVisitOption; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Arrays; +import java.util.Comparators; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; +import java.util.function.BiPredicate; +import java.util.stream.CloseableStream; +import java.util.stream.Collectors; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; +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.mapToInt(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 testWalkOneLevel() { + try (CloseableStream s = Files.walk(testFolder, 1)) { + Object[] actual = s.filter(path -> ! path.equals(testFolder)) + .sorted(Comparators.naturalOrder()) + .toArray(); + assertEquals(actual, level1); + } 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.mapToInt(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); + } + } + + private static class PathBiPredicate implements BiPredicate { + private final BiPredicate pred; + private final Set visited = new TreeSet(); + + PathBiPredicate(BiPredicate pred) { + this.pred = Objects.requireNonNull(pred); + } + + public boolean test(Path path, BasicFileAttributes attrs) { + visited.add(path); + return pred.test(path, attrs); + } + + public Path[] visited() { + return visited.toArray(new Path[0]); + } + } + + public void testFind() throws IOException { + PathBiPredicate pred = new PathBiPredicate((path, attrs) -> true); + + try (CloseableStream s = Files.find(testFolder, Integer.MAX_VALUE, pred)) { + Set result = s.collect(Collectors.toCollection(TreeSet::new)); + assertEquals(pred.visited(), all); + assertEquals(result.toArray(new Path[0]), pred.visited()); + } + + pred = new PathBiPredicate((path, attrs) -> attrs.isSymbolicLink()); + try (CloseableStream s = Files.find(testFolder, Integer.MAX_VALUE, pred)) { + s.forEach(path -> assertTrue(Files.isSymbolicLink(path))); + assertEquals(pred.visited(), all); + } + + pred = new PathBiPredicate((path, attrs) -> + path.getFileName().toString().startsWith("e")); + try (CloseableStream s = Files.find(testFolder, Integer.MAX_VALUE, pred)) { + s.forEach(path -> assertEquals(path.getFileName().toString(), "empty")); + assertEquals(pred.visited(), all); + } + + pred = new PathBiPredicate((path, attrs) -> + path.getFileName().toString().startsWith("l") && attrs.isRegularFile()); + try (CloseableStream s = Files.find(testFolder, Integer.MAX_VALUE, pred)) { + s.forEach(path -> fail("Expect empty stream")); + assertEquals(pred.visited(), all); + } + } + + // Test borrowed from BytesAndLines + 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.mapToInt(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); + } + } + + public void testDirectoryIteratorException() throws IOException { + Path dir = testFolder.resolve("dir2"); + Path trigger = dir.resolve("DirectoryIteratorException"); + Files.createFile(trigger); + FaultyFileSystem.FaultyFSProvider fsp = FaultyFileSystem.FaultyFSProvider.getInstance(); + FaultyFileSystem fs = (FaultyFileSystem) fsp.newFileSystem(dir, null); + + try { + fsp.setFaultyMode(false); + Path fakeRoot = fs.getRoot(); + try { + try (CloseableStream s = Files.list(fakeRoot)) { + s.forEach(path -> assertEquals(path.getFileName().toString(), "DirectoryIteratorException")); + } + } catch (UncheckedIOException uioe) { + fail("Unexpected exception."); + } + + fsp.setFaultyMode(true); + try { + try (DirectoryStream ds = Files.newDirectoryStream(fakeRoot)) { + Iterator itor = ds.iterator(); + while (itor.hasNext()) { + itor.next(); + } + } + fail("Shoule throw DirectoryIteratorException"); + } catch (DirectoryIteratorException die) { + } + + try { + try (CloseableStream s = Files.list(fakeRoot)) { + s.forEach(path -> fail("should not get here")); + } + } catch (UncheckedIOException uioe) { + assertTrue(uioe.getCause() instanceof FaultyFileSystem.FaultyException); + } catch (DirectoryIteratorException die) { + fail("Should have been converted into UncheckedIOException."); + } + } finally { + // Cleanup + if (fs != null) { + fs.close(); + } + Files.delete(trigger); + } + } + + public void testUncheckedIOException() throws IOException { + Path triggerFile = testFolder.resolve(Paths.get("dir2", "IOException")); + Files.createFile(triggerFile); + Path triggerDir = testFolder.resolve(Paths.get("empty", "IOException")); + Files.createDirectories(triggerDir); + Files.createFile(triggerDir.resolve("file")); + FaultyFileSystem.FaultyFSProvider fsp = FaultyFileSystem.FaultyFSProvider.getInstance(); + FaultyFileSystem fs = (FaultyFileSystem) fsp.newFileSystem(testFolder, null); + + try { + fsp.setFaultyMode(false); + Path fakeRoot = fs.getRoot(); + try (CloseableStream s = Files.list(fakeRoot.resolve("dir2"))) { + // only one file + s.forEach(path -> assertEquals(path.getFileName().toString(), "IOException")); + } + + try (CloseableStream s = Files.walk(fakeRoot.resolve("empty"))) { + String[] result = s.map(path -> path.getFileName().toString()) + .toArray(String[]::new); + // ordered as depth-first + assertEquals(result, new String[] { "empty", "IOException", "file"}); + } + + fsp.setFaultyMode(true); + try (CloseableStream s = Files.list(fakeRoot.resolve("dir2"))) { + s.forEach(path -> fail("should have caused exception")); + } catch (UncheckedIOException uioe) { + assertTrue(uioe.getCause() instanceof FaultyFileSystem.FaultyException); + } + + try (CloseableStream s = Files.walk(fakeRoot.resolve("empty"))) { + String[] result = s.map(path -> path.getFileName().toString()) + .toArray(String[]::new); + fail("should not reach here due to IOException"); + } catch (UncheckedIOException uioe) { + assertTrue(uioe.getCause() instanceof FaultyFileSystem.FaultyException); + } + + try (CloseableStream s = Files.walk( + fakeRoot.resolve("empty").resolve("IOException"))) + { + String[] result = s.map(path -> path.getFileName().toString()) + .toArray(String[]::new); + fail("should not reach here due to IOException"); + } catch (IOException ioe) { + assertTrue(ioe instanceof FaultyFileSystem.FaultyException); + } catch (UncheckedIOException ex) { + fail("Top level should be repored as is"); + } + } finally { + // Cleanup + if (fs != null) { + fs.close(); + } + Files.delete(triggerFile); + TestUtil.removeAll(triggerDir); + } + } + + public void testSecurityException() throws IOException { + Path triggerFile = testFolder.resolve(Paths.get("dir", "SecurityException")); + Files.createFile(triggerFile); + Path sampleFile = testFolder.resolve(Paths.get("dir", "sample")); + Files.createFile(sampleFile); + Path triggerDir = testFolder.resolve(Paths.get("dir2", "SecurityException")); + Files.createDirectories(triggerDir); + Files.createFile(triggerDir.resolve("fileInSE")); + Path sample = testFolder.resolve(Paths.get("dir2", "file")); + Files.createFile(sample); + FaultyFileSystem.FaultyFSProvider fsp = FaultyFileSystem.FaultyFSProvider.getInstance(); + FaultyFileSystem fs = (FaultyFileSystem) fsp.newFileSystem(testFolder, null); + + try { + fsp.setFaultyMode(false); + Path fakeRoot = fs.getRoot(); + // validate setting + try (CloseableStream s = Files.list(fakeRoot.resolve("dir"))) { + String[] result = s.map(path -> path.getFileName().toString()) + .toArray(String[]::new); + assertEqualsNoOrder(result, new String[] { "d1","f1", "lnDir2", "SecurityException", "sample" }); + } + + try (CloseableStream s = Files.walk(fakeRoot.resolve("dir2"))) { + String[] result = s.map(path -> path.getFileName().toString()) + .toArray(String[]::new); + assertEqualsNoOrder(result, new String[] { "dir2", "SecurityException", "fileInSE", "file" }); + } + + // execute test + fsp.setFaultyMode(true); + // ignore file cause SecurityException + try (CloseableStream s = Files.walk(fakeRoot.resolve("dir"))) { + String[] result = s.map(path -> path.getFileName().toString()) + .toArray(String[]::new); + assertEqualsNoOrder(result, new String[] { "dir", "d1","f1", "lnDir2", "sample" }); + } + // skip folder cause SecurityException + try (CloseableStream s = Files.walk(fakeRoot.resolve("dir2"))) { + String[] result = s.map(path -> path.getFileName().toString()) + .toArray(String[]::new); + assertEqualsNoOrder(result, new String[] { "dir2", "file" }); + } + + // list instead of walk + try (CloseableStream s = Files.list(fakeRoot.resolve("dir"))) { + String[] result = s.map(path -> path.getFileName().toString()) + .toArray(String[]::new); + assertEqualsNoOrder(result, new String[] { "d1","f1", "lnDir2", "sample" }); + } + try (CloseableStream s = Files.list(fakeRoot.resolve("dir2"))) { + String[] result = s.map(path -> path.getFileName().toString()) + .toArray(String[]::new); + assertEqualsNoOrder(result, new String[] { "file" }); + } + + // root cause SecurityException should be reported + try (CloseableStream s = Files.walk( + fakeRoot.resolve("dir2").resolve("SecurityException"))) + { + String[] result = s.map(path -> path.getFileName().toString()) + .toArray(String[]::new); + fail("should not reach here due to SecurityException"); + } catch (SecurityException se) { + assertTrue(se.getCause() instanceof FaultyFileSystem.FaultyException); + } + + // Walk a file cause SecurityException, we should get SE + try (CloseableStream s = Files.walk( + fakeRoot.resolve("dir").resolve("SecurityException"))) + { + String[] result = s.map(path -> path.getFileName().toString()) + .toArray(String[]::new); + fail("should not reach here due to SecurityException"); + } catch (SecurityException se) { + assertTrue(se.getCause() instanceof FaultyFileSystem.FaultyException); + } + + // List a file cause SecurityException, we should get SE as cannot read attribute + try (CloseableStream s = Files.list( + fakeRoot.resolve("dir2").resolve("SecurityException"))) + { + String[] result = s.map(path -> path.getFileName().toString()) + .toArray(String[]::new); + fail("should not reach here due to SecurityException"); + } catch (SecurityException se) { + assertTrue(se.getCause() instanceof FaultyFileSystem.FaultyException); + } + + try (CloseableStream s = Files.list( + fakeRoot.resolve("dir").resolve("SecurityException"))) + { + String[] result = s.map(path -> path.getFileName().toString()) + .toArray(String[]::new); + fail("should not reach here due to SecurityException"); + } catch (SecurityException se) { + assertTrue(se.getCause() instanceof FaultyFileSystem.FaultyException); + } + } finally { + // Cleanup + if (fs != null) { + fs.close(); + } + Files.delete(triggerFile); + Files.delete(sampleFile); + Files.delete(sample); + TestUtil.removeAll(triggerDir); + } + } + + public void testConstructException() { + try (CloseableStream s = Files.lines(testFolder.resolve("notExist"), Charset.forName("UTF-8"))) { + s.forEach(l -> fail("File is not even exist!")); + } catch (IOException ioe) { + ioe.printStackTrace(System.err); + assertTrue(ioe instanceof NoSuchFileException); + } + } + + public void testClosedStream() throws IOException { + try (CloseableStream s = Files.list(testFolder)) { + s.close(); + Object[] actual = s.sorted(Comparators.naturalOrder()).toArray(); + assertTrue(actual.length <= level1.length); + } + + try (CloseableStream s = Files.walk(testFolder)) { + s.close(); + Object[] actual = s.sorted(Comparators.naturalOrder()).toArray(); + fail("Operate on closed stream should throw IllegalStateException"); + } catch (IllegalStateException ex) { + // expected + } + + try (CloseableStream s = Files.find(testFolder, Integer.MAX_VALUE, + (p, attr) -> true)) { + s.close(); + Object[] actual = s.sorted(Comparators.naturalOrder()).toArray(); + fail("Operate on closed stream should throw IllegalStateException"); + } catch (IllegalStateException ex) { + // expected + } + } +}