15
15
*/
16
16
package rx .exceptions ;
17
17
18
+ import java .io .PrintStream ;
19
+ import java .io .PrintWriter ;
18
20
import java .util .ArrayList ;
19
21
import java .util .Collection ;
20
22
import java .util .Collections ;
21
23
import java .util .HashSet ;
24
+ import java .util .LinkedHashSet ;
22
25
import java .util .List ;
23
26
import java .util .Set ;
24
27
25
28
/**
26
29
* Exception that is a composite of 1 or more other exceptions.
27
- * <p>
28
- * Use <code>getMessage()</code> to retrieve a concatenation of the composite exceptions.
30
+ * A CompositeException does not modify the structure of any exception it wraps, but at print-time
31
+ * iterates through the list of contained Throwables to print them all.
32
+ *
33
+ * Its invariant is to contains an immutable, ordered (by insertion order), unique list of non-composite exceptions.
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.
29
41
*/
30
42
public final class CompositeException extends RuntimeException {
31
43
32
44
private static final long serialVersionUID = 3026362227162912146L ;
33
45
34
46
private final List <Throwable > exceptions ;
35
47
private final String message ;
36
- private final Throwable cause ;
37
48
38
- public CompositeException (String messagePrefix , Collection <Throwable > errors ) {
49
+ public CompositeException (String messagePrefix , Collection <? extends Throwable > errors ) {
50
+ Set <Throwable > deDupedExceptions = new LinkedHashSet <Throwable >();
39
51
List <Throwable > _exceptions = new ArrayList <Throwable >();
40
- CompositeExceptionCausalChain _cause = new CompositeExceptionCausalChain ();
41
- int count = errors . size ();
42
- errors = removeDuplicatedCauses ( errors );
43
- for ( Throwable e : errors ) {
44
- attachCallingThreadStack ( _cause , e );
45
- _exceptions . add ( e );
52
+ for ( Throwable ex : errors ) {
53
+ if ( ex instanceof CompositeException ) {
54
+ deDupedExceptions . addAll ((( CompositeException ) ex ). getExceptions () );
55
+ } else {
56
+ deDupedExceptions . add ( ex );
57
+ }
46
58
}
59
+
60
+ _exceptions .addAll (deDupedExceptions );
47
61
this .exceptions = Collections .unmodifiableList (_exceptions );
48
-
49
- String msg = count + " exceptions occurred. See them in causal chain below." ;
50
- if (messagePrefix != null ) {
51
- msg = messagePrefix + " " + msg ;
52
- }
53
- this .message = msg ;
54
- this .cause = _cause ;
62
+ this .message = exceptions .size () + " exceptions occurred. " ;
55
63
}
56
64
57
- public CompositeException (Collection <Throwable > errors ) {
65
+ public CompositeException (Collection <? extends Throwable > errors ) {
58
66
this (null , errors );
59
67
}
60
68
61
69
/**
62
70
* Retrieves the list of exceptions that make up the {@code CompositeException}
63
71
*
64
- * @return the exceptions that make up the {@code CompositeException}, as a {@link List} of
65
- * {@link Throwable}s
72
+ * @return the exceptions that make up the {@code CompositeException}, as a {@link List} of {@link Throwable}s
66
73
*/
67
74
public List <Throwable > getExceptions () {
68
75
return exceptions ;
@@ -73,82 +80,175 @@ public String getMessage() {
73
80
return message ;
74
81
}
75
82
83
+ private Throwable cause = null ;
84
+
76
85
@ Override
77
86
public synchronized Throwable getCause () {
78
- return cause ;
79
- }
80
-
81
- private Collection <Throwable > removeDuplicatedCauses (Collection <Throwable > errors ) {
82
- Set <Throwable > duplicated = new HashSet <Throwable >();
83
- for (Throwable cause : errors ) {
84
- for (Throwable error : errors ) {
85
- if (cause == error || duplicated .contains (error )) {
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
86
96
continue ;
87
97
}
88
- while (error .getCause () != null ) {
89
- error = error .getCause ();
90
- if (error == cause ) {
91
- duplicated .add (cause );
92
- break ;
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 ;
93
107
}
108
+ seenCauses .add (child );
94
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 ();
95
120
}
121
+ cause = _cause ;
122
+ }
123
+ return cause ;
124
+ }
125
+
126
+ /**
127
+ * All of the following printStackTrace functionality is derived from JDK Throwable printStackTrace.
128
+ * In particular, the PrintStreamOrWriter abstraction is copied wholesale.
129
+ *
130
+ * Changes from the official JDK implementation:
131
+ * * No infinite loop detection
132
+ * * Smaller critical section holding printStream lock
133
+ * * Explicit knowledge about exceptions List that this loops through
134
+ */
135
+ @ Override
136
+ public void printStackTrace () {
137
+ printStackTrace (System .err );
138
+ }
139
+
140
+ @ Override
141
+ public void printStackTrace (PrintStream s ) {
142
+ printStackTrace (new WrappedPrintStream (s ));
143
+ }
144
+
145
+ @ Override
146
+ public void printStackTrace (PrintWriter s ) {
147
+ printStackTrace (new WrappedPrintWriter (s ));
148
+ }
149
+
150
+ /**
151
+ * Special handling for printing out a CompositeException
152
+ * Loop through all inner exceptions and print them out
153
+ *
154
+ * @param s
155
+ * stream to print to
156
+ */
157
+ private void printStackTrace (PrintStreamOrWriter s ) {
158
+ StringBuilder bldr = new StringBuilder ();
159
+ bldr .append (this ).append ("\n " );
160
+ for (StackTraceElement myStackElement : getStackTrace ()) {
161
+ bldr .append ("\t at " ).append (myStackElement ).append ("\n " );
96
162
}
97
- if (!duplicated .isEmpty ()) {
98
- errors = new ArrayList <Throwable >(errors );
99
- errors .removeAll (duplicated );
163
+ int i = 1 ;
164
+ for (Throwable ex : exceptions ) {
165
+ bldr .append (" ComposedException " ).append (i ).append (" :" ).append ("\n " );
166
+ appendStackTrace (bldr , ex , "\t " );
167
+ i ++;
168
+ }
169
+ synchronized (s .lock ()) {
170
+ s .println (bldr .toString ());
100
171
}
101
- return errors ;
102
172
}
103
173
104
- @ SuppressWarnings ("unused" )
105
- // useful when debugging but don't want to make part of publicly supported API
106
- private static String getStackTraceAsString (StackTraceElement [] stack ) {
107
- StringBuilder s = new StringBuilder ();
108
- boolean firstLine = true ;
109
- for (StackTraceElement e : stack ) {
110
- if (e .toString ().startsWith ("java.lang.Thread.getStackTrace" )) {
111
- // we'll ignore this one
112
- continue ;
113
- }
114
- if (!firstLine ) {
115
- s .append ("\n \t " );
116
- }
117
- s .append (e .toString ());
118
- firstLine = false ;
174
+ private void appendStackTrace (StringBuilder bldr , Throwable ex , String prefix ) {
175
+ bldr .append (prefix ).append (ex ).append ("\n " );
176
+ for (StackTraceElement stackElement : ex .getStackTrace ()) {
177
+ bldr .append ("\t \t at " ).append (stackElement ).append ("\n " );
178
+ }
179
+ if (ex .getCause () != null ) {
180
+ bldr .append ("\t Caused by: " );
181
+ appendStackTrace (bldr , ex .getCause (), "" );
119
182
}
120
- return s .toString ();
121
183
}
122
184
123
- /* package-private */ static void attachCallingThreadStack (Throwable e , Throwable cause ) {
124
- Set <Throwable > seenCauses = new HashSet <Throwable >();
185
+ private abstract static class PrintStreamOrWriter {
186
+ /** Returns the object to be locked when using this StreamOrWriter */
187
+ abstract Object lock ();
125
188
126
- while (e .getCause () != null ) {
127
- e = e .getCause ();
128
- if (seenCauses .contains (e .getCause ())) {
129
- break ;
130
- } else {
131
- seenCauses .add (e .getCause ());
132
- }
189
+ /** Prints the specified string as a line on this StreamOrWriter */
190
+ abstract void println (Object o );
191
+ }
192
+
193
+ /**
194
+ * Same abstraction and implementation as in JDK to allow PrintStream and PrintWriter to share implementation
195
+ */
196
+ private static class WrappedPrintStream extends PrintStreamOrWriter {
197
+ private final PrintStream printStream ;
198
+
199
+ WrappedPrintStream (PrintStream printStream ) {
200
+ this .printStream = printStream ;
201
+ }
202
+
203
+ Object lock () {
204
+ return printStream ;
205
+ }
206
+
207
+ void println (Object o ) {
208
+ printStream .println (o );
209
+ }
210
+ }
211
+
212
+ private static class WrappedPrintWriter extends PrintStreamOrWriter {
213
+ private final PrintWriter printWriter ;
214
+
215
+ WrappedPrintWriter (PrintWriter printWriter ) {
216
+ this .printWriter = printWriter ;
133
217
}
134
- // we now have 'e' as the last in the chain
135
- try {
136
- e . initCause ( cause ) ;
137
- } catch ( Throwable t ) {
138
- // ignore
139
- // the javadocs say that some Throwables (depending on how they're made) will never
140
- // let me call initCause without blowing up even if it returns null
218
+
219
+ Object lock () {
220
+ return printWriter ;
221
+ }
222
+
223
+ void println ( Object o ) {
224
+ printWriter . println ( o );
141
225
}
142
226
}
143
227
144
- /* package-private */ final static class CompositeExceptionCausalChain extends RuntimeException {
228
+ /* package-private */ final static class CompositeExceptionCausalChain extends RuntimeException {
145
229
private static final long serialVersionUID = 3875212506787802066L ;
146
- /* package-private */ static String MESSAGE = "Chain of Causes for CompositeException In Order Received =>" ;
230
+ /* package-private */ static String MESSAGE = "Chain of Causes for CompositeException In Order Received =>" ;
147
231
148
232
@ Override
149
233
public String getMessage () {
150
234
return MESSAGE ;
151
235
}
152
236
}
153
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
+ }
154
254
}
0 commit comments