diff options
author | Gary Gregory <garydgregory@gmail.com> | 2022-06-18 13:23:48 -0400 |
---|---|---|
committer | Gary Gregory <garydgregory@gmail.com> | 2022-06-18 13:23:48 -0400 |
commit | 70b2250f80ac098e3fbb136bcd765ad90e8d3c0e (patch) | |
tree | ff343ea24f0bb64ad1dae17b70aa2b907710db2f /src | |
parent | 7912894eb8545ea076732a95dc0124d6426b3e76 (diff) | |
download | apache-commons-lang-70b2250f80ac098e3fbb136bcd765ad90e8d3c0e.tar.gz |
Add ExceptionUtils.forEach(Throwable, Consumer<Throwable>)
Add ExceptionUtils.stream(Throwable).
Diffstat (limited to 'src')
3 files changed, 144 insertions, 1 deletions
diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 33d7b7234..ddfbc2534 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -147,6 +147,8 @@ The <action> type attribute can be add,update,fix,remove. <action type="add" dev="ggregory" due-to="Gary Gregory">Add coverage.yml.</action> <action type="add" dev="ggregory" due-to="Gary Gregory">Add DurationUtils.since(Temporal).</action> <action type="add" dev="ggregory" due-to="Gary Gregory">Add DurationUtils.of(FailableConsumer|FailableRunnbale).</action> + <action type="add" dev="ggregory" due-to="Gary Gregory">Add ExceptionUtils.forEach(Throwable, Consumer<Throwable>).</action> + <action type="add" dev="ggregory" due-to="Gary Gregory">Add ExceptionUtils.stream(Throwable).</action> <!-- UPDATE --> <action type="update" dev="ggregory" due-to="Dependabot, XenoAmess, Gary Gregory">Bump actions/cache from 2.1.4 to 3.0.4 #742, #752, #764, #833, #867.</action> <action type="update" dev="ggregory" due-to="Dependabot">Bump actions/checkout from 2 to 3 #819, #825, #859.</action> diff --git a/src/main/java/org/apache/commons/lang3/exception/ExceptionUtils.java b/src/main/java/org/apache/commons/lang3/exception/ExceptionUtils.java index 986fd0bf2..35cde8021 100644 --- a/src/main/java/org/apache/commons/lang3/exception/ExceptionUtils.java +++ b/src/main/java/org/apache/commons/lang3/exception/ExceptionUtils.java @@ -26,6 +26,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.StringTokenizer; +import java.util.function.Consumer; +import java.util.stream.Stream; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ClassUtils; @@ -428,6 +430,26 @@ public class ExceptionUtils { } /** + * Performs an action for each Throwable causes of the given Throwable. + * <p> + * A throwable without cause will return a stream containing one element - the input throwable. A throwable with one cause + * will return a stream containing two elements. - the input throwable and the cause throwable. A {@code null} throwable + * will return a stream of count zero. + * </p> + * + * <p> + * This method handles recursive cause structures that might otherwise cause infinite loops. The cause chain is + * processed until the end is reached, or until the next item in the chain is already in the result set. + * </p> + * @param throwable The Throwable to traverse. + * @param consumer a non-interfering action to perform on the elements. + * @since 3.13.0 + */ + public static void forEach(final Throwable throwable, final Consumer<Throwable> consumer) { + stream(throwable).forEach(consumer); + } + + /** * Gets the list of {@code Throwable} objects in the * exception chain. * @@ -771,6 +793,28 @@ public class ExceptionUtils { } /** + * Streams causes of a Throwable. + * <p> + * A throwable without cause will return a stream containing one element - the input throwable. A throwable with one cause + * will return a stream containing two elements. - the input throwable and the cause throwable. A {@code null} throwable + * will return a stream of count zero. + * </p> + * + * <p> + * This method handles recursive cause structures that might otherwise cause infinite loops. The cause chain is + * processed until the end is reached, or until the next item in the chain is already in the result set. + * </p> + * + * @param throwable The Throwable to traverse + * @return A new Stream of Throwable causes. + * @since 3.13.0 + */ + public static Stream<Throwable> stream(final Throwable throwable) { + // No point building a custom Iterable as it would keep track of visited elements to avoid infinite loops + return getThrowableList(throwable).stream(); + } + + /** * Worker method for the {@code throwableOfType} methods. * * @param <T> the type of Throwable you are searching. diff --git a/src/test/java/org/apache/commons/lang3/exception/ExceptionUtilsTest.java b/src/test/java/org/apache/commons/lang3/exception/ExceptionUtilsTest.java index 38b6127d1..27a2495b1 100644 --- a/src/test/java/org/apache/commons/lang3/exception/ExceptionUtilsTest.java +++ b/src/test/java/org/apache/commons/lang3/exception/ExceptionUtilsTest.java @@ -32,7 +32,9 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; +import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import org.apache.commons.lang3.test.NotVisibleExceptionFactory; import org.junit.jupiter.api.AfterEach; @@ -216,6 +218,58 @@ public class ExceptionUtilsTest { assertFalse(Modifier.isFinal(ExceptionUtils.class.getModifiers())); } + @Test + public void testForEach_jdkNoCause() { + final List<Throwable> throwables = new ArrayList<>(); + ExceptionUtils.forEach(jdkNoCause, throwables::add); + assertEquals(1, throwables.size()); + assertSame(jdkNoCause, throwables.get(0)); + } + + @Test + public void testForEach_nested() { + final List<Throwable> throwables = new ArrayList<>(); + ExceptionUtils.forEach(nested, throwables::add); + assertEquals(2, throwables.size()); + assertSame(nested, throwables.get(0)); + assertSame(withoutCause, throwables.get(1)); + } + + @Test + public void testForEach_null() { + final List<Throwable> throwables = new ArrayList<>(); + ExceptionUtils.forEach(null, throwables::add); + assertEquals(0, throwables.size()); + } + + @Test + public void testForEach_recursiveCause() { + final List<Throwable> throwables = new ArrayList<>(); + ExceptionUtils.forEach(cyclicCause, throwables::add); + assertEquals(3, throwables.size()); + assertSame(cyclicCause, throwables.get(0)); + assertSame(cyclicCause.getCause(), throwables.get(1)); + assertSame(cyclicCause.getCause().getCause(), throwables.get(2)); + } + + @Test + public void testForEach_withCause() { + final List<Throwable> throwables = new ArrayList<>(); + ExceptionUtils.forEach(withCause, throwables::add); + assertEquals(3, throwables.size()); + assertSame(withCause, throwables.get(0)); + assertSame(nested, throwables.get(1)); + assertSame(withoutCause, throwables.get(2)); + } + + @Test + public void testForEach_withoutCause() { + final List<Throwable> throwables = new ArrayList<>(); + ExceptionUtils.forEach(withoutCause, throwables::add); + assertEquals(1, throwables.size()); + assertSame(withoutCause, throwables.get(0)); + } + @SuppressWarnings("deprecation") // Specifically tests the deprecated methods @Test public void testGetCause_Throwable() { @@ -497,7 +551,6 @@ public class ExceptionUtilsTest { assertEquals(0, ExceptionUtils.indexOfType(withCause, Throwable.class)); } - @Test public void testIndexOfType_ThrowableClassInt() { assertEquals(-1, ExceptionUtils.indexOfType(null, null, 0)); @@ -588,6 +641,50 @@ public class ExceptionUtilsTest { } @Test + public void testStream_jdkNoCause() { + assertEquals(1, ExceptionUtils.stream(jdkNoCause).count()); + assertSame(jdkNoCause, ExceptionUtils.stream(jdkNoCause).toArray()[0]); + } + + @Test + public void testStream_nested() { + assertEquals(2, ExceptionUtils.stream(nested).count()); + final Object[] array = ExceptionUtils.stream(nested).toArray(); + assertSame(nested, array[0]); + assertSame(withoutCause, array[1]); + } + + @Test + public void testStream_null() { + assertEquals(0, ExceptionUtils.stream(null).count()); + } + + @Test + public void testStream_recursiveCause() { + final List<?> throwables = ExceptionUtils.stream(cyclicCause).collect(Collectors.toList()); + assertEquals(3, throwables.size()); + assertSame(cyclicCause, throwables.get(0)); + assertSame(cyclicCause.getCause(), throwables.get(1)); + assertSame(cyclicCause.getCause().getCause(), throwables.get(2)); + } + + @Test + public void testStream_withCause() { + final List<?> throwables = ExceptionUtils.stream(withCause).collect(Collectors.toList()); + assertEquals(3, throwables.size()); + assertSame(withCause, throwables.get(0)); + assertSame(nested, throwables.get(1)); + assertSame(withoutCause, throwables.get(2)); + } + + @Test + public void testStream_withoutCause() { + final List<?> throwables = ExceptionUtils.stream(withoutCause).collect(Collectors.toList()); + assertEquals(1, throwables.size()); + assertSame(withoutCause, throwables.get(0)); + } + + @Test public void testThrow() { final Exception expected = new InterruptedException(); final Exception actual = assertThrows(Exception.class, () -> ExceptionUtils.rethrow(expected)); |