src/share/classes/java/nio/file/Files.java
Print this page
rev 7982 : 8017513: Support for closeable streams
8022237: j.u.s.BaseStream.onClose() has an issue in implementation or requires spec clarification
8022572: Same exception instances thrown from j.u.stream.Stream.onClose() handlers are not listed as suppressed
Summary: BaseStream implements AutoCloseable; Remove CloseableStream and DelegatingStream
Reviewed-by: alanb, mduigou, psandoz
Contributed-by: brian.goetz@oracle.com
*** 23,60 ****
* questions.
*/
package java.nio.file;
- import java.nio.file.attribute.*;
- import java.nio.file.spi.FileSystemProvider;
- import java.nio.file.spi.FileTypeDetector;
- import java.nio.channels.Channels;
- import java.nio.channels.FileChannel;
- 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;
/**
* This class consists exclusively of static methods that operate on files,
* directories, or other types of files.
*
--- 23,82 ----
* questions.
*/
package java.nio.file;
import java.io.BufferedReader;
import java.io.BufferedWriter;
+ import java.io.Closeable;
+ import java.io.File;
+ import java.io.IOException;
+ import java.io.InputStream;
import java.io.InputStreamReader;
+ import java.io.OutputStream;
import java.io.OutputStreamWriter;
! import java.io.Reader;
import java.io.UncheckedIOException;
! import java.io.Writer;
! import java.nio.channels.Channels;
! import java.nio.channels.FileChannel;
! import java.nio.channels.SeekableByteChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
+ import java.nio.file.attribute.BasicFileAttributeView;
+ import java.nio.file.attribute.BasicFileAttributes;
+ import java.nio.file.attribute.DosFileAttributes;
+ import java.nio.file.attribute.FileAttribute;
+ import java.nio.file.attribute.FileAttributeView;
+ import java.nio.file.attribute.FileOwnerAttributeView;
+ import java.nio.file.attribute.FileStoreAttributeView;
+ import java.nio.file.attribute.FileTime;
+ import java.nio.file.attribute.PosixFileAttributeView;
+ import java.nio.file.attribute.PosixFileAttributes;
+ import java.nio.file.attribute.PosixFilePermission;
+ import java.nio.file.attribute.UserPrincipal;
+ import java.nio.file.spi.FileSystemProvider;
+ import java.nio.file.spi.FileTypeDetector;
+ import java.security.AccessController;
+ import java.security.PrivilegedAction;
+ import java.util.ArrayList;
+ import java.util.Arrays;
+ import java.util.Collections;
+ import java.util.EnumSet;
+ import java.util.HashSet;
+ import java.util.Iterator;
+ import java.util.List;
+ import java.util.Map;
+ import java.util.Objects;
+ import java.util.ServiceLoader;
+ import java.util.Set;
+ import java.util.Spliterator;
+ import java.util.Spliterators;
+ import java.util.function.BiPredicate;
+ import java.util.stream.Stream;
+ import java.util.stream.StreamSupport;
/**
* This class consists exclusively of static methods that operate on files,
* directories, or other types of files.
*
*** 72,81 ****
--- 94,118 ----
*/
private static FileSystemProvider provider(Path path) {
return path.getFileSystem().provider();
}
+ /**
+ * Convert a Closeable to a Runnable by converting checked IOException
+ * to UncheckedIOException
+ */
+ private static Runnable asUncheckedRunnable(Closeable c) {
+ return () -> {
+ try {
+ c.close();
+ }
+ catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ };
+ }
+
// -- File contents --
/**
* Opens a file, returning an input stream to read from the file. The stream
* will not be buffered, and is not required to support the {@link
*** 3226,3258 ****
}
// -- 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
--- 3263,3273 ----
}
// -- Stream APIs --
/**
! * Return a lazily populated {@code Stream}, 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
*** 3262,3275 ****
* <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 returning from this
* method.
*
! * <p> 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.
*
* <p> If an {@link IOException} is thrown when accessing the directory
* after this method has returned, it is wrapped in an {@link
--- 3277,3293 ----
* <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 returning from this
* method.
*
! * <p> The returned stream encapsulates a {@link DirectoryStream}.
! * If timely disposal of file system resources is required, the
! * {@code try}-with-resources construct should be used to ensure that the
! * stream's {@link Stream#close close} method is invoked after the stream
! * operations are completed.
! *
! * <p> 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.
*
* <p> If an {@link IOException} is thrown when accessing the directory
* after this method has returned, it is wrapped in an {@link
*** 3276,3286 ****
* 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>
--- 3294,3304 ----
* 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 Stream} 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>
*** 3292,3338 ****
* method is invoked to check read access to the directory.
*
* @see #newDirectoryStream(Path)
* @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());
}
}
};
! Stream<Path> s = StreamSupport.stream(
! Spliterators.spliteratorUnknownSize(it, Spliterator.DISTINCT),
! false);
! return new DelegatingCloseableStream<>(ds, s);
}
/**
! * 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
--- 3310,3367 ----
* method is invoked to check read access to the directory.
*
* @see #newDirectoryStream(Path)
* @since 1.8
*/
! public static Stream<Path> list(Path dir) throws IOException {
DirectoryStream<Path> ds = Files.newDirectoryStream(dir);
+ try {
final Iterator<Path> delegate = ds.iterator();
// Re-wrap DirectoryIteratorException to UncheckedIOException
Iterator<Path> it = new Iterator<Path>() {
+ @Override
public boolean hasNext() {
try {
return delegate.hasNext();
} catch (DirectoryIteratorException e) {
throw new UncheckedIOException(e.getCause());
}
}
+ @Override
public Path next() {
try {
return delegate.next();
} catch (DirectoryIteratorException e) {
throw new UncheckedIOException(e.getCause());
}
}
};
! return StreamSupport.stream(Spliterators.spliteratorUnknownSize(it, Spliterator.DISTINCT), false)
! .onClose(asUncheckedRunnable(ds));
! } catch (Error|RuntimeException e) {
! try {
! ds.close();
! } catch (IOException ex) {
! try {
! e.addSuppressed(ex);
! } catch (Throwable ignore) {}
}
+ throw e;
+ }
+ }
/**
! * Return a {@code Stream} 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 Stream} 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
*** 3368,3381 ****
* 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 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}.
*
* <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
--- 3397,3411 ----
* 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> The returned stream encapsulates one or more {@link DirectoryStream}s.
! * If timely disposal of file system resources is required, the
! * {@code try}-with-resources construct should be used to ensure that the
! * stream's {@link Stream#close close} method is invoked after the stream
! * operations are completed. Operating on a closed stream will result in an
* {@link java.lang.IllegalStateException}.
*
* <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
*** 3386,3396 ****
* @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.
--- 3416,3426 ----
* @param maxDepth
* the maximum number of directory levels to visit
* @param options
* options to configure the traversal
*
! * @return the {@link Stream} of {@link Path}
*
* @throws IllegalArgumentException
* if the {@code maxDepth} parameter is negative
* @throws SecurityException
* If the security manager denies access to the starting file.
*** 3399,3423 ****
* 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);
!
! Stream<Path> s = StreamSupport.stream(
! Spliterators.spliteratorUnknownSize(iterator, Spliterator.DISTINCT),
! false).
! map(entry -> entry.file());
! return new DelegatingCloseableStream<>(iterator, s);
}
/**
! * 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}.
*
--- 3429,3454 ----
* 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 Stream<Path> walk(Path start, int maxDepth,
FileVisitOption... options)
! throws IOException {
FileTreeIterator iterator = new FileTreeIterator(start, maxDepth, options);
! try {
! return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.DISTINCT), false)
! .onClose(iterator::close)
! .map(entry -> entry.file());
! } catch (Error|RuntimeException e) {
! iterator.close();
! throw e;
}
+ }
/**
! * Return a {@code Stream} 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}.
*
*** 3426,3441 ****
* <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
--- 3457,3479 ----
* <blockquote><pre>
* walk(start, Integer.MAX_VALUE, options)
* </pre></blockquote>
* In other words, it visits all levels of the file tree.
*
+ * <p> The returned stream encapsulates one or more {@link DirectoryStream}s.
+ * If timely disposal of file system resources is required, the
+ * {@code try}-with-resources construct should be used to ensure that the
+ * stream's {@link Stream#close close} method is invoked after the stream
+ * operations are completed. Operating on a closed stream will result in an
+ * {@link java.lang.IllegalStateException}.
+ *
* @param start
* the starting file
* @param options
* options to configure the traversal
*
! * @return the {@link Stream} 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
*** 3444,3476 ****
* 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 may be 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.
*
--- 3482,3520 ----
* if an I/O error is thrown when accessing the starting file.
*
* @see #walk(Path, int, FileVisitOption...)
* @since 1.8
*/
! public static Stream<Path> walk(Path start,
FileVisitOption... options)
! throws IOException {
return walk(start, Integer.MAX_VALUE, options);
}
/**
! * Return a {@code Stream} 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 Stream} 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}.
*
+ * <p> The returned stream encapsulates one or more {@link DirectoryStream}s.
+ * If timely disposal of file system resources is required, the
+ * {@code try}-with-resources construct should be used to ensure that the
+ * stream's {@link Stream#close close} method is invoked after the stream
+ * operations are completed. Operating on a closed stream will result in an
+ * {@link java.lang.IllegalStateException}.
+ *
* <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.
*
*** 3482,3492 ****
* 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.
--- 3526,3536 ----
* 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 Stream} of {@link Path}
*
* @throws IllegalArgumentException
* if the {@code maxDepth} parameter is negative
* @throws SecurityException
* If the security manager denies access to the starting file.
*** 3497,3524 ****
* 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);
!
! Stream<Path> s = StreamSupport.stream(
! Spliterators.spliteratorUnknownSize(iterator, Spliterator.DISTINCT),
! false).
! filter(entry -> matcher.test(entry.file(), entry.attributes())).
! map(entry -> entry.file());
! return new DelegatingCloseableStream<>(iterator, s);
}
/**
! * 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
--- 3541,3569 ----
* if an I/O error is thrown when accessing the starting file.
*
* @see #walk(Path, int, FileVisitOption...)
* @since 1.8
*/
! public static Stream<Path> find(Path start,
int maxDepth,
BiPredicate<Path, BasicFileAttributes> matcher,
FileVisitOption... options)
! throws IOException {
FileTreeIterator iterator = new FileTreeIterator(start, maxDepth, options);
! try {
! return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.DISTINCT), false)
! .onClose(iterator::close)
! .filter(entry -> matcher.test(entry.file(), entry.attributes()))
! .map(entry -> entry.file());
! } catch (Error|RuntimeException e) {
! iterator.close();
! throw e;
}
+ }
/**
! * Read all lines from a file as a {@code Stream}. 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
*** 3526,3551 ****
* 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
--- 3571,3598 ----
* 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 from 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> The returned stream encapsulates a {@link Reader}. If timely
! * disposal of file system resources is required, the try-with-resources
! * construct should be used to ensure that the stream's
! * {@link Stream#close close} method is invoked after the stream operations
! * are completed.
*
+ *
* @param path
* the path to the file
* @param cs
* the charset to use for decoding
*
! * @return the lines from the file as a {@code Stream}
*
* @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
*** 3555,3566 ****
* @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());
}
}
--- 3602,3622 ----
* @see #readAllLines(Path, Charset)
* @see #newBufferedReader(Path, Charset)
* @see java.io.BufferedReader#lines()
* @since 1.8
*/
! public static Stream<String> lines(Path path, Charset cs) throws IOException {
BufferedReader br = Files.newBufferedReader(path, cs);
! try {
! return br.lines().onClose(asUncheckedRunnable(br));
! } catch (Error|RuntimeException e) {
! try {
! br.close();
! } catch (IOException ex) {
! try {
! e.addSuppressed(ex);
! } catch (Throwable ignore) {}
}
+ throw e;
+ }
+ }
}