1515 */
1616package rx .exceptions ;
1717
18- import java .io .PrintStream ;
19- import java .io .PrintWriter ;
2018import java .util .ArrayList ;
2119import java .util .Collection ;
2220import java .util .Collections ;
23- import java .util .LinkedHashSet ;
21+ import java .util .HashSet ;
2422import java .util .List ;
2523import java .util .Set ;
2624
2725/**
2826 * Exception that is a composite of 1 or more other 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()}
27+ * <p>
28+ * Use <code>getMessage()</code> to retrieve a concatenation of the composite exceptions.
3429 */
3530public final class CompositeException extends RuntimeException {
3631
3732 private static final long serialVersionUID = 3026362227162912146L ;
3833
3934 private final List <Throwable > exceptions ;
4035 private final String message ;
36+ private final Throwable cause ;
4137
4238 public CompositeException (String messagePrefix , Collection <Throwable > errors ) {
43- Set <Throwable > deDupedExceptions = new LinkedHashSet <Throwable >();
4439 List <Throwable > _exceptions = new ArrayList <Throwable >();
45- for ( Throwable ex : errors ) {
46- if ( ex instanceof CompositeException ) {
47- deDupedExceptions . addAll ((( CompositeException ) ex ). getExceptions () );
48- } else {
49- deDupedExceptions . add ( ex );
50- }
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 );
5146 }
52-
53- _exceptions .addAll (deDupedExceptions );
5447 this .exceptions = Collections .unmodifiableList (_exceptions );
55- this .message = exceptions .size () + " exceptions occurred. See them in causal chain below." ;
48+ this .message = count + " exceptions occurred. See them in causal chain below." ;
49+ this .cause = _cause ;
5650 }
5751
5852 public CompositeException (Collection <Throwable > errors ) {
@@ -76,106 +70,80 @@ public String getMessage() {
7670
7771 @ Override
7872 public synchronized Throwable getCause () {
79- return null ;
80- }
81-
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 );
73+ return cause ;
9474 }
9575
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 " );
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 ++;
76+ private Collection <Throwable > removeDuplicatedCauses (Collection <Throwable > errors ) {
77+ Set <Throwable > duplicated = new HashSet <Throwable >();
78+ for (Throwable cause : errors ) {
79+ for (Throwable error : errors ) {
80+ if (cause == error || duplicated .contains (error )) {
81+ continue ;
82+ }
83+ while (error .getCause () != null ) {
84+ error = error .getCause ();
85+ if (error == cause ) {
86+ duplicated .add (cause );
87+ break ;
88+ }
89+ }
90+ }
12291 }
123- synchronized (s .lock ()) {
124- s .println (bldr .toString ());
92+ if (!duplicated .isEmpty ()) {
93+ errors = new ArrayList <Throwable >(errors );
94+ errors .removeAll (duplicated );
12595 }
96+ return errors ;
12697 }
12798
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 (), "" );
99+ @ SuppressWarnings ("unused" )
100+ // useful when debugging but don't want to make part of publicly supported API
101+ private static String getStackTraceAsString (StackTraceElement [] stack ) {
102+ StringBuilder s = new StringBuilder ();
103+ boolean firstLine = true ;
104+ for (StackTraceElement e : stack ) {
105+ if (e .toString ().startsWith ("java.lang.Thread.getStackTrace" )) {
106+ // we'll ignore this one
107+ continue ;
108+ }
109+ if (!firstLine ) {
110+ s .append ("\n \t " );
111+ }
112+ s .append (e .toString ());
113+ firstLine = false ;
136114 }
115+ return s .toString ();
137116 }
138117
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- }
118+ /* package-private */ static void attachCallingThreadStack (Throwable e , Throwable cause ) {
119+ Set <Throwable > seenCauses = new HashSet <Throwable >();
156120
157- Object lock () {
158- return printStream ;
121+ while (e .getCause () != null ) {
122+ e = e .getCause ();
123+ if (seenCauses .contains (e .getCause ())) {
124+ break ;
125+ } else {
126+ seenCauses .add (e .getCause ());
127+ }
159128 }
160-
161- void println (Object o ) {
162- printStream .println (o );
129+ // we now have 'e' as the last in the chain
130+ try {
131+ e .initCause (cause );
132+ } catch (Throwable t ) {
133+ // ignore
134+ // the javadocs say that some Throwables (depending on how they're made) will never
135+ // let me call initCause without blowing up even if it returns null
163136 }
164137 }
165138
166- private static class WrappedPrintWriter extends PrintStreamOrWriter {
167- private final PrintWriter printWriter ;
139+ /* package-private */ final static class CompositeExceptionCausalChain extends RuntimeException {
140+ private static final long serialVersionUID = 3875212506787802066L ;
141+ /* package-private */ static String MESSAGE = "Chain of Causes for CompositeException In Order Received =>" ;
168142
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 );
143+ @ Override
144+ public String getMessage () {
145+ return MESSAGE ;
179146 }
180147 }
148+
181149}
0 commit comments