View Javadoc

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