aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGary Gregory <garydgregory@gmail.com>2022-06-18 13:23:48 -0400
committerGary Gregory <garydgregory@gmail.com>2022-06-18 13:23:48 -0400
commit70b2250f80ac098e3fbb136bcd765ad90e8d3c0e (patch)
treeff343ea24f0bb64ad1dae17b70aa2b907710db2f /src
parent7912894eb8545ea076732a95dc0124d6426b3e76 (diff)
downloadapache-commons-lang-70b2250f80ac098e3fbb136bcd765ad90e8d3c0e.tar.gz
Add ExceptionUtils.forEach(Throwable, Consumer<Throwable>)
Add ExceptionUtils.stream(Throwable).
Diffstat (limited to 'src')
-rw-r--r--src/changes/changes.xml2
-rw-r--r--src/main/java/org/apache/commons/lang3/exception/ExceptionUtils.java44
-rw-r--r--src/test/java/org/apache/commons/lang3/exception/ExceptionUtilsTest.java99
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&lt;Throwable&gt;).</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));