001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     * 
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     * 
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.lang3.exception;
018    
019    import java.io.PrintStream;
020    import java.io.PrintWriter;
021    import java.io.StringWriter;
022    import java.lang.reflect.InvocationTargetException;
023    import java.lang.reflect.Method;
024    import java.util.ArrayList;
025    import java.util.List;
026    import java.util.StringTokenizer;
027    
028    import org.apache.commons.lang3.ArrayUtils;
029    import org.apache.commons.lang3.ClassUtils;
030    import org.apache.commons.lang3.StringUtils;
031    import org.apache.commons.lang3.SystemUtils;
032    
033    /**
034     * <p>Provides utilities for manipulating and examining 
035     * <code>Throwable</code> objects.</p>
036     *
037     * @since 1.0
038     * @version $Id: ExceptionUtils.java 1199894 2011-11-09 17:53:59Z ggregory $
039     */
040    public class ExceptionUtils {
041        
042        /**
043         * <p>Used when printing stack frames to denote the start of a
044         * wrapped exception.</p>
045         *
046         * <p>Package private for accessibility by test suite.</p>
047         */
048        static final String WRAPPED_MARKER = " [wrapped] ";
049    
050        /**
051         * <p>The names of methods commonly used to access a wrapped exception.</p>
052         */
053        // TODO: Remove in Lang 4.0
054        private static final String[] CAUSE_METHOD_NAMES = {
055            "getCause",
056            "getNextException",
057            "getTargetException",
058            "getException",
059            "getSourceException",
060            "getRootCause",
061            "getCausedByException",
062            "getNested",
063            "getLinkedException",
064            "getNestedException",
065            "getLinkedCause",
066            "getThrowable",
067        };
068    
069        /**
070         * <p>
071         * Public constructor allows an instance of <code>ExceptionUtils</code> to be created, although that is not
072         * normally necessary.
073         * </p>
074         */
075        public ExceptionUtils() {
076            super();
077        }
078    
079        //-----------------------------------------------------------------------
080        /**
081         * <p>Returns the default names used when searching for the cause of an exception.</p>
082         *
083         * <p>This may be modified and used in the overloaded getCause(Throwable, String[]) method.</p>
084         *
085         * @return cloned array of the default method names
086         * @since 3.0
087         * @deprecated This feature will be removed in Lang 4.0
088         */
089        @Deprecated
090        public static String[] getDefaultCauseMethodNames() {
091            return ArrayUtils.clone(CAUSE_METHOD_NAMES);
092        }
093    
094        //-----------------------------------------------------------------------
095        /**
096         * <p>Introspects the <code>Throwable</code> to obtain the cause.</p>
097         *
098         * <p>The method searches for methods with specific names that return a 
099         * <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
121         */
122        @Deprecated
123        public static Throwable getCause(Throwable throwable) {
124            return getCause(throwable, CAUSE_METHOD_NAMES);
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
139         */
140        @Deprecated
141        public static Throwable getCause(Throwable throwable, String[] methodNames) {
142            if (throwable == null) {
143                return null;
144            }
145    
146            if (methodNames == null) {
147                methodNames = CAUSE_METHOD_NAMES;
148            }
149    
150            for (String methodName : methodNames) {
151                if (methodName != null) {
152                    Throwable cause = getCauseUsingMethodName(throwable, methodName);
153                    if (cause != null) {
154                        return cause;
155                    }
156                }
157            }
158    
159            return null;
160        }
161    
162        /**
163         * <p>Introspects the <code>Throwable</code> to obtain the root cause.</p>
164         *
165         * <p>This method walks through the exception chain to the last element,
166         * "root" of the tree, using {@link #getCause(Throwable)}, and
167         * returns that exception.</p>
168         *
169         * <p>From version 2.2, this method handles recursive cause structures
170         * that might otherwise cause infinite loops. If the throwable parameter
171         * has a cause of itself, then null will be returned. If the throwable
172         * parameter cause chain loops, the last element in the chain before the
173         * loop is returned.</p>
174         *
175         * @param throwable  the throwable to get the root cause for, may be null
176         * @return the root cause of the <code>Throwable</code>,
177         *  <code>null</code> if none found or null throwable input
178         */
179        public static Throwable getRootCause(Throwable throwable) {
180            List<Throwable> list = getThrowableList(throwable);
181            return list.size() < 2 ? null : (Throwable)list.get(list.size() - 1);
182        }
183    
184        /**
185         * <p>Finds a <code>Throwable</code> by method name.</p>
186         *
187         * @param throwable  the exception to examine
188         * @param methodName  the name of the method to find and invoke
189         * @return the wrapped exception, or <code>null</code> if not found
190         */
191        // TODO: Remove in Lang 4.0
192        private static Throwable getCauseUsingMethodName(Throwable throwable, String methodName) {
193            Method method = null;
194            try {
195                method = throwable.getClass().getMethod(methodName);
196            } catch (NoSuchMethodException ignored) { // NOPMD
197                // exception ignored
198            } catch (SecurityException ignored) { // NOPMD
199                // exception ignored
200            }
201    
202            if (method != null && Throwable.class.isAssignableFrom(method.getReturnType())) {
203                try {
204                    return (Throwable) method.invoke(throwable);
205                } catch (IllegalAccessException ignored) { // NOPMD
206                    // exception ignored
207                } catch (IllegalArgumentException ignored) { // NOPMD
208                    // exception ignored
209                } catch (InvocationTargetException ignored) { // NOPMD
210                    // exception ignored
211                }
212            }
213            return null;
214        }
215    
216        //-----------------------------------------------------------------------
217        /**
218         * <p>Counts the number of <code>Throwable</code> objects in the
219         * exception chain.</p>
220         *
221         * <p>A throwable without cause will return <code>1</code>.
222         * A throwable with one cause will return <code>2</code> and so on.
223         * A <code>null</code> throwable will return <code>0</code>.</p>
224         *
225         * <p>From version 2.2, this method handles recursive cause structures
226         * that might otherwise cause infinite loops. The cause chain is
227         * processed until the end is reached, or until the next item in the
228         * chain is already in the result set.</p>
229         *
230         * @param throwable  the throwable to inspect, may be null
231         * @return the count of throwables, zero if null input
232         */
233        public static int getThrowableCount(Throwable throwable) {
234            return getThrowableList(throwable).size();
235        }
236    
237        /**
238         * <p>Returns the list of <code>Throwable</code> objects in the
239         * exception chain.</p>
240         *
241         * <p>A throwable without cause will return an array containing
242         * one element - the input throwable.
243         * A throwable with one cause will return an array containing
244         * two elements. - the input throwable and the cause throwable.
245         * A <code>null</code> throwable will return an array of size zero.</p>
246         *
247         * <p>From version 2.2, this method handles recursive cause structures
248         * that might otherwise cause infinite loops. The cause chain is
249         * processed until the end is reached, or until the next item in the
250         * chain is already in the result set.</p>
251         *
252         * @see #getThrowableList(Throwable)
253         * @param throwable  the throwable to inspect, may be null
254         * @return the array of throwables, never null
255         */
256        public static Throwable[] getThrowables(Throwable throwable) {
257            List<Throwable> list = getThrowableList(throwable);
258            return list.toArray(new Throwable[list.size()]);
259        }
260    
261        /**
262         * <p>Returns the list of <code>Throwable</code> objects in the
263         * exception chain.</p>
264         *
265         * <p>A throwable without cause will return a list containing
266         * one element - the input throwable.
267         * A throwable with one cause will return a list containing
268         * two elements. - the input throwable and the cause throwable.
269         * A <code>null</code> throwable will return a list of size zero.</p>
270         *
271         * <p>This method handles recursive cause structures that might
272         * otherwise cause infinite loops. The cause chain is processed until
273         * the end is reached, or until the next item in the chain is already
274         * in the result set.</p>
275         *
276         * @param throwable  the throwable to inspect, may be null
277         * @return the list of throwables, never null
278         * @since Commons Lang 2.2
279         */
280        public static List<Throwable> getThrowableList(Throwable throwable) {
281            List<Throwable> list = new ArrayList<Throwable>();
282            while (throwable != null && list.contains(throwable) == false) {
283                list.add(throwable);
284                throwable = ExceptionUtils.getCause(throwable);
285            }
286            return list;
287        }
288    
289        //-----------------------------------------------------------------------
290        /**
291         * <p>Returns the (zero based) index of the first <code>Throwable</code>
292         * that matches the specified class (exactly) in the exception chain.
293         * Subclasses of the specified class do not match - see
294         * {@link #indexOfType(Throwable, Class)} for the opposite.</p>
295         *
296         * <p>A <code>null</code> throwable returns <code>-1</code>.
297         * A <code>null</code> type returns <code>-1</code>.
298         * No match in the chain returns <code>-1</code>.</p>
299         *
300         * @param throwable  the throwable to inspect, may be null
301         * @param clazz  the class to search for, subclasses do not match, null returns -1
302         * @return the index into the throwable chain, -1 if no match or null input
303         */
304        public static int indexOfThrowable(Throwable throwable, Class<?> clazz) {
305            return indexOf(throwable, clazz, 0, false);
306        }
307    
308        /**
309         * <p>Returns the (zero based) index of the first <code>Throwable</code>
310         * that matches the specified type in the exception chain from
311         * a specified index.
312         * Subclasses of the specified class do not match - see
313         * {@link #indexOfType(Throwable, Class, int)} for the opposite.</p>
314         *
315         * <p>A <code>null</code> throwable returns <code>-1</code>.
316         * A <code>null</code> type returns <code>-1</code>.
317         * No match in the chain returns <code>-1</code>.
318         * A negative start index is treated as zero.
319         * A start index greater than the number of throwables returns <code>-1</code>.</p>
320         *
321         * @param throwable  the throwable to inspect, may be null
322         * @param clazz  the class to search for, subclasses do not match, null returns -1
323         * @param fromIndex  the (zero based) index of the starting position,
324         *  negative treated as zero, larger than chain size returns -1
325         * @return the index into the throwable chain, -1 if no match or null input
326         */
327        public static int indexOfThrowable(Throwable throwable, Class<?> clazz, int fromIndex) {
328            return indexOf(throwable, clazz, fromIndex, false);
329        }
330    
331        //-----------------------------------------------------------------------
332        /**
333         * <p>Returns the (zero based) index of the first <code>Throwable</code>
334         * that matches the specified class or subclass in the exception chain.
335         * Subclasses of the specified class do match - see
336         * {@link #indexOfThrowable(Throwable, Class)} for the opposite.</p>
337         *
338         * <p>A <code>null</code> throwable returns <code>-1</code>.
339         * A <code>null</code> type returns <code>-1</code>.
340         * No match in the chain returns <code>-1</code>.</p>
341         *
342         * @param throwable  the throwable to inspect, may be null
343         * @param type  the type to search for, subclasses match, null returns -1
344         * @return the index into the throwable chain, -1 if no match or null input
345         * @since 2.1
346         */
347        public static int indexOfType(Throwable throwable, Class<?> type) {
348            return indexOf(throwable, type, 0, true);
349        }
350    
351        /**
352         * <p>Returns the (zero based) index of the first <code>Throwable</code>
353         * that matches the specified type in the exception chain from
354         * a specified index.
355         * Subclasses of the specified class do match - see
356         * {@link #indexOfThrowable(Throwable, Class)} for the opposite.</p>
357         *
358         * <p>A <code>null</code> throwable returns <code>-1</code>.
359         * A <code>null</code> type returns <code>-1</code>.
360         * No match in the chain returns <code>-1</code>.
361         * A negative start index is treated as zero.
362         * A start index greater than the number of throwables returns <code>-1</code>.</p>
363         *
364         * @param throwable  the throwable to inspect, may be null
365         * @param type  the type to search for, subclasses match, null returns -1
366         * @param fromIndex  the (zero based) index of the starting position,
367         *  negative treated as zero, larger than chain size returns -1
368         * @return the index into the throwable chain, -1 if no match or null input
369         * @since 2.1
370         */
371        public static int indexOfType(Throwable throwable, Class<?> type, int fromIndex) {
372            return indexOf(throwable, type, fromIndex, true);
373        }
374    
375        /**
376         * <p>Worker method for the <code>indexOfType</code> methods.</p>
377         *
378         * @param throwable  the throwable to inspect, may be null
379         * @param type  the type to search for, subclasses match, null returns -1
380         * @param fromIndex  the (zero based) index of the starting position,
381         *  negative treated as zero, larger than chain size returns -1
382         * @param subclass if <code>true</code>, compares with {@link Class#isAssignableFrom(Class)}, otherwise compares
383         * using references
384         * @return index of the <code>type</code> within throwables nested withing the specified <code>throwable</code>
385         */
386        private static int indexOf(Throwable throwable, Class<?> type, int fromIndex, boolean subclass) {
387            if (throwable == null || type == null) {
388                return -1;
389            }
390            if (fromIndex < 0) {
391                fromIndex = 0;
392            }
393            Throwable[] throwables = ExceptionUtils.getThrowables(throwable);
394            if (fromIndex >= throwables.length) {
395                return -1;
396            }
397            if (subclass) {
398                for (int i = fromIndex; i < throwables.length; i++) {
399                    if (type.isAssignableFrom(throwables[i].getClass())) {
400                        return i;
401                    }
402                }
403            } else {
404                for (int i = fromIndex; i < throwables.length; i++) {
405                    if (type.equals(throwables[i].getClass())) {
406                        return i;
407                    }
408                }
409            }
410            return -1;
411        }
412    
413        //-----------------------------------------------------------------------
414        /**
415         * <p>Prints a compact stack trace for the root cause of a throwable
416         * to <code>System.err</code>.</p>
417         *
418         * <p>The compact stack trace starts with the root cause and prints
419         * stack frames up to the place where it was caught and wrapped.
420         * Then it prints the wrapped exception and continues with stack frames
421         * until the wrapper exception is caught and wrapped again, etc.</p>
422         *
423         * <p>The output of this method is consistent across JDK versions.
424         * Note that this is the opposite order to the JDK1.4 display.</p>
425         *
426         * <p>The method is equivalent to <code>printStackTrace</code> for throwables
427         * that don't have nested causes.</p>
428         *
429         * @param throwable  the throwable to output
430         * @since 2.0
431         */
432        public static void printRootCauseStackTrace(Throwable throwable) {
433            printRootCauseStackTrace(throwable, System.err);
434        }
435    
436        /**
437         * <p>Prints a compact stack trace for the root cause of a throwable.</p>
438         *
439         * <p>The compact stack trace starts with the root cause and prints
440         * stack frames up to the place where it was caught and wrapped.
441         * Then it prints the wrapped exception and continues with stack frames
442         * until the wrapper exception is caught and wrapped again, etc.</p>
443         *
444         * <p>The output of this method is consistent across JDK versions.
445         * Note that this is the opposite order to the JDK1.4 display.</p>
446         *
447         * <p>The method is equivalent to <code>printStackTrace</code> for throwables
448         * that don't have nested causes.</p>
449         *
450         * @param throwable  the throwable to output, may be null
451         * @param stream  the stream to output to, may not be null
452         * @throws IllegalArgumentException if the stream is <code>null</code>
453         * @since 2.0
454         */
455        public static void printRootCauseStackTrace(Throwable throwable, PrintStream stream) {
456            if (throwable == null) {
457                return;
458            }
459            if (stream == null) {
460                throw new IllegalArgumentException("The PrintStream must not be null");
461            }
462            String trace[] = getRootCauseStackTrace(throwable);
463            for (String element : trace) {
464                stream.println(element);
465            }
466            stream.flush();
467        }
468    
469        /**
470         * <p>Prints a compact stack trace for the root cause of a throwable.</p>
471         *
472         * <p>The compact stack trace starts with the root cause and prints
473         * stack frames up to the place where it was caught and wrapped.
474         * Then it prints the wrapped exception and continues with stack frames
475         * until the wrapper exception is caught and wrapped again, etc.</p>
476         *
477         * <p>The output of this method is consistent across JDK versions.
478         * Note that this is the opposite order to the JDK1.4 display.</p>
479         *
480         * <p>The method is equivalent to <code>printStackTrace</code> for throwables
481         * that don't have nested causes.</p>
482         *
483         * @param throwable  the throwable to output, may be null
484         * @param writer  the writer to output to, may not be null
485         * @throws IllegalArgumentException if the writer is <code>null</code>
486         * @since 2.0
487         */
488        public static void printRootCauseStackTrace(Throwable throwable, PrintWriter writer) {
489            if (throwable == null) {
490                return;
491            }
492            if (writer == null) {
493                throw new IllegalArgumentException("The PrintWriter must not be null");
494            }
495            String trace[] = getRootCauseStackTrace(throwable);
496            for (String element : trace) {
497                writer.println(element);
498            }
499            writer.flush();
500        }
501    
502        //-----------------------------------------------------------------------
503        /**
504         * <p>Creates a compact stack trace for the root cause of the supplied
505         * <code>Throwable</code>.</p>
506         *
507         * <p>The output of this method is consistent across JDK versions.
508         * It consists of the root exception followed by each of its wrapping
509         * exceptions separated by '[wrapped]'. Note that this is the opposite
510         * order to the JDK1.4 display.</p>
511         *
512         * @param throwable  the throwable to examine, may be null
513         * @return an array of stack trace frames, never null
514         * @since 2.0
515         */
516        public static String[] getRootCauseStackTrace(Throwable throwable) {
517            if (throwable == null) {
518                return ArrayUtils.EMPTY_STRING_ARRAY;
519            }
520            Throwable throwables[] = getThrowables(throwable);
521            int count = throwables.length;
522            List<String> frames = new ArrayList<String>();
523            List<String> nextTrace = getStackFrameList(throwables[count - 1]);
524            for (int i = count; --i >= 0;) {
525                List<String> trace = nextTrace;
526                if (i != 0) {
527                    nextTrace = getStackFrameList(throwables[i - 1]);
528                    removeCommonFrames(trace, nextTrace);
529                }
530                if (i == count - 1) {
531                    frames.add(throwables[i].toString());
532                } else {
533                    frames.add(WRAPPED_MARKER + throwables[i].toString());
534                }
535                for (int j = 0; j < trace.size(); j++) {
536                    frames.add(trace.get(j));
537                }
538            }
539            return frames.toArray(new String[frames.size()]);
540        }
541    
542        /**
543         * <p>Removes common frames from the cause trace given the two stack traces.</p>
544         *
545         * @param causeFrames  stack trace of a cause throwable
546         * @param wrapperFrames  stack trace of a wrapper throwable
547         * @throws IllegalArgumentException if either argument is null
548         * @since 2.0
549         */
550        public static void removeCommonFrames(List<String> causeFrames, List<String> wrapperFrames) {
551            if (causeFrames == null || wrapperFrames == null) {
552                throw new IllegalArgumentException("The List must not be null");
553            }
554            int causeFrameIndex = causeFrames.size() - 1;
555            int wrapperFrameIndex = wrapperFrames.size() - 1;
556            while (causeFrameIndex >= 0 && wrapperFrameIndex >= 0) {
557                // Remove the frame from the cause trace if it is the same
558                // as in the wrapper trace
559                String causeFrame = causeFrames.get(causeFrameIndex);
560                String wrapperFrame = wrapperFrames.get(wrapperFrameIndex);
561                if (causeFrame.equals(wrapperFrame)) {
562                    causeFrames.remove(causeFrameIndex);
563                }
564                causeFrameIndex--;
565                wrapperFrameIndex--;
566            }
567        }
568    
569        //-----------------------------------------------------------------------
570        /**
571         * <p>Gets the stack trace from a Throwable as a String.</p>
572         *
573         * <p>The result of this method vary by JDK version as this method
574         * uses {@link Throwable#printStackTrace(java.io.PrintWriter)}.
575         * On JDK1.3 and earlier, the cause exception will not be shown
576         * unless the specified throwable alters printStackTrace.</p>
577         *
578         * @param throwable  the <code>Throwable</code> to be examined
579         * @return the stack trace as generated by the exception's
580         *  <code>printStackTrace(PrintWriter)</code> method
581         */
582        public static String getStackTrace(Throwable throwable) {
583            StringWriter sw = new StringWriter();
584            PrintWriter pw = new PrintWriter(sw, true);
585            throwable.printStackTrace(pw);
586            return sw.getBuffer().toString();
587        }
588    
589        /**
590         * <p>Captures the stack trace associated with the specified
591         * <code>Throwable</code> object, decomposing it into a list of
592         * stack frames.</p>
593         *
594         * <p>The result of this method vary by JDK version as this method
595         * uses {@link Throwable#printStackTrace(java.io.PrintWriter)}.
596         * On JDK1.3 and earlier, the cause exception will not be shown
597         * unless the specified throwable alters printStackTrace.</p>
598         *
599         * @param throwable  the <code>Throwable</code> to examine, may be null
600         * @return an array of strings describing each stack frame, never null
601         */
602        public static String[] getStackFrames(Throwable throwable) {
603            if (throwable == null) {
604                return ArrayUtils.EMPTY_STRING_ARRAY;
605            }
606            return getStackFrames(getStackTrace(throwable));
607        }
608    
609        //-----------------------------------------------------------------------
610        /**
611         * <p>Returns an array where each element is a line from the argument.</p>
612         *
613         * <p>The end of line is determined by the value of {@link SystemUtils#LINE_SEPARATOR}.</p>
614         *
615         * @param stackTrace  a stack trace String
616         * @return an array where each element is a line from the argument
617         */
618        static String[] getStackFrames(String stackTrace) {
619            String linebreak = SystemUtils.LINE_SEPARATOR;
620            StringTokenizer frames = new StringTokenizer(stackTrace, linebreak);
621            List<String> list = new ArrayList<String>();
622            while (frames.hasMoreTokens()) {
623                list.add(frames.nextToken());
624            }
625            return list.toArray(new String[list.size()]);
626        }
627    
628        /**
629         * <p>Produces a <code>List</code> of stack frames - the message
630         * is not included. Only the trace of the specified exception is
631         * returned, any caused by trace is stripped.</p>
632         *
633         * <p>This works in most cases - it will only fail if the exception
634         * message contains a line that starts with:
635         * <code>&quot;&nbsp;&nbsp;&nbsp;at&quot;.</code></p>
636         * 
637         * @param t is any throwable
638         * @return List of stack frames
639         */
640        static List<String> getStackFrameList(Throwable t) {
641            String stackTrace = getStackTrace(t);
642            String linebreak = SystemUtils.LINE_SEPARATOR;
643            StringTokenizer frames = new StringTokenizer(stackTrace, linebreak);
644            List<String> list = new ArrayList<String>();
645            boolean traceStarted = false;
646            while (frames.hasMoreTokens()) {
647                String token = frames.nextToken();
648                // Determine if the line starts with <whitespace>at
649                int at = token.indexOf("at");
650                if (at != -1 && token.substring(0, at).trim().length() == 0) {
651                    traceStarted = true;
652                    list.add(token);
653                } else if (traceStarted) {
654                    break;
655                }
656            }
657            return list;
658        }
659    
660        //-----------------------------------------------------------------------
661        /**
662         * Gets a short message summarising the exception.
663         * <p>
664         * The message returned is of the form
665         * {ClassNameWithoutPackage}: {ThrowableMessage}
666         *
667         * @param th  the throwable to get a message for, null returns empty string
668         * @return the message, non-null
669         * @since Commons Lang 2.2
670         */
671        public static String getMessage(Throwable th) {
672            if (th == null) {
673                return "";
674            }
675            String clsName = ClassUtils.getShortClassName(th, null);
676            String msg = th.getMessage();
677            return clsName + ": " + StringUtils.defaultString(msg);
678        }
679    
680        //-----------------------------------------------------------------------
681        /**
682         * Gets a short message summarising the root cause exception.
683         * <p>
684         * The message returned is of the form
685         * {ClassNameWithoutPackage}: {ThrowableMessage}
686         *
687         * @param th  the throwable to get a message for, null returns empty string
688         * @return the message, non-null
689         * @since Commons Lang 2.2
690         */
691        public static String getRootCauseMessage(Throwable th) {
692            Throwable root = ExceptionUtils.getRootCause(th);
693            root = root == null ? th : root;
694            return getMessage(root);
695        }
696    
697    }