-
Notifications
You must be signed in to change notification settings - Fork 7.6k
/
Copy pathExceptions.java
226 lines (213 loc) · 8.59 KB
/
Exceptions.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
/**
* Copyright 2014 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package rx.exceptions;
import java.util.*;
import rx.Observer;
import rx.SingleSubscriber;
/**
* Utility class with methods to wrap checked exceptions and
* manage fatal and regular exception delivery.
*/
public final class Exceptions {
private static final int MAX_DEPTH = 25;
/** Utility class, no instances. */
private Exceptions() {
throw new IllegalStateException("No instances!");
}
/**
* Convenience method to throw a {@code RuntimeException} and {@code Error} directly
* or wrap any other exception type into a {@code RuntimeException}.
* @param t the exception to throw directly or wrapped
* @return because {@code propagate} itself throws an exception or error, this is a sort of phantom return
* value; {@code propagate} does not actually return anything
*/
public static RuntimeException propagate(Throwable t) {
/*
* The return type of RuntimeException is a trick for code to be like this:
*
* throw Exceptions.propagate(e);
*
* Even though nothing will return and throw via that 'throw', it allows the code to look like it
* so it's easy to read and understand that it will always result in a throw.
*/
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else if (t instanceof Error) {
throw (Error) t;
} else {
throw new RuntimeException(t); // NOPMD
}
}
/**
* Throws a particular {@code Throwable} only if it belongs to a set of "fatal" error varieties. These
* varieties are as follows:
* <ul>
* <li>{@link OnErrorNotImplementedException}</li>
* <li>{@link OnErrorFailedException}</li>
* <li>{@link OnCompletedFailedException}</li>
* <li>{@code VirtualMachineError}</li>
* <li>{@code ThreadDeath}</li>
* <li>{@code LinkageError}</li>
* </ul>
* This can be useful if you are writing an operator that calls user-supplied code, and you want to
* notify subscribers of errors encountered in that code by calling their {@code onError} methods, but only
* if the errors are not so catastrophic that such a call would be futile, in which case you simply want to
* rethrow the error.
*
* @param t
* the {@code Throwable} to test and perhaps throw
* @see <a href="https://github.com/ReactiveX/RxJava/issues/748#issuecomment-32471495">RxJava: StackOverflowError is swallowed (Issue #748)</a>
*/
public static void throwIfFatal(Throwable t) {
if (t instanceof OnErrorNotImplementedException) {
throw (OnErrorNotImplementedException) t;
} else if (t instanceof OnErrorFailedException) {
throw (OnErrorFailedException) t;
} else if (t instanceof OnCompletedFailedException) {
throw (OnCompletedFailedException) t;
}
// values here derived from https://github.com/ReactiveX/RxJava/issues/748#issuecomment-32471495
else if (t instanceof VirtualMachineError) {
throw (VirtualMachineError) t;
} else if (t instanceof ThreadDeath) {
throw (ThreadDeath) t;
} else if (t instanceof LinkageError) {
throw (LinkageError) t;
}
}
/**
* Adds a {@code Throwable} to a causality-chain of Throwables, as an additional cause (if it does not
* already appear in the chain among the causes).
*
* @param e
* the {@code Throwable} at the head of the causality chain
* @param cause
* the {@code Throwable} you want to add as a cause of the chain
*/
public static void addCause(Throwable e, Throwable cause) {
Set<Throwable> seenCauses = new HashSet<Throwable>();
int i = 0;
while (e.getCause() != null) {
if (i++ >= MAX_DEPTH) {
// stack too deep to associate cause
return;
}
e = e.getCause();
if (seenCauses.contains(e.getCause())) {
break;
} else {
seenCauses.add(e.getCause());
}
}
// we now have 'e' as the last in the chain
try {
e.initCause(cause);
} catch (Throwable t) { // NOPMD
// ignore
// the javadocs say that some Throwables (depending on how they're made) will never
// let me call initCause without blowing up even if it returns null
}
}
/**
* Get the {@code Throwable} at the end of the causality-chain for a particular {@code Throwable}
*
* @param e
* the {@code Throwable} whose final cause you are curious about
* @return the last {@code Throwable} in the causality-chain of {@code e} (or a "Stack too deep to get
* final cause" {@code RuntimeException} if the chain is too long to traverse)
*/
public static Throwable getFinalCause(Throwable e) {
int i = 0;
while (e.getCause() != null) {
if (i++ >= MAX_DEPTH) {
// stack too deep to get final cause
return new RuntimeException("Stack too deep to get final cause");
}
e = e.getCause();
}
return e;
}
/**
* Throws a single or multiple exceptions contained in the collection, wrapping it into
* {@code CompositeException} if necessary.
* @param exceptions the collection of exceptions. If null or empty, no exception is thrown.
* If the collection contains a single exception, that exception is either thrown as-is or wrapped into a
* CompositeException. Multiple exceptions are wrapped into a CompositeException.
* @since 1.1.0
*/
public static void throwIfAny(List<? extends Throwable> exceptions) {
if (exceptions != null && !exceptions.isEmpty()) {
if (exceptions.size() == 1) {
Throwable t = exceptions.get(0);
// had to manually inline propagate because some tests attempt StackOverflowError
// and can't handle it with the stack space remaining
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else if (t instanceof Error) {
throw (Error) t;
} else {
throw new RuntimeException(t); // NOPMD
}
}
throw new CompositeException(exceptions);
}
}
/**
* Forwards a fatal exception or reports it along with the value
* caused it to the given Observer.
* @param t the exception
* @param o the observer to report to
* @param value the value that caused the exception
* @since 1.3
*/
public static void throwOrReport(Throwable t, Observer<?> o, Object value) {
Exceptions.throwIfFatal(t);
o.onError(OnErrorThrowable.addValueAsLastCause(t, value));
}
/**
* Forwards a fatal exception or reports it along with the value
* caused it to the given SingleSubscriber.
* @param t the exception
* @param o the observer to report to
* @param value the value that caused the exception
* @since 1.3
*/
public static void throwOrReport(Throwable t, SingleSubscriber<?> o, Object value) {
Exceptions.throwIfFatal(t);
o.onError(OnErrorThrowable.addValueAsLastCause(t, value));
}
/**
* Forwards a fatal exception or reports it to the given Observer.
* @param t the exception
* @param o the observer to report to
* @since 1.3
*/
public static void throwOrReport(Throwable t, Observer<?> o) {
Exceptions.throwIfFatal(t);
o.onError(t);
}
/**
* Forwards a fatal exception or reports it to the given Observer.
*
* @param throwable the exception.
* @param subscriber the subscriber to report to.
* @since 1.3
*/
public static void throwOrReport(Throwable throwable, SingleSubscriber<?> subscriber) {
Exceptions.throwIfFatal(throwable);
subscriber.onError(throwable);
}
}