0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017: package org.apache.commons.lang.exception;
0018:
0019: import java.io.PrintStream;
0020: import java.io.PrintWriter;
0021: import java.io.StringWriter;
0022: import java.lang.reflect.Field;
0023: import java.lang.reflect.InvocationTargetException;
0024: import java.lang.reflect.Method;
0025: import java.sql.SQLException;
0026: import java.util.ArrayList;
0027: import java.util.Arrays;
0028: import java.util.List;
0029: import java.util.StringTokenizer;
0030:
0031: import org.apache.commons.lang.ArrayUtils;
0032: import org.apache.commons.lang.ClassUtils;
0033: import org.apache.commons.lang.NullArgumentException;
0034: import org.apache.commons.lang.StringUtils;
0035: import org.apache.commons.lang.SystemUtils;
0036:
0037: /**
0038: * <p>Provides utilities for manipulating and examining
0039: * <code>Throwable</code> objects.</p>
0040: *
0041: * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
0042: * @author Dmitri Plotnikov
0043: * @author Stephen Colebourne
0044: * @author <a href="mailto:ggregory@seagullsw.com">Gary Gregory</a>
0045: * @author Pete Gieser
0046: * @since 1.0
0047: * @version $Id: ExceptionUtils.java 437554 2006-08-28 06:21:41Z bayard $
0048: */
0049: public class ExceptionUtils {
0050:
0051: /**
0052: * <p>Used when printing stack frames to denote the start of a
0053: * wrapped exception.</p>
0054: *
0055: * <p>Package private for accessibility by test suite.</p>
0056: */
0057: static final String WRAPPED_MARKER = " [wrapped] ";
0058:
0059: /**
0060: * <p>The names of methods commonly used to access a wrapped exception.</p>
0061: */
0062: private static String[] CAUSE_METHOD_NAMES = { "getCause",
0063: "getNextException", "getTargetException", "getException",
0064: "getSourceException", "getRootCause",
0065: "getCausedByException", "getNested", "getLinkedException",
0066: "getNestedException", "getLinkedCause", "getThrowable", };
0067:
0068: /**
0069: * <p>The Method object for Java 1.4 getCause.</p>
0070: */
0071: private static final Method THROWABLE_CAUSE_METHOD;
0072:
0073: /**
0074: * <p>The Method object for Java 1.4 initCause.</p>
0075: */
0076: private static final Method THROWABLE_INITCAUSE_METHOD;
0077:
0078: static {
0079: Method causeMethod;
0080: try {
0081: causeMethod = Throwable.class.getMethod("getCause", null);
0082: } catch (Exception e) {
0083: causeMethod = null;
0084: }
0085: THROWABLE_CAUSE_METHOD = causeMethod;
0086: try {
0087: causeMethod = Throwable.class.getMethod("initCause",
0088: new Class[] { Throwable.class });
0089: } catch (Exception e) {
0090: causeMethod = null;
0091: }
0092: THROWABLE_INITCAUSE_METHOD = causeMethod;
0093: }
0094:
0095: /**
0096: * <p>
0097: * Public constructor allows an instance of <code>ExceptionUtils</code> to be created, although that is not
0098: * normally necessary.
0099: * </p>
0100: */
0101: public ExceptionUtils() {
0102: super ();
0103: }
0104:
0105: //-----------------------------------------------------------------------
0106: /**
0107: * <p>Adds to the list of method names used in the search for <code>Throwable</code>
0108: * objects.</p>
0109: *
0110: * @param methodName the methodName to add to the list, <code>null</code>
0111: * and empty strings are ignored
0112: * @since 2.0
0113: */
0114: public static void addCauseMethodName(String methodName) {
0115: if (StringUtils.isNotEmpty(methodName)
0116: && !isCauseMethodName(methodName)) {
0117: List list = getCauseMethodNameList();
0118: if (list.add(methodName)) {
0119: CAUSE_METHOD_NAMES = toArray(list);
0120: }
0121: }
0122: }
0123:
0124: /**
0125: * <p>Removes from the list of method names used in the search for <code>Throwable</code>
0126: * objects.</p>
0127: *
0128: * @param methodName the methodName to remove from the list, <code>null</code>
0129: * and empty strings are ignored
0130: * @since 2.1
0131: */
0132: public static void removeCauseMethodName(String methodName) {
0133: if (StringUtils.isNotEmpty(methodName)) {
0134: List list = getCauseMethodNameList();
0135: if (list.remove(methodName)) {
0136: CAUSE_METHOD_NAMES = toArray(list);
0137: }
0138: }
0139: }
0140:
0141: /**
0142: * <p>Sets the cause of a <code>Throwable</code> using introspection, allowing
0143: * source code compatibility between pre-1.4 and post-1.4 Java releases.</p>
0144: *
0145: * <p>The typical use of this method is inside a constructor as in
0146: * the following example:</p>
0147: *
0148: * <pre>
0149: * import org.apache.commons.lang.exception.ExceptionUtils;
0150: *
0151: * public class MyException extends Exception {
0152: *
0153: * public MyException(String msg) {
0154: * super(msg);
0155: * }
0156: *
0157: * public MyException(String msg, Throwable cause) {
0158: * super(msg);
0159: * ExceptionUtils.setCause(this, cause);
0160: * }
0161: * }
0162: * </pre>
0163: *
0164: * @param target the target <code>Throwable</code>
0165: * @param cause the <code>Throwable</code> to set in the target
0166: * @return a <code>true</code> if the target has been modified
0167: * @since 2.2
0168: */
0169: public static boolean setCause(Throwable target, Throwable cause) {
0170: if (target == null) {
0171: throw new NullArgumentException("target");
0172: }
0173: Object[] causeArgs = new Object[] { cause };
0174: boolean modifiedTarget = false;
0175: if (THROWABLE_INITCAUSE_METHOD != null) {
0176: try {
0177: THROWABLE_INITCAUSE_METHOD.invoke(target, causeArgs);
0178: modifiedTarget = true;
0179: } catch (IllegalAccessException ignored) {
0180: // Exception ignored.
0181: } catch (InvocationTargetException ignored) {
0182: // Exception ignored.
0183: }
0184: }
0185: try {
0186: Method setCauseMethod = target.getClass().getMethod(
0187: "setCause", new Class[] { Throwable.class });
0188: setCauseMethod.invoke(target, causeArgs);
0189: modifiedTarget = true;
0190: } catch (NoSuchMethodException ignored) {
0191: // Exception ignored.
0192: } catch (IllegalAccessException ignored) {
0193: // Exception ignored.
0194: } catch (InvocationTargetException ignored) {
0195: // Exception ignored.
0196: }
0197: return modifiedTarget;
0198: }
0199:
0200: /**
0201: * Returns the given list as a <code>String[]</code>.
0202: * @param list a list to transform.
0203: * @return the given list as a <code>String[]</code>.
0204: */
0205: private static String[] toArray(List list) {
0206: return (String[]) list.toArray(new String[list.size()]);
0207: }
0208:
0209: /**
0210: * Returns {@link #CAUSE_METHOD_NAMES} as a List.
0211: *
0212: * @return {@link #CAUSE_METHOD_NAMES} as a List.
0213: */
0214: private static ArrayList getCauseMethodNameList() {
0215: return new ArrayList(Arrays.asList(CAUSE_METHOD_NAMES));
0216: }
0217:
0218: /**
0219: * <p>Tests if the list of method names used in the search for <code>Throwable</code>
0220: * objects include the given name.</p>
0221: *
0222: * @param methodName the methodName to search in the list.
0223: * @return if the list of method names used in the search for <code>Throwable</code>
0224: * objects include the given name.
0225: * @since 2.1
0226: */
0227: public static boolean isCauseMethodName(String methodName) {
0228: return ArrayUtils.indexOf(CAUSE_METHOD_NAMES, methodName) >= 0;
0229: }
0230:
0231: //-----------------------------------------------------------------------
0232: /**
0233: * <p>Introspects the <code>Throwable</code> to obtain the cause.</p>
0234: *
0235: * <p>The method searches for methods with specific names that return a
0236: * <code>Throwable</code> object. This will pick up most wrapping exceptions,
0237: * including those from JDK 1.4, and
0238: * {@link org.apache.commons.lang.exception.NestableException NestableException}.
0239: * The method names can be added to using {@link #addCauseMethodName(String)}.</p>
0240: *
0241: * <p>The default list searched for are:</p>
0242: * <ul>
0243: * <li><code>getCause()</code></li>
0244: * <li><code>getNextException()</code></li>
0245: * <li><code>getTargetException()</code></li>
0246: * <li><code>getException()</code></li>
0247: * <li><code>getSourceException()</code></li>
0248: * <li><code>getRootCause()</code></li>
0249: * <li><code>getCausedByException()</code></li>
0250: * <li><code>getNested()</code></li>
0251: * </ul>
0252: *
0253: * <p>In the absence of any such method, the object is inspected for a
0254: * <code>detail</code> field assignable to a <code>Throwable</code>.</p>
0255: *
0256: * <p>If none of the above is found, returns <code>null</code>.</p>
0257: *
0258: * @param throwable the throwable to introspect for a cause, may be null
0259: * @return the cause of the <code>Throwable</code>,
0260: * <code>null</code> if none found or null throwable input
0261: * @since 1.0
0262: */
0263: public static Throwable getCause(Throwable throwable) {
0264: return getCause(throwable, CAUSE_METHOD_NAMES);
0265: }
0266:
0267: /**
0268: * <p>Introspects the <code>Throwable</code> to obtain the cause.</p>
0269: *
0270: * <ol>
0271: * <li>Try known exception types.</li>
0272: * <li>Try the supplied array of method names.</li>
0273: * <li>Try the field 'detail'.</li>
0274: * </ol>
0275: *
0276: * <p>A <code>null</code> set of method names means use the default set.
0277: * A <code>null</code> in the set of method names will be ignored.</p>
0278: *
0279: * @param throwable the throwable to introspect for a cause, may be null
0280: * @param methodNames the method names, null treated as default set
0281: * @return the cause of the <code>Throwable</code>,
0282: * <code>null</code> if none found or null throwable input
0283: * @since 1.0
0284: */
0285: public static Throwable getCause(Throwable throwable,
0286: String[] methodNames) {
0287: if (throwable == null) {
0288: return null;
0289: }
0290: Throwable cause = getCauseUsingWellKnownTypes(throwable);
0291: if (cause == null) {
0292: if (methodNames == null) {
0293: methodNames = CAUSE_METHOD_NAMES;
0294: }
0295: for (int i = 0; i < methodNames.length; i++) {
0296: String methodName = methodNames[i];
0297: if (methodName != null) {
0298: cause = getCauseUsingMethodName(throwable,
0299: methodName);
0300: if (cause != null) {
0301: break;
0302: }
0303: }
0304: }
0305:
0306: if (cause == null) {
0307: cause = getCauseUsingFieldName(throwable, "detail");
0308: }
0309: }
0310: return cause;
0311: }
0312:
0313: /**
0314: * <p>Introspects the <code>Throwable</code> to obtain the root cause.</p>
0315: *
0316: * <p>This method walks through the exception chain to the last element,
0317: * "root" of the tree, using {@link #getCause(Throwable)}, and
0318: * returns that exception.</p>
0319: *
0320: * <p>From version 2.2, this method handles recursive cause structures
0321: * that might otherwise cause infinite loops. If the throwable parameter
0322: * has a cause of itself, then null will be returned. If the throwable
0323: * parameter cause chain loops, the last element in the chain before the
0324: * loop is returned.</p>
0325: *
0326: * @param throwable the throwable to get the root cause for, may be null
0327: * @return the root cause of the <code>Throwable</code>,
0328: * <code>null</code> if none found or null throwable input
0329: */
0330: public static Throwable getRootCause(Throwable throwable) {
0331: List list = getThrowableList(throwable);
0332: return (list.size() < 2 ? null : (Throwable) list.get(list
0333: .size() - 1));
0334: }
0335:
0336: /**
0337: * <p>Finds a <code>Throwable</code> for known types.</p>
0338: *
0339: * <p>Uses <code>instanceof</code> checks to examine the exception,
0340: * looking for well known types which could contain chained or
0341: * wrapped exceptions.</p>
0342: *
0343: * @param throwable the exception to examine
0344: * @return the wrapped exception, or <code>null</code> if not found
0345: */
0346: private static Throwable getCauseUsingWellKnownTypes(
0347: Throwable throwable) {
0348: if (throwable instanceof Nestable) {
0349: return ((Nestable) throwable).getCause();
0350: } else if (throwable instanceof SQLException) {
0351: return ((SQLException) throwable).getNextException();
0352: } else if (throwable instanceof InvocationTargetException) {
0353: return ((InvocationTargetException) throwable)
0354: .getTargetException();
0355: } else {
0356: return null;
0357: }
0358: }
0359:
0360: /**
0361: * <p>Finds a <code>Throwable</code> by method name.</p>
0362: *
0363: * @param throwable the exception to examine
0364: * @param methodName the name of the method to find and invoke
0365: * @return the wrapped exception, or <code>null</code> if not found
0366: */
0367: private static Throwable getCauseUsingMethodName(
0368: Throwable throwable, String methodName) {
0369: Method method = null;
0370: try {
0371: method = throwable.getClass().getMethod(methodName, null);
0372: } catch (NoSuchMethodException ignored) {
0373: // exception ignored
0374: } catch (SecurityException ignored) {
0375: // exception ignored
0376: }
0377:
0378: if (method != null
0379: && Throwable.class.isAssignableFrom(method
0380: .getReturnType())) {
0381: try {
0382: return (Throwable) method.invoke(throwable,
0383: ArrayUtils.EMPTY_OBJECT_ARRAY);
0384: } catch (IllegalAccessException ignored) {
0385: // exception ignored
0386: } catch (IllegalArgumentException ignored) {
0387: // exception ignored
0388: } catch (InvocationTargetException ignored) {
0389: // exception ignored
0390: }
0391: }
0392: return null;
0393: }
0394:
0395: /**
0396: * <p>Finds a <code>Throwable</code> by field name.</p>
0397: *
0398: * @param throwable the exception to examine
0399: * @param fieldName the name of the attribute to examine
0400: * @return the wrapped exception, or <code>null</code> if not found
0401: */
0402: private static Throwable getCauseUsingFieldName(
0403: Throwable throwable, String fieldName) {
0404: Field field = null;
0405: try {
0406: field = throwable.getClass().getField(fieldName);
0407: } catch (NoSuchFieldException ignored) {
0408: // exception ignored
0409: } catch (SecurityException ignored) {
0410: // exception ignored
0411: }
0412:
0413: if (field != null
0414: && Throwable.class.isAssignableFrom(field.getType())) {
0415: try {
0416: return (Throwable) field.get(throwable);
0417: } catch (IllegalAccessException ignored) {
0418: // exception ignored
0419: } catch (IllegalArgumentException ignored) {
0420: // exception ignored
0421: }
0422: }
0423: return null;
0424: }
0425:
0426: //-----------------------------------------------------------------------
0427: /**
0428: * <p>Checks if the Throwable class has a <code>getCause</code> method.</p>
0429: *
0430: * <p>This is true for JDK 1.4 and above.</p>
0431: *
0432: * @return true if Throwable is nestable
0433: * @since 2.0
0434: */
0435: public static boolean isThrowableNested() {
0436: return THROWABLE_CAUSE_METHOD != null;
0437: }
0438:
0439: /**
0440: * <p>Checks whether this <code>Throwable</code> class can store a cause.</p>
0441: *
0442: * <p>This method does <b>not</b> check whether it actually does store a cause.<p>
0443: *
0444: * @param throwable the <code>Throwable</code> to examine, may be null
0445: * @return boolean <code>true</code> if nested otherwise <code>false</code>
0446: * @since 2.0
0447: */
0448: public static boolean isNestedThrowable(Throwable throwable) {
0449: if (throwable == null) {
0450: return false;
0451: }
0452:
0453: if (throwable instanceof Nestable) {
0454: return true;
0455: } else if (throwable instanceof SQLException) {
0456: return true;
0457: } else if (throwable instanceof InvocationTargetException) {
0458: return true;
0459: } else if (isThrowableNested()) {
0460: return true;
0461: }
0462:
0463: Class cls = throwable.getClass();
0464: for (int i = 0, isize = CAUSE_METHOD_NAMES.length; i < isize; i++) {
0465: try {
0466: Method method = cls.getMethod(CAUSE_METHOD_NAMES[i],
0467: null);
0468: if (method != null
0469: && Throwable.class.isAssignableFrom(method
0470: .getReturnType())) {
0471: return true;
0472: }
0473: } catch (NoSuchMethodException ignored) {
0474: // exception ignored
0475: } catch (SecurityException ignored) {
0476: // exception ignored
0477: }
0478: }
0479:
0480: try {
0481: Field field = cls.getField("detail");
0482: if (field != null) {
0483: return true;
0484: }
0485: } catch (NoSuchFieldException ignored) {
0486: // exception ignored
0487: } catch (SecurityException ignored) {
0488: // exception ignored
0489: }
0490:
0491: return false;
0492: }
0493:
0494: //-----------------------------------------------------------------------
0495: /**
0496: * <p>Counts the number of <code>Throwable</code> objects in the
0497: * exception chain.</p>
0498: *
0499: * <p>A throwable without cause will return <code>1</code>.
0500: * A throwable with one cause will return <code>2</code> and so on.
0501: * A <code>null</code> throwable will return <code>0</code>.</p>
0502: *
0503: * <p>From version 2.2, this method handles recursive cause structures
0504: * that might otherwise cause infinite loops. The cause chain is
0505: * processed until the end is reached, or until the next item in the
0506: * chain is already in the result set.</p>
0507: *
0508: * @param throwable the throwable to inspect, may be null
0509: * @return the count of throwables, zero if null input
0510: */
0511: public static int getThrowableCount(Throwable throwable) {
0512: return getThrowableList(throwable).size();
0513: }
0514:
0515: /**
0516: * <p>Returns the list of <code>Throwable</code> objects in the
0517: * exception chain.</p>
0518: *
0519: * <p>A throwable without cause will return an array containing
0520: * one element - the input throwable.
0521: * A throwable with one cause will return an array containing
0522: * two elements. - the input throwable and the cause throwable.
0523: * A <code>null</code> throwable will return an array of size zero.</p>
0524: *
0525: * <p>From version 2.2, this method handles recursive cause structures
0526: * that might otherwise cause infinite loops. The cause chain is
0527: * processed until the end is reached, or until the next item in the
0528: * chain is already in the result set.</p>
0529: *
0530: * @see #getThrowableList(Throwable)
0531: * @param throwable the throwable to inspect, may be null
0532: * @return the array of throwables, never null
0533: */
0534: public static Throwable[] getThrowables(Throwable throwable) {
0535: List list = getThrowableList(throwable);
0536: return (Throwable[]) list.toArray(new Throwable[list.size()]);
0537: }
0538:
0539: /**
0540: * <p>Returns the list of <code>Throwable</code> objects in the
0541: * exception chain.</p>
0542: *
0543: * <p>A throwable without cause will return a list containing
0544: * one element - the input throwable.
0545: * A throwable with one cause will return a list containing
0546: * two elements. - the input throwable and the cause throwable.
0547: * A <code>null</code> throwable will return a list of size zero.</p>
0548: *
0549: * <p>This method handles recursive cause structures that might
0550: * otherwise cause infinite loops. The cause chain is processed until
0551: * the end is reached, or until the next item in the chain is already
0552: * in the result set.</p>
0553: *
0554: * @param throwable the throwable to inspect, may be null
0555: * @return the list of throwables, never null
0556: * @since Commons Lang 2.2
0557: */
0558: public static List getThrowableList(Throwable throwable) {
0559: List list = new ArrayList();
0560: while (throwable != null && list.contains(throwable) == false) {
0561: list.add(throwable);
0562: throwable = ExceptionUtils.getCause(throwable);
0563: }
0564: return list;
0565: }
0566:
0567: //-----------------------------------------------------------------------
0568: /**
0569: * <p>Returns the (zero based) index of the first <code>Throwable</code>
0570: * that matches the specified class (exactly) in the exception chain.
0571: * Subclasses of the specified class do not match - see
0572: * {@link #indexOfType(Throwable, Class)} for the opposite.</p>
0573: *
0574: * <p>A <code>null</code> throwable returns <code>-1</code>.
0575: * A <code>null</code> type returns <code>-1</code>.
0576: * No match in the chain returns <code>-1</code>.</p>
0577: *
0578: * @param throwable the throwable to inspect, may be null
0579: * @param clazz the class to search for, subclasses do not match, null returns -1
0580: * @return the index into the throwable chain, -1 if no match or null input
0581: */
0582: public static int indexOfThrowable(Throwable throwable, Class clazz) {
0583: return indexOf(throwable, clazz, 0, false);
0584: }
0585:
0586: /**
0587: * <p>Returns the (zero based) index of the first <code>Throwable</code>
0588: * that matches the specified type in the exception chain from
0589: * a specified index.
0590: * Subclasses of the specified class do not match - see
0591: * {@link #indexOfType(Throwable, Class, int)} for the opposite.</p>
0592: *
0593: * <p>A <code>null</code> throwable returns <code>-1</code>.
0594: * A <code>null</code> type returns <code>-1</code>.
0595: * No match in the chain returns <code>-1</code>.
0596: * A negative start index is treated as zero.
0597: * A start index greater than the number of throwables returns <code>-1</code>.</p>
0598: *
0599: * @param throwable the throwable to inspect, may be null
0600: * @param clazz the class to search for, subclasses do not match, null returns -1
0601: * @param fromIndex the (zero based) index of the starting position,
0602: * negative treated as zero, larger than chain size returns -1
0603: * @return the index into the throwable chain, -1 if no match or null input
0604: */
0605: public static int indexOfThrowable(Throwable throwable,
0606: Class clazz, int fromIndex) {
0607: return indexOf(throwable, clazz, fromIndex, false);
0608: }
0609:
0610: //-----------------------------------------------------------------------
0611: /**
0612: * <p>Returns the (zero based) index of the first <code>Throwable</code>
0613: * that matches the specified class or subclass in the exception chain.
0614: * Subclasses of the specified class do match - see
0615: * {@link #indexOfThrowable(Throwable, Class)} for the opposite.</p>
0616: *
0617: * <p>A <code>null</code> throwable returns <code>-1</code>.
0618: * A <code>null</code> type returns <code>-1</code>.
0619: * No match in the chain returns <code>-1</code>.</p>
0620: *
0621: * @param throwable the throwable to inspect, may be null
0622: * @param type the type to search for, subclasses match, null returns -1
0623: * @return the index into the throwable chain, -1 if no match or null input
0624: * @since 2.1
0625: */
0626: public static int indexOfType(Throwable throwable, Class type) {
0627: return indexOf(throwable, type, 0, true);
0628: }
0629:
0630: /**
0631: * <p>Returns the (zero based) index of the first <code>Throwable</code>
0632: * that matches the specified type in the exception chain from
0633: * a specified index.
0634: * Subclasses of the specified class do match - see
0635: * {@link #indexOfThrowable(Throwable, Class)} for the opposite.</p>
0636: *
0637: * <p>A <code>null</code> throwable returns <code>-1</code>.
0638: * A <code>null</code> type returns <code>-1</code>.
0639: * No match in the chain returns <code>-1</code>.
0640: * A negative start index is treated as zero.
0641: * A start index greater than the number of throwables returns <code>-1</code>.</p>
0642: *
0643: * @param throwable the throwable to inspect, may be null
0644: * @param type the type to search for, subclasses match, null returns -1
0645: * @param fromIndex the (zero based) index of the starting position,
0646: * negative treated as zero, larger than chain size returns -1
0647: * @return the index into the throwable chain, -1 if no match or null input
0648: * @since 2.1
0649: */
0650: public static int indexOfType(Throwable throwable, Class type,
0651: int fromIndex) {
0652: return indexOf(throwable, type, fromIndex, true);
0653: }
0654:
0655: /**
0656: * <p>Worker method for the <code>indexOfType</code> methods.</p>
0657: *
0658: * @param throwable the throwable to inspect, may be null
0659: * @param type the type to search for, subclasses match, null returns -1
0660: * @param fromIndex the (zero based) index of the starting position,
0661: * negative treated as zero, larger than chain size returns -1
0662: * @param subclass if <code>true</code>, compares with {@link Class#isAssignableFrom(Class)}, otherwise compares
0663: * using references
0664: * @return index of the <code>type</code> within throwables nested withing the specified <code>throwable</code>
0665: */
0666: private static int indexOf(Throwable throwable, Class type,
0667: int fromIndex, boolean subclass) {
0668: if (throwable == null || type == null) {
0669: return -1;
0670: }
0671: if (fromIndex < 0) {
0672: fromIndex = 0;
0673: }
0674: Throwable[] throwables = ExceptionUtils
0675: .getThrowables(throwable);
0676: if (fromIndex >= throwables.length) {
0677: return -1;
0678: }
0679: if (subclass) {
0680: for (int i = fromIndex; i < throwables.length; i++) {
0681: if (type.isAssignableFrom(throwables[i].getClass())) {
0682: return i;
0683: }
0684: }
0685: } else {
0686: for (int i = fromIndex; i < throwables.length; i++) {
0687: if (type.equals(throwables[i].getClass())) {
0688: return i;
0689: }
0690: }
0691: }
0692: return -1;
0693: }
0694:
0695: //-----------------------------------------------------------------------
0696: /**
0697: * <p>Prints a compact stack trace for the root cause of a throwable
0698: * to <code>System.err</code>.</p>
0699: *
0700: * <p>The compact stack trace starts with the root cause and prints
0701: * stack frames up to the place where it was caught and wrapped.
0702: * Then it prints the wrapped exception and continues with stack frames
0703: * until the wrapper exception is caught and wrapped again, etc.</p>
0704: *
0705: * <p>The output of this method is consistent across JDK versions.
0706: * Note that this is the opposite order to the JDK1.4 display.</p>
0707: *
0708: * <p>The method is equivalent to <code>printStackTrace</code> for throwables
0709: * that don't have nested causes.</p>
0710: *
0711: * @param throwable the throwable to output
0712: * @since 2.0
0713: */
0714: public static void printRootCauseStackTrace(Throwable throwable) {
0715: printRootCauseStackTrace(throwable, System.err);
0716: }
0717:
0718: /**
0719: * <p>Prints a compact stack trace for the root cause of a throwable.</p>
0720: *
0721: * <p>The compact stack trace starts with the root cause and prints
0722: * stack frames up to the place where it was caught and wrapped.
0723: * Then it prints the wrapped exception and continues with stack frames
0724: * until the wrapper exception is caught and wrapped again, etc.</p>
0725: *
0726: * <p>The output of this method is consistent across JDK versions.
0727: * Note that this is the opposite order to the JDK1.4 display.</p>
0728: *
0729: * <p>The method is equivalent to <code>printStackTrace</code> for throwables
0730: * that don't have nested causes.</p>
0731: *
0732: * @param throwable the throwable to output, may be null
0733: * @param stream the stream to output to, may not be null
0734: * @throws IllegalArgumentException if the stream is <code>null</code>
0735: * @since 2.0
0736: */
0737: public static void printRootCauseStackTrace(Throwable throwable,
0738: PrintStream stream) {
0739: if (throwable == null) {
0740: return;
0741: }
0742: if (stream == null) {
0743: throw new IllegalArgumentException(
0744: "The PrintStream must not be null");
0745: }
0746: String trace[] = getRootCauseStackTrace(throwable);
0747: for (int i = 0; i < trace.length; i++) {
0748: stream.println(trace[i]);
0749: }
0750: stream.flush();
0751: }
0752:
0753: /**
0754: * <p>Prints a compact stack trace for the root cause of a throwable.</p>
0755: *
0756: * <p>The compact stack trace starts with the root cause and prints
0757: * stack frames up to the place where it was caught and wrapped.
0758: * Then it prints the wrapped exception and continues with stack frames
0759: * until the wrapper exception is caught and wrapped again, etc.</p>
0760: *
0761: * <p>The output of this method is consistent across JDK versions.
0762: * Note that this is the opposite order to the JDK1.4 display.</p>
0763: *
0764: * <p>The method is equivalent to <code>printStackTrace</code> for throwables
0765: * that don't have nested causes.</p>
0766: *
0767: * @param throwable the throwable to output, may be null
0768: * @param writer the writer to output to, may not be null
0769: * @throws IllegalArgumentException if the writer is <code>null</code>
0770: * @since 2.0
0771: */
0772: public static void printRootCauseStackTrace(Throwable throwable,
0773: PrintWriter writer) {
0774: if (throwable == null) {
0775: return;
0776: }
0777: if (writer == null) {
0778: throw new IllegalArgumentException(
0779: "The PrintWriter must not be null");
0780: }
0781: String trace[] = getRootCauseStackTrace(throwable);
0782: for (int i = 0; i < trace.length; i++) {
0783: writer.println(trace[i]);
0784: }
0785: writer.flush();
0786: }
0787:
0788: //-----------------------------------------------------------------------
0789: /**
0790: * <p>Creates a compact stack trace for the root cause of the supplied
0791: * <code>Throwable</code>.</p>
0792: *
0793: * <p>The output of this method is consistent across JDK versions.
0794: * It consists of the root exception followed by each of its wrapping
0795: * exceptions separated by '[wrapped]'. Note that this is the opposite
0796: * order to the JDK1.4 display.</p>
0797: *
0798: * @param throwable the throwable to examine, may be null
0799: * @return an array of stack trace frames, never null
0800: * @since 2.0
0801: */
0802: public static String[] getRootCauseStackTrace(Throwable throwable) {
0803: if (throwable == null) {
0804: return ArrayUtils.EMPTY_STRING_ARRAY;
0805: }
0806: Throwable throwables[] = getThrowables(throwable);
0807: int count = throwables.length;
0808: ArrayList frames = new ArrayList();
0809: List nextTrace = getStackFrameList(throwables[count - 1]);
0810: for (int i = count; --i >= 0;) {
0811: List trace = nextTrace;
0812: if (i != 0) {
0813: nextTrace = getStackFrameList(throwables[i - 1]);
0814: removeCommonFrames(trace, nextTrace);
0815: }
0816: if (i == count - 1) {
0817: frames.add(throwables[i].toString());
0818: } else {
0819: frames.add(WRAPPED_MARKER + throwables[i].toString());
0820: }
0821: for (int j = 0; j < trace.size(); j++) {
0822: frames.add(trace.get(j));
0823: }
0824: }
0825: return (String[]) frames.toArray(new String[0]);
0826: }
0827:
0828: /**
0829: * <p>Removes common frames from the cause trace given the two stack traces.</p>
0830: *
0831: * @param causeFrames stack trace of a cause throwable
0832: * @param wrapperFrames stack trace of a wrapper throwable
0833: * @throws IllegalArgumentException if either argument is null
0834: * @since 2.0
0835: */
0836: public static void removeCommonFrames(List causeFrames,
0837: List wrapperFrames) {
0838: if (causeFrames == null || wrapperFrames == null) {
0839: throw new IllegalArgumentException(
0840: "The List must not be null");
0841: }
0842: int causeFrameIndex = causeFrames.size() - 1;
0843: int wrapperFrameIndex = wrapperFrames.size() - 1;
0844: while (causeFrameIndex >= 0 && wrapperFrameIndex >= 0) {
0845: // Remove the frame from the cause trace if it is the same
0846: // as in the wrapper trace
0847: String causeFrame = (String) causeFrames
0848: .get(causeFrameIndex);
0849: String wrapperFrame = (String) wrapperFrames
0850: .get(wrapperFrameIndex);
0851: if (causeFrame.equals(wrapperFrame)) {
0852: causeFrames.remove(causeFrameIndex);
0853: }
0854: causeFrameIndex--;
0855: wrapperFrameIndex--;
0856: }
0857: }
0858:
0859: //-----------------------------------------------------------------------
0860: /**
0861: * <p>A way to get the entire nested stack-trace of an throwable.</p>
0862: *
0863: * <p>The result of this method is highly dependent on the JDK version
0864: * and whether the exceptions override printStackTrace or not.</p>
0865: *
0866: * @param throwable the <code>Throwable</code> to be examined
0867: * @return the nested stack trace, with the root cause first
0868: * @since 2.0
0869: */
0870: public static String getFullStackTrace(Throwable throwable) {
0871: StringWriter sw = new StringWriter();
0872: PrintWriter pw = new PrintWriter(sw, true);
0873: Throwable[] ts = getThrowables(throwable);
0874: for (int i = 0; i < ts.length; i++) {
0875: ts[i].printStackTrace(pw);
0876: if (isNestedThrowable(ts[i])) {
0877: break;
0878: }
0879: }
0880: return sw.getBuffer().toString();
0881: }
0882:
0883: //-----------------------------------------------------------------------
0884: /**
0885: * <p>Gets the stack trace from a Throwable as a String.</p>
0886: *
0887: * <p>The result of this method vary by JDK version as this method
0888: * uses {@link Throwable#printStackTrace(java.io.PrintWriter)}.
0889: * On JDK1.3 and earlier, the cause exception will not be shown
0890: * unless the specified throwable alters printStackTrace.</p>
0891: *
0892: * @param throwable the <code>Throwable</code> to be examined
0893: * @return the stack trace as generated by the exception's
0894: * <code>printStackTrace(PrintWriter)</code> method
0895: */
0896: public static String getStackTrace(Throwable throwable) {
0897: StringWriter sw = new StringWriter();
0898: PrintWriter pw = new PrintWriter(sw, true);
0899: throwable.printStackTrace(pw);
0900: return sw.getBuffer().toString();
0901: }
0902:
0903: /**
0904: * <p>Captures the stack trace associated with the specified
0905: * <code>Throwable</code> object, decomposing it into a list of
0906: * stack frames.</p>
0907: *
0908: * <p>The result of this method vary by JDK version as this method
0909: * uses {@link Throwable#printStackTrace(java.io.PrintWriter)}.
0910: * On JDK1.3 and earlier, the cause exception will not be shown
0911: * unless the specified throwable alters printStackTrace.</p>
0912: *
0913: * @param throwable the <code>Throwable</code> to examine, may be null
0914: * @return an array of strings describing each stack frame, never null
0915: */
0916: public static String[] getStackFrames(Throwable throwable) {
0917: if (throwable == null) {
0918: return ArrayUtils.EMPTY_STRING_ARRAY;
0919: }
0920: return getStackFrames(getStackTrace(throwable));
0921: }
0922:
0923: //-----------------------------------------------------------------------
0924: /**
0925: * <p>Returns an array where each element is a line from the argument.</p>
0926: *
0927: * <p>The end of line is determined by the value of {@link SystemUtils#LINE_SEPARATOR}.</p>
0928: *
0929: * <p>Functionality shared between the
0930: * <code>getStackFrames(Throwable)</code> methods of this and the
0931: * {@link org.apache.commons.lang.exception.NestableDelegate} classes.</p>
0932: *
0933: * @param stackTrace a stack trace String
0934: * @return an array where each element is a line from the argument
0935: */
0936: static String[] getStackFrames(String stackTrace) {
0937: String linebreak = SystemUtils.LINE_SEPARATOR;
0938: StringTokenizer frames = new StringTokenizer(stackTrace,
0939: linebreak);
0940: List list = new ArrayList();
0941: while (frames.hasMoreTokens()) {
0942: list.add(frames.nextToken());
0943: }
0944: return toArray(list);
0945: }
0946:
0947: /**
0948: * <p>Produces a <code>List</code> of stack frames - the message
0949: * is not included. Only the trace of the specified exception is
0950: * returned, any caused by trace is stripped.</p>
0951: *
0952: * <p>This works in most cases - it will only fail if the exception
0953: * message contains a line that starts with:
0954: * <code>" at".</code></p>
0955: *
0956: * @param t is any throwable
0957: * @return List of stack frames
0958: */
0959: static List getStackFrameList(Throwable t) {
0960: String stackTrace = getStackTrace(t);
0961: String linebreak = SystemUtils.LINE_SEPARATOR;
0962: StringTokenizer frames = new StringTokenizer(stackTrace,
0963: linebreak);
0964: List list = new ArrayList();
0965: boolean traceStarted = false;
0966: while (frames.hasMoreTokens()) {
0967: String token = frames.nextToken();
0968: // Determine if the line starts with <whitespace>at
0969: int at = token.indexOf("at");
0970: if (at != -1 && token.substring(0, at).trim().length() == 0) {
0971: traceStarted = true;
0972: list.add(token);
0973: } else if (traceStarted) {
0974: break;
0975: }
0976: }
0977: return list;
0978: }
0979:
0980: //-----------------------------------------------------------------------
0981: /**
0982: * Gets a short message summarising the exception.
0983: * <p>
0984: * The message returned is of the form
0985: * {ClassNameWithoutPackage}: {ThrowableMessage}
0986: *
0987: * @param th the throwable to get a message for, null returns empty string
0988: * @return the message, non-null
0989: * @since Commons Lang 2.2
0990: */
0991: public static String getMessage(Throwable th) {
0992: if (th == null) {
0993: return "";
0994: }
0995: String clsName = ClassUtils.getShortClassName(th, null);
0996: String msg = th.getMessage();
0997: return clsName + ": " + StringUtils.defaultString(msg);
0998: }
0999:
1000: //-----------------------------------------------------------------------
1001: /**
1002: * Gets a short message summarising the root cause exception.
1003: * <p>
1004: * The message returned is of the form
1005: * {ClassNameWithoutPackage}: {ThrowableMessage}
1006: *
1007: * @param th the throwable to get a message for, null returns empty string
1008: * @return the message, non-null
1009: * @since Commons Lang 2.2
1010: */
1011: public static String getRootCauseMessage(Throwable th) {
1012: Throwable root = ExceptionUtils.getRootCause(th);
1013: root = (root == null ? th : root);
1014: return getMessage(root);
1015: }
1016:
1017: }
|