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/**
2629 * 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.
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,175 @@ 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 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 " );
96162 }
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 ());
100171 }
101- return errors ;
102172 }
103173
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 (), "" );
119182 }
120- return s .toString ();
121183 }
122184
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 ();
125188
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 ;
133217 }
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 );
141225 }
142226 }
143227
144- /* package-private */ final static class CompositeExceptionCausalChain extends RuntimeException {
228+ /* package-private */ final static class CompositeExceptionCausalChain extends RuntimeException {
145229 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 =>" ;
147231
148232 @ Override
149233 public String getMessage () {
150234 return MESSAGE ;
151235 }
152236 }
153237
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+ }
154254}
0 commit comments