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