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 ;
21- import java .util .HashSet ;
23+ import java .util .LinkedHashSet ;
2224import java .util .List ;
2325import java .util .Set ;
2426
2527/**
2628 * 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+ * A CompositeException does not modify the structure of any exception it wraps, but at print-time
30+ * iterates through the list of contained Throwables to print them all.
31+ *
32+ * Its invariant is to contains an immutable, ordered (by insertion order), unique list of non-composite exceptions.
33+ * This list may be queried by {@code #getExceptions()}
2934 */
3035public final class CompositeException extends RuntimeException {
3136
3237 private static final long serialVersionUID = 3026362227162912146L ;
3338
3439 private final List <Throwable > exceptions ;
3540 private final String message ;
36- private final Throwable cause ;
3741
38- public CompositeException (String messagePrefix , Collection <Throwable > errors ) {
42+ public CompositeException (String messagePrefix , Collection <? extends Throwable > errors ) {
43+ Set <Throwable > deDupedExceptions = new LinkedHashSet <Throwable >();
3944 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 );
45+ for ( Throwable ex : errors ) {
46+ if ( ex instanceof CompositeException ) {
47+ deDupedExceptions . addAll ((( CompositeException ) ex ). getExceptions () );
48+ } else {
49+ deDupedExceptions . add ( ex );
50+ }
4651 }
52+
53+ _exceptions .addAll (deDupedExceptions );
4754 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 ;
55+ this .message = exceptions .size () + " exceptions occurred. See them in causal chain below." ;
5556 }
5657
57- public CompositeException (Collection <Throwable > errors ) {
58+ public CompositeException (Collection <? extends Throwable > errors ) {
5859 this (null , errors );
5960 }
6061
@@ -75,80 +76,106 @@ public String getMessage() {
7576
7677 @ Override
7778 public synchronized Throwable getCause () {
78- return cause ;
79+ return null ;
7980 }
8081
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 )) {
86- continue ;
87- }
88- while (error .getCause () != null ) {
89- error = error .getCause ();
90- if (error == cause ) {
91- duplicated .add (cause );
92- break ;
93- }
94- }
95- }
82+ /**
83+ * All of the following printStackTrace functionality is derived from JDK Throwable printStackTrace.
84+ * In particular, the PrintStreamOrWriter abstraction is copied wholesale.
85+ *
86+ * Changes from the official JDK implementation:
87+ * * No infinite loop detection
88+ * * Smaller critical section holding printStream lock
89+ * * Explicit knowledge about exceptions List that this loops through
90+ */
91+ @ Override
92+ public void printStackTrace () {
93+ printStackTrace (System .err );
94+ }
95+
96+ @ Override
97+ public void printStackTrace (PrintStream s ) {
98+ printStackTrace (new WrappedPrintStream (s ));
99+ }
100+
101+ @ Override
102+ public void printStackTrace (PrintWriter s ) {
103+ printStackTrace (new WrappedPrintWriter (s ));
104+ }
105+
106+ /**
107+ * Special handling for printing out a CompositeException
108+ * Loop through all inner exceptions and print them out
109+ * @param s stream to print to
110+ */
111+ private void printStackTrace (PrintStreamOrWriter s ) {
112+ StringBuilder bldr = new StringBuilder ();
113+ bldr .append (this ).append ("\n " );
114+ for (StackTraceElement myStackElement : getStackTrace ()) {
115+ bldr .append ("\t at " ).append (myStackElement ).append ("\n " );
96116 }
97- if (!duplicated .isEmpty ()) {
98- errors = new ArrayList <Throwable >(errors );
99- errors .removeAll (duplicated );
117+ int i = 1 ;
118+ for (Throwable ex : exceptions ) {
119+ bldr .append (" ComposedException " ).append (i ).append (" :" ).append ("\n " );
120+ appendStackTrace (bldr , ex , "\t " );
121+ i ++;
122+ }
123+ synchronized (s .lock ()) {
124+ s .println (bldr .toString ());
100125 }
101- return errors ;
102126 }
103127
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 ;
128+ private void appendStackTrace (StringBuilder bldr , Throwable ex , String prefix ) {
129+ bldr .append (prefix ).append (ex ).append ("\n " );
130+ for (StackTraceElement stackElement : ex .getStackTrace ()) {
131+ bldr .append ("\t \t at " ).append (stackElement ).append ("\n " );
132+ }
133+ if (ex .getCause () != null ) {
134+ bldr .append ("\t Caused by: " );
135+ appendStackTrace (bldr , ex .getCause (), "" );
119136 }
120- return s .toString ();
121137 }
122138
123- /* package-private */ static void attachCallingThreadStack (Throwable e , Throwable cause ) {
124- Set <Throwable > seenCauses = new HashSet <Throwable >();
139+ private abstract static class PrintStreamOrWriter {
140+ /** Returns the object to be locked when using this StreamOrWriter */
141+ abstract Object lock ();
125142
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- }
143+ /** Prints the specified string as a line on this StreamOrWriter */
144+ abstract void println (Object o );
145+ }
146+
147+ /**
148+ * Same abstraction and implementation as in JDK to allow PrintStream and PrintWriter to share implementation
149+ */
150+ private static class WrappedPrintStream extends PrintStreamOrWriter {
151+ private final PrintStream printStream ;
152+
153+ WrappedPrintStream (PrintStream printStream ) {
154+ this .printStream = printStream ;
133155 }
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
156+
157+ Object lock () {
158+ return printStream ;
159+ }
160+
161+ void println ( Object o ) {
162+ printStream . println ( o );
141163 }
142164 }
143165
144- /* package-private */ final static class CompositeExceptionCausalChain extends RuntimeException {
145- private static final long serialVersionUID = 3875212506787802066L ;
146- /* package-private */ static String MESSAGE = "Chain of Causes for CompositeException In Order Received =>" ;
166+ private static class WrappedPrintWriter extends PrintStreamOrWriter {
167+ private final PrintWriter printWriter ;
147168
148- @ Override
149- public String getMessage () {
150- return MESSAGE ;
169+ WrappedPrintWriter (PrintWriter printWriter ) {
170+ this .printWriter = printWriter ;
151171 }
152- }
153172
173+ Object lock () {
174+ return printWriter ;
175+ }
176+
177+ void println (Object o ) {
178+ printWriter .println (o );
179+ }
180+ }
154181}
0 commit comments