diff --git a/src/java/org/apache/commons/lang/exception/ExceptionUtils.java b/src/java/org/apache/commons/lang/exception/ExceptionUtils.java index 2964fb8b3..e435db093 100644 --- a/src/java/org/apache/commons/lang/exception/ExceptionUtils.java +++ b/src/java/org/apache/commons/lang/exception/ExceptionUtils.java @@ -67,6 +67,7 @@ import java.util.StringTokenizer; import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang.NullArgumentException; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.SystemUtils; @@ -79,7 +80,7 @@ * @author Stephen Colebourne * @author Gary Gregory * @since 1.0 - * @version $Id: ExceptionUtils.java,v 1.28 2003/07/26 00:43:08 ggregory Exp $ + * @version $Id: ExceptionUtils.java,v 1.29 2003/07/26 13:05:21 scolebourne Exp $ */ public class ExceptionUtils { @@ -92,8 +93,7 @@ public class ExceptionUtils { static final String WRAPPED_MARKER = " [wrapped] "; /** - *

The names of methods commonly used to access a wrapped - * exception.

+ *

The names of methods commonly used to access a wrapped exception.

*/ private static String[] CAUSE_METHOD_NAMES = { "getCause", @@ -107,12 +107,27 @@ public class ExceptionUtils { }; /** - *

Constructs a new ExceptionUtils. Protected to - * discourage instantiation.

+ *

The Method object for JDK1.4 getCause.

*/ - protected ExceptionUtils() { + private static final Method THROWABLE_CAUSE_METHOD; + static { + Method getCauseMethod; + try { + getCauseMethod = Throwable.class.getMethod("getCause", null); + } catch (Exception e) { + getCauseMethod = null; + } + THROWABLE_CAUSE_METHOD = getCauseMethod; + } + + /** + *

Public constructor allows an instance of ExceptionUtils + * to be created, although that is not normally necessary.

+ */ + public ExceptionUtils() { } + //----------------------------------------------------------------------- /** *

Adds to the list of method names used in the search for Throwable * objects.

@@ -121,7 +136,7 @@ protected ExceptionUtils() { * and empty strings are ignored */ public static void addCauseMethodName(String methodName) { - if(StringUtils.isNotEmpty(methodName)) { + if (StringUtils.isNotEmpty(methodName)) { List list = new ArrayList(Arrays.asList(CAUSE_METHOD_NAMES)); list.add(methodName); CAUSE_METHOD_NAMES = (String[]) list.toArray(new String[list.size()]); @@ -129,7 +144,7 @@ public static void addCauseMethodName(String methodName) { } /** - *

Introspects the specified Throwable to obtain the cause.

+ *

Introspects the Throwable to obtain the cause.

* *

The method searches for methods with specific names that return a * Throwable object. This will pick up most wrapping exceptions, @@ -154,31 +169,47 @@ public static void addCauseMethodName(String methodName) { * *

If none of the above is found, returns null.

* - * @param throwable The exception to introspect for a cause. - * @return The cause of the Throwable. - * @throws NullPointerException if the throwable is null + * @param throwable the throwable to introspect for a cause, may be null + * @return the cause of the Throwable, + * null if none found or null throwable input */ public static Throwable getCause(Throwable throwable) { return getCause(throwable, CAUSE_METHOD_NAMES); } /** - *

Introspects the specified Throwable to obtain the cause - * using a supplied array of method names.

+ *

Introspects the Throwable to obtain the cause.

+ * + *
    + *
  1. Try known exception types.

    + *
  2. Try the supplied array of method names.

    + *
  3. Try the field 'detail'.

    + *
+ * + *

A null set of method names means use the default set. + * A null in the set of method names will be ignored.

* - * @param throwable The exception to introspect for a cause. - * @return The cause of the Throwable. - * @throws NullPointerException if the method names array is null - * or contains null - * @throws NullPointerException if the throwable is null + * @param throwable the throwable to introspect for a cause, may be null + * @param methodNames the method names, null treated as default set + * @return the cause of the Throwable, + * null if none found or null throwable input */ public static Throwable getCause(Throwable throwable, String[] methodNames) { + if (throwable == null) { + return null; + } Throwable cause = getCauseUsingWellKnownTypes(throwable); if (cause == null) { + if (methodNames == null) { + methodNames = CAUSE_METHOD_NAMES; + } for (int i = 0; i < methodNames.length; i++) { - cause = getCauseUsingMethodName(throwable, methodNames[i]); - if (cause != null) { - break; + String methodName = methodNames[i]; + if (methodName != null) { + cause = getCauseUsingMethodName(throwable, methodName); + if (cause != null) { + break; + } } } @@ -190,12 +221,15 @@ public static Throwable getCause(Throwable throwable, String[] methodNames) { } /** - *

Walks through the exception chain to the last element -- the - * "root" of the tree -- using {@link #getCause(Throwable)}, and + *

Introspects the Throwable to obtain the root cause.

+ * + *

This method walks through the exception chain to the last element, + * "root" of the tree, using {@link #getCause(Throwable)}, and * returns that exception.

* - * @param throwable the throwable to get the root cause for - * @return The root cause of the Throwable. + * @param throwable the throwable to get the root cause for, may be null + * @return the root cause of the Throwable, + * null if none found or null throwable input */ public static Throwable getRootCause(Throwable throwable) { Throwable cause = getCause(throwable); @@ -209,13 +243,14 @@ public static Throwable getRootCause(Throwable throwable) { } /** + *

Finds a Throwable for known types.

+ * *

Uses instanceof checks to examine the exception, * looking for well known types which could contain chained or * wrapped exceptions.

* * @param throwable the exception to examine - * @return The wrapped exception, or null if not - * found. + * @return the wrapped exception, or null if not found */ private static Throwable getCauseUsingWellKnownTypes(Throwable throwable) { if (throwable instanceof Nestable) { @@ -234,8 +269,7 @@ private static Throwable getCauseUsingWellKnownTypes(Throwable throwable) { * * @param throwable the exception to examine * @param methodName the name of the method to find and invoke - * @return The wrapped exception, or null if not - * found. + * @return the wrapped exception, or null if not found */ private static Throwable getCauseUsingMethodName(Throwable throwable, String methodName) { Method method = null; @@ -261,8 +295,7 @@ private static Throwable getCauseUsingMethodName(Throwable throwable, String met * * @param throwable the exception to examine * @param fieldName the name of the attribute to examine - * @return The wrapped exception, or null if not - * found. + * @return the wrapped exception, or null if not found */ private static Throwable getCauseUsingFieldName(Throwable throwable, String fieldName) { Field field = null; @@ -282,15 +315,78 @@ private static Throwable getCauseUsingFieldName(Throwable throwable, String fiel return null; } + //----------------------------------------------------------------------- /** - *

Returns the number of Throwable objects in the - * exception chain.

+ *

Checks if the Throwable class has a getCause method.

+ * + *

This is true for JDK 1.4 and above.

+ * + * @return true if Throwable is nestable + */ + public static boolean isThrowableNested() { + return (THROWABLE_CAUSE_METHOD != null); + } + + /** + *

Checks whether this Throwable class can store a cause.

+ * + *

This method does not check whether it actually does store a cause.

* - * @param throwable the exception to inspect - * @return The throwable count. + * @param throwable the Throwable to examine, may be null + * @return boolean true if nested otherwise false + */ + public static boolean isNestedThrowable(Throwable throwable) { + if (throwable == null) { + return false; + } + + if (throwable instanceof Nestable) { + return true; + } else if (throwable instanceof SQLException) { + return true; + } else if (throwable instanceof InvocationTargetException) { + return true; + } else if (isThrowableNested()) { + return true; + } + + Class cls = throwable.getClass(); + for (int i = 0, isize = CAUSE_METHOD_NAMES.length; i < isize; i++) { + try { + Method method = cls.getMethod(CAUSE_METHOD_NAMES[i], null); + if (method != null) { + return true; + } + } catch (NoSuchMethodException ignored) { + } catch (SecurityException ignored) { + } + } + + try { + Field field = cls.getField("detail"); + if (field != null) { + return true; + } + } catch (NoSuchFieldException ignored) { + } catch (SecurityException ignored) { + } + + return false; + } + + //----------------------------------------------------------------------- + /** + *

Counts the number of Throwable objects in the + * exception chain.

+ * + *

A throwable without cause will return 1. + * A throwable with one cause will return 2 and so on. + * A null throwable will return 0.

+ * + * @param throwable the throwable to inspect, may be null + * @return the count of throwables, zero if null input */ public static int getThrowableCount(Throwable throwable) { - // Count the number of throwables int count = 0; while (throwable != null) { count++; @@ -302,9 +398,15 @@ public static int getThrowableCount(Throwable throwable) { /** *

Returns the list of Throwable objects in the * exception chain.

+ * + *

A throwable without cause will return an array containing + * one element - the input throwable. + * A throwable with one cause will return an array containing + * two elements. - the input throwable and the cause throwable. + * A null throwable will return an array size zero.

* - * @param throwable the exception to inspect - * @return The list of Throwable objects. + * @param throwable the throwable to inspect, may be null + * @return the array of throwables, never null */ public static Throwable[] getThrowables(Throwable throwable) { List list = new ArrayList(); @@ -315,40 +417,50 @@ public static Throwable[] getThrowables(Throwable throwable) { return (Throwable[]) list.toArray(new Throwable[list.size()]); } + //----------------------------------------------------------------------- /** - *

Delegates to {@link #indexOfThrowable(Throwable, Class, int)}, - * starting the search at the beginning of the exception chain.

+ *

Returns the (zero based) index of the first Throwable + * that matches the specified type in the exception chain.

+ * + *

A null throwable returns -1. + * A null type returns -1. + * No match in the chain returns -1.

* - * @see #indexOfThrowable(Throwable, Class, int) + * @param throwable the throwable to inspect, may be null + * @param type the type to search for + * @return the index into the throwable chain, -1 if no match or null input */ public static int indexOfThrowable(Throwable throwable, Class type) { return indexOfThrowable(throwable, type, 0); } /** - *

Returns the (zero based) index, of the first - * Throwable that matches the specified type in the - * exception chain of Throwable objects with an index - * greater than or equal to the specified index, or - * -1 if the type is not found.

+ *

Returns the (zero based) index of the first Throwable + * that matches the specified type in the exception chain from + * a specified index.

+ * + *

A null throwable returns -1. + * A null type returns -1. + * No match in the chain returns -1. + * A negative start index is treated as zero. + * A start index greater than the number of throwables returns -1.

* - * @param throwable the exception to inspect - * @param type Class to look for - * @param fromIndex the (zero based) index of the starting - * position in the chain to be searched - * @return the first occurrence of the type in the chain, or - * -1 if the type is not found - * @throws IndexOutOfBoundsException If the fromIndex - * argument is negative or not less than the count of - * Throwables in the chain. + * @param throwable the throwable to inspect, may be null + * @param type the type to search for + * @param fromIndex the (zero based) index of the starting position, + * negative treated as zero, larger than chain size returns -1 + * @return the index into the throwable chain, -1 if no match or null input */ public static int indexOfThrowable(Throwable throwable, Class type, int fromIndex) { + if (throwable == null) { + return -1; + } if (fromIndex < 0) { - throw new IndexOutOfBoundsException("Throwable index out of range: " + fromIndex); + fromIndex = 0; } Throwable[] throwables = ExceptionUtils.getThrowables(throwable); if (fromIndex >= throwables.length) { - throw new IndexOutOfBoundsException("Throwable index out of range: " + fromIndex); + return -1; } for (int i = fromIndex; i < throwables.length; i++) { if (throwables[i].getClass().equals(type)) { @@ -358,6 +470,25 @@ public static int indexOfThrowable(Throwable throwable, Class type, int fromInde return -1; } + //----------------------------------------------------------------------- + /** + *

Prints a compact stack trace for the root cause of a throwable + * to System.err.

+ * + *

The compact stack trace starts with the root cause and prints + * stack frames up to the place where it was caught and wrapped. + * Then it prints the wrapped exception and continues with stack frames + * until the wrapper exception is caught and wrapped again, etc.

+ * + *

The method is equivalent to printStackTrace for throwables + * that don't have nested causes.

+ * + * @param throwable the throwable to output + */ + public static void printRootCauseStackTrace(Throwable throwable) { + printRootCauseStackTrace(throwable, System.err); + } + /** *

Prints a compact stack trace for the root cause of a throwable.

* @@ -366,11 +497,21 @@ public static int indexOfThrowable(Throwable throwable, Class type, int fromInde * Then it prints the wrapped exception and continues with stack frames * until the wrapper exception is caught and wrapped again, etc.

* - *

The method is equivalent to t.printStackTrace() for throwables + *

The method is equivalent to printStackTrace for throwables * that don't have nested causes.

+ * + * @param throwable the throwable to output, may be null + * @param stream the stream to output to, may not be null + * @throws IllegalArgumentException if the stream is null */ - public static void printRootCauseStackTrace(Throwable t, PrintStream stream) { - String trace[] = getRootCauseStackTrace(t); + public static void printRootCauseStackTrace(Throwable throwable, PrintStream stream) { + if (throwable == null) { + return; + } + if (stream == null) { + throw new NullArgumentException("PrintStream"); + } + String trace[] = getRootCauseStackTrace(throwable); for (int i = 0; i < trace.length; i++) { stream.println(trace[i]); } @@ -378,33 +519,47 @@ public static void printRootCauseStackTrace(Throwable t, PrintStream stream) { } /** - *

Calls printRootCauseStackTraceprintRootCauseStackTrace.

- * Same as:
printRootCauseStackTrace(t, System.err);
+ *

Prints a compact stack trace for the root cause of a throwable.

+ * + *

The compact stack trace starts with the root cause and prints + * stack frames up to the place where it was caught and wrapped. + * Then it prints the wrapped exception and continues with stack frames + * until the wrapper exception is caught and wrapped again, etc.

+ * + *

The method is equivalent to printStackTrace for throwables + * that don't have nested causes.

* - * @see #printRootCauseStackTrace(Throwable,PrintWriter) + * @param throwable the throwable to output, may be null + * @param writer the writer to output to, may not be null + * @throws IllegalArgumentException if the writer is null */ - public static void printRootCauseStackTrace(Throwable t) { - printRootCauseStackTrace(t, System.err); - } - - /** - *

Same as {@link #printRootCauseStackTrace(Throwable,java.io.PrintStream)}, - * except it takes a PrintWriter as an argument.

- */ - public static void printRootCauseStackTrace(Throwable t, PrintWriter writer) { - String trace[] = getRootCauseStackTrace(t); + public static void printRootCauseStackTrace(Throwable throwable, PrintWriter writer) { + if (throwable == null) { + return; + } + if (writer == null) { + throw new NullArgumentException("PrintWriter"); + } + String trace[] = getRootCauseStackTrace(throwable); for (int i = 0; i < trace.length; i++) { writer.println(trace[i]); } writer.flush(); } + //----------------------------------------------------------------------- /** *

Creates a compact stack trace for the root cause of the supplied * Throwable.

+ * + * @param throwable the throwable to examine, may be null + * @return an array of stack trace frames, never null */ - public static String[] getRootCauseStackTrace(Throwable t) { - Throwable throwables[] = getThrowables(t); + public static String[] getRootCauseStackTrace(Throwable throwable) { + if (throwable == null) { + return ArrayUtils.EMPTY_STRING_ARRAY; + } + Throwable throwables[] = getThrowables(throwable); int count = throwables.length; ArrayList frames = new ArrayList(); List nextTrace = getStackFrameList(throwables[count - 1]); @@ -430,9 +585,13 @@ public static String[] getRootCauseStackTrace(Throwable t) { *

Removes common frames from the cause trace given the two stack traces.

* * @param causeFrames stack trace of a cause throwable - * @param wrapperFrames stack trace of a wrapper throwable + * @param wrapperFrames stack trace of a wrapper throwable + * @throws IllegalArgumentException if either argument is null */ public static void removeCommonFrames(List causeFrames, List wrapperFrames) { + if (causeFrames == null || wrapperFrames == null) { + throw new NullArgumentException("List"); + } int causeFrameIndex = causeFrames.size() - 1; int wrapperFrameIndex = wrapperFrames.size() - 1; while (causeFrameIndex >= 0 && wrapperFrameIndex >= 0) { @@ -448,94 +607,54 @@ public static void removeCommonFrames(List causeFrames, List wrapperFrames) { } } + //----------------------------------------------------------------------- /** - *

A convenient way of extracting the stack trace from an - * exception.

+ *

Gets the stack trace from a Throwable as a String.

* - * @param t The Throwable. - * @return The stack trace as generated by the exception's - * printStackTrace(PrintWriter) method. + * @param throwable the Throwable to be examined + * @return the stack trace as generated by the exception's + * printStackTrace(PrintWriter) method */ - public static String getStackTrace(Throwable t) { + public static String getStackTrace(Throwable throwable) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw, true); - t.printStackTrace(pw); + throwable.printStackTrace(pw); return sw.getBuffer().toString(); } /** *

A way to get the entire nested stack-trace of an throwable.

* - * @param t The Throwable. - * @return The nested stack trace, with the root cause first. + * @param throwable the Throwable to be examined + * @return the nested stack trace, with the root cause first */ - public static String getFullStackTrace(Throwable t) { + public static String getFullStackTrace(Throwable throwable) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw, true); - Throwable[] ts = getThrowables(t); - for(int i=0; iReturns whether a Throwable is considered nested - * or not.

- * - * @param throwable The Throwable. - * @return boolean true if nested otherwise false - */ - public static boolean isNestedThrowable(Throwable throwable) { - if(throwable == null) { - return false; - } - - if (throwable instanceof Nestable) { - return true; - } else if (throwable instanceof SQLException) { - return true; - } else if (throwable instanceof InvocationTargetException) { - return true; - } - - int sz = CAUSE_METHOD_NAMES.length; - for(int i=0; iCaptures the stack trace associated with the specified * Throwable object, decomposing it into a list of * stack frames.

* - * @param t The Throwable. - * @return An array of strings describing each stack frame. + * @param throwable the Throwable to exaamine, may be null + * @return an array of strings describing each stack frame, never null */ - public static String[] getStackFrames(Throwable t) { - return getStackFrames(getStackTrace(t)); + public static String[] getStackFrames(Throwable throwable) { + if (throwable == null) { + return ArrayUtils.EMPTY_STRING_ARRAY; + } + return getStackFrames(getStackTrace(throwable)); } /** @@ -551,8 +670,7 @@ static String[] getStackFrames(String stackTrace) { while (frames.hasMoreTokens()) { list.add(frames.nextToken()); } - return (String[]) list.toArray(new String[] { - }); + return (String[]) list.toArray(new String[list.size()]); } /** @@ -586,19 +704,4 @@ static List getStackFrameList(Throwable t) { return list; } - private static Object getCauseMethod = null; - static { - try { - getCauseMethod = Throwable.class.getMethod("getCause", null); - } catch (Exception e) { - // ignore - } - } - - /** - *

Checks if the Throwable class has a getCause method.

- */ - public static boolean isThrowableNested() { - return (getCauseMethod != null); - } } diff --git a/src/java/org/apache/commons/lang/exception/NestableDelegate.java b/src/java/org/apache/commons/lang/exception/NestableDelegate.java index 41673db22..da8d3b63c 100644 --- a/src/java/org/apache/commons/lang/exception/NestableDelegate.java +++ b/src/java/org/apache/commons/lang/exception/NestableDelegate.java @@ -79,7 +79,7 @@ * @author Sean C. Sullivan * @author Stephen Colebourne * @since 1.0 - * @version $Id: NestableDelegate.java,v 1.16 2003/07/25 23:05:22 ggregory Exp $ + * @version $Id: NestableDelegate.java,v 1.17 2003/07/26 13:05:21 scolebourne Exp $ */ public class NestableDelegate implements Serializable { @@ -99,11 +99,13 @@ public class NestableDelegate implements Serializable { /** * Whether to print the stack trace top-down. + * This public flag may be set by calling code, typically in initialisation. */ public static boolean topDown = true; /** * Whether to trim the repeated stack trace. + * This public flag may be set by calling code, typically in initialisation. */ public static boolean trimStackFrames = true; @@ -253,7 +255,20 @@ public Throwable[] getThrowables() { * chain */ public int indexOfThrowable(Class type, int fromIndex) { - return ExceptionUtils.indexOfThrowable(this.nestable, type, fromIndex); + if (fromIndex < 0) { + throw new IndexOutOfBoundsException("The start index was out of bounds: " + fromIndex); + } + Throwable[] throwables = ExceptionUtils.getThrowables(this.nestable); + if (fromIndex >= throwables.length) { + throw new IndexOutOfBoundsException("The start index was out of bounds: " + + fromIndex + " >= " + throwables.length); + } + for (int i = fromIndex; i < throwables.length; i++) { + if (throwables[i].getClass().equals(type)) { + return i; + } + } + return -1; } /**