Skip to content

Commit 1ccb075

Browse files
committed
Expand exception handling, don't explicitly close Streams in IOUtils
1 parent 8097e96 commit 1ccb075

File tree

4 files changed

+120
-63
lines changed

4 files changed

+120
-63
lines changed

src/main/java/org/apache/commons/io/IOExceptionList.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.apache.commons.io;
1919

2020
import java.io.IOException;
21+
import java.util.ArrayList;
2122
import java.util.Collections;
2223
import java.util.List;
2324
import java.util.Objects;
@@ -35,6 +36,30 @@ public class IOExceptionList extends IOException {
3536

3637
private static final long serialVersionUID = 1L;
3738

39+
/**
40+
* Unwinds a {@code IOExceptionList} into a {@link List} of {@link Throwable}
41+
* containing all of the underlying {@code Throwable} instances using
42+
* {@link #getCauseList()}.
43+
*
44+
* Any instances of {@code IOExceptionList} encountered will be recursively
45+
* unwound as well, and the contents of their {@code #getCauseList()} will
46+
* be included in the returned {@code List}.
47+
*
48+
* @param ioExceptionList The {@code IOExceptionList} to recursively unwind,
49+
* may be null, and {@code IOExceptionList#getCauseList()} may be null or empty.
50+
* @return A {@code List} containing all of the {@code Throwable} instances
51+
* inside the given {@code IOExceptionList} using {@code IOExceptionList#getCauseList()},
52+
* this {@code List} will never contain instances of {@code IOExceptionList} itself.
53+
* @since 2.12.0
54+
*/
55+
public static List<Throwable> unwind(IOExceptionList ioExceptionList) {
56+
if (ioExceptionList != null && !IOExceptionList.isEmpty(ioExceptionList.getCauseList())) {
57+
return unwind(ioExceptionList.getCauseList());
58+
} else {
59+
return Collections.emptyList();
60+
}
61+
}
62+
3863
/**
3964
* Throws this exception if the list is not null or empty.
4065
*
@@ -49,6 +74,20 @@ public static void checkEmpty(final List<? extends Throwable> causeList, final O
4974
}
5075
}
5176

77+
private static List<Throwable> unwind(final List<? extends Throwable> causeList) {
78+
final List<Throwable> exceptions = new ArrayList<>();
79+
if (Objects.nonNull(causeList)) {
80+
for (Throwable t : causeList) {
81+
if (t instanceof IOExceptionList) {
82+
exceptions.addAll(unwind((IOExceptionList)t));
83+
} else {
84+
exceptions.add(t);
85+
}
86+
}
87+
}
88+
return exceptions;
89+
}
90+
5291
private static boolean isEmpty(final List<? extends Throwable> causeList) {
5392
return causeList == null || causeList.isEmpty();
5493
}

src/main/java/org/apache/commons/io/IOUtils.java

Lines changed: 20 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -395,21 +395,16 @@ public static void close(final Closeable... closeables) throws IOException {
395395
}
396396

397397
/**
398-
* Closes the entries in the given {@link Stream} as null-safe operations,
399-
* and closes the underlying {@code Stream}.
398+
* Closes the entries in the given {@link Stream} as null-safe operations.
400399
*
401400
* @param <T> The element type.
402-
* @param closeables The resource(s) to close, may be null.
401+
* @param closeables The resource(s) to close, may be null or empty.
403402
* @throws IOExceptionList if an I/O error occurs.
404403
* @since 2.12.0
405404
*/
406405
public static <T extends Closeable> void close(final Stream<T> closeables) throws IOExceptionList {
407406
if (closeables != null) {
408-
try {
409-
IOConsumer.forEachIndexed(closeables, IOUtils::close);
410-
} finally {
411-
closeables.close();
412-
}
407+
IOConsumer.forEachIndexed(closeables.filter(Objects::nonNull), IOUtils::close);
413408
}
414409
}
415410

@@ -434,24 +429,19 @@ public static void close(final Closeable closeable, final IOConsumer<IOException
434429
}
435430

436431
/**
437-
* Closes the entries in the given {@link Stream} as null-safe operations,
438-
* and closes the underlying {@code Stream}.
432+
* Closes the entries in the given {@link Stream} as null-safe operations.
439433
*
440434
* @param <T> The element type.
441435
* @param consumer Consume the IOException thrown by {@link Closeable#close()}.
442-
* @param closeables The resource(s) to close, may be null.
436+
* @param closeables The resource(s) to close, may be null or empty.
443437
* @throws IOException if an I/O error occurs.
444438
*/
445439
public static <T extends Closeable> void close(final IOConsumer<IOException> consumer, final Stream<T> closeables) throws IOException {
446-
if (closeables != null) {
447-
try {
448-
close(closeables);
449-
} catch (final IOException e) {
450-
if (consumer != null) {
451-
consumer.accept(e);
452-
}
453-
} finally {
454-
closeables.close();
440+
try {
441+
close(closeables);
442+
} catch (final IOException e) {
443+
if (consumer != null) {
444+
consumer.accept(e);
455445
}
456446
}
457447
}
@@ -604,40 +594,30 @@ public static void closeQuietly(final Closeable closeable, final Consumer<IOExce
604594
}
605595

606596
/**
607-
* Closes the given {@link Stream} as a null-safe operation while consuming IOException by the given {@code consumer},
608-
* and closes the underlying {@code Stream}.
597+
* Closes the given {@link Stream} as a null-safe operation while consuming IOException by the given {@code consumer}.
609598
*
610599
* @param <T> The element type.
611-
* @param closeables The resource(s) to close, may be null.
600+
* @param closeables The resource(s) to close, may be null or empty.
612601
* @since 2.12.0
613602
*/
614603
public static <T extends Closeable> void closeQuietly(final Stream<T> closeables) {
615604
closeQuietly(null, closeables);
616605
}
617606

618607
/**
619-
* Closes the given {@link Stream} as a null-safe operation while consuming IOException by the given {@code consumer},
620-
* and closes the underlying {@code Stream}.
608+
* Closes the given {@link Stream} as a null-safe operation while consuming IOException by the given {@code consumer}.
621609
*
622610
* @param <T> The element type.
623-
* @param consumer Consume the IOException thrown by {@link Closeable#close()}.
624-
* @param closeables The resource(s) to close, may be null.
611+
* @param consumer Consume the IOException thrown by {@link Closeable#close()}, may be null.
612+
* @param closeables The resource(s) to close, may be null or empty.
625613
* @since 2.12.0
626614
*/
627615
public static <T extends Closeable> void closeQuietly(final Consumer<IOException> consumer, final Stream<T> closeables) {
628-
if (closeables != null) {
629-
try {
630-
close(closeables);
631-
} catch (final IOException e) {
632-
if (consumer != null) {
633-
consumer.accept(e);
634-
}
635-
} finally {
636-
try {
637-
closeables.close();
638-
} catch (Exception e) {
639-
// Do nothing.
640-
}
616+
try {
617+
close(closeables);
618+
} catch (final IOException e) {
619+
if (consumer != null) {
620+
consumer.accept(e);
641621
}
642622
}
643623
}

src/main/java/org/apache/commons/io/function/IOConsumer.java

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import org.apache.commons.io.IOExceptionList;
2828
import org.apache.commons.io.IOIndexedException;
29+
import org.apache.commons.io.UncheckedIOExceptions;
2930

3031
/**
3132
* Like {@link Consumer} but throws {@link IOException}.
@@ -51,14 +52,37 @@ public interface IOConsumer<T> {
5152
* @return a {@code Consumer} that wraps the given {@code IOConsumer}.
5253
* @since 2.12.0
5354
*/
54-
static <T> Consumer<T> wrap(IOConsumer<T> consumer) {
55+
static <T> Consumer<T> asConsumer(IOConsumer<T> consumer) {
5556
return new Consumer<T>() {
5657
@Override
5758
public void accept(T t) {
5859
try {
5960
consumer.accept(t);
6061
} catch (IOException e) {
61-
throw new UncheckedIOException(e);
62+
throw UncheckedIOExceptions.create(String.format("%s thrown from %s", e.getClass().getName(), String.valueOf(consumer)), e);
63+
}
64+
}
65+
};
66+
}
67+
68+
/**
69+
* Wraps a {@link Consumer} inside of a {@code IOConsumer}
70+
* that catches {@link UncheckedIOException}s that are thrown by the underlying
71+
* {@code IOConsumer} and rethrows them as {@link IOException}
72+
*
73+
* @param <T> The element type.
74+
* @param consumer The {@code Consumer} to wrap.
75+
* @return a {@code IOConsumer} that wraps the given {@code Consumer}.
76+
* @since 2.12.0
77+
*/
78+
static <T> IOConsumer<T> wrap(Consumer<T> consumer) {
79+
return new IOConsumer<T>() {
80+
@Override
81+
public void accept(T t) throws IOException {
82+
try {
83+
consumer.accept(t);
84+
} catch (UncheckedIOException e) {
85+
throw e.getCause() == null ? new IOException(e) : e.getCause();
6286
}
6387
}
6488
};
@@ -150,18 +174,6 @@ static <T> IOConsumer<T> noop() {
150174
*/
151175
void accept(T t) throws IOException;
152176

153-
/**
154-
* Returns this {@code IOConsumer} wrapped inside of a {@link Consumer}
155-
* that throws {@link UncheckedIOException} for any {@link IOException}s
156-
* that are thrown by this {@code IOConsumer}.
157-
*
158-
* @return a {@code Consumer} that wraps this {@code IOConsumer}.
159-
* @since 2.12.0
160-
*/
161-
default Consumer<T> asConsumer() {
162-
return wrap(this);
163-
}
164-
165177
/**
166178
* Returns a composed {@code IOConsumer} that performs, in sequence, this operation followed by the {@code after}
167179
* operation. If performing either operation throws an exception, it is relayed to the caller of the composed operation.

src/main/java/org/apache/commons/io/function/IOStreams.java

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@
1919

2020
import java.io.IOException;
2121
import java.util.ArrayList;
22+
import java.util.Collection;
2223
import java.util.List;
24+
import java.util.Objects;
2325
import java.util.concurrent.atomic.AtomicInteger;
2426
import java.util.concurrent.atomic.AtomicReference;
2527
import java.util.function.BiFunction;
28+
import java.util.function.Supplier;
2629
import java.util.stream.Stream;
2730

2831
import org.apache.commons.io.IOExceptionList;
@@ -52,27 +55,50 @@ static <T> void forEach(final Stream<T> stream, final IOConsumer<T> action) thro
5255

5356
static <T> void forEachIndexed(final Stream<T> stream, final IOConsumer<T> action, final BiFunction<Integer, IOException, IOException> exSupplier)
5457
throws IOExceptionList {
55-
final AtomicReference<List<Throwable>> causeList = new AtomicReference<>();
58+
final LazyAtomicReference<List<Throwable>> causeList = new LazyAtomicReference<>(() -> new ArrayList<>());
5659
final AtomicInteger index = new AtomicInteger();
5760
try {
5861
stream.forEach(e -> {
5962
try {
6063
action.accept(e);
61-
} catch (final IOException ioex) {
62-
if (causeList.get() == null) {
63-
causeList.set(new ArrayList<>());
64+
} catch (final IOExceptionList ioexl) {
65+
final Collection<Throwable> exceptions = IOExceptionList.unwind(ioexl);
66+
for (final Throwable t : exceptions) {
67+
if (t instanceof IOException) {
68+
causeList.getLazy().add(exSupplier.apply(index.get(), (IOException)t));
69+
} else {
70+
causeList.getLazy().add(exSupplier.apply(index.get(), new IOException(t)));
71+
}
6472
}
65-
causeList.get().add(exSupplier.apply(index.get(), ioex));
73+
} catch (final IOException ioex) {
74+
causeList.getLazy().add(exSupplier.apply(index.get(), ioex));
75+
} catch (final Throwable t) {
76+
causeList.getLazy().add(exSupplier.apply(index.get(), new IOException(t)));
6677
}
6778
index.incrementAndGet();
6879
});
6980
}
7081
catch (Throwable t) {
71-
if (causeList.get() == null) {
72-
causeList.set(new ArrayList<>());
73-
}
74-
causeList.get().add(t);
82+
causeList.getLazy().add(exSupplier.apply(index.get(), new IOException(t)));
7583
}
7684
IOExceptionList.checkEmpty(causeList.get(), "forEach");
7785
}
86+
87+
private static class LazyAtomicReference<T> extends AtomicReference<T> {
88+
89+
private static final long serialVersionUID = 1L;
90+
private final Supplier<T> supplier;
91+
92+
public LazyAtomicReference(Supplier<T> supplier) {
93+
Objects.requireNonNull(supplier);
94+
this.supplier = supplier;
95+
}
96+
97+
public final T getLazy() {
98+
if (Objects.isNull(get())) {
99+
set(supplier.get());
100+
}
101+
return get();
102+
}
103+
}
78104
}

0 commit comments

Comments
 (0)