2020import java .util .ArrayList ;
2121import java .util .Collection ;
2222import java .util .Collections ;
23+ import java .util .HashSet ;
2324import java .util .LinkedHashSet ;
2425import java .util .List ;
2526import java .util .Set ;
3132 *
3233 * Its invariant is to contains an immutable, ordered (by insertion order), unique list of non-composite exceptions.
3334 * This list may be queried by {@code #getExceptions()}
35+ *
36+ * The `printStackTrace()` implementation does custom handling of the StackTrace instead of using `getCause()` so it
37+ * can avoid circular references.
38+ *
39+ * If `getCause()` is invoked, it will lazily create the causal chain but stop if it finds any Throwable in the chain
40+ * that it has already seen.
3441 */
3542public final class CompositeException extends RuntimeException {
3643
@@ -42,7 +49,7 @@ public final class CompositeException extends RuntimeException {
4249 public CompositeException (String messagePrefix , Collection <? extends Throwable > errors ) {
4350 Set <Throwable > deDupedExceptions = new LinkedHashSet <Throwable >();
4451 List <Throwable > _exceptions = new ArrayList <Throwable >();
45- for (Throwable ex : errors ) {
52+ for (Throwable ex : errors ) {
4653 if (ex instanceof CompositeException ) {
4754 deDupedExceptions .addAll (((CompositeException ) ex ).getExceptions ());
4855 } else {
@@ -52,7 +59,7 @@ public CompositeException(String messagePrefix, Collection<? extends Throwable>
5259
5360 _exceptions .addAll (deDupedExceptions );
5461 this .exceptions = Collections .unmodifiableList (_exceptions );
55- this .message = exceptions .size () + " exceptions occurred. See them in causal chain below. " ;
62+ this .message = exceptions .size () + " exceptions occurred. " ;
5663 }
5764
5865 public CompositeException (Collection <? extends Throwable > errors ) {
@@ -62,8 +69,7 @@ public CompositeException(Collection<? extends Throwable> errors) {
6269 /**
6370 * Retrieves the list of exceptions that make up the {@code CompositeException}
6471 *
65- * @return the exceptions that make up the {@code CompositeException}, as a {@link List} of
66- * {@link Throwable}s
72+ * @return the exceptions that make up the {@code CompositeException}, as a {@link List} of {@link Throwable}s
6773 */
6874 public List <Throwable > getExceptions () {
6975 return exceptions ;
@@ -74,9 +80,47 @@ public String getMessage() {
7480 return message ;
7581 }
7682
83+ private Throwable cause = null ;
84+
7785 @ Override
7886 public synchronized Throwable getCause () {
79- return null ;
87+ if (cause == null ) {
88+ // we lazily generate this causal chain if this is called
89+ CompositeExceptionCausalChain _cause = new CompositeExceptionCausalChain ();
90+ Set <Throwable > seenCauses = new HashSet <Throwable >();
91+
92+ Throwable chain = _cause ;
93+ for (Throwable e : exceptions ) {
94+ if (seenCauses .contains (e )) {
95+ // already seen this outer Throwable so skip
96+ continue ;
97+ }
98+ seenCauses .add (e );
99+
100+ List <Throwable > listOfCauses = getListOfCauses (e );
101+ // check if any of them have been seen before
102+ for (Throwable child : listOfCauses ) {
103+ if (seenCauses .contains (child )) {
104+ // already seen this outer Throwable so skip
105+ e = new RuntimeException ("Duplicate found in causal chain so cropping to prevent loop ..." );
106+ continue ;
107+ }
108+ seenCauses .add (child );
109+ }
110+
111+ // we now have 'e' as the last in the chain
112+ try {
113+ chain .initCause (e );
114+ } catch (Throwable t ) {
115+ // ignore
116+ // the javadocs say that some Throwables (depending on how they're made) will never
117+ // let me call initCause without blowing up even if it returns null
118+ }
119+ chain = chain .getCause ();
120+ }
121+ cause = _cause ;
122+ }
123+ return cause ;
80124 }
81125
82126 /**
@@ -106,16 +150,18 @@ public void printStackTrace(PrintWriter s) {
106150 /**
107151 * Special handling for printing out a CompositeException
108152 * Loop through all inner exceptions and print them out
109- * @param s stream to print to
153+ *
154+ * @param s
155+ * stream to print to
110156 */
111157 private void printStackTrace (PrintStreamOrWriter s ) {
112158 StringBuilder bldr = new StringBuilder ();
113159 bldr .append (this ).append ("\n " );
114- for (StackTraceElement myStackElement : getStackTrace ()) {
160+ for (StackTraceElement myStackElement : getStackTrace ()) {
115161 bldr .append ("\t at " ).append (myStackElement ).append ("\n " );
116162 }
117163 int i = 1 ;
118- for (Throwable ex : exceptions ) {
164+ for (Throwable ex : exceptions ) {
119165 bldr .append (" ComposedException " ).append (i ).append (" :" ).append ("\n " );
120166 appendStackTrace (bldr , ex , "\t " );
121167 i ++;
@@ -127,7 +173,7 @@ private void printStackTrace(PrintStreamOrWriter s) {
127173
128174 private void appendStackTrace (StringBuilder bldr , Throwable ex , String prefix ) {
129175 bldr .append (prefix ).append (ex ).append ("\n " );
130- for (StackTraceElement stackElement : ex .getStackTrace ()) {
176+ for (StackTraceElement stackElement : ex .getStackTrace ()) {
131177 bldr .append ("\t \t at " ).append (stackElement ).append ("\n " );
132178 }
133179 if (ex .getCause () != null ) {
@@ -178,4 +224,31 @@ void println(Object o) {
178224 printWriter .println (o );
179225 }
180226 }
227+
228+ /* package-private */ final static class CompositeExceptionCausalChain extends RuntimeException {
229+ private static final long serialVersionUID = 3875212506787802066L ;
230+ /* package-private */ static String MESSAGE = "Chain of Causes for CompositeException In Order Received =>" ;
231+
232+ @ Override
233+ public String getMessage () {
234+ return MESSAGE ;
235+ }
236+ }
237+
238+ private final List <Throwable > getListOfCauses (Throwable ex ) {
239+ List <Throwable > list = new ArrayList <Throwable >();
240+ Throwable root = ex .getCause ();
241+ if (root == null ) {
242+ return list ;
243+ } else {
244+ while (true ) {
245+ list .add (root );
246+ if (root .getCause () == null ) {
247+ return list ;
248+ } else {
249+ root = root .getCause ();
250+ }
251+ }
252+ }
253+ }
181254}
0 commit comments