1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 package org.apache.commons.lang.exception;
18 
19 import java.io.PrintStream;
20 import java.io.PrintWriter;
21 import java.io.StringWriter;
22 import java.lang.reflect.Field;
23 import java.lang.reflect.InvocationTargetException;
24 import java.lang.reflect.Method;
25 import java.sql.SQLException;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.List;
29 import java.util.StringTokenizer;
30 
31 import org.apache.commons.lang.ArrayUtils;
32 import org.apache.commons.lang.ClassUtils;
33 import org.apache.commons.lang.NullArgumentException;
34 import org.apache.commons.lang.StringUtils;
35 import org.apache.commons.lang.SystemUtils;
36 
37 /**
38  * <p>Provides utilities for manipulating and examining
39  * <code>Throwable</code> objects.</p>
40  *
41  * @author Apache Software Foundation
42  * @author Daniel L. Rall
43  * @author Dmitri Plotnikov
44  * @author <a href="mailto:ggregory@seagullsw.com">Gary Gregory</a>
45  * @author Pete Gieser
46  * @since 1.0
47  * @version $Id: ExceptionUtils.java 905837 2010-02-02 23:32:11Z niallp $
48  */
49 public class ExceptionUtils {
50 
51     /**
52      * <p>Used when printing stack frames to denote the start of a
53      * wrapped exception.</p>
54      *
55      * <p>Package private for accessibility by test suite.</p>
56      */
57     static final String WRAPPED_MARKER = " [wrapped] ";
58 
59     // Lock object for CAUSE_METHOD_NAMES
60     private static final Object CAUSE_METHOD_NAMES_LOCK = new Object();
61 
62     /**
63      * <p>The names of methods commonly used to access a wrapped exception.</p>
64      */
65     private static String[] CAUSE_METHOD_NAMES = {
66         "getCause",
67         "getNextException",
68         "getTargetException",
69         "getException",
70         "getSourceException",
71         "getRootCause",
72         "getCausedByException",
73         "getNested",
74         "getLinkedException",
75         "getNestedException",
76         "getLinkedCause",
77         "getThrowable",
78     };
79 
80     /**
81      * <p>The Method object for Java 1.4 getCause.</p>
82      */
83     private static final Method THROWABLE_CAUSE_METHOD;
84 
85     /**
86      * <p>The Method object for Java 1.4 initCause.</p>
87      */
88     private static final Method THROWABLE_INITCAUSE_METHOD;
89 
90     static {
91         Method causeMethod;
92         try {
93             causeMethod = Throwable.class.getMethod("getCause", null);
94         } catch (Exception e) {
95             causeMethod = null;
96         }
97         THROWABLE_CAUSE_METHOD = causeMethod;
98         try {
99             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      */
ExceptionUtils()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      */
addCauseMethodName(String methodName)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      */
removeCauseMethodName(String methodName)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      */
setCause(Throwable target, Throwable cause)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      */
toArray(List list)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      */
getCauseMethodNameList()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      */
isCauseMethodName(String methodName)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      */
getCause(Throwable throwable)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      */
getCause(Throwable throwable, String[] methodNames)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      */
getRootCause(Throwable throwable)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      */
getCauseUsingWellKnownTypes(Throwable throwable)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      */
getCauseUsingMethodName(Throwable throwable, String methodName)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      */
getCauseUsingFieldName(Throwable throwable, String fieldName)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      */
isThrowableNested()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      */
isNestedThrowable(Throwable throwable)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      */
getThrowableCount(Throwable throwable)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      */
getThrowables(Throwable throwable)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      */
getThrowableList(Throwable throwable)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      */
indexOfThrowable(Throwable throwable, Class clazz)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      */
indexOfThrowable(Throwable throwable, Class clazz, int fromIndex)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      */
indexOfType(Throwable throwable, Class type)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      */
indexOfType(Throwable throwable, Class type, int fromIndex)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      */
indexOf(Throwable throwable, Class type, int fromIndex, boolean subclass)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      */
printRootCauseStackTrace(Throwable throwable)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      */
printRootCauseStackTrace(Throwable throwable, PrintStream stream)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      */
printRootCauseStackTrace(Throwable throwable, PrintWriter writer)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      */
getRootCauseStackTrace(Throwable throwable)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      */
removeCommonFrames(List causeFrames, List wrapperFrames)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      */
getFullStackTrace(Throwable throwable)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      */
getStackTrace(Throwable throwable)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      */
getStackFrames(Throwable throwable)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      */
getStackFrames(String stackTrace)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>&quot;&nbsp;&nbsp;&nbsp;at&quot;.</code></p>
951      *
952      * @param t is any throwable
953      * @return List of stack frames
954      */
getStackFrameList(Throwable t)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      */
getMessage(Throwable th)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      */
getRootCauseMessage(Throwable th)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 }
1013