|
30 | 30 | import java.lang.invoke.MethodHandle; |
31 | 31 | import java.lang.invoke.MethodType; |
32 | 32 | import java.lang.invoke.WrongMethodTypeException; |
33 | | -import java.lang.reflect.Field; |
34 | | -import java.lang.reflect.InvocationTargetException; |
35 | | -import java.lang.reflect.Method; |
36 | | -import java.sql.SQLException; |
37 | | -import java.util.ArrayList; |
38 | 33 | import java.util.IdentityHashMap; |
39 | 34 | import java.util.List; |
40 | 35 | import java.util.Objects; |
|
58 | 53 | */ |
59 | 54 | public final class ExceptionUtils { |
60 | 55 | /** |
61 | | - * The names of methods commonly used to access a wrapped exception. |
62 | | - */ |
63 | | - private static final String[] CAUSE_METHOD_NAMES = { |
64 | | - "getCause", |
65 | | - "getNextException", |
66 | | - "getTargetException", |
67 | | - "getException", |
68 | | - "getSourceException", |
69 | | - "getRootCause", |
70 | | - "getCausedByException", |
71 | | - "getNested", |
72 | | - "getLinkedException", |
73 | | - "getNestedException", |
74 | | - "getLinkedCause", |
75 | | - "getThrowable" |
76 | | - }; |
77 | | - |
78 | | - /** |
79 | | - * The Method object for Java 1.4 getCause. |
80 | | - */ |
81 | | - private static final Method THROWABLE_CAUSE_METHOD; |
82 | | - |
83 | | - static { |
84 | | - Method causeMtd; |
85 | | - |
86 | | - try { |
87 | | - causeMtd = Throwable.class.getMethod("getCause", (Class<?>) null); |
88 | | - } catch (Exception ignored) { |
89 | | - causeMtd = null; |
90 | | - } |
91 | | - |
92 | | - THROWABLE_CAUSE_METHOD = causeMtd; |
93 | | - } |
94 | | - |
95 | | - /** |
96 | | - * Introspects the {@code Throwable} to obtain the cause. |
97 | | - * |
98 | | - * @param throwable The exception to examine. |
99 | | - * @return The wrapped exception, or {@code null} if not found. |
100 | | - */ |
101 | | - @Nullable |
102 | | - private static Throwable getCauseUsingWellKnownTypes(Throwable throwable) { |
103 | | - if (throwable instanceof SQLException) { |
104 | | - return ((SQLException) throwable).getNextException(); |
105 | | - } |
106 | | - |
107 | | - if (throwable instanceof InvocationTargetException) { |
108 | | - return ((InvocationTargetException) throwable).getTargetException(); |
109 | | - } |
110 | | - |
111 | | - return null; |
112 | | - } |
113 | | - |
114 | | - /** |
115 | | - * Finds a {@code Throwable} by method name. |
116 | | - * |
117 | | - * @param throwable The exception to examine. |
118 | | - * @param mtdName The name of the method to find and invoke. |
119 | | - * @return The wrapped exception, or {@code null} if not found. |
120 | | - */ |
121 | | - private static @Nullable Throwable getCauseUsingMethodName(Throwable throwable, String mtdName) { |
122 | | - Method mtd = null; |
123 | | - |
124 | | - try { |
125 | | - mtd = throwable.getClass().getMethod(mtdName, (Class<?>) null); |
126 | | - } catch (NoSuchMethodException | SecurityException ignored) { |
127 | | - // exception ignored |
128 | | - } |
129 | | - |
130 | | - if (mtd != null && Throwable.class.isAssignableFrom(mtd.getReturnType())) { |
131 | | - try { |
132 | | - return (Throwable) mtd.invoke(throwable, ArrayUtils.OBJECT_EMPTY_ARRAY); |
133 | | - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ignored) { |
134 | | - // exception ignored |
135 | | - } |
136 | | - } |
137 | | - |
138 | | - return null; |
139 | | - } |
140 | | - |
141 | | - /** |
142 | | - * Finds a {@code Throwable} by field name. |
143 | | - * |
144 | | - * @param throwable The exception to examine. |
145 | | - * @param fieldName The name of the attribute to examine. |
146 | | - * @return The wrapped exception, or {@code null} if not found. |
147 | | - */ |
148 | | - private static @Nullable Throwable getCauseUsingFieldName(Throwable throwable, String fieldName) { |
149 | | - Field field = null; |
150 | | - |
151 | | - try { |
152 | | - field = throwable.getClass().getField(fieldName); |
153 | | - } catch (NoSuchFieldException | SecurityException ignored) { |
154 | | - // exception ignored |
155 | | - } |
156 | | - |
157 | | - if (field != null && Throwable.class.isAssignableFrom(field.getType())) { |
158 | | - try { |
159 | | - return (Throwable) field.get(throwable); |
160 | | - } catch (IllegalAccessException | IllegalArgumentException ignored) { |
161 | | - // exception ignored |
162 | | - } |
163 | | - } |
164 | | - |
165 | | - return null; |
166 | | - } |
167 | | - |
168 | | - /** |
169 | | - * Checks if the Throwable class has a {@code getCause} method. |
170 | | - * |
171 | | - * @return True if Throwable is nestable. |
172 | | - */ |
173 | | - public static boolean isThrowableNested() { |
174 | | - return THROWABLE_CAUSE_METHOD != null; |
175 | | - } |
176 | | - |
177 | | - /** |
178 | | - * Checks whether this {@code Throwable} class can store a cause. |
179 | | - * |
180 | | - * @param throwable The {@code Throwable} to examine, may be null. |
181 | | - * @return Boolean {@code true} if nested otherwise {@code false}. |
182 | | - */ |
183 | | - public static boolean isNestedThrowable(Throwable throwable) { |
184 | | - if (throwable == null) { |
185 | | - return false; |
186 | | - } |
187 | | - |
188 | | - if (throwable instanceof SQLException || throwable instanceof InvocationTargetException) { |
189 | | - return true; |
190 | | - } |
191 | | - |
192 | | - if (isThrowableNested()) { |
193 | | - return true; |
194 | | - } |
195 | | - |
196 | | - Class<?> cls = throwable.getClass(); |
197 | | - for (String methodName : CAUSE_METHOD_NAMES) { |
198 | | - try { |
199 | | - Method mtd = cls.getMethod(methodName, (Class<?>) null); |
200 | | - |
201 | | - if (Throwable.class.isAssignableFrom(mtd.getReturnType())) { |
202 | | - return true; |
203 | | - } |
204 | | - } catch (NoSuchMethodException | SecurityException ignored) { |
205 | | - // exception ignored |
206 | | - } |
207 | | - } |
208 | | - |
209 | | - try { |
210 | | - cls.getField("detail"); |
211 | | - |
212 | | - return true; |
213 | | - } catch (NoSuchFieldException | SecurityException ignored) { |
214 | | - // exception ignored |
215 | | - } |
216 | | - |
217 | | - return false; |
218 | | - } |
219 | | - |
220 | | - /** |
221 | | - * Introspects the {@code Throwable} to obtain the cause. |
222 | | - * |
223 | | - * @param throwable The throwable to introspect for a cause, may be null. |
224 | | - * @return The cause of the {@code Throwable}, {@code null} if none found or null throwable input. |
225 | | - */ |
226 | | - public static @Nullable Throwable getCause(Throwable throwable) { |
227 | | - return getCause(throwable, CAUSE_METHOD_NAMES); |
228 | | - } |
229 | | - |
230 | | - /** |
231 | | - * Introspects the {@code Throwable} to obtain the cause. |
232 | | - * |
233 | | - * @param throwable The throwable to introspect for a cause, may be null. |
234 | | - * @param mtdNames The method names, null treated as default set. |
235 | | - * @return The cause of the {@code Throwable}, {@code null} if none found or null throwable input. |
236 | | - */ |
237 | | - @Nullable |
238 | | - public static Throwable getCause(@Nullable Throwable throwable, String[] mtdNames) { |
239 | | - if (throwable == null) { |
240 | | - return null; |
241 | | - } |
242 | | - |
243 | | - Throwable cause = getCauseUsingWellKnownTypes(throwable); |
244 | | - |
245 | | - if (cause == null) { |
246 | | - if (mtdNames == null) { |
247 | | - mtdNames = CAUSE_METHOD_NAMES; |
248 | | - } |
249 | | - |
250 | | - for (String mtdName : mtdNames) { |
251 | | - if (mtdName != null) { |
252 | | - cause = getCauseUsingMethodName(throwable, mtdName); |
253 | | - |
254 | | - if (cause != null) { |
255 | | - break; |
256 | | - } |
257 | | - } |
258 | | - } |
259 | | - |
260 | | - if (cause == null) { |
261 | | - cause = getCauseUsingFieldName(throwable, "detail"); |
262 | | - } |
263 | | - } |
264 | | - |
265 | | - return cause; |
266 | | - } |
267 | | - |
268 | | - /** |
269 | | - * Returns the list of {@code Throwable} objects in the exception chain. |
270 | | - * |
271 | | - * <p>A throwable without cause will return a list containing one element - the input throwable. A throwable with one cause |
272 | | - * will return a list containing two elements - the input throwable and the cause throwable. A {@code null} throwable will return a list |
273 | | - * of size zero. |
274 | | - * |
275 | | - * <p>This method handles recursive cause structures that might otherwise cause infinite loops. The cause chain is processed until |
276 | | - * the end is reached, or until the next item in the chain is already in the result set. |
277 | | - * |
278 | | - * @param throwable The throwable to inspect, may be null. |
279 | | - * @return The list of throwables, never null. |
280 | | - */ |
281 | | - public static List<Throwable> getThrowableList(Throwable throwable) { |
282 | | - List<Throwable> list = new ArrayList<>(); |
283 | | - |
284 | | - // TODO: https://issues.apache.org/jira/browse/IGNITE-28026 |
285 | | - while (throwable != null && !list.contains(throwable)) { |
286 | | - list.add(throwable); |
287 | | - throwable = getCause(throwable); |
288 | | - } |
289 | | - |
290 | | - return list; |
291 | | - } |
292 | | - |
293 | | - /** |
294 | | - * Collects suppressed exceptions from throwable and all it causes. |
295 | | - * |
296 | | - * @param t Throwable. |
297 | | - * @return List of suppressed throwables. |
298 | | - */ |
299 | | - public static List<Throwable> getSuppressedList(@Nullable Throwable t) { |
300 | | - List<Throwable> result = new ArrayList<>(); |
301 | | - |
302 | | - if (t == null) { |
303 | | - return result; |
304 | | - } |
305 | | - |
306 | | - // TODO: https://issues.apache.org/jira/browse/IGNITE-28026 |
307 | | - do { |
308 | | - for (Throwable suppressed : t.getSuppressed()) { |
309 | | - result.add(suppressed); |
310 | | - |
311 | | - result.addAll(getSuppressedList(suppressed)); |
312 | | - } |
313 | | - } while ((t = t.getCause()) != null); |
314 | | - |
315 | | - return result; |
316 | | - } |
317 | | - |
318 | | - /** |
319 | | - * A way to get the entire nested stack-trace of an throwable. |
| 56 | + * Gets the stack trace as a char sequence. |
320 | 57 | * |
321 | 58 | * @param throwable The {@code Throwable} to be examined. |
322 | | - * @return The nested stack trace, with the root cause first. |
| 59 | + * @return The stack trace. |
323 | 60 | */ |
324 | | - public static String getFullStackTrace(Throwable throwable) { |
| 61 | + public static CharSequence getFullStackTrace(Throwable throwable) { |
325 | 62 | StringWriter sw = new StringWriter(); |
326 | 63 | PrintWriter pw = new PrintWriter(sw, true); |
327 | | - var ts = getThrowableList(throwable); |
328 | | - |
329 | | - for (Throwable t : ts) { |
330 | | - t.printStackTrace(pw); |
331 | | - |
332 | | - if (isNestedThrowable(t)) { |
333 | | - break; |
334 | | - } |
335 | | - } |
336 | | - |
337 | | - return sw.getBuffer().toString(); |
| 64 | + throwable.printStackTrace(pw); |
| 65 | + return sw.getBuffer(); |
338 | 66 | } |
339 | 67 |
|
340 | 68 | /** |
|
0 commit comments