20
20
import java .util .ArrayList ;
21
21
import java .util .Collection ;
22
22
import java .util .Collections ;
23
+ import java .util .HashSet ;
23
24
import java .util .LinkedHashSet ;
24
25
import java .util .List ;
25
26
import java .util .Set ;
31
32
*
32
33
* Its invariant is to contains an immutable, ordered (by insertion order), unique list of non-composite exceptions.
33
34
* 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.
34
41
*/
35
42
public final class CompositeException extends RuntimeException {
36
43
@@ -42,7 +49,7 @@ public final class CompositeException extends RuntimeException {
42
49
public CompositeException (String messagePrefix , Collection <? extends Throwable > errors ) {
43
50
Set <Throwable > deDupedExceptions = new LinkedHashSet <Throwable >();
44
51
List <Throwable > _exceptions = new ArrayList <Throwable >();
45
- for (Throwable ex : errors ) {
52
+ for (Throwable ex : errors ) {
46
53
if (ex instanceof CompositeException ) {
47
54
deDupedExceptions .addAll (((CompositeException ) ex ).getExceptions ());
48
55
} else {
@@ -52,7 +59,7 @@ public CompositeException(String messagePrefix, Collection<? extends Throwable>
52
59
53
60
_exceptions .addAll (deDupedExceptions );
54
61
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. " ;
56
63
}
57
64
58
65
public CompositeException (Collection <? extends Throwable > errors ) {
@@ -62,8 +69,7 @@ public CompositeException(Collection<? extends Throwable> errors) {
62
69
/**
63
70
* Retrieves the list of exceptions that make up the {@code CompositeException}
64
71
*
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
67
73
*/
68
74
public List <Throwable > getExceptions () {
69
75
return exceptions ;
@@ -74,9 +80,47 @@ public String getMessage() {
74
80
return message ;
75
81
}
76
82
83
+ private Throwable cause = null ;
84
+
77
85
@ Override
78
86
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 ;
80
124
}
81
125
82
126
/**
@@ -106,16 +150,18 @@ public void printStackTrace(PrintWriter s) {
106
150
/**
107
151
* Special handling for printing out a CompositeException
108
152
* 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
110
156
*/
111
157
private void printStackTrace (PrintStreamOrWriter s ) {
112
158
StringBuilder bldr = new StringBuilder ();
113
159
bldr .append (this ).append ("\n " );
114
- for (StackTraceElement myStackElement : getStackTrace ()) {
160
+ for (StackTraceElement myStackElement : getStackTrace ()) {
115
161
bldr .append ("\t at " ).append (myStackElement ).append ("\n " );
116
162
}
117
163
int i = 1 ;
118
- for (Throwable ex : exceptions ) {
164
+ for (Throwable ex : exceptions ) {
119
165
bldr .append (" ComposedException " ).append (i ).append (" :" ).append ("\n " );
120
166
appendStackTrace (bldr , ex , "\t " );
121
167
i ++;
@@ -127,7 +173,7 @@ private void printStackTrace(PrintStreamOrWriter s) {
127
173
128
174
private void appendStackTrace (StringBuilder bldr , Throwable ex , String prefix ) {
129
175
bldr .append (prefix ).append (ex ).append ("\n " );
130
- for (StackTraceElement stackElement : ex .getStackTrace ()) {
176
+ for (StackTraceElement stackElement : ex .getStackTrace ()) {
131
177
bldr .append ("\t \t at " ).append (stackElement ).append ("\n " );
132
178
}
133
179
if (ex .getCause () != null ) {
@@ -178,4 +224,31 @@ void println(Object o) {
178
224
printWriter .println (o );
179
225
}
180
226
}
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
+ }
181
254
}
0 commit comments