15
15
*/
16
16
package rx .exceptions ;
17
17
18
+ import java .io .PrintStream ;
19
+ import java .io .PrintWriter ;
18
20
import java .util .ArrayList ;
19
21
import java .util .Collection ;
20
22
import java .util .Collections ;
21
- import java .util .HashSet ;
23
+ import java .util .LinkedHashSet ;
22
24
import java .util .List ;
23
25
import java .util .Set ;
24
26
25
27
/**
26
28
* 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()}
29
34
*/
30
35
public final class CompositeException extends RuntimeException {
31
36
32
37
private static final long serialVersionUID = 3026362227162912146L ;
33
38
34
39
private final List <Throwable > exceptions ;
35
40
private final String message ;
36
- private final Throwable cause ;
37
41
38
42
public CompositeException (String messagePrefix , Collection <Throwable > errors ) {
43
+ Set <Throwable > deDupedExceptions = new LinkedHashSet <Throwable >();
39
44
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
+ }
46
51
}
52
+
53
+ _exceptions .addAll (deDupedExceptions );
47
54
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." ;
50
56
}
51
57
52
58
public CompositeException (Collection <Throwable > errors ) {
@@ -70,57 +76,106 @@ public String getMessage() {
70
76
71
77
@ Override
72
78
public synchronized Throwable getCause () {
73
- return cause ;
79
+ return null ;
74
80
}
75
81
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 );
93
94
}
94
95
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
+ }
97
100
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 ++;
105
122
}
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 ());
113
125
}
114
126
}
115
127
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
+ }
119
160
120
- @ Override
121
- public String getMessage () {
122
- return MESSAGE ;
161
+ void println (Object o ) {
162
+ printStream .println (o );
123
163
}
124
164
}
125
165
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
+ }
126
181
}
0 commit comments