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

Adding Handling Tools for MultiException #350

Open
wants to merge 1 commit 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
v5.1.21
------
* Add methods `allMultiCauses` and `anyMultiCause` to retrieve all causes and the first non-MultiException cause, respectively.

v5.1.20
------
* Upgrade bytebuddy and asm version for JDK 17 and JDK 21 support
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
version=5.1.20
version=5.1.21
group=com.linkedin.parseq
org.gradle.parallel=true
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,17 @@

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import java.util.Stack;
import java.util.concurrent.TimeoutException;


Expand Down Expand Up @@ -72,4 +81,85 @@ public static Exception timeoutException(String desc) {
}
}

/**
* Returns whether the given exception is a {@link MultiException}.
*/
public static boolean isMultiple(final Throwable e) {
return e instanceof MultiException;
}

/**
* Returns all causes of the given exception.
* If the exception is a {@link MultiException}, it will return a list of all causes.
* If the exception is not a {@link MultiException} and not null, it will return a list with the exception; otherwise, it will return an empty list.
*
* @param e the exception to search, nullable
* @return a list of all causes, or an empty list if none found, never null
*/
public static List<Throwable> allMultiCauses(final Throwable e) {
if (e == null) {
return Collections.emptyList();
}
if (!isMultiple(e)) {
return Collections.singletonList(e);
}

Deque<MultiException> meStack = new ArrayDeque<>();
meStack.push((MultiException) e);
List<Throwable> causes = new ArrayList<>();

while (!meStack.isEmpty()) {
MultiException me = meStack.pop();
if (me.getCauses() == null) {
continue;
}

for (Throwable cause : me.getCauses()) {
if (cause == null) {
continue;
}
if (isMultiple(cause)) {
meStack.push((MultiException) cause);
} else {
causes.add(cause);
}
}
}
return causes;
}

/**
* Finds the first non-MultiException cause of the given exception.
*
* @param e the exception to search, nullable
* @return the first non-MultiException cause, or null if none found, nullable
*/
public static Throwable anyMultiCause(final Throwable e) {
if (!isMultiple(e)) {
return e;
}

Deque<MultiException> meStack = new ArrayDeque<>();
meStack.push((MultiException) e);

while (!meStack.isEmpty()) {
MultiException curMe = meStack.pop();
Collection<? extends Throwable> causes = curMe.getCauses();
if (causes == null) {
continue;
}

for (Throwable subCause : causes) {
if (subCause == null) {
continue;
}
if (isMultiple(subCause)) {
meStack.push((MultiException) subCause);
} else {
return subCause;
}
}
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package com.linkedin.parseq;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import org.testng.annotations.Test;

/**
* test {@link Exceptions}
*
* @author wiilei
*/
public class TestExceptions {
@Test
public void testIsMultiException() {
assertTrue(Exceptions.isMultiple(new MultiException(Collections.emptyList())));
assertFalse(Exceptions.isMultiple(new Exception()));
assertFalse(Exceptions.isMultiple(null));
}

@Test
public void testAllMultiCauses_withNull() {
Collection<? extends Throwable> realCauses = Exceptions.allMultiCauses(null);

assertNotNull(realCauses, "Real causes is null");
assertEquals(realCauses.size(), 0, "The size of real causes is not correct");
}

/**
* test single layer multi exception, ME1[E1, E2]
*/
@Test
public void testAllMultiCauses_singleLayerMultiExceptionNonEmpty() {
Exception e1 = new Exception("E1");
Exception e2 = new Exception("E2");
MultiException me1 = new MultiException(Arrays.asList(e1, e2));

Collection<? extends Throwable> realCauses = Exceptions.allMultiCauses(me1);

assertEquals(realCauses.size(), 2, "The size of real causes is not correct");
assertTrue(realCauses.contains(e1), "Real causes does not contain Exception 1");
assertTrue(realCauses.contains(e2), "Real causes does not contain Exception 2");
}

/**
* test single layer multi exception, ME1[]
*/
@Test
public void testAllMultiCauses_singleLayerMultiExceptionEmpty() {
MultiException me1 = new MultiException(Collections.emptyList());
Collection<? extends Throwable> realCauses = Exceptions.allMultiCauses(me1);
assertEquals(realCauses.size(), 0, "The size of real causes is not correct");
}

/**
* test nested head multi exception, ME1[ ME2[E1, E2], E3 ]
*/
@Test
public void testAllMultiCauses_nestedHeadMultiException() {
Exception e1 = new Exception("Exception 1");
Exception e2 = new Exception("Exception 2");
Exception e3 = new Exception("Exception 3");
MultiException me2 = new MultiException(Arrays.asList(e1, e2));
MultiException me1 = new MultiException(Arrays.asList(me2, e3));

Collection<? extends Throwable> realCauses = Exceptions.allMultiCauses(me1);

assertEquals(realCauses.size(), 3, "The size of real causes is not correct");
assertTrue(realCauses.contains(e1), "Real causes does not contain Exception 1");
assertTrue(realCauses.contains(e2), "Real causes does not contain Exception 2");
assertTrue(realCauses.contains(e3), "Real causes does not contain Exception 3");
}

/**
* test nested single multi exception with empty, ME1[ ME2[] ]
*/
@Test
public void testAllMultiCauses_nestedSingleMultiExceptionWithEmpty() {
MultiException me2 = new MultiException(Collections.emptyList());
MultiException me1 = new MultiException(Collections.singletonList(me2));

Collection<? extends Throwable> realCauses = Exceptions.allMultiCauses(me1);

assertEquals(realCauses.size(), 0, "The size of real causes is not correct");
}

/**
* test nested single multi exception with tail non-empty, ME1[ ME2[E1] ]
*/
@Test
public void testAllMultiCauses_nestedSingleMultiExceptionWithTailNonEmpty() {
Exception e1 = new Exception("Exception 1");
MultiException me2 = new MultiException(Collections.singletonList(e1));
MultiException me1 = new MultiException(Collections.singletonList(me2));

Collection<? extends Throwable> realCauses = Exceptions.allMultiCauses(me1);

assertEquals(realCauses.size(), 1, "The size of real causes is not correct");
assertTrue(realCauses.contains(e1), "Real causes does not contain Exception 1");
}

/**
* test nested multi exception with tail non-empty, ME1[ ME2[E1], E1, ME3[E2] ]
*/
@Test
public void testAllMultiCauses_nestedMultiExceptionWithTailNonEmpty() {
Exception e1 = new Exception("Exception 1");
Exception e2 = new Exception("Exception 2");

MultiException me2 = new MultiException(Collections.emptyList());
MultiException me3 = new MultiException(Collections.singletonList(e2));
MultiException me1 = new MultiException(Arrays.asList(me2, e1, me3));

Collection<? extends Throwable> realCauses = Exceptions.allMultiCauses(me1);

assertEquals(realCauses.size(), 2, "The size of real causes is not correct");
assertTrue(realCauses.contains(e1), "Real causes does not contain Exception 1");
assertTrue(realCauses.contains(e2), "Real causes does not contain Exception 2");
}

/**
* test takes the first non-MultiException cause of the given exception, ME1[ ME2[E1], E1, ME3[E2] ]
*/
@Test
public void testAnyMultiCause_nestedMultiExceptionWithTailNonEmpty() {
Exception e1 = new Exception("Exception 1");
Exception e2 = new Exception("Exception 2");

MultiException me2 = new MultiException(Collections.emptyList());
MultiException me3 = new MultiException(Collections.singletonList(e2));
MultiException me1 = new MultiException(Arrays.asList(me2, e1, me3));

Throwable anyCause = Exceptions.anyMultiCause(me1);
assertEquals(anyCause, e1, "The first non-MultiException cause is not correct");
}

/**
* test takes the first non-MultiException cause of the given exception, ME1[ ME2[], E1, ME3[E2] ]
*/
@Test
public void testAnyMultiCause_nestedSingleMultiExceptionWithTailNonEmpty() {
Exception e1 = new Exception("Exception 1");
Exception e2 = new Exception("Exception 1");
MultiException me2 = new MultiException(Collections.emptyList());
MultiException me3 = new MultiException(Collections.singletonList(e2));
MultiException me1 = new MultiException(Arrays.asList(me2, e1, me3));

Throwable anyCause = Exceptions.anyMultiCause(me1);
assertEquals(anyCause, e1, "The first non-MultiException cause is not correct");
}

@Test
public void testAnyMultiCause_singleLayerMultiExceptionNonEmpty() {
Throwable anyCause = Exceptions.anyMultiCause(null);
assertNull(anyCause, "The first non-MultiException cause is not correct");

Exception e1 = new Exception("Exception 1");
anyCause = Exceptions.anyMultiCause(e1);
assertEquals(anyCause, e1, "The first non-MultiException cause is not correct");
}
}