From d97b7918777cf52b8f695adc40ced673179ad7eb Mon Sep 17 00:00:00 2001 From: Woden Date: Fri, 5 Nov 2021 10:49:37 -0500 Subject: [PATCH 1/4] IOUtils chain together IOException for multiple Closeable instances, add overload --- .../java/org/apache/commons/io/IOUtils.java | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/apache/commons/io/IOUtils.java b/src/main/java/org/apache/commons/io/IOUtils.java index be574e5117b..ada16b6ad4d 100644 --- a/src/main/java/org/apache/commons/io/IOUtils.java +++ b/src/main/java/org/apache/commons/io/IOUtils.java @@ -392,8 +392,19 @@ public static void close(final Closeable closeable) throws IOException { */ public static void close(final Closeable... closeables) throws IOException { if (closeables != null) { + IOException ioexception = null; for (final Closeable closeable : closeables) { - close(closeable); + try { + close(closeable); + } catch (IOException ex) { + if (ioexception == null) { + ioexception = new IOException("IOUtils.close failed"); + } + ioexception.addSuppressed(ex); + } + } + if (ioexception != null) { + throw ioexception; } } } @@ -418,6 +429,25 @@ public static void close(final Closeable closeable, final IOConsumer consumer) throws IOException { + if (closeables != null) { + try { + close(closeables); + } catch (final IOException e) { + if (consumer != null) { + consumer.accept(e); + } + } + } + } + /** * Closes a URLConnection. * From c2751e6fabc6535d23d809760055e5bc23361c5e Mon Sep 17 00:00:00 2001 From: Woden Date: Fri, 5 Nov 2021 18:37:22 -0500 Subject: [PATCH 2/4] Use IOExceptionList to aggregate all exceptions from close operation --- src/main/java/org/apache/commons/io/IOUtils.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/apache/commons/io/IOUtils.java b/src/main/java/org/apache/commons/io/IOUtils.java index ada16b6ad4d..7ce831d5615 100644 --- a/src/main/java/org/apache/commons/io/IOUtils.java +++ b/src/main/java/org/apache/commons/io/IOUtils.java @@ -392,19 +392,19 @@ public static void close(final Closeable closeable) throws IOException { */ public static void close(final Closeable... closeables) throws IOException { if (closeables != null) { - IOException ioexception = null; + List exceptions = null; for (final Closeable closeable : closeables) { try { close(closeable); } catch (IOException ex) { - if (ioexception == null) { - ioexception = new IOException("IOUtils.close failed"); + if (exceptions == null) { + exceptions = new ArrayList<>(); } - ioexception.addSuppressed(ex); + exceptions.add(ex); } } - if (ioexception != null) { - throw ioexception; + if (exceptions != null) { + throw new IOExceptionList(exceptions); } } } From 8097e961f8900fe5be8d3d4f70be006107f7bd4e Mon Sep 17 00:00:00 2001 From: Woden Date: Thu, 11 Nov 2021 08:45:35 -0600 Subject: [PATCH 3/4] Additional changes to IOUtils close and exception handling --- .../java/org/apache/commons/io/IOUtils.java | 108 +++++++++++++++++- .../commons/io/function/IOConsumer.java | 79 ++++++++++++- .../apache/commons/io/function/IOStreams.java | 29 +++-- .../io/input/ObservableInputStream.java | 2 +- .../io/output/FilterCollectionWriter.java | 3 +- .../org/apache/commons/io/IOUtilsTest.java | 22 +++- 6 files changed, 224 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/apache/commons/io/IOUtils.java b/src/main/java/org/apache/commons/io/IOUtils.java index ac150efa2f3..06337162eca 100644 --- a/src/main/java/org/apache/commons/io/IOUtils.java +++ b/src/main/java/org/apache/commons/io/IOUtils.java @@ -45,11 +45,11 @@ import java.nio.charset.Charset; import java.nio.file.Files; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.function.Consumer; +import java.util.stream.Stream; import org.apache.commons.io.function.IOConsumer; import org.apache.commons.io.input.QueueInputStream; @@ -394,6 +394,25 @@ public static void close(final Closeable... closeables) throws IOException { IOConsumer.forEach(closeables, IOUtils::close); } + /** + * Closes the entries in the given {@link Stream} as null-safe operations, + * and closes the underlying {@code Stream}. + * + * @param The element type. + * @param closeables The resource(s) to close, may be null. + * @throws IOExceptionList if an I/O error occurs. + * @since 2.12.0 + */ + public static void close(final Stream closeables) throws IOExceptionList { + if (closeables != null) { + try { + IOConsumer.forEachIndexed(closeables, IOUtils::close); + } finally { + closeables.close(); + } + } + } + /** * Closes the given {@link Closeable} as a null-safe operation. * @@ -415,13 +434,36 @@ public static void close(final Closeable closeable, final IOConsumer The element type. + * @param consumer Consume the IOException thrown by {@link Closeable#close()}. * @param closeables The resource(s) to close, may be null. + * @throws IOException if an I/O error occurs. + */ + public static void close(final IOConsumer consumer, final Stream closeables) throws IOException { + if (closeables != null) { + try { + close(closeables); + } catch (final IOException e) { + if (consumer != null) { + consumer.accept(e); + } + } finally { + closeables.close(); + } + } + } + + /** + * Closes the given {@link Closeable} as a null-safe operation. + * * @param consumer Consume the IOException thrown by {@link Closeable#close()}. + * @param closeables The resource(s) to close, may be null. * @throws IOException if an I/O error occurs. */ - public static void close(final Closeable[] closeables, final IOConsumer consumer) throws IOException { + public static void close(final IOConsumer consumer, final Closeable... closeables) throws IOException { if (closeables != null) { try { close(closeables); @@ -538,7 +580,7 @@ public static void closeQuietly(final Closeable closeable) { */ public static void closeQuietly(final Closeable... closeables) { if (closeables != null) { - Arrays.stream(closeables).forEach(IOUtils::closeQuietly); + IOConsumer.forEachQuietly(closeables, IOUtils::closeQuietly); } } @@ -561,6 +603,64 @@ public static void closeQuietly(final Closeable closeable, final Consumer The element type. + * @param closeables The resource(s) to close, may be null. + * @since 2.12.0 + */ + public static void closeQuietly(final Stream closeables) { + closeQuietly(null, closeables); + } + + /** + * Closes the given {@link Stream} as a null-safe operation while consuming IOException by the given {@code consumer}, + * and closes the underlying {@code Stream}. + * + * @param The element type. + * @param consumer Consume the IOException thrown by {@link Closeable#close()}. + * @param closeables The resource(s) to close, may be null. + * @since 2.12.0 + */ + public static void closeQuietly(final Consumer consumer, final Stream closeables) { + if (closeables != null) { + try { + close(closeables); + } catch (final IOException e) { + if (consumer != null) { + consumer.accept(e); + } + } finally { + try { + closeables.close(); + } catch (Exception e) { + // Do nothing. + } + } + } + } + + /** + * Closes the given {@link Closeable}s as a null-safe operation while consuming IOException by the given {@code consumer}. + * + * @param consumer Consume the IOException thrown by {@link Closeable#close()}. + * @param closeables The resource(s) to close, may be null. + * @since 2.12.0 + */ + public static void closeQuietly(final Consumer consumer, final Closeable... closeables) { + if (closeables != null) { + try { + close(closeables); + } catch (final IOException e) { + if (consumer != null) { + consumer.accept(e); + } + } + } + } + /** * Closes an {@code InputStream} unconditionally. *

diff --git a/src/main/java/org/apache/commons/io/function/IOConsumer.java b/src/main/java/org/apache/commons/io/function/IOConsumer.java index 662cd34d254..1719fce7e52 100644 --- a/src/main/java/org/apache/commons/io/function/IOConsumer.java +++ b/src/main/java/org/apache/commons/io/function/IOConsumer.java @@ -18,7 +18,9 @@ package org.apache.commons.io.function; import java.io.IOException; +import java.io.UncheckedIOException; import java.util.Objects; +import java.util.Optional; import java.util.function.Consumer; import java.util.stream.Stream; @@ -39,6 +41,29 @@ public interface IOConsumer { */ IOConsumer NOOP_IO_CONSUMER = t -> {/* noop */}; + /** + * Wraps an {@code IOConsumer} inside of a {@link Consumer} + * that throws {@link UncheckedIOException} for any {@link IOException}s + * that are thrown by the underlying {@code IOConsumer}. + * + * @param The element type. + * @param consumer The {@code IOConsumer} to wrap. + * @return a {@code Consumer} that wraps the given {@code IOConsumer}. + * @since 2.12.0 + */ + static Consumer wrap(IOConsumer consumer) { + return new Consumer() { + @Override + public void accept(T t) { + try { + consumer.accept(t); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + }; + } + /** * Performs an action for each element of this stream. * @@ -52,6 +77,46 @@ static void forEach(final T[] array, final IOConsumer action) throws IOEx IOStreams.forEach(IOStreams.of(array), action); } + /** + * Performs an action for each element of this array, returning + * a {@link Optional} that either contains an {@link IOException} + * if one occurred, or {@link Optional#empty()}. + * + * @param The element type. + * @param array The input to stream. + * @param action The action to apply to each input element. + * @return a {@code Optional} that may wrap a {@code IOException}. + * @since 2.12.0 + */ + static Optional forEachQuietly(final T[] array, final IOConsumer action) { + try { + IOStreams.forEach(IOStreams.of(array), action); + return Optional.empty(); + } catch (IOException e) { + return Optional.of(e); + } + } + + /** + * Performs an action for each element of this stream, returning + * a {@link Optional} that either contains an {@link IOExceptionList} + * if one occurred, or {@link Optional#empty()}. + * + * @param The element type. + * @param stream The input to stream. + * @param action The action to apply to each input element. + * @return a {@code Optional} that may wrap a {@code IOExceptionList}. + * @since 2.12.0 + */ + static Optional forEachIndexedQuietly(final Stream stream, final IOConsumer action) { + try { + IOStreams.forEachIndexed(stream, action, IOIndexedException::new); + return Optional.empty(); + } catch (IOExceptionList e) { + return Optional.of(e); + } + } + /** * Performs an action for each element of this stream. * @@ -85,13 +150,25 @@ static IOConsumer noop() { */ void accept(T t) throws IOException; + /** + * Returns this {@code IOConsumer} wrapped inside of a {@link Consumer} + * that throws {@link UncheckedIOException} for any {@link IOException}s + * that are thrown by this {@code IOConsumer}. + * + * @return a {@code Consumer} that wraps this {@code IOConsumer}. + * @since 2.12.0 + */ + default Consumer asConsumer() { + return wrap(this); + } + /** * Returns a composed {@code IOConsumer} that performs, in sequence, this operation followed by the {@code after} * operation. If performing either operation throws an exception, it is relayed to the caller of the composed operation. * If performing this operation throws an exception, the {@code after} operation will not be performed. * * @param after the operation to perform after this operation - * @return a composed {@code Consumer} that performs in sequence this operation followed by the {@code after} operation + * @return a composed {@code IOConsumer} that performs in sequence this operation followed by the {@code after} operation * @throws NullPointerException if {@code after} is null */ default IOConsumer andThen(final IOConsumer after) { diff --git a/src/main/java/org/apache/commons/io/function/IOStreams.java b/src/main/java/org/apache/commons/io/function/IOStreams.java index 52654cbc48b..249f870d224 100644 --- a/src/main/java/org/apache/commons/io/function/IOStreams.java +++ b/src/main/java/org/apache/commons/io/function/IOStreams.java @@ -52,20 +52,27 @@ static void forEach(final Stream stream, final IOConsumer action) thro static void forEachIndexed(final Stream stream, final IOConsumer action, final BiFunction exSupplier) throws IOExceptionList { - final AtomicReference> causeList = new AtomicReference<>(); + final AtomicReference> causeList = new AtomicReference<>(); final AtomicInteger index = new AtomicInteger(); - stream.forEach(e -> { - try { - action.accept(e); - } catch (final IOException ioex) { - if (causeList.get() == null) { - causeList.set(new ArrayList<>()); + try { + stream.forEach(e -> { + try { + action.accept(e); + } catch (final IOException ioex) { + if (causeList.get() == null) { + causeList.set(new ArrayList<>()); + } + causeList.get().add(exSupplier.apply(index.get(), ioex)); } - causeList.get().add(exSupplier.apply(index.get(), ioex)); + index.incrementAndGet(); + }); + } + catch (Throwable t) { + if (causeList.get() == null) { + causeList.set(new ArrayList<>()); } - index.incrementAndGet(); - }); + causeList.get().add(t); + } IOExceptionList.checkEmpty(causeList.get(), "forEach"); } - } diff --git a/src/main/java/org/apache/commons/io/input/ObservableInputStream.java b/src/main/java/org/apache/commons/io/input/ObservableInputStream.java index d57eb0585db..795ac07b8b5 100644 --- a/src/main/java/org/apache/commons/io/input/ObservableInputStream.java +++ b/src/main/java/org/apache/commons/io/input/ObservableInputStream.java @@ -186,7 +186,7 @@ public List getObservers() { } /** - * Notifies the observers by invoking {@link Observer#finished()}. + * Notifies the observers by invoking {@link Observer#closed()}. * * @throws IOException Some observer has thrown an exception, which is being passed down. */ diff --git a/src/main/java/org/apache/commons/io/output/FilterCollectionWriter.java b/src/main/java/org/apache/commons/io/output/FilterCollectionWriter.java index 08e798038b2..bebf7978f18 100644 --- a/src/main/java/org/apache/commons/io/output/FilterCollectionWriter.java +++ b/src/main/java/org/apache/commons/io/output/FilterCollectionWriter.java @@ -28,6 +28,7 @@ import org.apache.commons.io.IOExceptionList; import org.apache.commons.io.IOIndexedException; +import org.apache.commons.io.IOUtils; import org.apache.commons.io.function.IOConsumer; /** @@ -102,7 +103,7 @@ public Writer append(final CharSequence csq, final int start, final int end) thr @Override public void close() throws IOException { - IOConsumer.forEachIndexed(writers(), Writer::close); + IOUtils.close(writers()); } /** diff --git a/src/test/java/org/apache/commons/io/IOUtilsTest.java b/src/test/java/org/apache/commons/io/IOUtilsTest.java index 9b7214128c9..788c2ea9eea 100644 --- a/src/test/java/org/apache/commons/io/IOUtilsTest.java +++ b/src/test/java/org/apache/commons/io/IOUtilsTest.java @@ -20,6 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; @@ -57,8 +58,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; import java.util.List; - import org.apache.commons.io.function.IOConsumer; import org.apache.commons.io.input.CircularInputStream; import org.apache.commons.io.input.NullInputStream; @@ -67,7 +69,9 @@ import org.apache.commons.io.output.NullOutputStream; import org.apache.commons.io.output.StringBuilderWriter; import org.apache.commons.io.test.TestUtils; +import org.apache.commons.io.test.ThrowOnCloseInputStream; import org.apache.commons.io.test.ThrowOnCloseReader; +import org.apache.commons.io.test.ThrowOnCloseWriter; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -380,6 +384,22 @@ public void testCloseMulti() { () -> IOUtils.close(nullCloseable, new ThrowOnCloseReader(new StringReader("s")))); } + @Test + public void testCloseMultiConsumer() { + final Collection exceptionCollection = new HashSet<>(); + final IOConsumer checkConsumer = i -> { + exceptionCollection.add(i); + }; + + final Closeable[] closeables = {null, new ThrowOnCloseInputStream(), new ThrowOnCloseReader(), new ThrowOnCloseWriter()}; + assertDoesNotThrow(() -> IOUtils.close(checkConsumer, closeables)); + assertEquals(exceptionCollection.size(), 1); + + final IOException exception = exceptionCollection.iterator().next(); + assertInstanceOf(IOExceptionList.class, exception); + assertEquals(((IOExceptionList)exception).getCauseList().size(), 3); + } + @Test public void testCloseQuietly_AllCloseableIOException() { final Closeable closeable = () -> { From 1ccb075f79ec06e763e6b62417d0602c98524749 Mon Sep 17 00:00:00 2001 From: Woden Date: Sat, 13 Nov 2021 02:26:27 -0600 Subject: [PATCH 4/4] Expand exception handling, don't explicitly close Streams in IOUtils --- .../apache/commons/io/IOExceptionList.java | 39 ++++++++++++ .../java/org/apache/commons/io/IOUtils.java | 60 +++++++------------ .../commons/io/function/IOConsumer.java | 40 ++++++++----- .../apache/commons/io/function/IOStreams.java | 44 +++++++++++--- 4 files changed, 120 insertions(+), 63 deletions(-) diff --git a/src/main/java/org/apache/commons/io/IOExceptionList.java b/src/main/java/org/apache/commons/io/IOExceptionList.java index e4343576078..d0db70a2b4a 100644 --- a/src/main/java/org/apache/commons/io/IOExceptionList.java +++ b/src/main/java/org/apache/commons/io/IOExceptionList.java @@ -18,6 +18,7 @@ package org.apache.commons.io; import java.io.IOException; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -35,6 +36,30 @@ public class IOExceptionList extends IOException { private static final long serialVersionUID = 1L; + /** + * Unwinds a {@code IOExceptionList} into a {@link List} of {@link Throwable} + * containing all of the underlying {@code Throwable} instances using + * {@link #getCauseList()}. + * + * Any instances of {@code IOExceptionList} encountered will be recursively + * unwound as well, and the contents of their {@code #getCauseList()} will + * be included in the returned {@code List}. + * + * @param ioExceptionList The {@code IOExceptionList} to recursively unwind, + * may be null, and {@code IOExceptionList#getCauseList()} may be null or empty. + * @return A {@code List} containing all of the {@code Throwable} instances + * inside the given {@code IOExceptionList} using {@code IOExceptionList#getCauseList()}, + * this {@code List} will never contain instances of {@code IOExceptionList} itself. + * @since 2.12.0 + */ + public static List unwind(IOExceptionList ioExceptionList) { + if (ioExceptionList != null && !IOExceptionList.isEmpty(ioExceptionList.getCauseList())) { + return unwind(ioExceptionList.getCauseList()); + } else { + return Collections.emptyList(); + } + } + /** * Throws this exception if the list is not null or empty. * @@ -49,6 +74,20 @@ public static void checkEmpty(final List causeList, final O } } + private static List unwind(final List causeList) { + final List exceptions = new ArrayList<>(); + if (Objects.nonNull(causeList)) { + for (Throwable t : causeList) { + if (t instanceof IOExceptionList) { + exceptions.addAll(unwind((IOExceptionList)t)); + } else { + exceptions.add(t); + } + } + } + return exceptions; + } + private static boolean isEmpty(final List causeList) { return causeList == null || causeList.isEmpty(); } diff --git a/src/main/java/org/apache/commons/io/IOUtils.java b/src/main/java/org/apache/commons/io/IOUtils.java index 06337162eca..8244ffa66cb 100644 --- a/src/main/java/org/apache/commons/io/IOUtils.java +++ b/src/main/java/org/apache/commons/io/IOUtils.java @@ -395,21 +395,16 @@ public static void close(final Closeable... closeables) throws IOException { } /** - * Closes the entries in the given {@link Stream} as null-safe operations, - * and closes the underlying {@code Stream}. + * Closes the entries in the given {@link Stream} as null-safe operations. * * @param The element type. - * @param closeables The resource(s) to close, may be null. + * @param closeables The resource(s) to close, may be null or empty. * @throws IOExceptionList if an I/O error occurs. * @since 2.12.0 */ public static void close(final Stream closeables) throws IOExceptionList { if (closeables != null) { - try { - IOConsumer.forEachIndexed(closeables, IOUtils::close); - } finally { - closeables.close(); - } + IOConsumer.forEachIndexed(closeables.filter(Objects::nonNull), IOUtils::close); } } @@ -434,24 +429,19 @@ public static void close(final Closeable closeable, final IOConsumer The element type. * @param consumer Consume the IOException thrown by {@link Closeable#close()}. - * @param closeables The resource(s) to close, may be null. + * @param closeables The resource(s) to close, may be null or empty. * @throws IOException if an I/O error occurs. */ public static void close(final IOConsumer consumer, final Stream closeables) throws IOException { - if (closeables != null) { - try { - close(closeables); - } catch (final IOException e) { - if (consumer != null) { - consumer.accept(e); - } - } finally { - closeables.close(); + try { + close(closeables); + } catch (final IOException e) { + if (consumer != null) { + consumer.accept(e); } } } @@ -604,11 +594,10 @@ public static void closeQuietly(final Closeable closeable, final Consumer The element type. - * @param closeables The resource(s) to close, may be null. + * @param closeables The resource(s) to close, may be null or empty. * @since 2.12.0 */ public static void closeQuietly(final Stream closeables) { @@ -616,28 +605,19 @@ public static void closeQuietly(final Stream closeables } /** - * Closes the given {@link Stream} as a null-safe operation while consuming IOException by the given {@code consumer}, - * and closes the underlying {@code Stream}. + * Closes the given {@link Stream} as a null-safe operation while consuming IOException by the given {@code consumer}. * * @param The element type. - * @param consumer Consume the IOException thrown by {@link Closeable#close()}. - * @param closeables The resource(s) to close, may be null. + * @param consumer Consume the IOException thrown by {@link Closeable#close()}, may be null. + * @param closeables The resource(s) to close, may be null or empty. * @since 2.12.0 */ public static void closeQuietly(final Consumer consumer, final Stream closeables) { - if (closeables != null) { - try { - close(closeables); - } catch (final IOException e) { - if (consumer != null) { - consumer.accept(e); - } - } finally { - try { - closeables.close(); - } catch (Exception e) { - // Do nothing. - } + try { + close(closeables); + } catch (final IOException e) { + if (consumer != null) { + consumer.accept(e); } } } diff --git a/src/main/java/org/apache/commons/io/function/IOConsumer.java b/src/main/java/org/apache/commons/io/function/IOConsumer.java index 1719fce7e52..02a45d5e597 100644 --- a/src/main/java/org/apache/commons/io/function/IOConsumer.java +++ b/src/main/java/org/apache/commons/io/function/IOConsumer.java @@ -26,6 +26,7 @@ import org.apache.commons.io.IOExceptionList; import org.apache.commons.io.IOIndexedException; +import org.apache.commons.io.UncheckedIOExceptions; /** * Like {@link Consumer} but throws {@link IOException}. @@ -51,14 +52,37 @@ public interface IOConsumer { * @return a {@code Consumer} that wraps the given {@code IOConsumer}. * @since 2.12.0 */ - static Consumer wrap(IOConsumer consumer) { + static Consumer asConsumer(IOConsumer consumer) { return new Consumer() { @Override public void accept(T t) { try { consumer.accept(t); } catch (IOException e) { - throw new UncheckedIOException(e); + throw UncheckedIOExceptions.create(String.format("%s thrown from %s", e.getClass().getName(), String.valueOf(consumer)), e); + } + } + }; + } + + /** + * Wraps a {@link Consumer} inside of a {@code IOConsumer} + * that catches {@link UncheckedIOException}s that are thrown by the underlying + * {@code IOConsumer} and rethrows them as {@link IOException} + * + * @param The element type. + * @param consumer The {@code Consumer} to wrap. + * @return a {@code IOConsumer} that wraps the given {@code Consumer}. + * @since 2.12.0 + */ + static IOConsumer wrap(Consumer consumer) { + return new IOConsumer() { + @Override + public void accept(T t) throws IOException { + try { + consumer.accept(t); + } catch (UncheckedIOException e) { + throw e.getCause() == null ? new IOException(e) : e.getCause(); } } }; @@ -150,18 +174,6 @@ static IOConsumer noop() { */ void accept(T t) throws IOException; - /** - * Returns this {@code IOConsumer} wrapped inside of a {@link Consumer} - * that throws {@link UncheckedIOException} for any {@link IOException}s - * that are thrown by this {@code IOConsumer}. - * - * @return a {@code Consumer} that wraps this {@code IOConsumer}. - * @since 2.12.0 - */ - default Consumer asConsumer() { - return wrap(this); - } - /** * Returns a composed {@code IOConsumer} that performs, in sequence, this operation followed by the {@code after} * operation. If performing either operation throws an exception, it is relayed to the caller of the composed operation. diff --git a/src/main/java/org/apache/commons/io/function/IOStreams.java b/src/main/java/org/apache/commons/io/function/IOStreams.java index 249f870d224..557663e5467 100644 --- a/src/main/java/org/apache/commons/io/function/IOStreams.java +++ b/src/main/java/org/apache/commons/io/function/IOStreams.java @@ -19,10 +19,13 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiFunction; +import java.util.function.Supplier; import java.util.stream.Stream; import org.apache.commons.io.IOExceptionList; @@ -52,27 +55,50 @@ static void forEach(final Stream stream, final IOConsumer action) thro static void forEachIndexed(final Stream stream, final IOConsumer action, final BiFunction exSupplier) throws IOExceptionList { - final AtomicReference> causeList = new AtomicReference<>(); + final LazyAtomicReference> causeList = new LazyAtomicReference<>(() -> new ArrayList<>()); final AtomicInteger index = new AtomicInteger(); try { stream.forEach(e -> { try { action.accept(e); - } catch (final IOException ioex) { - if (causeList.get() == null) { - causeList.set(new ArrayList<>()); + } catch (final IOExceptionList ioexl) { + final Collection exceptions = IOExceptionList.unwind(ioexl); + for (final Throwable t : exceptions) { + if (t instanceof IOException) { + causeList.getLazy().add(exSupplier.apply(index.get(), (IOException)t)); + } else { + causeList.getLazy().add(exSupplier.apply(index.get(), new IOException(t))); + } } - causeList.get().add(exSupplier.apply(index.get(), ioex)); + } catch (final IOException ioex) { + causeList.getLazy().add(exSupplier.apply(index.get(), ioex)); + } catch (final Throwable t) { + causeList.getLazy().add(exSupplier.apply(index.get(), new IOException(t))); } index.incrementAndGet(); }); } catch (Throwable t) { - if (causeList.get() == null) { - causeList.set(new ArrayList<>()); - } - causeList.get().add(t); + causeList.getLazy().add(exSupplier.apply(index.get(), new IOException(t))); } IOExceptionList.checkEmpty(causeList.get(), "forEach"); } + + private static class LazyAtomicReference extends AtomicReference { + + private static final long serialVersionUID = 1L; + private final Supplier supplier; + + public LazyAtomicReference(Supplier supplier) { + Objects.requireNonNull(supplier); + this.supplier = supplier; + } + + public final T getLazy() { + if (Objects.isNull(get())) { + set(supplier.get()); + } + return get(); + } + } }