Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IOUtils chain together IOException for Closeables, add overload #300

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/main/java/org/apache/commons/io/IOExceptionList.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Throwable> 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.
*
Expand All @@ -49,6 +74,20 @@ public static void checkEmpty(final List<? extends Throwable> causeList, final O
}
}

private static List<Throwable> unwind(final List<? extends Throwable> causeList) {
final List<Throwable> 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<? extends Throwable> causeList) {
return causeList == null || causeList.isEmpty();
}
Expand Down
103 changes: 101 additions & 2 deletions src/main/java/org/apache/commons/io/IOUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -394,6 +394,20 @@ 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.
*
* @param <T> The element type.
* @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 <T extends Closeable> void close(final Stream<T> closeables) throws IOExceptionList {
if (closeables != null) {
IOConsumer.forEachIndexed(closeables.filter(Objects::nonNull), IOUtils::close);
}
}

/**
* Closes the given {@link Closeable} as a null-safe operation.
*
Expand All @@ -414,6 +428,43 @@ public static void close(final Closeable closeable, final IOConsumer<IOException
}
}

/**
* Closes the entries in the given {@link Stream} as null-safe operations.
*
* @param <T> The element type.
* @param consumer Consume the IOException thrown by {@link Closeable#close()}.
* @param closeables The resource(s) to close, may be null or empty.
* @throws IOException if an I/O error occurs.
*/
public static <T extends Closeable> void close(final IOConsumer<IOException> consumer, final Stream<T> closeables) throws IOException {
try {
close(closeables);
} catch (final IOException e) {
if (consumer != null) {
consumer.accept(e);
}
}
}

/**
* 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 IOConsumer<IOException> consumer, final Closeable... closeables) throws IOException {
if (closeables != null) {
try {
close(closeables);
} catch (final IOException e) {
if (consumer != null) {
consumer.accept(e);
}
}
}
}

/**
* Closes a URLConnection.
*
Expand Down Expand Up @@ -519,7 +570,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);
}
}

Expand All @@ -542,6 +593,54 @@ public static void closeQuietly(final Closeable closeable, final Consumer<IOExce
}
}

/**
* Closes the given {@link Stream} as a null-safe operation while consuming IOException by the given {@code consumer}.
*
* @param <T> The element type.
* @param closeables The resource(s) to close, may be null or empty.
* @since 2.12.0
*/
public static <T extends Closeable> void closeQuietly(final Stream<T> closeables) {
closeQuietly(null, closeables);
}

/**
* Closes the given {@link Stream} as a null-safe operation while consuming IOException by the given {@code consumer}.
*
* @param <T> The element type.
* @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 <T extends Closeable> void closeQuietly(final Consumer<IOException> consumer, final Stream<T> closeables) {
try {
close(closeables);
} catch (final IOException e) {
if (consumer != null) {
consumer.accept(e);
}
}
}

/**
* 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<IOException> 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.
* <p>
Expand Down
91 changes: 90 additions & 1 deletion src/main/java/org/apache/commons/io/function/IOConsumer.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@
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;

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}.
Expand All @@ -39,6 +42,52 @@ public interface IOConsumer<T> {
*/
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 <T> 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 <T> Consumer<T> asConsumer(IOConsumer<T> consumer) {
return new Consumer<T>() {
@Override
public void accept(T t) {
try {
consumer.accept(t);
} catch (IOException 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 <T> 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 <T> IOConsumer<T> wrap(Consumer<T> consumer) {
return new IOConsumer<T>() {
@Override
public void accept(T t) throws IOException {
try {
consumer.accept(t);
} catch (UncheckedIOException e) {
throw e.getCause() == null ? new IOException(e) : e.getCause();
}
}
};
}

/**
* Performs an action for each element of this stream.
*
Expand All @@ -52,6 +101,46 @@ static <T> void forEach(final T[] array, final IOConsumer<T> 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 <T> 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 <T> Optional<IOException> forEachQuietly(final T[] array, final IOConsumer<T> 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 <T> 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 <T> Optional<IOExceptionList> forEachIndexedQuietly(final Stream<T> stream, final IOConsumer<T> 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.
*
Expand Down Expand Up @@ -91,7 +180,7 @@ static <T> IOConsumer<T> noop() {
* 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<T> andThen(final IOConsumer<? super T> after) {
Expand Down
Loading