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
- * 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.
29
+ * An Exception that is a composite of one or more other Exceptions. A {@code CompositeException} does not
30
+ * modify the structure of any exception it wraps, but at print-time it iterates through the list of
31
+ * Throwables contained in the composit in order to print them all.
32
+ *
33
+ * Its invariant is to contain an immutable, ordered (by insertion order), unique list of non-composite
34
+ * exceptions. You can retrieve individual exceptions in this list with {@link #getExceptions()}.
35
+ *
36
+ * The {@link #printStackTrace()} implementation handles the StackTrace in a customized way instead of using
37
+ * {@code getCause()} so that it can avoid circular references.
38
+ *
39
+ * If you invoke {@link #getCause()}, it will lazily create the causal chain but will stop if it finds any
40
+ * Throwable in the chain 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,176 @@ 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 {@code printStackTrace} functionality is derived from JDK {@link Throwable}
128
+ * {@code printStackTrace}. In particular, the {@code PrintStreamOrWriter} abstraction is copied wholesale.
129
+ *
130
+ * Changes from the official JDK implementation:<ul>
131
+ * <li>no infinite loop detection</li>
132
+ * <li>smaller critical section holding {@link PrintStream} lock</li>
133
+ * <li>explicit knowledge about the exceptions {@link List} that this loops through</li>
134
+ * </ul>
135
+ */
136
+ @ Override
137
+ public void printStackTrace () {
138
+ printStackTrace (System .err );
139
+ }
140
+
141
+ @ Override
142
+ public void printStackTrace (PrintStream s ) {
143
+ printStackTrace (new WrappedPrintStream (s ));
144
+ }
145
+
146
+ @ Override
147
+ public void printStackTrace (PrintWriter s ) {
148
+ printStackTrace (new WrappedPrintWriter (s ));
149
+ }
150
+
151
+ /**
152
+ * Special handling for printing out a {@code CompositeException}.
153
+ * Loops through all inner exceptions and prints them out.
154
+ *
155
+ * @param s
156
+ * stream to print to
157
+ */
158
+ private void printStackTrace (PrintStreamOrWriter s ) {
159
+ StringBuilder bldr = new StringBuilder ();
160
+ bldr .append (this ).append ("\n " );
161
+ for (StackTraceElement myStackElement : getStackTrace ()) {
162
+ bldr .append ("\t at " ).append (myStackElement ).append ("\n " );
96
163
}
97
- if (!duplicated .isEmpty ()) {
98
- errors = new ArrayList <Throwable >(errors );
99
- errors .removeAll (duplicated );
164
+ int i = 1 ;
165
+ for (Throwable ex : exceptions ) {
166
+ bldr .append (" ComposedException " ).append (i ).append (" :" ).append ("\n " );
167
+ appendStackTrace (bldr , ex , "\t " );
168
+ i ++;
169
+ }
170
+ synchronized (s .lock ()) {
171
+ s .println (bldr .toString ());
100
172
}
101
- return errors ;
102
173
}
103
174
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 ;
175
+ private void appendStackTrace (StringBuilder bldr , Throwable ex , String prefix ) {
176
+ bldr .append (prefix ).append (ex ).append ("\n " );
177
+ for (StackTraceElement stackElement : ex .getStackTrace ()) {
178
+ bldr .append ("\t \t at " ).append (stackElement ).append ("\n " );
179
+ }
180
+ if (ex .getCause () != null ) {
181
+ bldr .append ("\t Caused by: " );
182
+ appendStackTrace (bldr , ex .getCause (), "" );
119
183
}
120
- return s .toString ();
121
184
}
122
185
123
- /* package-private */ static void attachCallingThreadStack (Throwable e , Throwable cause ) {
124
- Set <Throwable > seenCauses = new HashSet <Throwable >();
186
+ private abstract static class PrintStreamOrWriter {
187
+ /** Returns the object to be locked when using this StreamOrWriter */
188
+ abstract Object lock ();
125
189
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
- }
190
+ /** Prints the specified string as a line on this StreamOrWriter */
191
+ abstract void println (Object o );
192
+ }
193
+
194
+ /**
195
+ * Same abstraction and implementation as in JDK to allow PrintStream and PrintWriter to share implementation
196
+ */
197
+ private static class WrappedPrintStream extends PrintStreamOrWriter {
198
+ private final PrintStream printStream ;
199
+
200
+ WrappedPrintStream (PrintStream printStream ) {
201
+ this .printStream = printStream ;
202
+ }
203
+
204
+ Object lock () {
205
+ return printStream ;
206
+ }
207
+
208
+ void println (Object o ) {
209
+ printStream .println (o );
210
+ }
211
+ }
212
+
213
+ private static class WrappedPrintWriter extends PrintStreamOrWriter {
214
+ private final PrintWriter printWriter ;
215
+
216
+ WrappedPrintWriter (PrintWriter printWriter ) {
217
+ this .printWriter = printWriter ;
133
218
}
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
219
+
220
+ Object lock () {
221
+ return printWriter ;
222
+ }
223
+
224
+ void println ( Object o ) {
225
+ printWriter . println ( o );
141
226
}
142
227
}
143
228
144
- /* package-private */ final static class CompositeExceptionCausalChain extends RuntimeException {
229
+ /* package-private */ final static class CompositeExceptionCausalChain extends RuntimeException {
145
230
private static final long serialVersionUID = 3875212506787802066L ;
146
- /* package-private */ static String MESSAGE = "Chain of Causes for CompositeException In Order Received =>" ;
231
+ /* package-private */ static String MESSAGE = "Chain of Causes for CompositeException In Order Received =>" ;
147
232
148
233
@ Override
149
234
public String getMessage () {
150
235
return MESSAGE ;
151
236
}
152
237
}
153
238
154
- }
239
+ private final List <Throwable > getListOfCauses (Throwable ex ) {
240
+ List <Throwable > list = new ArrayList <Throwable >();
241
+ Throwable root = ex .getCause ();
242
+ if (root == null ) {
243
+ return list ;
244
+ } else {
245
+ while (true ) {
246
+ list .add (root );
247
+ if (root .getCause () == null ) {
248
+ return list ;
249
+ } else {
250
+ root = root .getCause ();
251
+ }
252
+ }
253
+ }
254
+ }
255
+ }
0 commit comments