    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    }