6881498: (file) Re-examine DirectoryStream exception handling
authoralanb
Fri, 10 Sep 2010 16:36:48 +0100
changeset 6537 7aa4e7bb5dae
parent 6535 77ffd0e75bfb
child 6538 cb0da65ba680
6881498: (file) Re-examine DirectoryStream exception handling Reviewed-by: forax
jdk/make/java/nio/FILES_java.gmk
jdk/src/share/classes/java/nio/file/DirectoryIteratorException.java
jdk/src/share/classes/java/nio/file/DirectoryStream.java
jdk/src/share/classes/java/nio/file/Path.java
jdk/src/share/classes/java/nio/file/SecureDirectoryStream.java
jdk/src/share/classes/java/util/ConcurrentModificationException.java
jdk/src/solaris/classes/sun/nio/fs/UnixDirectoryStream.java
jdk/src/solaris/classes/sun/nio/fs/UnixSecureDirectoryStream.java
jdk/src/windows/classes/sun/nio/fs/WindowsDirectoryStream.java
jdk/test/java/nio/file/DirectoryStream/Basic.java
jdk/test/java/nio/file/DirectoryStream/SecureDS.java
jdk/test/java/nio/file/etc/Exceptions.java
--- a/jdk/make/java/nio/FILES_java.gmk	Thu Sep 09 11:50:40 2010 -0700
+++ b/jdk/make/java/nio/FILES_java.gmk	Fri Sep 10 16:36:48 2010 +0100
@@ -83,6 +83,7 @@
 	java/nio/file/ClosedFileSystemException.java \
 	java/nio/file/ClosedWatchServiceException.java \
 	java/nio/file/CopyOption.java \
+	java/nio/file/DirectoryIteratorException.java \
 	java/nio/file/DirectoryNotEmptyException.java \
 	java/nio/file/DirectoryStream.java \
 	java/nio/file/FileAlreadyExistsException.java \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/java/nio/file/DirectoryIteratorException.java	Fri Sep 10 16:36:48 2010 +0100
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2010, 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.util.ConcurrentModificationException;
+import java.util.Objects;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.InvalidObjectException;
+
+/**
+ * Runtime exception thrown if an I/O error is encountered when iterating over
+ * the entries in a directory. The I/O error is retrieved as an {@link
+ * IOException} using the {@link #getCause() getCause()} method.
+ *
+ * @since 1.7
+ * @see DirectoryStream
+ */
+
+public final class DirectoryIteratorException
+    extends ConcurrentModificationException
+{
+    private static final long serialVersionUID = -6012699886086212874L;
+
+    /**
+     * Constructs an instance of this class.
+     *
+     * @param   cause
+     *          the {@code IOException} that caused the directory iteration
+     *          to fail
+     *
+     * @throws  NullPointerException
+     *          if the cause is {@code null}
+     */
+    public DirectoryIteratorException(IOException cause) {
+        super(Objects.nonNull(cause));
+    }
+
+    /**
+     * Returns the cause of this exception.
+     *
+     * @return  the cause
+     */
+    @Override
+    public IOException getCause() {
+        return (IOException)super.getCause();
+    }
+
+    /**
+     * Called to read the object from a stream.
+     *
+     * @throws  InvalidObjectException
+     *          if the object is invalid or has a cause that is not
+     *          an {@code IOException}
+     */
+    private void readObject(ObjectInputStream s)
+        throws IOException, ClassNotFoundException
+    {
+        s.defaultReadObject();
+        Throwable cause = super.getCause();
+        if (!(cause instanceof IOException))
+            throw new InvalidObjectException("Cause must be an IOException");
+    }
+}
--- a/jdk/src/share/classes/java/nio/file/DirectoryStream.java	Thu Sep 09 11:50:40 2010 -0700
+++ b/jdk/src/share/classes/java/nio/file/DirectoryStream.java	Fri Sep 10 16:36:48 2010 +0100
@@ -31,60 +31,84 @@
 
 /**
  * An object to iterate over the entries in a directory. A directory stream
- * allows for convenient use of the for-each construct:
+ * allows for the convenient use of the for-each construct to iterate over a
+ * directory.
+ *
+ * <p> <b> While {@code DirectoryStream} extends {@code Iterable}, it is not a
+ * general-purpose {@code Iterable} as it supports only a single {@code
+ * Iterator}; invoking the {@link #iterator iterator} method to obtain a second
+ * or subsequent iterator throws {@code IllegalStateException}. </b>
+ *
+ * <p> An important property of the directory stream's {@code Iterator} is that
+ * its {@link Iterator#hasNext() hasNext} method is guaranteed to read-ahead by
+ * at least one element. If {@code hasNext} method returns {@code true}, and is
+ * followed by a call to the {@code next} method, it is guaranteed that the
+ * {@code next} method will not throw an exception due to an I/O error, or
+ * because the stream has been {@link #close closed}. The {@code Iterator} does
+ * not support the {@link Iterator#remove remove} operation.
+ *
+ * <p> A {@code DirectoryStream} is opened upon creation and is closed by
+ * invoking the {@code close} method. Closing a directory stream releases any
+ * resources associated with the stream. Failure to close the stream may result
+ * in a resource leak. The try-with-resources statement provides a useful
+ * construct to ensure that the stream is closed:
  * <pre>
  *   Path dir = ...
- *   DirectoryStream&lt;Path&gt; stream = dir.newDirectoryStream();
- *   try {
+ *   try (DirectoryStream&lt;Path&gt; stream = dir.newDirectoryStream()) {
  *       for (Path entry: stream) {
- *         ..
+ *           ...
  *       }
- *   } finally {
- *       stream.close();
  *   }
  * </pre>
  *
- * <p><b> A {@code DirectoryStream} is not a general-purpose {@code Iterable}.
- * While this interface extends {@code Iterable}, the {@code iterator} method
- * may only be invoked once to obtain the iterator; a second, or subsequent,
- * call to the {@code iterator} method throws {@code IllegalStateException}. </b>
- *
- * <p> A {@code DirectoryStream} is opened upon creation and is closed by
- * invoking the {@link #close close} method. Closing the directory stream
- * releases any resources associated with the stream. Once a directory stream
- * is closed, all further method invocations on the iterator throw {@link
- * java.util.ConcurrentModificationException} with cause {@link
- * ClosedDirectoryStreamException}.
+ * <p> Once a directory stream is closed, then further access to the directory,
+ * using the {@code Iterator}, behaves as if the end of stream has been reached.
+ * Due to read-ahead, the {@code Iterator} may return one or more elements
+ * after the directory stream has been closed. Once these buffered elements
+ * have been read, then subsequent calls to the {@code hasNext} method returns
+ * {@code false}, and subsequent calls to the {@code next} method will throw
+ * {@code NoSuchElementException}.
  *
  * <p> A directory stream is not required to be <i>asynchronously closeable</i>.
  * If a thread is blocked on the directory stream's iterator reading from the
  * directory, and another thread invokes the {@code close} method, then the
  * second thread may block until the read operation is complete.
  *
- * <p> The {@link Iterator#hasNext() hasNext} and {@link Iterator#next() next}
- * methods can encounter an I/O error when iterating over the directory in which
- * case {@code ConcurrentModificationException} is thrown with cause
- * {@link java.io.IOException}. The {@code hasNext} method is guaranteed to
- * read-ahead by at least one element. This means that if the {@code hasNext}
- * method returns {@code true} and is followed by a call to the {@code next}
- * method then it is guaranteed not to fail with a {@code
- * ConcurrentModificationException}.
+ * <p> If an I/O error is encountered when accessing the directory then it
+ * causes the {@code Iterator}'s {@code hasNext} or {@code next} methods to
+ * throw {@link DirectoryIteratorException} with the {@link IOException} as the
+ * cause. As stated above, the {@code hasNext} method is guaranteed to
+ * read-ahead by at least one element. This means that if {@code hasNext} method
+ * returns {@code true}, and is followed by a call to the {@code next} method,
+ * then it is guaranteed that the {@code next} method will not fail with a
+ * {@code DirectoryIteratorException}.
  *
  * <p> The elements returned by the iterator are in no specific order. Some file
  * systems maintain special links to the directory itself and the directory's
  * parent directory. Entries representing these links are not returned by the
  * iterator.
  *
- * <p> The iterator's {@link Iterator#remove() remove} method removes the
- * directory entry for the last element returned by the iterator, as if by
- * invoking the {@link Path#delete delete} method. If an I/O error or
- * security exception occurs then {@code ConcurrentModificationException} is
- * thrown with the cause.
- *
  * <p> The iterator 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 the {@code DirectoryStream} is created.
  *
+ * <p> <b>Usage Examples:</b>
+ * Suppose we want a list of the source files in a directory. This example uses
+ * both the for-each and try-with-resources constructs.
+ * <pre>
+ *   List&lt;Path&gt; listSourceFiles(Path dir) throws IOException {
+ *       List&lt;Path&gt; result = new ArrayList&lt;Path&gt;();
+ *       try (DirectoryStream&lt;Path&gt; stream = dir.newDirectoryStream("*.{c,h,cpp,hpp,java}")) {
+ *           for (Path entry: stream) {
+ *               result.add(entry);
+ *           }
+ *       } catch (DirectoryIteratorException ex) {
+ *           // I/O error encounted during the iteration, the cause is an IOException
+ *           throw ex.getCause();
+ *       }
+ *       return result;
+ *   }
+ * </pre>
  * @param   <T>     The type of element returned by the iterator
  *
  * @since 1.7
--- a/jdk/src/share/classes/java/nio/file/Path.java	Thu Sep 09 11:50:40 2010 -0700
+++ b/jdk/src/share/classes/java/nio/file/Path.java	Fri Sep 10 16:36:48 2010 +0100
@@ -984,11 +984,11 @@
      * directory.
      *
      * <p> Where the filter terminates due to an uncaught error or runtime
-     * exception then it is propogated to the iterator's {@link Iterator#hasNext()
+     * exception then it is propagated to the {@link Iterator#hasNext()
      * hasNext} or {@link Iterator#next() next} method. Where an {@code
-     * IOException} is thrown, it is propogated as a {@link
-     * java.util.ConcurrentModificationException} with the {@code
-     * IOException} as the cause.
+     * IOException} is thrown, it results in the {@code hasNext} or {@code
+     * next} method throwing a {@link DirectoryIteratorException} with the
+     * {@code IOException} as the cause.
      *
      * <p> When an implementation supports operations on entries in the
      * directory that execute in a race-free manner then the returned directory
--- a/jdk/src/share/classes/java/nio/file/SecureDirectoryStream.java	Thu Sep 09 11:50:40 2010 -0700
+++ b/jdk/src/share/classes/java/nio/file/SecureDirectoryStream.java	Fri Sep 10 16:36:48 2010 +0100
@@ -47,15 +47,6 @@
  * newDirectoryStream} method will be a {@code SecureDirectoryStream} and must
  * be cast to that type in order to invoke the methods defined by this interface.
  *
- * <p> As specified by {@code DirectoryStream}, the iterator's {@link
- * java.util.Iterator#remove() remove} method removes the directory entry for
- * the last element returned by the iterator. In the case of a {@code
- * SecureDirectoryStream} the {@code remove} method behaves as if by invoking
- * the {@link #deleteFile deleteFile} or {@link #deleteDirectory deleteDirectory}
- * methods defined by this interface. The {@code remove} may require to examine
- * the file to determine if the file is a directory, and consequently, it may
- * not be atomic with respect to other file system operations.
- *
  * <p> In the case of the default {@link java.nio.file.spi.FileSystemProvider
  * provider}, and a security manager is set, then the permission checks are
  * performed using the path obtained by resolving the given relative path
--- a/jdk/src/share/classes/java/util/ConcurrentModificationException.java	Thu Sep 09 11:50:40 2010 -0700
+++ b/jdk/src/share/classes/java/util/ConcurrentModificationException.java	Fri Sep 10 16:36:48 2010 +0100
@@ -49,9 +49,9 @@
  * <p>Note that fail-fast behavior cannot be guaranteed as it is, generally
  * speaking, impossible to make any hard guarantees in the presence of
  * unsynchronized concurrent modification.  Fail-fast operations
- * throw <tt>ConcurrentModificationException</tt> on a best-effort basis.
+ * throw {@code ConcurrentModificationException} on a best-effort basis.
  * Therefore, it would be wrong to write a program that depended on this
- * exception for its correctness: <i><tt>ConcurrentModificationException</tt>
+ * exception for its correctness: <i>{@code ConcurrentModificationException}
  * should be used only to detect bugs.</i>
  *
  * @author  Josh Bloch
@@ -77,7 +77,7 @@
     }
 
     /**
-     * Constructs a <tt>ConcurrentModificationException</tt> with the
+     * Constructs a {@code ConcurrentModificationException} with the
      * specified detail message.
      *
      * @param message the detail message pertaining to this exception.
@@ -85,4 +85,39 @@
     public ConcurrentModificationException(String message) {
         super(message);
     }
+
+    /**
+     * Constructs a new exception with the specified cause and a detail
+     * message of {@code (cause==null ? null : cause.toString())} (which
+     * typically contains the class and detail message of {@code cause}.
+     *
+     * @param  cause the cause (which is saved for later retrieval by the
+     *         {@link Throwable#getCause()} method).  (A {@code null} value is
+     *         permitted, and indicates that the cause is nonexistent or
+     *         unknown.)
+     * @since  1.7
+     */
+    public ConcurrentModificationException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new exception with the specified detail message and
+     * cause.
+     *
+     * <p>Note that the detail message associated with <code>cause</code> is
+     * <i>not</i> automatically incorporated in this exception's detail
+     * message.
+     *
+     * @param  message the detail message (which is saved for later retrieval
+     *         by the {@link Throwable#getMessage()} method).
+     * @param  cause the cause (which is saved for later retrieval by the
+     *         {@link Throwable#getCause()} method).  (A {@code null} value
+     *         is permitted, and indicates that the cause is nonexistent or
+     *         unknown.)
+     * @since 1.7
+     */
+    public ConcurrentModificationException(String message, Throwable cause) {
+        super(message, cause);
+    }
 }
--- a/jdk/src/solaris/classes/sun/nio/fs/UnixDirectoryStream.java	Thu Sep 09 11:50:40 2010 -0700
+++ b/jdk/src/solaris/classes/sun/nio/fs/UnixDirectoryStream.java	Fri Sep 10 16:36:48 2010 +0100
@@ -27,7 +27,6 @@
 
 import java.nio.file.*;
 import java.util.Iterator;
-import java.util.ConcurrentModificationException;
 import java.util.NoSuchElementException;
 import java.util.concurrent.locks.*;
 import java.io.IOException;
@@ -139,9 +138,6 @@
         // next entry to return
         private Path nextEntry;
 
-        // previous entry returned by next method (needed by remove method)
-        private Path prevEntry;
-
         UnixDirectoryIterator(DirectoryStream<Path> stream) {
             atEof = false;
             this.stream = stream;
@@ -168,24 +164,19 @@
                 // prevent close while reading
                 readLock().lock();
                 try {
-                    if (isClosed)
-                        throwAsConcurrentModificationException(new
-                            ClosedDirectoryStreamException());
-                    try {
+                    if (isOpen()) {
                         nameAsBytes = readdir(dp);
-                    } catch (UnixException x) {
-                        try {
-                            x.rethrowAsIOException(dir);
-                        } catch (IOException ioe) {
-                            throwAsConcurrentModificationException(ioe);
-                        }
                     }
+                } catch (UnixException x) {
+                    IOException ioe = x.asIOException(dir);
+                    throw new DirectoryIteratorException(ioe);
                 } finally {
                     readLock().unlock();
                 }
 
                 // EOF
                 if (nameAsBytes == null) {
+                    atEof = true;
                     return null;
                 }
 
@@ -198,7 +189,7 @@
                         if (filter == null || filter.accept(entry))
                             return entry;
                     } catch (IOException ioe) {
-                        throwAsConcurrentModificationException(ioe);
+                        throw new DirectoryIteratorException(ioe);
                     }
                 }
             }
@@ -206,66 +197,28 @@
 
         @Override
         public synchronized boolean hasNext() {
-            if (nextEntry == null && !atEof) {
+            if (nextEntry == null && !atEof)
                 nextEntry = readNextEntry();
-
-                // at EOF?
-                if (nextEntry == null)
-                    atEof = true;
-            }
             return nextEntry != null;
         }
 
         @Override
         public synchronized Path next() {
-            if (nextEntry == null) {
-                if (!atEof) {
-                    nextEntry = readNextEntry();
-                }
-                if (nextEntry == null) {
-                    atEof = true;
-                    throw new NoSuchElementException();
-                }
+            Path result;
+            if (nextEntry == null && !atEof) {
+                result = readNextEntry();
+            } else {
+                result = nextEntry;
+                nextEntry = null;
             }
-            prevEntry = nextEntry;
-            nextEntry = null;
-            return prevEntry;
+            if (result == null)
+                throw new NoSuchElementException();
+            return result;
         }
 
         @Override
         public void remove() {
-            if (isClosed) {
-                throwAsConcurrentModificationException(new
-                    ClosedDirectoryStreamException());
-            }
-            Path entry;
-            synchronized (this) {
-                if (prevEntry == null)
-                    throw new IllegalStateException("No previous entry to remove");
-                entry = prevEntry;
-                prevEntry = null;
-            }
-
-            // use (race-free) unlinkat if available
-            try {
-                if (stream instanceof UnixSecureDirectoryStream) {
-                    ((UnixSecureDirectoryStream)stream)
-                        .implDelete(entry.getName(), false, 0);
-                } else {
-                    entry.delete();
-                }
-            } catch (IOException ioe) {
-                throwAsConcurrentModificationException(ioe);
-            } catch (SecurityException se) {
-                throwAsConcurrentModificationException(se);
-            }
+            throw new UnsupportedOperationException();
         }
     }
-
-    private static void throwAsConcurrentModificationException(Throwable t) {
-        ConcurrentModificationException cme = new ConcurrentModificationException();
-        cme.initCause(t);
-        throw cme;
-    }
-
 }
--- a/jdk/src/solaris/classes/sun/nio/fs/UnixSecureDirectoryStream.java	Thu Sep 09 11:50:40 2010 -0700
+++ b/jdk/src/solaris/classes/sun/nio/fs/UnixSecureDirectoryStream.java	Fri Sep 10 16:36:48 2010 +0100
@@ -166,7 +166,7 @@
      * Deletes file/directory in this directory. Works in a race-free manner
      * when invoked with flags.
      */
-    void implDelete(Path obj, boolean haveFlags, int flags)
+    private void implDelete(Path obj, boolean haveFlags, int flags)
         throws IOException
     {
         UnixPath file = getName(obj);
--- a/jdk/src/windows/classes/sun/nio/fs/WindowsDirectoryStream.java	Thu Sep 09 11:50:40 2010 -0700
+++ b/jdk/src/windows/classes/sun/nio/fs/WindowsDirectoryStream.java	Fri Sep 10 16:36:48 2010 +0100
@@ -28,7 +28,6 @@
 import java.nio.file.*;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.util.Iterator;
-import java.util.ConcurrentModificationException;
 import java.util.NoSuchElementException;
 import java.io.IOException;
 
@@ -121,17 +120,10 @@
         }
     }
 
-    private static void throwAsConcurrentModificationException(Throwable t) {
-        ConcurrentModificationException cme = new ConcurrentModificationException();
-        cme.initCause(t);
-        throw cme;
-    }
-
     private class WindowsDirectoryIterator implements Iterator<Path> {
         private boolean atEof;
         private String first;
         private Path nextEntry;
-        private Path prevEntry;
 
         WindowsDirectoryIterator(String first) {
             atEof = false;
@@ -156,7 +148,7 @@
                 if (filter.accept(entry))
                     return entry;
             } catch (IOException ioe) {
-                throwAsConcurrentModificationException(ioe);
+                throw new DirectoryIteratorException(ioe);
             }
             return null;
         }
@@ -177,21 +169,19 @@
 
                 // synchronize on closeLock to prevent close while reading
                 synchronized (closeLock) {
-                    if (!isOpen)
-                        throwAsConcurrentModificationException(new
-                            ClosedDirectoryStreamException());
                     try {
-                        name = FindNextFile(handle, findDataBuffer.address());
-                        if (name == null) {
-                            // NO_MORE_FILES
-                            return null;
+                        if (isOpen) {
+                            name = FindNextFile(handle, findDataBuffer.address());
                         }
                     } catch (WindowsException x) {
-                        try {
-                            x.rethrowAsIOException(dir);
-                        } catch (IOException ioe) {
-                            throwAsConcurrentModificationException(ioe);
-                        }
+                        IOException ioe = x.asIOException(dir);
+                        throw new DirectoryIteratorException(ioe);
+                    }
+
+                    // NO_MORE_FILES or stream closed
+                    if (name == null) {
+                        atEof = true;
+                        return null;
                     }
 
                     // grab the attributes from the WIN32_FIND_DATA structure
@@ -210,49 +200,28 @@
 
         @Override
         public synchronized boolean hasNext() {
-            if (nextEntry == null && !atEof) {
+            if (nextEntry == null && !atEof)
                 nextEntry = readNextEntry();
-                atEof = (nextEntry == null);
-            }
             return nextEntry != null;
         }
 
         @Override
         public synchronized Path next() {
-            if (nextEntry == null) {
-                if (!atEof) {
-                    nextEntry = readNextEntry();
-                }
-                if (nextEntry == null) {
-                    atEof = true;
-                    throw new NoSuchElementException();
-                }
+            Path result = null;
+            if (nextEntry == null && !atEof) {
+                result = readNextEntry();
+            } else {
+                result = nextEntry;
+                nextEntry = null;
             }
-            prevEntry = nextEntry;
-            nextEntry = null;
-            return prevEntry;
+            if (result == null)
+                throw new NoSuchElementException();
+            return result;
         }
 
         @Override
         public void remove() {
-            if (!isOpen) {
-                throwAsConcurrentModificationException(new
-                    ClosedDirectoryStreamException());
-            }
-            Path entry;
-            synchronized (this) {
-                if (prevEntry == null)
-                    throw new IllegalStateException("no last element");
-                entry = prevEntry;
-                prevEntry = null;
-            }
-            try {
-                entry.delete();
-            } catch (IOException ioe) {
-                throwAsConcurrentModificationException(ioe);
-            } catch (SecurityException se) {
-                throwAsConcurrentModificationException(se);
-            }
+            throw new UnsupportedOperationException();
         }
     }
 }
--- a/jdk/test/java/nio/file/DirectoryStream/Basic.java	Thu Sep 09 11:50:40 2010 -0700
+++ b/jdk/test/java/nio/file/DirectoryStream/Basic.java	Fri Sep 10 16:36:48 2010 +0100
@@ -104,20 +104,20 @@
             stream.close();
         }
 
-        // check that IOExceptions throws by filters are propagated
+        // check that an IOException thrown by a filter is propagated
         filter = new DirectoryStream.Filter<Path>() {
             public boolean accept(Path file) throws IOException {
-                throw new IOException();
+                throw new java.util.zip.ZipException();
             }
         };
         stream = dir.newDirectoryStream(filter);
         try {
             stream.iterator().hasNext();
-            throw new RuntimeException("ConcurrentModificationException expected");
-        } catch (ConcurrentModificationException x) {
-            Throwable t = x.getCause();
-            if (!(t instanceof IOException))
-                throw new RuntimeException("Cause is not IOException as expected");
+            throw new RuntimeException("DirectoryIteratorException expected");
+        } catch (DirectoryIteratorException x) {
+            IOException cause = x.getCause();
+            if (!(cause instanceof java.util.zip.ZipException))
+                throw new RuntimeException("Expected IOException not propagated");
         } finally {
             stream.close();
         }
@@ -142,58 +142,49 @@
         } catch (NotDirectoryException x) {
         }
 
-        // test iterator remove method
-        stream = dir.newDirectoryStream();
+        // test UnsupportedOperationException
+        stream =  dir.newDirectoryStream();
         Iterator<Path> i = stream.iterator();
-        while (i.hasNext()) {
-            Path entry = i.next();
-            if (!entry.getName().equals(foo))
-                throw new RuntimeException("entry not expected");
+        i.next();
+        try {
             i.remove();
+            throw new RuntimeException("UnsupportedOperationException expected");
+        } catch (UnsupportedOperationException uoe) {
         }
-        stream.close();
 
         // test IllegalStateException
-        dir.resolve(foo).createFile();
         stream =  dir.newDirectoryStream();
-        i = stream.iterator();
-        i.next();
+        stream.iterator();
         try {
+            // attempt to obtain second iterator
             stream.iterator();
             throw new RuntimeException("IllegalStateException not thrown as expected");
         } catch (IllegalStateException x) {
         }
         stream.close();
+
+        stream =  dir.newDirectoryStream();
+        stream.close();
         try {
+            // attempt to obtain iterator after stream is closed
             stream.iterator();
             throw new RuntimeException("IllegalStateException not thrown as expected");
         } catch (IllegalStateException x) {
         }
-        try {
-            i.hasNext();
-            throw new RuntimeException("ConcurrentModificationException not thrown as expected");
-        } catch (ConcurrentModificationException x) {
-            Throwable t = x.getCause();
-            if (!(t instanceof ClosedDirectoryStreamException))
-                throw new RuntimeException("Cause is not ClosedDirectoryStreamException as expected");
-        }
-        try {
+
+        // test that iterator reads to end of stream when closed
+        stream =  dir.newDirectoryStream();
+        i = stream.iterator();
+        stream.close();
+        while (i.hasNext())
             i.next();
-            throw new RuntimeException("ConcurrentModificationException not thrown as expected");
-        } catch (ConcurrentModificationException x) {
-            Throwable t = x.getCause();
-            if (!(t instanceof ClosedDirectoryStreamException))
-                throw new RuntimeException("Cause is not ClosedDirectoryStreamException as expected");
-        }
+
+        stream =  dir.newDirectoryStream();
+        i = stream.iterator();
+        stream.close();
         try {
-            i.remove();
-            throw new RuntimeException("ConcurrentModificationException not thrown as expected");
-        } catch (ConcurrentModificationException x) {
-            Throwable t = x.getCause();
-            if (!(t instanceof ClosedDirectoryStreamException))
-                throw new RuntimeException("Cause is not ClosedDirectoryStreamException as expected");
-        }
-
+            for (;;) i.next();
+        } catch (NoSuchElementException expected) { }
     }
 
     public static void main(String[] args) throws IOException {
--- a/jdk/test/java/nio/file/DirectoryStream/SecureDS.java	Thu Sep 09 11:50:40 2010 -0700
+++ b/jdk/test/java/nio/file/DirectoryStream/SecureDS.java	Fri Sep 10 16:36:48 2010 +0100
@@ -166,22 +166,6 @@
         stream.deleteDirectory(dirEntry);
         stream.deleteFile(fileEntry);
 
-        // Test: remove
-        // (requires resetting environment to get new iterator)
-        stream.close();
-        dir2.moveTo(dir1);
-        dir1.resolve(fileEntry).createFile();
-        stream = (SecureDirectoryStream<Path>)dir1.newDirectoryStream();
-        dir1.moveTo(dir2);
-        Iterator<Path> iter = stream.iterator();
-        int removed = 0;
-        while (iter.hasNext()) {
-            iter.next();
-            iter.remove();
-            removed++;
-        }
-        assertTrue(removed == 1);
-
         // clean-up
         stream.close();
         dir2.delete();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/nio/file/etc/Exceptions.java	Fri Sep 10 16:36:48 2010 +0100
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2010, 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 4313887 6881498
+ * @summary Miscellenous tests on exceptions in java.nio.file
+ */
+
+import java.nio.file.*;
+import java.io.*;
+import java.util.Objects;
+import java.lang.reflect.*;
+
+public class Exceptions {
+
+    public static void main(String[] args) throws Exception {
+        testFileSystemException();
+        testDirectoryIteratorException();
+    }
+
+    static void testFileSystemException() throws Exception {
+        String thisFile = "source";
+        String otherFile = "target";
+        String reason = "Access denied";
+
+        // getFile/getOtherFile
+        testFileSystemException(thisFile, otherFile, reason);
+        testFileSystemException(null, otherFile, reason);
+        testFileSystemException(thisFile, null, reason);
+        testFileSystemException(thisFile, otherFile, null);
+
+        // serialization
+        FileSystemException exc;
+        exc = new FileSystemException(thisFile, otherFile, reason);
+        exc = (FileSystemException)deserialize(serialize(exc));
+        if (!exc.getFile().equals(thisFile) || !exc.getOtherFile().equals(otherFile))
+            throw new RuntimeException("Exception not reconstituted completely");
+    }
+
+    static void testFileSystemException(String thisFile,
+                                        String otherFile,
+                                        String reason)
+    {
+        FileSystemException exc = new FileSystemException(thisFile, otherFile, reason);
+        if (!Objects.equals(thisFile, exc.getFile()))
+            throw new RuntimeException("getFile returned unexpected result");
+        if (!Objects.equals(otherFile, exc.getOtherFile()))
+            throw new RuntimeException("getOtherFile returned unexpected result");
+        if (!Objects.equals(reason, exc.getReason()))
+            throw new RuntimeException("getReason returned unexpected result");
+    }
+
+    static void testDirectoryIteratorException() throws Exception {
+        // NullPointerException
+        try {
+            new DirectoryIteratorException(null);
+            throw new RuntimeException("NullPointerException expected");
+        } catch (NullPointerException expected) { }
+
+        // serialization
+        DirectoryIteratorException exc;
+        exc = new DirectoryIteratorException(new IOException());
+        exc = (DirectoryIteratorException)deserialize(serialize(exc));
+        IOException ioe = exc.getCause();
+        if (ioe == null)
+            throw new RuntimeException("Cause should not be null");
+
+        // when deserializing then the cause should be an IOException
+        hackCause(exc, null);
+        try {
+            deserialize(serialize(exc));
+            throw new RuntimeException("InvalidObjectException expected");
+        } catch (InvalidObjectException expected) { }
+
+        hackCause(exc, new RuntimeException());
+        try {
+            deserialize(serialize(exc));
+            throw new RuntimeException("InvalidObjectException expected");
+        } catch (InvalidObjectException expected) { }
+    }
+
+
+    // Use reflection to set a Throwable's cause.
+    static void hackCause(Throwable t, Throwable cause)
+        throws NoSuchFieldException, IllegalAccessException
+    {
+        Field f = Throwable.class.getDeclaredField("cause");
+        f.setAccessible(true);
+        f.set(t, cause);
+    }
+
+    // Serialize the given object to a byte[]
+    static byte[] serialize(Object o) throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        ObjectOutputStream oos = new ObjectOutputStream(baos);
+        oos.writeObject(o);
+        oos.close();
+        return baos.toByteArray();
+    }
+
+    // Read an object from its serialized form
+    static Object deserialize(byte[] bytes)
+        throws IOException, ClassNotFoundException
+    {
+        ByteArrayInputStream in = new ByteArrayInputStream(bytes);
+        ObjectInputStream ois = new ObjectInputStream(in);
+        Object result = ois.readObject();
+        ois.close();
+        return result;
+    }
+}