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.lang3.exception;
18  
19  import java.io.PrintStream;
20  import java.io.PrintWriter;
21  import java.io.StringWriter;
22  import java.lang.reflect.InvocationTargetException;
23  import java.lang.reflect.Method;
24  import java.lang.reflect.UndeclaredThrowableException;
25  import java.util.ArrayList;
26  import java.util.List;
27  import java.util.StringTokenizer;
28  
29  import org.apache.commons.lang3.ArrayUtils;
30  import org.apache.commons.lang3.ClassUtils;
31  import org.apache.commons.lang3.StringUtils;
32  import org.apache.commons.lang3.SystemUtils;
33  
34  /**
35   * <p>Provides utilities for manipulating and examining 
36   * <code>Throwable</code> objects.</p>
37   *
38   * @since 1.0
39   */
40  public class ExceptionUtils {
41      
42      /**
43       * <p>Used when printing stack frames to denote the start of a
44       * wrapped exception.</p>
45       *
46       * <p>Package private for accessibility by test suite.</p>
47       */
48      static final String WRAPPED_MARKER = " [wrapped] ";
49  
50      /**
51       * <p>The names of methods commonly used to access a wrapped exception.</p>
52       */
53      // TODO: Remove in Lang 4.0
54      private static final String[] CAUSE_METHOD_NAMES = {
55          "getCause",
56          "getNextException",
57          "getTargetException",
58          "getException",
59          "getSourceException",
60          "getRootCause",
61          "getCausedByException",
62          "getNested",
63          "getLinkedException",
64          "getNestedException",
65          "getLinkedCause",
66          "getThrowable",
67      };
68  
69      /**
70       * <p>
71       * Public constructor allows an instance of <code>ExceptionUtils</code> to be created, although that is not
72       * normally necessary.
73       * </p>
74       */
75      public ExceptionUtils() {
76          super();
77      }
78  
79      //-----------------------------------------------------------------------
80      /**
81       * <p>Returns the default names used when searching for the cause of an exception.</p>
82       *
83       * <p>This may be modified and used in the overloaded getCause(Throwable, String[]) method.</p>
84       *
85       * @return cloned array of the default method names
86       * @since 3.0
87       * @deprecated This feature will be removed in Lang 4.0
88       */
89      @Deprecated
90      public static String[] getDefaultCauseMethodNames() {
91          return ArrayUtils.clone(CAUSE_METHOD_NAMES);
92      }
93  
94      //-----------------------------------------------------------------------
95      /**
96       * <p>Introspects the <code>Throwable</code> to obtain the cause.</p>
97       *
98       * <p>The method searches for methods with specific names that return a 
99       * <code>Throwable</code> object. This will pick up most wrapping exceptions,
100      * including those from JDK 1.4.
101      *
102      * <p>The default list searched for are:</p>
103      * <ul>
104      *  <li><code>getCause()</code></li>
105      *  <li><code>getNextException()</code></li>
106      *  <li><code>getTargetException()</code></li>
107      *  <li><code>getException()</code></li>
108      *  <li><code>getSourceException()</code></li>
109      *  <li><code>getRootCause()</code></li>
110      *  <li><code>getCausedByException()</code></li>
111      *  <li><code>getNested()</code></li>
112      * </ul>
113      * 
114      * <p>If none of the above is found, returns <code>null</code>.</p>
115      *
116      * @param throwable  the throwable to introspect for a cause, may be null
117      * @return the cause of the <code>Throwable</code>,
118      *  <code>null</code> if none found or null throwable input
119      * @since 1.0
120      * @deprecated This feature will be removed in Lang 4.0, use {@link Throwable#getCause} instead
121      */
122     @Deprecated
123     public static Throwable getCause(final Throwable throwable) {
124         return getCause(throwable, null);
125     }
126 
127     /**
128      * <p>Introspects the <code>Throwable</code> to obtain the cause.</p>
129      *
130      * <p>A <code>null</code> set of method names means use the default set.
131      * A <code>null</code> in the set of method names will be ignored.</p>
132      *
133      * @param throwable  the throwable to introspect for a cause, may be null
134      * @param methodNames  the method names, null treated as default set
135      * @return the cause of the <code>Throwable</code>,
136      *  <code>null</code> if none found or null throwable input
137      * @since 1.0
138      * @deprecated This feature will be removed in Lang 4.0, use {@link Throwable#getCause} instead
139      */
140     @Deprecated
141     public static Throwable getCause(final Throwable throwable, String[] methodNames) {
142         if (throwable == null) {
143             return null;
144         }
145 
146         if (methodNames == null) {
147             final Throwable cause = throwable.getCause();
148             if (cause != null) {
149                 return cause;
150             }
151 
152             methodNames = CAUSE_METHOD_NAMES;
153         }
154 
155         for (final String methodName : methodNames) {
156             if (methodName != null) {
157                 final Throwable legacyCause = getCauseUsingMethodName(throwable, methodName);
158                 if (legacyCause != null) {
159                     return legacyCause;
160                 }
161             }
162         }
163 
164         return null;
165     }
166 
167     /**
168      * <p>Introspects the <code>Throwable</code> to obtain the root cause.</p>
169      *
170      * <p>This method walks through the exception chain to the last element,
171      * "root" of the tree, using {@link #getCause(Throwable)}, and
172      * returns that exception.</p>
173      *
174      * <p>From version 2.2, this method handles recursive cause structures
175      * that might otherwise cause infinite loops. If the throwable parameter
176      * has a cause of itself, then null will be returned. If the throwable
177      * parameter cause chain loops, the last element in the chain before the
178      * loop is returned.</p>
179      *
180      * @param throwable  the throwable to get the root cause for, may be null
181      * @return the root cause of the <code>Throwable</code>,
182      *  <code>null</code> if none found or null throwable input
183      */
184     public static Throwable getRootCause(final Throwable throwable) {
185         final List<Throwable> list = getThrowableList(throwable);
186         return list.size() < 2 ? null : (Throwable)list.get(list.size() - 1);
187     }
188 
189     /**
190      * <p>Finds a <code>Throwable</code> by method name.</p>
191      *
192      * @param throwable  the exception to examine
193      * @param methodName  the name of the method to find and invoke
194      * @return the wrapped exception, or <code>null</code> if not found
195      */
196     // TODO: Remove in Lang 4.0
197     private static Throwable getCauseUsingMethodName(final Throwable throwable, final String methodName) {
198         Method method = null;
199         try {
200             method = throwable.getClass().getMethod(methodName);
201         } catch (final NoSuchMethodException ignored) { // NOPMD
202             // exception ignored
203         } catch (final SecurityException ignored) { // NOPMD
204             // exception ignored
205         }
206 
207         if (method != null && Throwable.class.isAssignableFrom(method.getReturnType())) {
208             try {
209                 return (Throwable) method.invoke(throwable);
210             } catch (final IllegalAccessException ignored) { // NOPMD
211                 // exception ignored
212             } catch (final IllegalArgumentException ignored) { // NOPMD
213                 // exception ignored
214             } catch (final InvocationTargetException ignored) { // NOPMD
215                 // exception ignored
216             }
217         }
218         return null;
219     }
220 
221     //-----------------------------------------------------------------------
222     /**
223      * <p>Counts the number of <code>Throwable</code> objects in the
224      * exception chain.</p>
225      *
226      * <p>A throwable without cause will return <code>1</code>.
227      * A throwable with one cause will return <code>2</code> and so on.
228      * A <code>null</code> throwable will return <code>0</code>.</p>
229      *
230      * <p>From version 2.2, this method handles recursive cause structures
231      * that might otherwise cause infinite loops. The cause chain is
232      * processed until the end is reached, or until the next item in the
233      * chain is already in the result set.</p>
234      *
235      * @param throwable  the throwable to inspect, may be null
236      * @return the count of throwables, zero if null input
237      */
238     public static int getThrowableCount(final Throwable throwable) {
239         return getThrowableList(throwable).size();
240     }
241 
242     /**
243      * <p>Returns the list of <code>Throwable</code> objects in the
244      * exception chain.</p>
245      *
246      * <p>A throwable without cause will return an array containing
247      * one element - the input throwable.
248      * A throwable with one cause will return an array containing
249      * two elements. - the input throwable and the cause throwable.
250      * A <code>null</code> throwable will return an array of size zero.</p>
251      *
252      * <p>From version 2.2, this method handles recursive cause structures
253      * that might otherwise cause infinite loops. The cause chain is
254      * processed until the end is reached, or until the next item in the
255      * chain is already in the result set.</p>
256      *
257      * @see #getThrowableList(Throwable)
258      * @param throwable  the throwable to inspect, may be null
259      * @return the array of throwables, never null
260      */
261     public static Throwable[] getThrowables(final Throwable throwable) {
262         final List<Throwable> list = getThrowableList(throwable);
263         return list.toArray(new Throwable[list.size()]);
264     }
265 
266     /**
267      * <p>Returns the list of <code>Throwable</code> objects in the
268      * exception chain.</p>
269      *
270      * <p>A throwable without cause will return a list containing
271      * one element - the input throwable.
272      * A throwable with one cause will return a list containing
273      * two elements. - the input throwable and the cause throwable.
274      * A <code>null</code> throwable will return a list of size zero.</p>
275      *
276      * <p>This method handles recursive cause structures that might
277      * otherwise cause infinite loops. The cause chain is processed until
278      * the end is reached, or until the next item in the chain is already
279      * in the result set.</p>
280      *
281      * @param throwable  the throwable to inspect, may be null
282      * @return the list of throwables, never null
283      * @since Commons Lang 2.2
284      */
285     public static List<Throwable> getThrowableList(Throwable throwable) {
286         final List<Throwable> list = new ArrayList<Throwable>();
287         while (throwable != null && list.contains(throwable) == false) {
288             list.add(throwable);
289             throwable = ExceptionUtils.getCause(throwable);
290         }
291         return list;
292     }
293 
294     //-----------------------------------------------------------------------
295     /**
296      * <p>Returns the (zero based) index of the first <code>Throwable</code>
297      * that matches the specified class (exactly) in the exception chain.
298      * Subclasses of the specified class do not match - see
299      * {@link #indexOfType(Throwable, Class)} for the opposite.</p>
300      *
301      * <p>A <code>null</code> throwable returns <code>-1</code>.
302      * A <code>null</code> type returns <code>-1</code>.
303      * No match in the chain returns <code>-1</code>.</p>
304      *
305      * @param throwable  the throwable to inspect, may be null
306      * @param clazz  the class to search for, subclasses do not match, null returns -1
307      * @return the index into the throwable chain, -1 if no match or null input
308      */
309     public static int indexOfThrowable(final Throwable throwable, final Class<?> clazz) {
310         return indexOf(throwable, clazz, 0, false);
311     }
312 
313     /**
314      * <p>Returns the (zero based) index of the first <code>Throwable</code>
315      * that matches the specified type in the exception chain from
316      * a specified index.
317      * Subclasses of the specified class do not match - see
318      * {@link #indexOfType(Throwable, Class, int)} for the opposite.</p>
319      *
320      * <p>A <code>null</code> throwable returns <code>-1</code>.
321      * A <code>null</code> type returns <code>-1</code>.
322      * No match in the chain returns <code>-1</code>.
323      * A negative start index is treated as zero.
324      * A start index greater than the number of throwables returns <code>-1</code>.</p>
325      *
326      * @param throwable  the throwable to inspect, may be null
327      * @param clazz  the class to search for, subclasses do not match, null returns -1
328      * @param fromIndex  the (zero based) index of the starting position,
329      *  negative treated as zero, larger than chain size returns -1
330      * @return the index into the throwable chain, -1 if no match or null input
331      */
332     public static int indexOfThrowable(final Throwable throwable, final Class<?> clazz, final int fromIndex) {
333         return indexOf(throwable, clazz, fromIndex, false);
334     }
335 
336     //-----------------------------------------------------------------------
337     /**
338      * <p>Returns the (zero based) index of the first <code>Throwable</code>
339      * that matches the specified class or subclass in the exception chain.
340      * Subclasses of the specified class do match - see
341      * {@link #indexOfThrowable(Throwable, Class)} for the opposite.</p>
342      *
343      * <p>A <code>null</code> throwable returns <code>-1</code>.
344      * A <code>null</code> type returns <code>-1</code>.
345      * No match in the chain returns <code>-1</code>.</p>
346      *
347      * @param throwable  the throwable to inspect, may be null
348      * @param type  the type to search for, subclasses match, null returns -1
349      * @return the index into the throwable chain, -1 if no match or null input
350      * @since 2.1
351      */
352     public static int indexOfType(final Throwable throwable, final Class<?> type) {
353         return indexOf(throwable, type, 0, true);
354     }
355 
356     /**
357      * <p>Returns the (zero based) index of the first <code>Throwable</code>
358      * that matches the specified type in the exception chain from
359      * a specified index.
360      * Subclasses of the specified class do match - see
361      * {@link #indexOfThrowable(Throwable, Class)} for the opposite.</p>
362      *
363      * <p>A <code>null</code> throwable returns <code>-1</code>.
364      * A <code>null</code> type returns <code>-1</code>.
365      * No match in the chain returns <code>-1</code>.
366      * A negative start index is treated as zero.
367      * A start index greater than the number of throwables returns <code>-1</code>.</p>
368      *
369      * @param throwable  the throwable to inspect, may be null
370      * @param type  the type to search for, subclasses match, null returns -1
371      * @param fromIndex  the (zero based) index of the starting position,
372      *  negative treated as zero, larger than chain size returns -1
373      * @return the index into the throwable chain, -1 if no match or null input
374      * @since 2.1
375      */
376     public static int indexOfType(final Throwable throwable, final Class<?> type, final int fromIndex) {
377         return indexOf(throwable, type, fromIndex, true);
378     }
379 
380     /**
381      * <p>Worker method for the <code>indexOfType</code> methods.</p>
382      *
383      * @param throwable  the throwable to inspect, may be null
384      * @param type  the type to search for, subclasses match, null returns -1
385      * @param fromIndex  the (zero based) index of the starting position,
386      *  negative treated as zero, larger than chain size returns -1
387      * @param subclass if <code>true</code>, compares with {@link Class#isAssignableFrom(Class)}, otherwise compares
388      * using references
389      * @return index of the <code>type</code> within throwables nested within the specified <code>throwable</code>
390      */
391     private static int indexOf(final Throwable throwable, final Class<?> type, int fromIndex, final boolean subclass) {
392         if (throwable == null || type == null) {
393             return -1;
394         }
395         if (fromIndex < 0) {
396             fromIndex = 0;
397         }
398         final Throwable[] throwables = ExceptionUtils.getThrowables(throwable);
399         if (fromIndex >= throwables.length) {
400             return -1;
401         }
402         if (subclass) {
403             for (int i = fromIndex; i < throwables.length; i++) {
404                 if (type.isAssignableFrom(throwables[i].getClass())) {
405                     return i;
406                 }
407             }
408         } else {
409             for (int i = fromIndex; i < throwables.length; i++) {
410                 if (type.equals(throwables[i].getClass())) {
411                     return i;
412                 }
413             }
414         }
415         return -1;
416     }
417 
418     //-----------------------------------------------------------------------
419     /**
420      * <p>Prints a compact stack trace for the root cause of a throwable
421      * to <code>System.err</code>.</p>
422      *
423      * <p>The compact stack trace starts with the root cause and prints
424      * stack frames up to the place where it was caught and wrapped.
425      * Then it prints the wrapped exception and continues with stack frames
426      * until the wrapper exception is caught and wrapped again, etc.</p>
427      *
428      * <p>The output of this method is consistent across JDK versions.
429      * Note that this is the opposite order to the JDK1.4 display.</p>
430      *
431      * <p>The method is equivalent to <code>printStackTrace</code> for throwables
432      * that don't have nested causes.</p>
433      *
434      * @param throwable  the throwable to output
435      * @since 2.0
436      */
437     public static void printRootCauseStackTrace(final Throwable throwable) {
438         printRootCauseStackTrace(throwable, System.err);
439     }
440 
441     /**
442      * <p>Prints a compact stack trace for the root cause of a throwable.</p>
443      *
444      * <p>The compact stack trace starts with the root cause and prints
445      * stack frames up to the place where it was caught and wrapped.
446      * Then it prints the wrapped exception and continues with stack frames
447      * until the wrapper exception is caught and wrapped again, etc.</p>
448      *
449      * <p>The output of this method is consistent across JDK versions.
450      * Note that this is the opposite order to the JDK1.4 display.</p>
451      *
452      * <p>The method is equivalent to <code>printStackTrace</code> for throwables
453      * that don't have nested causes.</p>
454      *
455      * @param throwable  the throwable to output, may be null
456      * @param stream  the stream to output to, may not be null
457      * @throws IllegalArgumentException if the stream is <code>null</code>
458      * @since 2.0
459      */
460     public static void printRootCauseStackTrace(final Throwable throwable, final PrintStream stream) {
461         if (throwable == null) {
462             return;
463         }
464         if (stream == null) {
465             throw new IllegalArgumentException("The PrintStream must not be null");
466         }
467         final String trace[] = getRootCauseStackTrace(throwable);
468         for (final String element : trace) {
469             stream.println(element);
470         }
471         stream.flush();
472     }
473 
474     /**
475      * <p>Prints a compact stack trace for the root cause of a throwable.</p>
476      *
477      * <p>The compact stack trace starts with the root cause and prints
478      * stack frames up to the place where it was caught and wrapped.
479      * Then it prints the wrapped exception and continues with stack frames
480      * until the wrapper exception is caught and wrapped again, etc.</p>
481      *
482      * <p>The output of this method is consistent across JDK versions.
483      * Note that this is the opposite order to the JDK1.4 display.</p>
484      *
485      * <p>The method is equivalent to <code>printStackTrace</code> for throwables
486      * that don't have nested causes.</p>
487      *
488      * @param throwable  the throwable to output, may be null
489      * @param writer  the writer to output to, may not be null
490      * @throws IllegalArgumentException if the writer is <code>null</code>
491      * @since 2.0
492      */
493     public static void printRootCauseStackTrace(final Throwable throwable, final PrintWriter writer) {
494         if (throwable == null) {
495             return;
496         }
497         if (writer == null) {
498             throw new IllegalArgumentException("The PrintWriter must not be null");
499         }
500         final String trace[] = getRootCauseStackTrace(throwable);
501         for (final String element : trace) {
502             writer.println(element);
503         }
504         writer.flush();
505     }
506 
507     //-----------------------------------------------------------------------
508     /**
509      * <p>Creates a compact stack trace for the root cause of the supplied
510      * <code>Throwable</code>.</p>
511      *
512      * <p>The output of this method is consistent across JDK versions.
513      * It consists of the root exception followed by each of its wrapping
514      * exceptions separated by '[wrapped]'. Note that this is the opposite
515      * order to the JDK1.4 display.</p>
516      *
517      * @param throwable  the throwable to examine, may be null
518      * @return an array of stack trace frames, never null
519      * @since 2.0
520      */
521     public static String[] getRootCauseStackTrace(final Throwable throwable) {
522         if (throwable == null) {
523             return ArrayUtils.EMPTY_STRING_ARRAY;
524         }
525         final Throwable throwables[] = getThrowables(throwable);
526         final int count = throwables.length;
527         final List<String> frames = new ArrayList<String>();
528         List<String> nextTrace = getStackFrameList(throwables[count - 1]);
529         for (int i = count; --i >= 0;) {
530             final List<String> trace = nextTrace;
531             if (i != 0) {
532                 nextTrace = getStackFrameList(throwables[i - 1]);
533                 removeCommonFrames(trace, nextTrace);
534             }
535             if (i == count - 1) {
536                 frames.add(throwables[i].toString());
537             } else {
538                 frames.add(WRAPPED_MARKER + throwables[i].toString());
539             }
540             for (int j = 0; j < trace.size(); j++) {
541                 frames.add(trace.get(j));
542             }
543         }
544         return frames.toArray(new String[frames.size()]);
545     }
546 
547     /**
548      * <p>Removes common frames from the cause trace given the two stack traces.</p>
549      *
550      * @param causeFrames  stack trace of a cause throwable
551      * @param wrapperFrames  stack trace of a wrapper throwable
552      * @throws IllegalArgumentException if either argument is null
553      * @since 2.0
554      */
555     public static void removeCommonFrames(final List<String> causeFrames, final List<String> wrapperFrames) {
556         if (causeFrames == null || wrapperFrames == null) {
557             throw new IllegalArgumentException("The List must not be null");
558         }
559         int causeFrameIndex = causeFrames.size() - 1;
560         int wrapperFrameIndex = wrapperFrames.size() - 1;
561         while (causeFrameIndex >= 0 && wrapperFrameIndex >= 0) {
562             // Remove the frame from the cause trace if it is the same
563             // as in the wrapper trace
564             final String causeFrame = causeFrames.get(causeFrameIndex);
565             final String wrapperFrame = wrapperFrames.get(wrapperFrameIndex);
566             if (causeFrame.equals(wrapperFrame)) {
567                 causeFrames.remove(causeFrameIndex);
568             }
569             causeFrameIndex--;
570             wrapperFrameIndex--;
571         }
572     }
573 
574     //-----------------------------------------------------------------------
575     /**
576      * <p>Gets the stack trace from a Throwable as a String.</p>
577      *
578      * <p>The result of this method vary by JDK version as this method
579      * uses {@link Throwable#printStackTrace(java.io.PrintWriter)}.
580      * On JDK1.3 and earlier, the cause exception will not be shown
581      * unless the specified throwable alters printStackTrace.</p>
582      *
583      * @param throwable  the <code>Throwable</code> to be examined
584      * @return the stack trace as generated by the exception's
585      *  <code>printStackTrace(PrintWriter)</code> method
586      */
587     public static String getStackTrace(final Throwable throwable) {
588         final StringWriter sw = new StringWriter();
589         final PrintWriter pw = new PrintWriter(sw, true);
590         throwable.printStackTrace(pw);
591         return sw.getBuffer().toString();
592     }
593 
594     /**
595      * <p>Captures the stack trace associated with the specified
596      * <code>Throwable</code> object, decomposing it into a list of
597      * stack frames.</p>
598      *
599      * <p>The result of this method vary by JDK version as this method
600      * uses {@link Throwable#printStackTrace(java.io.PrintWriter)}.
601      * On JDK1.3 and earlier, the cause exception will not be shown
602      * unless the specified throwable alters printStackTrace.</p>
603      *
604      * @param throwable  the <code>Throwable</code> to examine, may be null
605      * @return an array of strings describing each stack frame, never null
606      */
607     public static String[] getStackFrames(final Throwable throwable) {
608         if (throwable == null) {
609             return ArrayUtils.EMPTY_STRING_ARRAY;
610         }
611         return getStackFrames(getStackTrace(throwable));
612     }
613 
614     //-----------------------------------------------------------------------
615     /**
616      * <p>Returns an array where each element is a line from the argument.</p>
617      *
618      * <p>The end of line is determined by the value of {@link SystemUtils#LINE_SEPARATOR}.</p>
619      *
620      * @param stackTrace  a stack trace String
621      * @return an array where each element is a line from the argument
622      */
623     static String[] getStackFrames(final String stackTrace) {
624         final String linebreak = SystemUtils.LINE_SEPARATOR;
625         final StringTokenizer frames = new StringTokenizer(stackTrace, linebreak);
626         final List<String> list = new ArrayList<String>();
627         while (frames.hasMoreTokens()) {
628             list.add(frames.nextToken());
629         }
630         return list.toArray(new String[list.size()]);
631     }
632 
633     /**
634      * <p>Produces a <code>List</code> of stack frames - the message
635      * is not included. Only the trace of the specified exception is
636      * returned, any caused by trace is stripped.</p>
637      *
638      * <p>This works in most cases - it will only fail if the exception
639      * message contains a line that starts with:
640      * <code>&quot;&nbsp;&nbsp;&nbsp;at&quot;.</code></p>
641      * 
642      * @param t is any throwable
643      * @return List of stack frames
644      */
645     static List<String> getStackFrameList(final Throwable t) {
646         final String stackTrace = getStackTrace(t);
647         final String linebreak = SystemUtils.LINE_SEPARATOR;
648         final StringTokenizer frames = new StringTokenizer(stackTrace, linebreak);
649         final List<String> list = new ArrayList<String>();
650         boolean traceStarted = false;
651         while (frames.hasMoreTokens()) {
652             final String token = frames.nextToken();
653             // Determine if the line starts with <whitespace>at
654             final int at = token.indexOf("at");
655             if (at != -1 && token.substring(0, at).trim().isEmpty()) {
656                 traceStarted = true;
657                 list.add(token);
658             } else if (traceStarted) {
659                 break;
660             }
661         }
662         return list;
663     }
664 
665     //-----------------------------------------------------------------------
666     /**
667      * Gets a short message summarising the exception.
668      * <p>
669      * The message returned is of the form
670      * {ClassNameWithoutPackage}: {ThrowableMessage}
671      *
672      * @param th  the throwable to get a message for, null returns empty string
673      * @return the message, non-null
674      * @since Commons Lang 2.2
675      */
676     public static String getMessage(final Throwable th) {
677         if (th == null) {
678             return StringUtils.EMPTY;
679         }
680         final String clsName = ClassUtils.getShortClassName(th, null);
681         final String msg = th.getMessage();
682         return clsName + ": " + StringUtils.defaultString(msg);
683     }
684 
685     //-----------------------------------------------------------------------
686     /**
687      * Gets a short message summarising the root cause exception.
688      * <p>
689      * The message returned is of the form
690      * {ClassNameWithoutPackage}: {ThrowableMessage}
691      *
692      * @param th  the throwable to get a message for, null returns empty string
693      * @return the message, non-null
694      * @since Commons Lang 2.2
695      */
696     public static String getRootCauseMessage(final Throwable th) {
697         Throwable root = ExceptionUtils.getRootCause(th);
698         root = root == null ? th : root;
699         return getMessage(root);
700     }
701 
702     /**
703      * Throw a checked exception without adding the exception to the throws
704      * clause of the calling method. This method prevents throws clause
705      * pollution and reduces the clutter of "Caused by" exceptions in the
706      * stacktrace.
707      * <p>
708      * The use of this technique may be controversial, but exceedingly useful to
709      * library developers.
710      * <code>
711      *  public int propagateExample { // note that there is no throws clause
712      *      try {
713      *          return invocation(); // throws IOException
714      *      } catch (Exception e) {
715      *          return ExceptionUtils.rethrow(e);  // propagates a checked exception
716      *      }
717      *  }
718      * </code>
719      * <p>
720      * This is an alternative to the more conservative approach of wrapping the
721      * checked exception in a RuntimeException:
722      * <code>
723      *  public int wrapExample { // note that there is no throws clause
724      *      try {
725      *          return invocation(); // throws IOException
726      *      } catch (Error e) {
727      *          throw e;
728      *      } catch (RuntimeException e) {
729      *          throw e;  // wraps a checked exception
730      *      } catch (Exception e) {
731      *          throw new UndeclaredThrowableException(e);  // wraps a checked exception
732      *      }
733      *  }
734      * </code>
735      * <p>
736      * One downside to using this approach is that the java compiler will not
737      * allow invoking code to specify a checked exception in a catch clause
738      * unless there is some code path within the try block that has invoked a
739      * method declared with that checked exception. If the invoking site wishes
740      * to catch the shaded checked exception, it must either invoke the shaded
741      * code through a method re-declaring the desired checked exception, or
742      * catch Exception and use the instanceof operator. Either of these
743      * techniques are required when interacting with non-java jvm code such as
744      * Jyton, Scala, or Groovy, since these languages do not consider any
745      * exceptions as checked.
746      *
747      * @param throwable
748      *            The throwable to rethrow.
749      * @param <R> The type of the returned value.
750      * @return Never actually returned, this generic type matches any type
751      *         which the calling site requires. "Returning" the results of this
752      *         method, as done in the propagateExample above, will satisfy the
753      *         java compiler requirement that all code paths return a value.
754      * @since 3.5
755      * @see #wrapAndThrow(Throwable)
756      */
757     public static <R> R rethrow(Throwable throwable) {
758         // claim that the typeErasure invocation throws a RuntimeException
759         return ExceptionUtils.<R, RuntimeException> typeErasure(throwable);
760     }
761 
762     /**
763      * Claim a Throwable is another Exception type using type erasure. This
764      * hides a checked exception from the java compiler, allowing a checked
765      * exception to be thrown without having the exception in the method's throw
766      * clause.
767      */
768     @SuppressWarnings("unchecked")
769     private static <R, T extends Throwable> R typeErasure(Throwable throwable) throws T {
770         throw (T) throwable;
771     }
772 
773     /**
774      * Throw a checked exception without adding the exception to the throws
775      * clause of the calling method. For checked exceptions, this method throws
776      * an UndeclaredThrowableException wrapping the checked exception. For
777      * Errors and RuntimeExceptions, the original exception is rethrown.
778      * <p>
779      * The downside to using this approach is that invoking code which needs to
780      * handle specific checked exceptions must sniff up the exception chain to
781      * determine if the caught exception was caused by the checked exception.
782      *
783      * @param throwable
784      *            The throwable to rethrow.
785      * @param <R> The type of the returned value.
786      * @return Never actually returned, this generic type matches any type
787      *         which the calling site requires. "Returning" the results of this
788      *         method will satisfy the java compiler requirement that all code
789      *         paths return a value.
790      * @since 3.5
791      * @see #rethrow(Throwable)
792      * @see #hasCause(Throwable, Class)
793      */
794     public static <R> R wrapAndThrow(Throwable throwable) {
795         if (throwable instanceof RuntimeException) {
796             throw (RuntimeException) throwable;
797         }
798         if (throwable instanceof Error) {
799             throw (Error) throwable;
800         }
801         throw new UndeclaredThrowableException(throwable);
802     }
803 
804     /**
805      * Does the throwable's causal chain have an immediate or wrapped exception
806      * of the given type?
807      *
808      * @param chain
809      *            The root of a Throwable causal chain.
810      * @param type
811      *            The exception type to test.
812      * @return true, if chain is an instance of type or is an
813      *         UndeclaredThrowableException wrapping a cause.
814      * @since 3.5
815      * @see #wrapAndThrow(Throwable)
816      */
817     public static boolean hasCause(Throwable chain,
818             Class<? extends Throwable> type) {
819         if (chain instanceof UndeclaredThrowableException) {
820             chain = chain.getCause();
821         }
822         return type.isInstance(chain);
823     }
824 }