1515 */
1616package rx .exceptions ;
1717
18+ import java .io .PrintStream ;
19+ import java .io .PrintWriter ;
1820import java .util .ArrayList ;
1921import java .util .Collection ;
2022import java .util .Collections ;
2123import java .util .HashSet ;
24+ import java .util .LinkedHashSet ;
2225import java .util .List ;
2326import java .util .Set ;
2427
2528/**
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.
2941 */
3042public final class CompositeException extends RuntimeException {
3143
3244 private static final long serialVersionUID = 3026362227162912146L ;
3345
3446 private final List <Throwable > exceptions ;
3547 private final String message ;
36- private final Throwable cause ;
3748
38- public CompositeException (String messagePrefix , Collection <Throwable > errors ) {
49+ public CompositeException (String messagePrefix , Collection <? extends Throwable > errors ) {
50+ Set <Throwable > deDupedExceptions = new LinkedHashSet <Throwable >();
3951 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+ }
4658 }
59+
60+ _exceptions .addAll (deDupedExceptions );
4761 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. " ;
5563 }
5664
57- public CompositeException (Collection <Throwable > errors ) {
65+ public CompositeException (Collection <? extends Throwable > errors ) {
5866 this (null , errors );
5967 }
6068
6169 /**
6270 * Retrieves the list of exceptions that make up the {@code CompositeException}
6371 *
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
6673 */
6774 public List <Throwable > getExceptions () {
6875 return exceptions ;
@@ -73,82 +80,176 @@ public String getMessage() {
7380 return message ;
7481 }
7582
83+ private Throwable cause = null ;
84+
7685 @ Override
7786 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
8696 continue ;
8797 }
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 ;
93107 }
108+ seenCauses .add (child );
94109 }
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 ();
95120 }
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 " );
96163 }
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 ());
100172 }
101- return errors ;
102173 }
103174
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 (), "" );
119183 }
120- return s .toString ();
121184 }
122185
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 ();
125189
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 ;
133218 }
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 );
141226 }
142227 }
143228
144- /* package-private */ final static class CompositeExceptionCausalChain extends RuntimeException {
229+ /* package-private */ final static class CompositeExceptionCausalChain extends RuntimeException {
145230 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 =>" ;
147232
148233 @ Override
149234 public String getMessage () {
150235 return MESSAGE ;
151236 }
152237 }
153238
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