    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 co.aikar.commands.apachecommonslang;
    018
    019import java.io.PrintStream;
    020import java.io.PrintWriter;
    021import java.io.StringWriter;
    022import java.lang.reflect.Field;
    023import java.lang.reflect.InvocationTargetException;
    024import java.lang.reflect.Method;
    025import java.sql.SQLException;
    026import java.util.ArrayList;
    027import java.util.Arrays;
    028import java.util.List;
    029import java.util.StringTokenizer;
    030
    031/**
    032 * <p>Provides utilities for manipulating and examining
    033 * <code>Throwable</code> objects.</p>
    034 *
    035 * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
    036 * @author Dmitri Plotnikov
    037 * @author Stephen Colebourne
    038 * @author <a href="mailto:ggregory@seagullsw.com">Gary Gregory</a>
    039 * @author Pete Gieser
    040 * @since 1.0
    041 * @version $Id$
    042 */
    043public class ApacheCommonsExceptionUtil {
    044    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
    045
    046    /**
    047     * <p>Used when printing stack frames to denote the start of a
    048     * wrapped exception.</p>
    049     *
    050     * <p>Package private for accessibility by test suite.</p>
    051     */
    052    static final String WRAPPED_MARKER = " [wrapped] ";
    053
    054    /**
    055     * <p>The names of methods commonly used to access a wrapped exception.</p>
    056     */
    057    private static String[] CAUSE_METHOD_NAMES = {
    058            "getCause",
    059            "getNextException",
    060            "getTargetException",
    061            "getException",
    062            "getSourceException",
    063            "getRootCause",
    064            "getCausedByException",
    065            "getNested",
    066            "getLinkedException",
    067            "getNestedException",
    068            "getLinkedCause",
    069            "getThrowable",
    070    };
    071
    072    /**
    073     * <p>The Method object for Java 1.4 getCause.</p>
    074     */
    075    private static final Method THROWABLE_CAUSE_METHOD;
    076
    077    /**
    078     * <p>The Method object for Java 1.4 initCause.</p>
    079     */
    080    private static final Method THROWABLE_INITCAUSE_METHOD;
    081
    082    static {
    083        Method causeMethod;
    084        try {
    085            causeMethod = Throwable.class.getMethod("getCause", null);
    086        } catch (Exception e) {
    087            causeMethod = null;
    088        }
    089        THROWABLE_CAUSE_METHOD = causeMethod;
    090        try {
    091            causeMethod = Throwable.class.getMethod("initCause", Throwable.class);
    092        } catch (Exception e) {
    093            causeMethod = null;
    094        }
    095        THROWABLE_INITCAUSE_METHOD = causeMethod;
    096    }
    097
    098    /**
    099     * <p>
    100     * Public constructor allows an instance of <code>ExceptionUtils</code> to be created, although that is not
    101     * normally necessary.
    102     * </p>
    103     */
    104    public ApacheCommonsExceptionUtil() {
    105        super();
    106    }
    107
    108    //-----------------------------------------------------------------------
    109    /**
    110     * <p>Adds to the list of method names used in the search for <code>Throwable</code>
    111     * objects.</p>
    112     *
    113     * @param methodName  the methodName to add to the list, <code>null</code>
    114     *  and empty strings are ignored
    115     * @since 2.0
    116     */
    117    public static void addCauseMethodName(String methodName) {
    118        if (methodName != null && !methodName.isEmpty() && !isCauseMethodName(methodName)) {
    119            List list = getCauseMethodNameList();
    120            if (list.add(methodName)) {
    121                CAUSE_METHOD_NAMES = toArray(list);
    122            }
    123        }
    124    }
    125
    126    /**
    127     * <p>Removes from the list of method names used in the search for <code>Throwable</code>
    128     * objects.</p>
    129     *
    130     * @param methodName  the methodName to remove from the list, <code>null</code>
    131     *  and empty strings are ignored
    132     * @since 2.1
    133     */
    134    public static void removeCauseMethodName(String methodName) {
    135        if (methodName != null && !methodName.isEmpty()) {
    136            List list = getCauseMethodNameList();
    137            if (list.remove(methodName)) {
    138                CAUSE_METHOD_NAMES = toArray(list);
    139            }
    140        }
    141    }
    142
    143    /**
    144     * <p>Sets the cause of a <code>Throwable</code> using introspection, allowing
    145     * source code compatibility between pre-1.4 and post-1.4 Java releases.</p>
    146     *
    147     * <p>The typical use of this method is inside a constructor as in
    148     * the following example:</p>
    149     *
    150     * <pre>
    151     * import org.apache.commons.lang.exception.ExceptionUtils;
    152     *
    153     * public class MyException extends Exception {
    154     *
    155     *    public MyException(String msg) {
    156     *       super(msg);
    157     *    }
    158     *
    159     *    public MyException(String msg, Throwable cause) {
    160     *       super(msg);
    161     *       ExceptionUtils.setCause(this, cause);
    162     *    }
    163     * }
    164     * </pre>
    165     *
    166     * @param target  the target <code>Throwable</code>
    167     * @param cause  the <code>Throwable</code> to set in the target
    168     * @return a <code>true</code> if the target has been modified
    169     * @since 2.2
    170     */
    171    public static boolean setCause(Throwable target, Throwable cause) {
    172        if (target == null) {
    173            throw new IllegalArgumentException("target");
    174        }
    175        Object[] causeArgs = new Object[]{cause};
    176        boolean modifiedTarget = false;
    177        if (THROWABLE_INITCAUSE_METHOD != null) {
    178            try {
    179                THROWABLE_INITCAUSE_METHOD.invoke(target, causeArgs);
    180                modifiedTarget = true;
    181            } catch (IllegalAccessException ignored) {
    182                // Exception ignored.
    183            } catch (InvocationTargetException ignored) {
    184                // Exception ignored.
    185            }
    186        }
    187        try {
    188            Method setCauseMethod = target.getClass().getMethod("setCause", Throwable.class);
    189            setCauseMethod.invoke(target, causeArgs);
    190            modifiedTarget = true;
    191        } catch (NoSuchMethodException ignored) {
    192            // Exception ignored.
    193        } catch (IllegalAccessException ignored) {
    194            // Exception ignored.
    195        } catch (InvocationTargetException ignored) {
    196            // Exception ignored.
    197        }
    198        return modifiedTarget;
    199    }
    200
    201    /**
    202     * Returns the given list as a <code>String[]</code>.
    203     * @param list a list to transform.
    204     * @return the given list as a <code>String[]</code>.
    205     */
    206    private static String[] toArray(List list) {
    207        return (String[]) list.toArray(new String[list.size()]);
    208    }
    209
    210    /**
    211     * Returns {@link #CAUSE_METHOD_NAMES} as a List.
    212     *
    213     * @return {@link #CAUSE_METHOD_NAMES} as a List.
    214     */
    215    private static ArrayList getCauseMethodNameList() {
    216        return new ArrayList(Arrays.asList(CAUSE_METHOD_NAMES));
    217    }
    218
    219    /**
    220     * <p>Tests if the list of method names used in the search for <code>Throwable</code>
    221     * objects include the given name.</p>
    222     *
    223     * @param methodName  the methodName to search in the list.
    224     * @return if the list of method names used in the search for <code>Throwable</code>
    225     *  objects include the given name.
    226     * @since 2.1
    227     */
    228    public static boolean isCauseMethodName(String methodName) {
    229        return ApacheCommonsLangUtil.indexOf(CAUSE_METHOD_NAMES, methodName) >= 0;
    230    }
    231
    232    //-----------------------------------------------------------------------
    233    /**
    234     * <p>Introspects the <code>Throwable</code> to obtain the cause.</p>
    235     *
    236     * <p>The method searches for methods with specific names that return a
    237     * <code>Throwable</code> object. This will pick up most wrapping exceptions,
    238     * including those from JDK 1.4, and
    239     * The method names can be added to using {@link #addCauseMethodName(String)}.</p>
    240     *
    241     * <p>The default list searched for are:</p>
    242     * <ul>
    243     *  <li><code>getCause()</code></li>
    244     *  <li><code>getNextException()</code></li>
    245     *  <li><code>getTargetException()</code></li>
    246     *  <li><code>getException()</code></li>
    247     *  <li><code>getSourceException()</code></li>
    248     *  <li><code>getRootCause()</code></li>
    249     *  <li><code>getCausedByException()</code></li>
    250     *  <li><code>getNested()</code></li>
    251     * </ul>
    252     *
    253     * <p>In the absence of any such method, the object is inspected for a
    254     * <code>detail</code> field assignable to a <code>Throwable</code>.</p>
    255     *
    256     * <p>If none of the above is found, returns <code>null</code>.</p>
    257     *
    258     * @param throwable  the throwable to introspect for a cause, may be null
    259     * @return the cause of the <code>Throwable</code>,
    260     *  <code>null</code> if none found or null throwable input
    261     * @since 1.0
    262     */
    263    public static Throwable getCause(Throwable throwable) {
    264        return getCause(throwable, CAUSE_METHOD_NAMES);
    265    }
    266
    267    /**
    268     * <p>Introspects the <code>Throwable</code> to obtain the cause.</p>
    269     *
    270     * <ol>
    271     * <li>Try known exception types.</li>
    272     * <li>Try the supplied array of method names.</li>
    273     * <li>Try the field 'detail'.</li>
    274     * </ol>
    275     *
    276     * <p>A <code>null</code> set of method names means use the default set.
    277     * A <code>null</code> in the set of method names will be ignored.</p>
    278     *
    279     * @param throwable  the throwable to introspect for a cause, may be null
    280     * @param methodNames  the method names, null treated as default set
    281     * @return the cause of the <code>Throwable</code>,
    282     *  <code>null</code> if none found or null throwable input
    283     * @since 1.0
    284     */
    285    public static Throwable getCause(Throwable throwable, String[] methodNames) {
    286        if (throwable == null) {
    287            return null;
    288        }
    289        Throwable cause = getCauseUsingWellKnownTypes(throwable);
    290        if (cause == null) {
    291            if (methodNames == null) {
    292                methodNames = CAUSE_METHOD_NAMES;
    293            }
    294            for (int i = 0; i < methodNames.length; i++) {
    295                String methodName = methodNames[i];
    296                if (methodName != null) {
    297                    cause = getCauseUsingMethodName(throwable, methodName);
    298                    if (cause != null) {
    299                        break;
    300                    }
    301                }
    302            }
    303
    304            if (cause == null) {
    305                cause = getCauseUsingFieldName(throwable, "detail");
    306            }
    307        }
    308        return cause;
    309    }
    310
    311    /**
    312     * <p>Introspects the <code>Throwable</code> to obtain the root cause.</p>
    313     *
    314     * <p>This method walks through the exception chain to the last element,
    315     * "root" of the tree, using {@link #getCause(Throwable)}, and
    316     * returns that exception.</p>
    317     *
    318     * <p>From version 2.2, this method handles recursive cause structures
    319     * that might otherwise cause infinite loops. If the throwable parameter
    320     * has a cause of itself, then null will be returned. If the throwable
    321     * parameter cause chain loops, the last element in the chain before the
    322     * loop is returned.</p>
    323     *
    324     * @param throwable  the throwable to get the root cause for, may be null
    325     * @return the root cause of the <code>Throwable</code>,
    326     *  <code>null</code> if none found or null throwable input
    327     */
    328    public static Throwable getRootCause(Throwable throwable) {
    329        List list = getThrowableList(throwable);
    330        return (list.size() < 2 ? null : (Throwable)list.get(list.size() - 1));
    331    }
    332
    333    /**
    334     * <p>Finds a <code>Throwable</code> for known types.</p>
    335     *
    336     * <p>Uses <code>instanceof</code> checks to examine the exception,
    337     * looking for well known types which could contain chained or
    338     * wrapped exceptions.</p>
    339     *
    340     * @param throwable  the exception to examine
    341     * @return the wrapped exception, or <code>null</code> if not found
    342     */
    343    private static Throwable getCauseUsingWellKnownTypes(Throwable throwable) {
    344        if (throwable instanceof Nestable) {
    345            return throwable.getCause();
    346        } else if (throwable instanceof SQLException) {
    347            return ((SQLException) throwable).getNextException();
    348        } else if (throwable instanceof InvocationTargetException) {
    349            return ((InvocationTargetException) throwable).getTargetException();
    350        } else {
    351            return null;
    352        }
    353    }
    354
    355    /**
    356     * <p>Finds a <code>Throwable</code> by method name.</p>
    357     *
    358     * @param throwable  the exception to examine
    359     * @param methodName  the name of the method to find and invoke
    360     * @return the wrapped exception, or <code>null</code> if not found
    361     */
    362    private static Throwable getCauseUsingMethodName(Throwable throwable, String methodName) {
    363        Method method = null;
    364        try {
    365            method = throwable.getClass().getMethod(methodName, null);
    366        } catch (NoSuchMethodException ignored) {
    367            // exception ignored
    368        } catch (SecurityException ignored) {
    369            // exception ignored
    370        }
    371
    372        if (method != null && Throwable.class.isAssignableFrom(method.getReturnType())) {
    373            try {
    374                return (Throwable) method.invoke(throwable);
    375            } catch (IllegalAccessException ignored) {
    376                // exception ignored
    377            } catch (IllegalArgumentException ignored) {
    378                // exception ignored
    379            } catch (InvocationTargetException ignored) {
    380                // exception ignored
    381            }
    382        }
    383        return null;
    384    }
    385
    386    /**
    387     * <p>Finds a <code>Throwable</code> by field name.</p>
    388     *
    389     * @param throwable  the exception to examine
    390     * @param fieldName  the name of the attribute to examine
    391     * @return the wrapped exception, or <code>null</code> if not found
    392     */
    393    private static Throwable getCauseUsingFieldName(Throwable throwable, String fieldName) {
    394        Field field = null;
    395        try {
    396            field = throwable.getClass().getField(fieldName);
    397        } catch (NoSuchFieldException ignored) {
    398            // exception ignored
    399        } catch (SecurityException ignored) {
    400            // exception ignored
    401        }
    402
    403        if (field != null && Throwable.class.isAssignableFrom(field.getType())) {
    404            try {
    405                return (Throwable) field.get(throwable);
    406            } catch (IllegalAccessException ignored) {
    407                // exception ignored
    408            } catch (IllegalArgumentException ignored) {
    409                // exception ignored
    410            }
    411        }
    412        return null;
    413    }
    414
    415    //-----------------------------------------------------------------------
    416    /**
    417     * <p>Checks if the Throwable class has a <code>getCause</code> method.</p>
    418     *
    419     * <p>This is true for JDK 1.4 and above.</p>
    420     *
    421     * @return true if Throwable is nestable
    422     * @since 2.0
    423     */
    424    public static boolean isThrowableNested() {
    425        return THROWABLE_CAUSE_METHOD != null;
    426    }
    427
    428    /**
    429     * <p>Checks whether this <code>Throwable</code> class can store a cause.</p>
    430     *
    431     * <p>This method does <b>not</b> check whether it actually does store a cause.<p>
    432     *
    433     * @param throwable  the <code>Throwable</code> to examine, may be null
    434     * @return boolean <code>true</code> if nested otherwise <code>false</code>
    435     * @since 2.0
    436     */
    437    public static boolean isNestedThrowable(Throwable throwable) {
    438        if (throwable == null) {
    439            return false;
    440        }
    441
    442        if (throwable instanceof Nestable) {
    443            return true;
    444        } else if (throwable instanceof SQLException) {
    445            return true;
    446        } else if (throwable instanceof InvocationTargetException) {
    447            return true;
    448        } else if (isThrowableNested()) {
    449            return true;
    450        }
    451
    452        Class cls = throwable.getClass();
    453        for (int i = 0, isize = CAUSE_METHOD_NAMES.length; i < isize; i++) {
    454            try {
    455                Method method = cls.getMethod(CAUSE_METHOD_NAMES[i], null);
    456                if (method != null && Throwable.class.isAssignableFrom(method.getReturnType())) {
    457                    return true;
    458                }
    459            } catch (NoSuchMethodException ignored) {
    460                // exception ignored
    461            } catch (SecurityException ignored) {
    462                // exception ignored
    463            }
    464        }
    465
    466        try {
    467            Field field = cls.getField("detail");
    468            if (field != null) {
    469                return true;
    470            }
    471        } catch (NoSuchFieldException ignored) {
    472            // exception ignored
    473        } catch (SecurityException ignored) {
    474            // exception ignored
    475        }
    476
    477        return false;
    478    }
    479
    480    //-----------------------------------------------------------------------
    481    /**
    482     * <p>Counts the number of <code>Throwable</code> objects in the
    483     * exception chain.</p>
    484     *
    485     * <p>A throwable without cause will return <code>1</code>.
    486     * A throwable with one cause will return <code>2</code> and so on.
    487     * A <code>null</code> throwable will return <code>0</code>.</p>
    488     *
    489     * <p>From version 2.2, this method handles recursive cause structures
    490     * that might otherwise cause infinite loops. The cause chain is
    491     * processed until the end is reached, or until the next item in the
    492     * chain is already in the result set.</p>
    493     *
    494     * @param throwable  the throwable to inspect, may be null
    495     * @return the count of throwables, zero if null input
    496     */
    497    public static int getThrowableCount(Throwable throwable) {
    498        return getThrowableList(throwable).size();
    499    }
    500
    501    /**
    502     * <p>Returns the list of <code>Throwable</code> objects in the
    503     * exception chain.</p>
    504     *
    505     * <p>A throwable without cause will return an array containing
    506     * one element - the input throwable.
    507     * A throwable with one cause will return an array containing
    508     * two elements. - the input throwable and the cause throwable.
    509     * A <code>null</code> throwable will return an array of size zero.</p>
    510     *
    511     * <p>From version 2.2, this method handles recursive cause structures
    512     * that might otherwise cause infinite loops. The cause chain is
    513     * processed until the end is reached, or until the next item in the
    514     * chain is already in the result set.</p>
    515     *
    516     * @see #getThrowableList(Throwable)
    517     * @param throwable  the throwable to inspect, may be null
    518     * @return the array of throwables, never null
    519     */
    520    public static Throwable[] getThrowables(Throwable throwable) {
    521        List list = getThrowableList(throwable);
    522        return (Throwable[]) list.toArray(new Throwable[list.size()]);
    523    }
    524
    525    /**
    526     * <p>Returns the list of <code>Throwable</code> objects in the
    527     * exception chain.</p>
    528     *
    529     * <p>A throwable without cause will return a list containing
    530     * one element - the input throwable.
    531     * A throwable with one cause will return a list containing
    532     * two elements. - the input throwable and the cause throwable.
    533     * A <code>null</code> throwable will return a list of size zero.</p>
    534     *
    535     * <p>This method handles recursive cause structures that might
    536     * otherwise cause infinite loops. The cause chain is processed until
    537     * the end is reached, or until the next item in the chain is already
    538     * in the result set.</p>
    539     *
    540     * @param throwable  the throwable to inspect, may be null
    541     * @return the list of throwables, never null
    542     * @since Commons Lang 2.2
    543     */
    544    public static List getThrowableList(Throwable throwable) {
    545        List list = new ArrayList();
    546        while (throwable != null && list.contains(throwable) == false) {
    547            list.add(throwable);
    548            throwable = getCause(throwable);
    549        }
    550        return list;
    551    }
    552
    553    //-----------------------------------------------------------------------
    554    /**
    555     * <p>Returns the (zero based) index of the first <code>Throwable</code>
    556     * that matches the specified class (exactly) in the exception chain.
    557     * Subclasses of the specified class do not match - see
    558     * {@link #indexOfType(Throwable, Class)} for the opposite.</p>
    559     *
    560     * <p>A <code>null</code> throwable returns <code>-1</code>.
    561     * A <code>null</code> type returns <code>-1</code>.
    562     * No match in the chain returns <code>-1</code>.</p>
    563     *
    564     * @param throwable  the throwable to inspect, may be null
    565     * @param clazz  the class to search for, subclasses do not match, null returns -1
    566     * @return the index into the throwable chain, -1 if no match or null input
    567     */
    568    public static int indexOfThrowable(Throwable throwable, Class clazz) {
    569        return indexOf(throwable, clazz, 0, false);
    570    }
    571
    572    /**
    573     * <p>Returns the (zero based) index of the first <code>Throwable</code>
    574     * that matches the specified type in the exception chain from
    575     * a specified index.
    576     * Subclasses of the specified class do not match - see
    577     * {@link #indexOfType(Throwable, Class, int)} for the opposite.</p>
    578     *
    579     * <p>A <code>null</code> throwable returns <code>-1</code>.
    580     * A <code>null</code> type returns <code>-1</code>.
    581     * No match in the chain returns <code>-1</code>.
    582     * A negative start index is treated as zero.
    583     * A start index greater than the number of throwables returns <code>-1</code>.</p>
    584     *
    585     * @param throwable  the throwable to inspect, may be null
    586     * @param clazz  the class to search for, subclasses do not match, null returns -1
    587     * @param fromIndex  the (zero based) index of the starting position,
    588     *  negative treated as zero, larger than chain size returns -1
    589     * @return the index into the throwable chain, -1 if no match or null input
    590     */
    591    public static int indexOfThrowable(Throwable throwable, Class clazz, int fromIndex) {
    592        return indexOf(throwable, clazz, fromIndex, false);
    593    }
    594
    595    //-----------------------------------------------------------------------
    596    /**
    597     * <p>Returns the (zero based) index of the first <code>Throwable</code>
    598     * that matches the specified class or subclass in the exception chain.
    599     * Subclasses of the specified class do match - see
    600     * {@link #indexOfThrowable(Throwable, Class)} for the opposite.</p>
    601     *
    602     * <p>A <code>null</code> throwable returns <code>-1</code>.
    603     * A <code>null</code> type returns <code>-1</code>.
    604     * No match in the chain returns <code>-1</code>.</p>
    605     *
    606     * @param throwable  the throwable to inspect, may be null
    607     * @param type  the type to search for, subclasses match, null returns -1
    608     * @return the index into the throwable chain, -1 if no match or null input
    609     * @since 2.1
    610     */
    611    public static int indexOfType(Throwable throwable, Class type) {
    612        return indexOf(throwable, type, 0, true);
    613    }
    614
    615    /**
    616     * <p>Returns the (zero based) index of the first <code>Throwable</code>
    617     * that matches the specified type in the exception chain from
    618     * a specified index.
    619     * Subclasses of the specified class do match - see
    620     * {@link #indexOfThrowable(Throwable, Class)} for the opposite.</p>
    621     *
    622     * <p>A <code>null</code> throwable returns <code>-1</code>.
    623     * A <code>null</code> type returns <code>-1</code>.
    624     * No match in the chain returns <code>-1</code>.
    625     * A negative start index is treated as zero.
    626     * A start index greater than the number of throwables returns <code>-1</code>.</p>
    627     *
    628     * @param throwable  the throwable to inspect, may be null
    629     * @param type  the type to search for, subclasses match, null returns -1
    630     * @param fromIndex  the (zero based) index of the starting position,
    631     *  negative treated as zero, larger than chain size returns -1
    632     * @return the index into the throwable chain, -1 if no match or null input
    633     * @since 2.1
    634     */
    635    public static int indexOfType(Throwable throwable, Class type, int fromIndex) {
    636        return indexOf(throwable, type, fromIndex, true);
    637    }
    638
    639    /**
    640     * <p>Worker method for the <code>indexOfType</code> methods.</p>
    641     *
    642     * @param throwable  the throwable to inspect, may be null
    643     * @param type  the type to search for, subclasses match, null returns -1
    644     * @param fromIndex  the (zero based) index of the starting position,
    645     *  negative treated as zero, larger than chain size returns -1
    646     * @param subclass if <code>true</code>, compares with {@link Class#isAssignableFrom(Class)}, otherwise compares
    647     * using references
    648     * @return index of the <code>type</code> within throwables nested withing the specified <code>throwable</code>
    649     */
    650    private static int indexOf(Throwable throwable, Class type, int fromIndex, boolean subclass) {
    651        if (throwable == null || type == null) {
    652            return -1;
    653        }
    654        if (fromIndex < 0) {
    655            fromIndex = 0;
    656        }
    657        Throwable[] throwables = getThrowables(throwable);
    658        if (fromIndex >= throwables.length) {
    659            return -1;
    660        }
    661        if (subclass) {
    662            for (int i = fromIndex; i < throwables.length; i++) {
    663                if (type.isAssignableFrom(throwables[i].getClass())) {
    664                    return i;
    665                }
    666            }
    667        } else {
    668            for (int i = fromIndex; i < throwables.length; i++) {
    669                if (type.equals(throwables[i].getClass())) {
    670                    return i;
    671                }
    672            }
    673        }
    674        return -1;
    675    }
    676
    677    /**
    678     * <p>Removes common frames from the cause trace given the two stack traces.</p>
    679     *
    680     * @param causeFrames  stack trace of a cause throwable
    681     * @param wrapperFrames  stack trace of a wrapper throwable
    682     * @throws IllegalArgumentException if either argument is null
    683     * @since 2.0
    684     */
    685    public static void removeCommonFrames(List causeFrames, List wrapperFrames) {
    686        if (causeFrames == null || wrapperFrames == null) {
    687            throw new IllegalArgumentException("The List must not be null");
    688        }
    689        int causeFrameIndex = causeFrames.size() - 1;
    690        int wrapperFrameIndex = wrapperFrames.size() - 1;
    691        while (causeFrameIndex >= 0 && wrapperFrameIndex >= 0) {
    692            // Remove the frame from the cause trace if it is the same
    693            // as in the wrapper trace
    694            String causeFrame = (String) causeFrames.get(causeFrameIndex);
    695            String wrapperFrame = (String) wrapperFrames.get(wrapperFrameIndex);
    696            if (causeFrame.equals(wrapperFrame)) {
    697                causeFrames.remove(causeFrameIndex);
    698            }
    699            causeFrameIndex--;
    700            wrapperFrameIndex--;
    701        }
    702    }
    703
    704    //-----------------------------------------------------------------------
    705    /**
    706     * <p>A way to get the entire nested stack-trace of an throwable.</p>
    707     *
    708     * <p>The result of this method is highly dependent on the JDK version
    709     * and whether the exceptions override printStackTrace or not.</p>
    710     *
    711     * @param throwable  the <code>Throwable</code> to be examined
    712     * @return the nested stack trace, with the root cause first
    713     * @since 2.0
    714     */
    715    public static String getFullStackTrace(Throwable throwable) {
    716        StringWriter sw = new StringWriter();
    717        PrintWriter pw = new PrintWriter(sw, true);
    718        Throwable[] ts = getThrowables(throwable);
    719        for (int i = 0; i < ts.length; i++) {
    720            ts[i].printStackTrace(pw);
    721            if (isNestedThrowable(ts[i])) {
    722                break;
    723            }
    724        }
    725        return sw.getBuffer().toString();
    726    }
    727
    728    //-----------------------------------------------------------------------
    729    /**
    730     * <p>Gets the stack trace from a Throwable as a String.</p>
    731     *
    732     * <p>The result of this method vary by JDK version as this method
    733     * uses {@link Throwable#printStackTrace(java.io.PrintWriter)}.
    734     * On JDK1.3 and earlier, the cause exception will not be shown
    735     * unless the specified throwable alters printStackTrace.</p>
    736     *
    737     * @param throwable  the <code>Throwable</code> to be examined
    738     * @return the stack trace as generated by the exception's
    739     *  <code>printStackTrace(PrintWriter)</code> method
    740     */
    741    public static String getStackTrace(Throwable throwable) {
    742        StringWriter sw = new StringWriter();
    743        PrintWriter pw = new PrintWriter(sw, true);
    744        throwable.printStackTrace(pw);
    745        return sw.getBuffer().toString();
    746    }
    747
    748    /**
    749     * <p>Captures the stack trace associated with the specified
    750     * <code>Throwable</code> object, decomposing it into a list of
    751     * stack frames.</p>
    752     *
    753     * <p>The result of this method vary by JDK version as this method
    754     * uses {@link Throwable#printStackTrace(java.io.PrintWriter)}.
    755     * On JDK1.3 and earlier, the cause exception will not be shown
    756     * unless the specified throwable alters printStackTrace.</p>
    757     *
    758     * @param throwable  the <code>Throwable</code> to examine, may be null
    759     * @return an array of strings describing each stack frame, never null
    760     */
    761//    public static String[] getStackFrames(Throwable throwable) {
    762//        if (throwable == null) {
    763//            return ArrayUtils.EMPTY_STRING_ARRAY;
    764//        }
    765//        return getStackFrames(getStackTrace(throwable));
    766//    }
    767
    768    //-----------------------------------------------------------------------
    769    /**
    770     * <p>Returns an array where each element is a line from the argument.</p>
    771     *
    772     * <p>The end of line is determined by the value of {@link SystemUtils#LINE_SEPARATOR}.</p>
    773     *
    774     * <p>Functionality shared between the
    775     * <code>getStackFrames(Throwable)</code> methods of this and the
    776     * {@link org.apache.commons.lang.exception.NestableDelegate} classes.</p>
    777     *
    778     * @param stackTrace  a stack trace String
    779     * @return an array where each element is a line from the argument
    780     */
    781//    static String[] getStackFrames(String stackTrace) {
    782//        String linebreak = SystemUtils.LINE_SEPARATOR;
    783//        StringTokenizer frames = new StringTokenizer(stackTrace, linebreak);
    784//        List list = new ArrayList();
    785//        while (frames.hasMoreTokens()) {
    786//            list.add(frames.nextToken());
    787//        }
    788//        return toArray(list);
    789//    }
    790
    791    /**
    792     * <p>Produces a <code>List</code> of stack frames - the message
    793     * is not included. Only the trace of the specified exception is
    794     * returned, any caused by trace is stripped.</p>
    795     *
    796     * <p>This works in most cases - it will only fail if the exception
    797     * message contains a line that starts with:
    798     * <code>&quot;&nbsp;&nbsp;&nbsp;at&quot;.</code></p>
    799     *
    800     * @param t is any throwable
    801     * @return List of stack frames
    802     */
    803    static List getStackFrameList(Throwable t) {
    804        String stackTrace = getStackTrace(t);
    805        String linebreak = LINE_SEPARATOR;
    806        StringTokenizer frames = new StringTokenizer(stackTrace, linebreak);
    807        List list = new ArrayList();
    808        boolean traceStarted = false;
    809        while (frames.hasMoreTokens()) {
    810            String token = frames.nextToken();
    811            // Determine if the line starts with <whitespace>at
    812            int at = token.indexOf("at");
    813            if (at != -1 && token.substring(0, at).trim().length() == 0) {
    814                traceStarted = true;
    815                list.add(token);
    816            } else if (traceStarted) {
    817                break;
    818            }
    819        }
    820        return list;
    821    }
    822
    823    //-----------------------------------------------------------------------
    824    /**
    825     * Gets a short message summarising the exception.
    826     * <p>
    827     * The message returned is of the form
    828     * {ClassNameWithoutPackage}: {ThrowableMessage}
    829     *
    830     * @param th  the throwable to get a message for, null returns empty string
    831     * @return the message, non-null
    832     * @since Commons Lang 2.2
    833     */
    834//    public static String getMessage(Throwable th) {
    835//        if (th == null) {
    836//            return "";
    837//        }
    838//        String clsName = ClassUtils.getShortClassName(th, null);
    839//        String msg = th.getMessage();
    840//        return clsName + ": " + StringUtils.defaultString(msg);
    841//    }
    842
    843    //-----------------------------------------------------------------------
    844    /**
    845     * Gets a short message summarising the root cause exception.
    846     * <p>
    847     * The message returned is of the form
    848     * {ClassNameWithoutPackage}: {ThrowableMessage}
    849     *
    850     * @param th  the throwable to get a message for, null returns empty string
    851     * @return the message, non-null
    852     * @since Commons Lang 2.2
    853     */
    854//    public static String getRootCauseMessage(Throwable th) {
    855//        Throwable root = ExceptionUtils.getRootCause(th);
    856//        root = (root == null ? th : root);
    857//        return getMessage(root);
    858//    }
    859
    860    /**
    861     * An interface to be implemented by {@link java.lang.Throwable}
    862     * extensions which would like to be able to nest root exceptions
    863     * inside themselves.
    864     *
    865     * @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
    866     * @author <a href="mailto:knielsen@apache.org">Kasper Nielsen</a>
    867     * @author <a href="mailto:steven@caswell.name">Steven Caswell</a>
    868     * @author Pete Gieser
    869     * @since 1.0
    870     * @version $Id$
    871     */
    872    public interface Nestable {
    873
    874        /**
    875         * Returns the reference to the exception or error that caused the
    876         * exception implementing the <code>Nestable</code> to be thrown.
    877         *
    878         * @return throwable that caused the original exception
    879         */
    880        Throwable getCause();
    881
    882        /**
    883         * Returns the error message of this and any nested
    884         * <code>Throwable</code>.
    885         *
    886         * @return the error message
    887         */
    888        String getMessage();
    889
    890        /**
    891         * Returns the error message of the <code>Throwable</code> in the chain
    892         * of <code>Throwable</code>s at the specified index, numbered from 0.
    893         *
    894         * @param index the index of the <code>Throwable</code> in the chain of
    895         * <code>Throwable</code>s
    896         * @return the error message, or null if the <code>Throwable</code> at the
    897         * specified index in the chain does not contain a message
    898         * @throws IndexOutOfBoundsException if the <code>index</code> argument is
    899         * negative or not less than the count of <code>Throwable</code>s in the
    900         * chain
    901         */
    902        String getMessage(int index);
    903
    904        /**
    905         * Returns the error message of this and any nested <code>Throwable</code>s
    906         * in an array of Strings, one element for each message. Any
    907         * <code>Throwable</code> not containing a message is represented in the
    908         * array by a null. This has the effect of cause the length of the returned
    909         * array to be equal to the result of the {@link #getThrowableCount()}
    910         * operation.
    911         *
    912         * @return the error messages
    913         */
    914        String[] getMessages();
    915
    916        /**
    917         * Returns the <code>Throwable</code> in the chain of
    918         * <code>Throwable</code>s at the specified index, numbered from 0.
    919         *
    920         * @param index the index, numbered from 0, of the <code>Throwable</code> in
    921         * the chain of <code>Throwable</code>s
    922         * @return the <code>Throwable</code>
    923         * @throws IndexOutOfBoundsException if the <code>index</code> argument is
    924         * negative or not less than the count of <code>Throwable</code>s in the
    925         * chain
    926         */
    927        Throwable getThrowable(int index);
    928
    929        /**
    930         * Returns the number of nested <code>Throwable</code>s represented by
    931         * this <code>Nestable</code>, including this <code>Nestable</code>.
    932         *
    933         * @return the throwable count
    934         */
    935        int getThrowableCount();
    936
    937        /**
    938         * Returns this <code>Nestable</code> and any nested <code>Throwable</code>s
    939         * in an array of <code>Throwable</code>s, one element for each
    940         * <code>Throwable</code>.
    941         *
    942         * @return the <code>Throwable</code>s
    943         */
    944        Throwable[] getThrowables();
    945
    946        /**
    947         * Returns the index, numbered from 0, of the first occurrence of the
    948         * specified type, or a subclass, in the chain of <code>Throwable</code>s.
    949         * The method returns -1 if the specified type is not found in the chain.
    950         * <p>
    951         * NOTE: From v2.1, we have clarified the <code>Nestable</code> interface
    952         * such that this method matches subclasses.
    953         * If you want to NOT match subclasses, please use
    954         * (which is avaiable in all versions of lang).
    955         *
    956         * @param type  the type to find, subclasses match, null returns -1
    957         * @return index of the first occurrence of the type in the chain, or -1 if
    958         * the type is not found
    959         */
    960        int indexOfThrowable(Class type);
    961
    962        /**
    963         * Returns the index, numbered from 0, of the first <code>Throwable</code>
    964         * that matches the specified type, or a subclass, in the chain of <code>Throwable</code>s
    965         * with an index greater than or equal to the specified index.
    966         * The method returns -1 if the specified type is not found in the chain.
    967         * <p>
    968         * NOTE: From v2.1, we have clarified the <code>Nestable</code> interface
    969         * such that this method matches subclasses.
    970         * If you want to NOT match subclasses, please use
    971         * (which is avaiable in all versions of lang).
    972         *
    973         * @param type  the type to find, subclasses match, null returns -1
    974         * @param fromIndex the index, numbered from 0, of the starting position in
    975         * the chain to be searched
    976         * @return index of the first occurrence of the type in the chain, or -1 if
    977         * the type is not found
    978         * @throws IndexOutOfBoundsException if the <code>fromIndex</code> argument
    979         * is negative or not less than the count of <code>Throwable</code>s in the
    980         * chain
    981         */
    982        int indexOfThrowable(Class type, int fromIndex);
    983
    984        /**
    985         * Prints the stack trace of this exception to the specified print
    986         * writer.  Includes information from the exception, if any,
    987         * which caused this exception.
    988         *
    989         * @param out <code>PrintWriter</code> to use for output.
    990         */
    991        void printStackTrace(PrintWriter out);
    992
    993        /**
    994         * Prints the stack trace of this exception to the specified print
    995         * stream.  Includes information from the exception, if any,
    996         * which caused this exception.
    997         *
    998         * @param out <code>PrintStream</code> to use for output.
    999         */
    1000        void printStackTrace(PrintStream out);
    1001
    1002        /**
    1003         * Prints the stack trace for this exception only--root cause not
    1004         * included--using the provided writer.  Used by
    1005         * individual stack traces to a buffer.  The implementation of
    1006         * this method should call
    1007         * <code>super.printStackTrace(out);</code> in most cases.
    1008         *
    1009         * @param out The writer to use.
    1010         */
    1011        void printPartialStackTrace(PrintWriter out);
    1012
    1013    }
    1014}