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
3842 public CompositeException (String messagePrefix , Collection <Throwable > errors ) {
43+ Set <Throwable > deDupedExceptions = new LinkedHashSet <Throwable >();
3944 List <Throwable > _exceptions = new ArrayList <Throwable >();
40- CompositeExceptionCausalChain _cause = new CompositeExceptionCausalChain ();
41- int count = 0 ;
42- for ( Throwable e : errors ) {
43- count ++;
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- this .message = count + " exceptions occurred. See them in causal chain below." ;
49- this .cause = _cause ;
55+ this .message = exceptions .size () + " exceptions occurred. See them in causal chain below." ;
5056 }
5157
5258 public CompositeException (Collection <Throwable > errors ) {
@@ -70,57 +76,106 @@ public String getMessage() {
7076
7177 @ Override
7278 public synchronized Throwable getCause () {
73- return cause ;
79+ return null ;
7480 }
7581
76- @ SuppressWarnings ("unused" )
77- // useful when debugging but don't want to make part of publicly supported API
78- private static String getStackTraceAsString (StackTraceElement [] stack ) {
79- StringBuilder s = new StringBuilder ();
80- boolean firstLine = true ;
81- for (StackTraceElement e : stack ) {
82- if (e .toString ().startsWith ("java.lang.Thread.getStackTrace" )) {
83- // we'll ignore this one
84- continue ;
85- }
86- if (!firstLine ) {
87- s .append ("\n \t " );
88- }
89- s .append (e .toString ());
90- firstLine = false ;
91- }
92- return s .toString ();
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 );
9394 }
9495
95- /* package-private */ static void attachCallingThreadStack (Throwable e , Throwable cause ) {
96- Set <Throwable > seenCauses = new HashSet <Throwable >();
96+ @ Override
97+ public void printStackTrace (PrintStream s ) {
98+ printStackTrace (new WrappedPrintStream (s ));
99+ }
97100
98- while (e .getCause () != null ) {
99- e = e .getCause ();
100- if (seenCauses .contains (e .getCause ())) {
101- break ;
102- } else {
103- seenCauses .add (e .getCause ());
104- }
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 " );
116+ }
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 ++;
105122 }
106- // we now have 'e' as the last in the chain
107- try {
108- e .initCause (cause );
109- } catch (Throwable t ) {
110- // ignore
111- // the javadocs say that some Throwables (depending on how they're made) will never
112- // let me call initCause without blowing up even if it returns null
123+ synchronized (s .lock ()) {
124+ s .println (bldr .toString ());
113125 }
114126 }
115127
116- /* package-private */ final static class CompositeExceptionCausalChain extends RuntimeException {
117- private static final long serialVersionUID = 3875212506787802066L ;
118- /* package-private */ static String MESSAGE = "Chain of Causes for CompositeException In Order Received =>" ;
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 (), "" );
136+ }
137+ }
138+
139+ private abstract static class PrintStreamOrWriter {
140+ /** Returns the object to be locked when using this StreamOrWriter */
141+ abstract Object lock ();
142+
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 ;
155+ }
156+
157+ Object lock () {
158+ return printStream ;
159+ }
119160
120- @ Override
121- public String getMessage () {
122- return MESSAGE ;
161+ void println (Object o ) {
162+ printStream .println (o );
123163 }
124164 }
125165
166+ private static class WrappedPrintWriter extends PrintStreamOrWriter {
167+ private final PrintWriter printWriter ;
168+
169+ WrappedPrintWriter (PrintWriter printWriter ) {
170+ this .printWriter = printWriter ;
171+ }
172+
173+ Object lock () {
174+ return printWriter ;
175+ }
176+
177+ void println (Object o ) {
178+ printWriter .println (o );
179+ }
180+ }
126181}
0 commit comments