001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.lang.exception;
018
019 import java.io.PrintStream;
020 import java.io.PrintWriter;
021 import java.io.StringWriter;
022 import java.lang.reflect.Field;
023 import java.lang.reflect.InvocationTargetException;
024 import java.lang.reflect.Method;
025 import java.sql.SQLException;
026 import java.util.ArrayList;
027 import java.util.Arrays;
028 import java.util.List;
029 import java.util.StringTokenizer;
030
031 import org.apache.commons.lang.ArrayUtils;
032 import org.apache.commons.lang.ClassUtils;
033 import org.apache.commons.lang.NullArgumentException;
034 import org.apache.commons.lang.StringUtils;
035 import org.apache.commons.lang.SystemUtils;
036
037 /**
038 * <p>Provides utilities for manipulating and examining
039 * <code>Throwable</code> objects.</p>
040 *
041 * @author Apache Software Foundation
042 * @author Daniel L. Rall
043 * @author Dmitri Plotnikov
044 * @author <a href="mailto:ggregory@seagullsw.com">Gary Gregory</a>
045 * @author Pete Gieser
046 * @since 1.0
047 * @version $Id: ExceptionUtils.java 905837 2010-02-02 23:32:11Z niallp $
048 */
049 public class ExceptionUtils {
050
051 /**
052 * <p>Used when printing stack frames to denote the start of a
053 * wrapped exception.</p>
054 *
055 * <p>Package private for accessibility by test suite.</p>
056 */
057 static final String WRAPPED_MARKER = " [wrapped] ";
058
059 // Lock object for CAUSE_METHOD_NAMES
060 private static final Object CAUSE_METHOD_NAMES_LOCK = new Object();
061
062 /**
063 * <p>The names of methods commonly used to access a wrapped exception.</p>
064 */
065 private static String[] CAUSE_METHOD_NAMES = {
066 "getCause",
067 "getNextException",
068 "getTargetException",
069 "getException",
070 "getSourceException",
071 "getRootCause",
072 "getCausedByException",
073 "getNested",
074 "getLinkedException",
075 "getNestedException",
076 "getLinkedCause",
077 "getThrowable",
078 };
079
080 /**
081 * <p>The Method object for Java 1.4 getCause.</p>
082 */
083 private static final Method THROWABLE_CAUSE_METHOD;
084
085 /**
086 * <p>The Method object for Java 1.4 initCause.</p>
087 */
088 private static final Method THROWABLE_INITCAUSE_METHOD;
089
090 static {
091 Method causeMethod;
092 try {
093 causeMethod = Throwable.class.getMethod("getCause", null);
094 } catch (Exception e) {
095 causeMethod = null;
096 }
097 THROWABLE_CAUSE_METHOD = causeMethod;
098 try {
099 causeMethod = Throwable.class.getMethod("initCause", new Class[]{Throwable.class});
100 } catch (Exception e) {
101 causeMethod = null;
102 }
103 THROWABLE_INITCAUSE_METHOD = causeMethod;
104 }
105
106 /**
107 * <p>
108 * Public constructor allows an instance of <code>ExceptionUtils</code> to be created, although that is not
109 * normally necessary.
110 * </p>
111 */
112 public ExceptionUtils() {
113 super();
114 }
115
116 //-----------------------------------------------------------------------
117 /**
118 * <p>Adds to the list of method names used in the search for <code>Throwable</code>
119 * objects.</p>
120 *
121 * @param methodName the methodName to add to the list, <code>null</code>
122 * and empty strings are ignored
123 * @since 2.0
124 */
125 public static void addCauseMethodName(String methodName) {
126 if (StringUtils.isNotEmpty(methodName) && !isCauseMethodName(methodName)) {
127 List list = getCauseMethodNameList();
128 if (list.add(methodName)) {
129 synchronized(CAUSE_METHOD_NAMES_LOCK) {
130 CAUSE_METHOD_NAMES = toArray(list);
131 }
132 }
133 }
134 }
135
136 /**
137 * <p>Removes from the list of method names used in the search for <code>Throwable</code>
138 * objects.</p>
139 *
140 * @param methodName the methodName to remove from the list, <code>null</code>
141 * and empty strings are ignored
142 * @since 2.1
143 */
144 public static void removeCauseMethodName(String methodName) {
145 if (StringUtils.isNotEmpty(methodName)) {
146 List list = getCauseMethodNameList();
147 if (list.remove(methodName)) {
148 synchronized(CAUSE_METHOD_NAMES_LOCK) {
149 CAUSE_METHOD_NAMES = toArray(list);
150 }
151 }
152 }
153 }
154
155 /**
156 * <p>Sets the cause of a <code>Throwable</code> using introspection, allowing
157 * source code compatibility between pre-1.4 and post-1.4 Java releases.</p>
158 *
159 * <p>The typical use of this method is inside a constructor as in
160 * the following example:</p>
161 *
162 * <pre>
163 * import org.apache.commons.lang.exception.ExceptionUtils;
164 *
165 * public class MyException extends Exception {
166 *
167 * public MyException(String msg) {
168 * super(msg);
169 * }
170 *
171 * public MyException(String msg, Throwable cause) {
172 * super(msg);
173 * ExceptionUtils.setCause(this, cause);
174 * }
175 * }
176 * </pre>
177 *
178 * @param target the target <code>Throwable</code>
179 * @param cause the <code>Throwable</code> to set in the target
180 * @return a <code>true</code> if the target has been modified
181 * @since 2.2
182 */
183 public static boolean setCause(Throwable target, Throwable cause) {
184 if (target == null) {
185 throw new NullArgumentException("target");
186 }
187 Object[] causeArgs = new Object[]{cause};
188 boolean modifiedTarget = false;
189 if (THROWABLE_INITCAUSE_METHOD != null) {
190 try {
191 THROWABLE_INITCAUSE_METHOD.invoke(target, causeArgs);
192 modifiedTarget = true;
193 } catch (IllegalAccessException ignored) {
194 // Exception ignored.
195 } catch (InvocationTargetException ignored) {
196 // Exception ignored.
197 }
198 }
199 try {
200 Method setCauseMethod = target.getClass().getMethod("setCause", new Class[]{Throwable.class});
201 setCauseMethod.invoke(target, causeArgs);
202 modifiedTarget = true;
203 } catch (NoSuchMethodException ignored) {
204 // Exception ignored.
205 } catch (IllegalAccessException ignored) {
206 // Exception ignored.
207 } catch (InvocationTargetException ignored) {
208 // Exception ignored.
209 }
210 return modifiedTarget;
211 }
212
213 /**
214 * Returns the given list as a <code>String[]</code>.
215 * @param list a list to transform.
216 * @return the given list as a <code>String[]</code>.
217 */
218 private static String[] toArray(List list) {
219 return (String[]) list.toArray(new String[list.size()]);
220 }
221
222 /**
223 * Returns {@link #CAUSE_METHOD_NAMES} as a List.
224 *
225 * @return {@link #CAUSE_METHOD_NAMES} as a List.
226 */
227 private static ArrayList getCauseMethodNameList() {
228 synchronized(CAUSE_METHOD_NAMES_LOCK) {
229 return new ArrayList(Arrays.asList(CAUSE_METHOD_NAMES));
230 }
231 }
232
233 /**
234 * <p>Tests if the list of method names used in the search for <code>Throwable</code>
235 * objects include the given name.</p>
236 *
237 * @param methodName the methodName to search in the list.
238 * @return if the list of method names used in the search for <code>Throwable</code>
239 * objects include the given name.
240 * @since 2.1
241 */
242 public static boolean isCauseMethodName(String methodName) {
243 synchronized(CAUSE_METHOD_NAMES_LOCK) {
244 return ArrayUtils.indexOf(CAUSE_METHOD_NAMES, methodName) >= 0;
245 }
246 }
247
248 //-----------------------------------------------------------------------
249 /**
250 * <p>Introspects the <code>Throwable</code> to obtain the cause.</p>
251 *
252 * <p>The method searches for methods with specific names that return a
253 * <code>Throwable</code> object. This will pick up most wrapping exceptions,
254 * including those from JDK 1.4, and
255 * {@link org.apache.commons.lang.exception.NestableException NestableException}.
256 * The method names can be added to using {@link #addCauseMethodName(String)}.</p>
257 *
258 * <p>The default list searched for are:</p>
259 * <ul>
260 * <li><code>getCause()</code></li>
261 * <li><code>getNextException()</code></li>
262 * <li><code>getTargetException()</code></li>
263 * <li><code>getException()</code></li>
264 * <li><code>getSourceException()</code></li>
265 * <li><code>getRootCause()</code></li>
266 * <li><code>getCausedByException()</code></li>
267 * <li><code>getNested()</code></li>
268 * </ul>
269 *
270 * <p>In the absence of any such method, the object is inspected for a
271 * <code>detail</code> field assignable to a <code>Throwable</code>.</p>
272 *
273 * <p>If none of the above is found, returns <code>null</code>.</p>
274 *
275 * @param throwable the throwable to introspect for a cause, may be null
276 * @return the cause of the <code>Throwable</code>,
277 * <code>null</code> if none found or null throwable input
278 * @since 1.0
279 */
280 public static Throwable getCause(Throwable throwable) {
281 synchronized(CAUSE_METHOD_NAMES_LOCK) {
282 return getCause(throwable, CAUSE_METHOD_NAMES);
283 }
284 }
285
286 /**
287 * <p>Introspects the <code>Throwable</code> to obtain the cause.</p>
288 *
289 * <ol>
290 * <li>Try known exception types.</li>
291 * <li>Try the supplied array of method names.</li>
292 * <li>Try the field 'detail'.</li>
293 * </ol>
294 *
295 * <p>A <code>null</code> set of method names means use the default set.
296 * A <code>null</code> in the set of method names will be ignored.</p>
297 *
298 * @param throwable the throwable to introspect for a cause, may be null
299 * @param methodNames the method names, null treated as default set
300 * @return the cause of the <code>Throwable</code>,
301 * <code>null</code> if none found or null throwable input
302 * @since 1.0
303 */
304 public static Throwable getCause(Throwable throwable, String[] methodNames) {
305 if (throwable == null) {
306 return null;
307 }
308 Throwable cause = getCauseUsingWellKnownTypes(throwable);
309 if (cause == null) {
310 if (methodNames == null) {
311 synchronized(CAUSE_METHOD_NAMES_LOCK) {
312 methodNames = CAUSE_METHOD_NAMES;
313 }
314 }
315 for (int i = 0; i < methodNames.length; i++) {
316 String methodName = methodNames[i];
317 if (methodName != null) {
318 cause = getCauseUsingMethodName(throwable, methodName);
319 if (cause != null) {
320 break;
321 }
322 }
323 }
324
325 if (cause == null) {
326 cause = getCauseUsingFieldName(throwable, "detail");
327 }
328 }
329 return cause;
330 }
331
332 /**
333 * <p>Introspects the <code>Throwable</code> to obtain the root cause.</p>
334 *
335 * <p>This method walks through the exception chain to the last element,
336 * "root" of the tree, using {@link #getCause(Throwable)}, and
337 * returns that exception.</p>
338 *
339 * <p>From version 2.2, this method handles recursive cause structures
340 * that might otherwise cause infinite loops. If the throwable parameter
341 * has a cause of itself, then null will be returned. If the throwable
342 * parameter cause chain loops, the last element in the chain before the
343 * loop is returned.</p>
344 *
345 * @param throwable the throwable to get the root cause for, may be null
346 * @return the root cause of the <code>Throwable</code>,
347 * <code>null</code> if none found or null throwable input
348 */
349 public static Throwable getRootCause(Throwable throwable) {
350 List list = getThrowableList(throwable);
351 return (list.size() < 2 ? null : (Throwable)list.get(list.size() - 1));
352 }
353
354 /**
355 * <p>Finds a <code>Throwable</code> for known types.</p>
356 *
357 * <p>Uses <code>instanceof</code> checks to examine the exception,
358 * looking for well known types which could contain chained or
359 * wrapped exceptions.</p>
360 *
361 * @param throwable the exception to examine
362 * @return the wrapped exception, or <code>null</code> if not found
363 */
364 private static Throwable getCauseUsingWellKnownTypes(Throwable throwable) {
365 if (throwable instanceof Nestable) {
366 return ((Nestable) throwable).getCause();
367 } else if (throwable instanceof SQLException) {
368 return ((SQLException) throwable).getNextException();
369 } else if (throwable instanceof InvocationTargetException) {
370 return ((InvocationTargetException) throwable).getTargetException();
371 } else {
372 return null;
373 }
374 }
375
376 /**
377 * <p>Finds a <code>Throwable</code> by method name.</p>
378 *
379 * @param throwable the exception to examine
380 * @param methodName the name of the method to find and invoke
381 * @return the wrapped exception, or <code>null</code> if not found
382 */
383 private static Throwable getCauseUsingMethodName(Throwable throwable, String methodName) {
384 Method method = null;
385 try {
386 method = throwable.getClass().getMethod(methodName, null);
387 } catch (NoSuchMethodException ignored) {
388 // exception ignored
389 } catch (SecurityException ignored) {
390 // exception ignored
391 }
392
393 if (method != null && Throwable.class.isAssignableFrom(method.getReturnType())) {
394 try {
395 return (Throwable) method.invoke(throwable, ArrayUtils.EMPTY_OBJECT_ARRAY);
396 } catch (IllegalAccessException ignored) {
397 // exception ignored
398 } catch (IllegalArgumentException ignored) {
399 // exception ignored
400 } catch (InvocationTargetException ignored) {
401 // exception ignored
402 }
403 }
404 return null;
405 }
406
407 /**
408 * <p>Finds a <code>Throwable</code> by field name.</p>
409 *
410 * @param throwable the exception to examine
411 * @param fieldName the name of the attribute to examine
412 * @return the wrapped exception, or <code>null</code> if not found
413 */
414 private static Throwable getCauseUsingFieldName(Throwable throwable, String fieldName) {
415 Field field = null;
416 try {
417 field = throwable.getClass().getField(fieldName);
418 } catch (NoSuchFieldException ignored) {
419 // exception ignored
420 } catch (SecurityException ignored) {
421 // exception ignored
422 }
423
424 if (field != null && Throwable.class.isAssignableFrom(field.getType())) {
425 try {
426 return (Throwable) field.get(throwable);
427 } catch (IllegalAccessException ignored) {
428 // exception ignored
429 } catch (IllegalArgumentException ignored) {
430 // exception ignored
431 }
432 }
433 return null;
434 }
435
436 //-----------------------------------------------------------------------
437 /**
438 * <p>Checks if the Throwable class has a <code>getCause</code> method.</p>
439 *
440 * <p>This is true for JDK 1.4 and above.</p>
441 *
442 * @return true if Throwable is nestable
443 * @since 2.0
444 */
445 public static boolean isThrowableNested() {
446 return THROWABLE_CAUSE_METHOD != null;
447 }
448
449 /**
450 * <p>Checks whether this <code>Throwable</code> class can store a cause.</p>
451 *
452 * <p>This method does <b>not</b> check whether it actually does store a cause.<p>
453 *
454 * @param throwable the <code>Throwable</code> to examine, may be null
455 * @return boolean <code>true</code> if nested otherwise <code>false</code>
456 * @since 2.0
457 */
458 public static boolean isNestedThrowable(Throwable throwable) {
459 if (throwable == null) {
460 return false;
461 }
462
463 if (throwable instanceof Nestable) {
464 return true;
465 } else if (throwable instanceof SQLException) {
466 return true;
467 } else if (throwable instanceof InvocationTargetException) {
468 return true;
469 } else if (isThrowableNested()) {
470 return true;
471 }
472
473 Class cls = throwable.getClass();
474 synchronized(CAUSE_METHOD_NAMES_LOCK) {
475 for (int i = 0, isize = CAUSE_METHOD_NAMES.length; i < isize; i++) {
476 try {
477 Method method = cls.getMethod(CAUSE_METHOD_NAMES[i], null);
478 if (method != null && Throwable.class.isAssignableFrom(method.getReturnType())) {
479 return true;
480 }
481 } catch (NoSuchMethodException ignored) {
482 // exception ignored
483 } catch (SecurityException ignored) {
484 // exception ignored
485 }
486 }
487 }
488
489 try {
490 Field field = cls.getField("detail");
491 if (field != null) {
492 return true;
493 }
494 } catch (NoSuchFieldException ignored) {
495 // exception ignored
496 } catch (SecurityException ignored) {
497 // exception ignored
498 }
499
500 return false;
501 }
502
503 //-----------------------------------------------------------------------
504 /**
505 * <p>Counts the number of <code>Throwable</code> objects in the
506 * exception chain.</p>
507 *
508 * <p>A throwable without cause will return <code>1</code>.
509 * A throwable with one cause will return <code>2</code> and so on.
510 * A <code>null</code> throwable will return <code>0</code>.</p>
511 *
512 * <p>From version 2.2, this method handles recursive cause structures
513 * that might otherwise cause infinite loops. The cause chain is
514 * processed until the end is reached, or until the next item in the
515 * chain is already in the result set.</p>
516 *
517 * @param throwable the throwable to inspect, may be null
518 * @return the count of throwables, zero if null input
519 */
520 public static int getThrowableCount(Throwable throwable) {
521 return getThrowableList(throwable).size();
522 }
523
524 /**
525 * <p>Returns the list of <code>Throwable</code> objects in the
526 * exception chain.</p>
527 *
528 * <p>A throwable without cause will return an array containing
529 * one element - the input throwable.
530 * A throwable with one cause will return an array containing
531 * two elements. - the input throwable and the cause throwable.
532 * A <code>null</code> throwable will return an array of size zero.</p>
533 *
534 * <p>From version 2.2, this method handles recursive cause structures
535 * that might otherwise cause infinite loops. The cause chain is
536 * processed until the end is reached, or until the next item in the
537 * chain is already in the result set.</p>
538 *
539 * @see #getThrowableList(Throwable)
540 * @param throwable the throwable to inspect, may be null
541 * @return the array of throwables, never null
542 */
543 public static Throwable[] getThrowables(Throwable throwable) {
544 List list = getThrowableList(throwable);
545 return (Throwable[]) list.toArray(new Throwable[list.size()]);
546 }
547
548 /**
549 * <p>Returns the list of <code>Throwable</code> objects in the
550 * exception chain.</p>
551 *
552 * <p>A throwable without cause will return a list containing
553 * one element - the input throwable.
554 * A throwable with one cause will return a list containing
555 * two elements. - the input throwable and the cause throwable.
556 * A <code>null</code> throwable will return a list of size zero.</p>
557 *
558 * <p>This method handles recursive cause structures that might
559 * otherwise cause infinite loops. The cause chain is processed until
560 * the end is reached, or until the next item in the chain is already
561 * in the result set.</p>
562 *
563 * @param throwable the throwable to inspect, may be null
564 * @return the list of throwables, never null
565 * @since Commons Lang 2.2
566 */
567 public static List getThrowableList(Throwable throwable) {
568 List list = new ArrayList();
569 while (throwable != null && list.contains(throwable) == false) {
570 list.add(throwable);
571 throwable = ExceptionUtils.getCause(throwable);
572 }
573 return list;
574 }
575
576 //-----------------------------------------------------------------------
577 /**
578 * <p>Returns the (zero based) index of the first <code>Throwable</code>
579 * that matches the specified class (exactly) in the exception chain.
580 * Subclasses of the specified class do not match - see
581 * {@link #indexOfType(Throwable, Class)} for the opposite.</p>
582 *
583 * <p>A <code>null</code> throwable returns <code>-1</code>.
584 * A <code>null</code> type returns <code>-1</code>.
585 * No match in the chain returns <code>-1</code>.</p>
586 *
587 * @param throwable the throwable to inspect, may be null
588 * @param clazz the class to search for, subclasses do not match, null returns -1
589 * @return the index into the throwable chain, -1 if no match or null input
590 */
591 public static int indexOfThrowable(Throwable throwable, Class clazz) {
592 return indexOf(throwable, clazz, 0, false);
593 }
594
595 /**
596 * <p>Returns the (zero based) index of the first <code>Throwable</code>
597 * that matches the specified type in the exception chain from
598 * a specified index.
599 * Subclasses of the specified class do not match - see
600 * {@link #indexOfType(Throwable, Class, int)} for the opposite.</p>
601 *
602 * <p>A <code>null</code> throwable returns <code>-1</code>.
603 * A <code>null</code> type returns <code>-1</code>.
604 * No match in the chain returns <code>-1</code>.
605 * A negative start index is treated as zero.
606 * A start index greater than the number of throwables returns <code>-1</code>.</p>
607 *
608 * @param throwable the throwable to inspect, may be null
609 * @param clazz the class to search for, subclasses do not match, null returns -1
610 * @param fromIndex the (zero based) index of the starting position,
611 * negative treated as zero, larger than chain size returns -1
612 * @return the index into the throwable chain, -1 if no match or null input
613 */
614 public static int indexOfThrowable(Throwable throwable, Class clazz, int fromIndex) {
615 return indexOf(throwable, clazz, fromIndex, false);
616 }
617
618 //-----------------------------------------------------------------------
619 /**
620 * <p>Returns the (zero based) index of the first <code>Throwable</code>
621 * that matches the specified class or subclass in the exception chain.
622 * Subclasses of the specified class do match - see
623 * {@link #indexOfThrowable(Throwable, Class)} for the opposite.</p>
624 *
625 * <p>A <code>null</code> throwable returns <code>-1</code>.
626 * A <code>null</code> type returns <code>-1</code>.
627 * No match in the chain returns <code>-1</code>.</p>
628 *
629 * @param throwable the throwable to inspect, may be null
630 * @param type the type to search for, subclasses match, null returns -1
631 * @return the index into the throwable chain, -1 if no match or null input
632 * @since 2.1
633 */
634 public static int indexOfType(Throwable throwable, Class type) {
635 return indexOf(throwable, type, 0, true);
636 }
637
638 /**
639 * <p>Returns the (zero based) index of the first <code>Throwable</code>
640 * that matches the specified type in the exception chain from
641 * a specified index.
642 * Subclasses of the specified class do match - see
643 * {@link #indexOfThrowable(Throwable, Class)} for the opposite.</p>
644 *
645 * <p>A <code>null</code> throwable returns <code>-1</code>.
646 * A <code>null</code> type returns <code>-1</code>.
647 * No match in the chain returns <code>-1</code>.
648 * A negative start index is treated as zero.
649 * A start index greater than the number of throwables returns <code>-1</code>.</p>
650 *
651 * @param throwable the throwable to inspect, may be null
652 * @param type the type to search for, subclasses match, null returns -1
653 * @param fromIndex the (zero based) index of the starting position,
654 * negative treated as zero, larger than chain size returns -1
655 * @return the index into the throwable chain, -1 if no match or null input
656 * @since 2.1
657 */
658 public static int indexOfType(Throwable throwable, Class type, int fromIndex) {
659 return indexOf(throwable, type, fromIndex, true);
660 }
661
662 /**
663 * <p>Worker method for the <code>indexOfType</code> methods.</p>
664 *
665 * @param throwable the throwable to inspect, may be null
666 * @param type the type to search for, subclasses match, null returns -1
667 * @param fromIndex the (zero based) index of the starting position,
668 * negative treated as zero, larger than chain size returns -1
669 * @param subclass if <code>true</code>, compares with {@link Class#isAssignableFrom(Class)}, otherwise compares
670 * using references
671 * @return index of the <code>type</code> within throwables nested withing the specified <code>throwable</code>
672 */
673 private static int indexOf(Throwable throwable, Class type, int fromIndex, boolean subclass) {
674 if (throwable == null || type == null) {
675 return -1;
676 }
677 if (fromIndex < 0) {
678 fromIndex = 0;
679 }
680 Throwable[] throwables = ExceptionUtils.getThrowables(throwable);
681 if (fromIndex >= throwables.length) {
682 return -1;
683 }
684 if (subclass) {
685 for (int i = fromIndex; i < throwables.length; i++) {
686 if (type.isAssignableFrom(throwables[i].getClass())) {
687 return i;
688 }
689 }
690 } else {
691 for (int i = fromIndex; i < throwables.length; i++) {
692 if (type.equals(throwables[i].getClass())) {
693 return i;
694 }
695 }
696 }
697 return -1;
698 }
699
700 //-----------------------------------------------------------------------
701 /**
702 * <p>Prints a compact stack trace for the root cause of a throwable
703 * to <code>System.err</code>.</p>
704 *
705 * <p>The compact stack trace starts with the root cause and prints
706 * stack frames up to the place where it was caught and wrapped.
707 * Then it prints the wrapped exception and continues with stack frames
708 * until the wrapper exception is caught and wrapped again, etc.</p>
709 *
710 * <p>The output of this method is consistent across JDK versions.
711 * Note that this is the opposite order to the JDK1.4 display.</p>
712 *
713 * <p>The method is equivalent to <code>printStackTrace</code> for throwables
714 * that don't have nested causes.</p>
715 *
716 * @param throwable the throwable to output
717 * @since 2.0
718 */
719 public static void printRootCauseStackTrace(Throwable throwable) {
720 printRootCauseStackTrace(throwable, System.err);
721 }
722
723 /**
724 * <p>Prints a compact stack trace for the root cause of a throwable.</p>
725 *
726 * <p>The compact stack trace starts with the root cause and prints
727 * stack frames up to the place where it was caught and wrapped.
728 * Then it prints the wrapped exception and continues with stack frames
729 * until the wrapper exception is caught and wrapped again, etc.</p>
730 *
731 * <p>The output of this method is consistent across JDK versions.
732 * Note that this is the opposite order to the JDK1.4 display.</p>
733 *
734 * <p>The method is equivalent to <code>printStackTrace</code> for throwables
735 * that don't have nested causes.</p>
736 *
737 * @param throwable the throwable to output, may be null
738 * @param stream the stream to output to, may not be null
739 * @throws IllegalArgumentException if the stream is <code>null</code>
740 * @since 2.0
741 */
742 public static void printRootCauseStackTrace(Throwable throwable, PrintStream stream) {
743 if (throwable == null) {
744 return;
745 }
746 if (stream == null) {
747 throw new IllegalArgumentException("The PrintStream must not be null");
748 }
749 String trace[] = getRootCauseStackTrace(throwable);
750 for (int i = 0; i < trace.length; i++) {
751 stream.println(trace[i]);
752 }
753 stream.flush();
754 }
755
756 /**
757 * <p>Prints a compact stack trace for the root cause of a throwable.</p>
758 *
759 * <p>The compact stack trace starts with the root cause and prints
760 * stack frames up to the place where it was caught and wrapped.
761 * Then it prints the wrapped exception and continues with stack frames
762 * until the wrapper exception is caught and wrapped again, etc.</p>
763 *
764 * <p>The output of this method is consistent across JDK versions.
765 * Note that this is the opposite order to the JDK1.4 display.</p>
766 *
767 * <p>The method is equivalent to <code>printStackTrace</code> for throwables
768 * that don't have nested causes.</p>
769 *
770 * @param throwable the throwable to output, may be null
771 * @param writer the writer to output to, may not be null
772 * @throws IllegalArgumentException if the writer is <code>null</code>
773 * @since 2.0
774 */
775 public static void printRootCauseStackTrace(Throwable throwable, PrintWriter writer) {
776 if (throwable == null) {
777 return;
778 }
779 if (writer == null) {
780 throw new IllegalArgumentException("The PrintWriter must not be null");
781 }
782 String trace[] = getRootCauseStackTrace(throwable);
783 for (int i = 0; i < trace.length; i++) {
784 writer.println(trace[i]);
785 }
786 writer.flush();
787 }
788
789 //-----------------------------------------------------------------------
790 /**
791 * <p>Creates a compact stack trace for the root cause of the supplied
792 * <code>Throwable</code>.</p>
793 *
794 * <p>The output of this method is consistent across JDK versions.
795 * It consists of the root exception followed by each of its wrapping
796 * exceptions separated by '[wrapped]'. Note that this is the opposite
797 * order to the JDK1.4 display.</p>
798 *
799 * @param throwable the throwable to examine, may be null
800 * @return an array of stack trace frames, never null
801 * @since 2.0
802 */
803 public static String[] getRootCauseStackTrace(Throwable throwable) {
804 if (throwable == null) {
805 return ArrayUtils.EMPTY_STRING_ARRAY;
806 }
807 Throwable throwables[] = getThrowables(throwable);
808 int count = throwables.length;
809 ArrayList frames = new ArrayList();
810 List nextTrace = getStackFrameList(throwables[count - 1]);
811 for (int i = count; --i >= 0;) {
812 List trace = nextTrace;
813 if (i != 0) {
814 nextTrace = getStackFrameList(throwables[i - 1]);
815 removeCommonFrames(trace, nextTrace);
816 }
817 if (i == count - 1) {
818 frames.add(throwables[i].toString());
819 } else {
820 frames.add(WRAPPED_MARKER + throwables[i].toString());
821 }
822 for (int j = 0; j < trace.size(); j++) {
823 frames.add(trace.get(j));
824 }
825 }
826 return (String[]) frames.toArray(new String[0]);
827 }
828
829 /**
830 * <p>Removes common frames from the cause trace given the two stack traces.</p>
831 *
832 * @param causeFrames stack trace of a cause throwable
833 * @param wrapperFrames stack trace of a wrapper throwable
834 * @throws IllegalArgumentException if either argument is null
835 * @since 2.0
836 */
837 public static void removeCommonFrames(List causeFrames, List wrapperFrames) {
838 if (causeFrames == null || wrapperFrames == null) {
839 throw new IllegalArgumentException("The List must not be null");
840 }
841 int causeFrameIndex = causeFrames.size() - 1;
842 int wrapperFrameIndex = wrapperFrames.size() - 1;
843 while (causeFrameIndex >= 0 && wrapperFrameIndex >= 0) {
844 // Remove the frame from the cause trace if it is the same
845 // as in the wrapper trace
846 String causeFrame = (String) causeFrames.get(causeFrameIndex);
847 String wrapperFrame = (String) wrapperFrames.get(wrapperFrameIndex);
848 if (causeFrame.equals(wrapperFrame)) {
849 causeFrames.remove(causeFrameIndex);
850 }
851 causeFrameIndex--;
852 wrapperFrameIndex--;
853 }
854 }
855
856 //-----------------------------------------------------------------------
857 /**
858 * <p>A way to get the entire nested stack-trace of an throwable.</p>
859 *
860 * <p>The result of this method is highly dependent on the JDK version
861 * and whether the exceptions override printStackTrace or not.</p>
862 *
863 * @param throwable the <code>Throwable</code> to be examined
864 * @return the nested stack trace, with the root cause first
865 * @since 2.0
866 */
867 public static String getFullStackTrace(Throwable throwable) {
868 StringWriter sw = new StringWriter();
869 PrintWriter pw = new PrintWriter(sw, true);
870 Throwable[] ts = getThrowables(throwable);
871 for (int i = 0; i < ts.length; i++) {
872 ts[i].printStackTrace(pw);
873 if (isNestedThrowable(ts[i])) {
874 break;
875 }
876 }
877 return sw.getBuffer().toString();
878 }
879
880 //-----------------------------------------------------------------------
881 /**
882 * <p>Gets the stack trace from a Throwable as a String.</p>
883 *
884 * <p>The result of this method vary by JDK version as this method
885 * uses {@link Throwable#printStackTrace(java.io.PrintWriter)}.
886 * On JDK1.3 and earlier, the cause exception will not be shown
887 * unless the specified throwable alters printStackTrace.</p>
888 *
889 * @param throwable the <code>Throwable</code> to be examined
890 * @return the stack trace as generated by the exception's
891 * <code>printStackTrace(PrintWriter)</code> method
892 */
893 public static String getStackTrace(Throwable throwable) {
894 StringWriter sw = new StringWriter();
895 PrintWriter pw = new PrintWriter(sw, true);
896 throwable.printStackTrace(pw);
897 return sw.getBuffer().toString();
898 }
899
900 /**
901 * <p>Captures the stack trace associated with the specified
902 * <code>Throwable</code> object, decomposing it into a list of
903 * stack frames.</p>
904 *
905 * <p>The result of this method vary by JDK version as this method
906 * uses {@link Throwable#printStackTrace(java.io.PrintWriter)}.
907 * On JDK1.3 and earlier, the cause exception will not be shown
908 * unless the specified throwable alters printStackTrace.</p>
909 *
910 * @param throwable the <code>Throwable</code> to examine, may be null
911 * @return an array of strings describing each stack frame, never null
912 */
913 public static String[] getStackFrames(Throwable throwable) {
914 if (throwable == null) {
915 return ArrayUtils.EMPTY_STRING_ARRAY;
916 }
917 return getStackFrames(getStackTrace(throwable));
918 }
919
920 //-----------------------------------------------------------------------
921 /**
922 * <p>Returns an array where each element is a line from the argument.</p>
923 *
924 * <p>The end of line is determined by the value of {@link SystemUtils#LINE_SEPARATOR}.</p>
925 *
926 * <p>Functionality shared between the
927 * <code>getStackFrames(Throwable)</code> methods of this and the
928 * {@link org.apache.commons.lang.exception.NestableDelegate} classes.</p>
929 *
930 * @param stackTrace a stack trace String
931 * @return an array where each element is a line from the argument
932 */
933 static String[] getStackFrames(String stackTrace) {
934 String linebreak = SystemUtils.LINE_SEPARATOR;
935 StringTokenizer frames = new StringTokenizer(stackTrace, linebreak);
936 List list = new ArrayList();
937 while (frames.hasMoreTokens()) {
938 list.add(frames.nextToken());
939 }
940 return toArray(list);
941 }
942
943 /**
944 * <p>Produces a <code>List</code> of stack frames - the message
945 * is not included. Only the trace of the specified exception is
946 * returned, any caused by trace is stripped.</p>
947 *
948 * <p>This works in most cases - it will only fail if the exception
949 * message contains a line that starts with:
950 * <code>" at".</code></p>
951 *
952 * @param t is any throwable
953 * @return List of stack frames
954 */
955 static List getStackFrameList(Throwable t) {
956 String stackTrace = getStackTrace(t);
957 String linebreak = SystemUtils.LINE_SEPARATOR;
958 StringTokenizer frames = new StringTokenizer(stackTrace, linebreak);
959 List list = new ArrayList();
960 boolean traceStarted = false;
961 while (frames.hasMoreTokens()) {
962 String token = frames.nextToken();
963 // Determine if the line starts with <whitespace>at
964 int at = token.indexOf("at");
965 if (at != -1 && token.substring(0, at).trim().length() == 0) {
966 traceStarted = true;
967 list.add(token);
968 } else if (traceStarted) {
969 break;
970 }
971 }
972 return list;
973 }
974
975 //-----------------------------------------------------------------------
976 /**
977 * Gets a short message summarising the exception.
978 * <p>
979 * The message returned is of the form
980 * {ClassNameWithoutPackage}: {ThrowableMessage}
981 *
982 * @param th the throwable to get a message for, null returns empty string
983 * @return the message, non-null
984 * @since Commons Lang 2.2
985 */
986 public static String getMessage(Throwable th) {
987 if (th == null) {
988 return "";
989 }
990 String clsName = ClassUtils.getShortClassName(th, null);
991 String msg = th.getMessage();
992 return clsName + ": " + StringUtils.defaultString(msg);
993 }
994
995 //-----------------------------------------------------------------------
996 /**
997 * Gets a short message summarising the root cause exception.
998 * <p>
999 * The message returned is of the form
1000 * {ClassNameWithoutPackage}: {ThrowableMessage}
1001 *
1002 * @param th the throwable to get a message for, null returns empty string
1003 * @return the message, non-null
1004 * @since Commons Lang 2.2
1005 */
1006 public static String getRootCauseMessage(Throwable th) {
1007 Throwable root = ExceptionUtils.getRootCause(th);
1008 root = (root == null ? th : root);
1009 return getMessage(root);
1010 }
1011
1012 }