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