Update null handling in ExceptionUtils

git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/lang/trunk@137508 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Stephen Colebourne 2003-07-26 13:05:21 +00:00
parent 5943137656
commit 96de08907e
2 changed files with 274 additions and 156 deletions

View File

@ -67,6 +67,7 @@ import java.util.List;
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 @@ import org.apache.commons.lang.SystemUtils;
* @author Stephen Colebourne
* @author <a href="mailto:ggregory@seagullsw.com">Gary Gregory</a>
* @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] ";
/**
* <p>The names of methods commonly used to access a wrapped
* exception.</p>
* <p>The names of methods commonly used to access a wrapped exception.</p>
*/
private static String[] CAUSE_METHOD_NAMES = {
"getCause",
@ -107,12 +107,27 @@ public class ExceptionUtils {
};
/**
* <p>Constructs a new <code>ExceptionUtils</code>. Protected to
* discourage instantiation.</p>
* <p>The Method object for JDK1.4 getCause.</p>
*/
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;
}
/**
* <p>Public constructor allows an instance of <code>ExceptionUtils</code>
* to be created, although that is not normally necessary.</p>
*/
public ExceptionUtils() {
}
//-----------------------------------------------------------------------
/**
* <p>Adds to the list of method names used in the search for <code>Throwable</code>
* objects.</p>
@ -121,7 +136,7 @@ public class 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 class ExceptionUtils {
}
/**
* <p>Introspects the specified <code>Throwable</code> to obtain the cause.</p>
* <p>Introspects the <code>Throwable</code> to obtain the cause.</p>
*
* <p>The method searches for methods with specific names that return a
* <code>Throwable</code> object. This will pick up most wrapping exceptions,
@ -154,31 +169,47 @@ public class ExceptionUtils {
*
* <p>If none of the above is found, returns <code>null</code>.</p>
*
* @param throwable The exception to introspect for a cause.
* @return The cause of the <code>Throwable</code>.
* @throws NullPointerException if the throwable is <code>null</code>
* @param throwable the throwable to introspect for a cause, may be null
* @return the cause of the <code>Throwable</code>,
* <code>null</code> if none found or null throwable input
*/
public static Throwable getCause(Throwable throwable) {
return getCause(throwable, CAUSE_METHOD_NAMES);
}
/**
* <p>Introspects the specified <code>Throwable</code> to obtain the cause
* using a supplied array of method names.</p>
* <p>Introspects the <code>Throwable</code> to obtain the cause.</p>
*
* <ol>
* <li>Try known exception types.</p>
* <li>Try the supplied array of method names.</p>
* <li>Try the field 'detail'.</p>
* </ol>
*
* <p>A <code>null</code> set of method names means use the default set.
* A <code>null</code> in the set of method names will be ignored.</p>
*
* @param throwable The exception to introspect for a cause.
* @return The cause of the <code>Throwable</code>.
* @throws NullPointerException if the method names array is <code>null</code>
* or contains <code>null</code>
* @throws NullPointerException if the throwable is <code>null</code>
* @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 <code>Throwable</code>,
* <code>null</code> 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 class ExceptionUtils {
}
/**
* <p>Walks through the exception chain to the last element -- the
* "root" of the tree -- using {@link #getCause(Throwable)}, and
* <p>Introspects the <code>Throwable</code> to obtain the root cause.</p>
*
* <p>This method walks through the exception chain to the last element,
* "root" of the tree, using {@link #getCause(Throwable)}, and
* returns that exception.</p>
*
* @param throwable the throwable to get the root cause for
* @return The root cause of the <code>Throwable</code>.
* @param throwable the throwable to get the root cause for, may be null
* @return the root cause of the <code>Throwable</code>,
* <code>null</code> if none found or null throwable input
*/
public static Throwable getRootCause(Throwable throwable) {
Throwable cause = getCause(throwable);
@ -209,13 +243,14 @@ public class ExceptionUtils {
}
/**
* <p>Finds a <code>Throwable</code> for known types.</p>
*
* <p>Uses <code>instanceof</code> checks to examine the exception,
* looking for well known types which could contain chained or
* wrapped exceptions.</p>
*
* @param throwable the exception to examine
* @return The wrapped exception, or <code>null</code> if not
* found.
* @return the wrapped exception, or <code>null</code> if not found
*/
private static Throwable getCauseUsingWellKnownTypes(Throwable throwable) {
if (throwable instanceof Nestable) {
@ -234,8 +269,7 @@ public class ExceptionUtils {
*
* @param throwable the exception to examine
* @param methodName the name of the method to find and invoke
* @return The wrapped exception, or <code>null</code> if not
* found.
* @return the wrapped exception, or <code>null</code> if not found
*/
private static Throwable getCauseUsingMethodName(Throwable throwable, String methodName) {
Method method = null;
@ -261,8 +295,7 @@ public class ExceptionUtils {
*
* @param throwable the exception to examine
* @param fieldName the name of the attribute to examine
* @return The wrapped exception, or <code>null</code> if not
* found.
* @return the wrapped exception, or <code>null</code> if not found
*/
private static Throwable getCauseUsingFieldName(Throwable throwable, String fieldName) {
Field field = null;
@ -282,15 +315,78 @@ public class ExceptionUtils {
return null;
}
//-----------------------------------------------------------------------
/**
* <p>Returns the number of <code>Throwable</code> objects in the
* exception chain.</p>
* <p>Checks if the Throwable class has a <code>getCause</code> method.</p>
*
* <p>This is true for JDK 1.4 and above.</p>
*
* @return true if Throwable is nestable
*/
public static boolean isThrowableNested() {
return (THROWABLE_CAUSE_METHOD != null);
}
/**
* <p>Checks whether this <code>Throwable</code> class can store a cause.</p>
*
* <p>This method does <b>not</b> check whether it actually does store a cause.<p>
*
* @param throwable the exception to inspect
* @return The throwable count.
* @param throwable the <code>Throwable</code> to examine, may be null
* @return boolean <code>true</code> if nested otherwise <code>false</code>
*/
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;
}
//-----------------------------------------------------------------------
/**
* <p>Counts the number of <code>Throwable</code> objects in the
* exception chain.</p>
*
* <p>A throwable without cause will return <code>1</code>.
* A throwable with one cause will return <code>2</code> and so on.
* A <code>null</code> throwable will return <code>0</code>.</p>
*
* @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 class ExceptionUtils {
/**
* <p>Returns the list of <code>Throwable</code> objects in the
* exception chain.</p>
*
* <p>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 <code>null</code> throwable will return an array size zero.</p>
*
* @param throwable the exception to inspect
* @return The list of <code>Throwable</code> 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 class ExceptionUtils {
return (Throwable[]) list.toArray(new Throwable[list.size()]);
}
//-----------------------------------------------------------------------
/**
* <p>Delegates to {@link #indexOfThrowable(Throwable, Class, int)},
* starting the search at the beginning of the exception chain.</p>
* <p>Returns the (zero based) index of the first <code>Throwable</code>
* that matches the specified type in the exception chain.</p>
*
* <p>A <code>null</code> throwable returns <code>-1</code>.
* A <code>null</code> type returns <code>-1</code>.
* No match in the chain returns <code>-1</code>.</p>
*
* @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);
}
/**
* <p>Returns the (zero based) index, of the first
* <code>Throwable</code> that matches the specified type in the
* exception chain of <code>Throwable</code> objects with an index
* greater than or equal to the specified index, or
* <code>-1</code> if the type is not found.</p>
* <p>Returns the (zero based) index of the first <code>Throwable</code>
* that matches the specified type in the exception chain from
* a specified index.</p>
*
* <p>A <code>null</code> throwable returns <code>-1</code>.
* A <code>null</code> type returns <code>-1</code>.
* No match in the chain returns <code>-1</code>.
* A negative start index is treated as zero.
* A start index greater than the number of throwables returns <code>-1</code>.</p>
*
* @param throwable the exception to inspect
* @param type <code>Class</code> 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
* <code>-1</code> if the type is not found
* @throws IndexOutOfBoundsException If the <code>fromIndex</code>
* argument is negative or not less than the count of
* <code>Throwable</code>s 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 class ExceptionUtils {
return -1;
}
//-----------------------------------------------------------------------
/**
* <p>Prints a compact stack trace for the root cause of a throwable
* to <code>System.err</code>.</p>
*
* <p>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.</p>
*
* <p>The method is equivalent to <code>printStackTrace</code> for throwables
* that don't have nested causes.</p>
*
* @param throwable the throwable to output
*/
public static void printRootCauseStackTrace(Throwable throwable) {
printRootCauseStackTrace(throwable, System.err);
}
/**
* <p>Prints a compact stack trace for the root cause of a throwable.</p>
*
@ -366,11 +497,21 @@ public class ExceptionUtils {
* Then it prints the wrapped exception and continues with stack frames
* until the wrapper exception is caught and wrapped again, etc.</p>
*
* <p>The method is equivalent to t.printStackTrace() for throwables
* <p>The method is equivalent to <code>printStackTrace</code> for throwables
* that don't have nested causes.</p>
*
* @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 <code>null</code>
*/
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 class ExceptionUtils {
}
/**
* <p>Calls <code>printRootCauseStackTraceprintRootCauseStackTrace</code>.</p>
* Same as: <pre>printRootCauseStackTrace(t, System.err);</pre>
* <p>Prints a compact stack trace for the root cause of a throwable.</p>
*
* <p>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.</p>
*
* <p>The method is equivalent to <code>printStackTrace</code> for throwables
* that don't have nested causes.</p>
*
* @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 <code>null</code>
*/
public static void printRootCauseStackTrace(Throwable t) {
printRootCauseStackTrace(t, System.err);
}
/**
* <p>Same as {@link #printRootCauseStackTrace(Throwable,java.io.PrintStream)},
* except it takes a <code>PrintWriter</code> as an argument.</p>
*/
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();
}
//-----------------------------------------------------------------------
/**
* <p>Creates a compact stack trace for the root cause of the supplied
* <code>Throwable</code>.</p>
*
* @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 class ExceptionUtils {
* <p>Removes common frames from the cause trace given the two stack traces.</p>
*
* @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 class ExceptionUtils {
}
}
//-----------------------------------------------------------------------
/**
* <p>A convenient way of extracting the stack trace from an
* exception.</p>
* <p>Gets the stack trace from a Throwable as a String.</p>
*
* @param t The <code>Throwable</code>.
* @return The stack trace as generated by the exception's
* <code>printStackTrace(PrintWriter)</code> method.
* @param throwable the <code>Throwable</code> to be examined
* @return the stack trace as generated by the exception's
* <code>printStackTrace(PrintWriter)</code> 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();
}
/**
* <p>A way to get the entire nested stack-trace of an throwable.</p>
*
* @param t The <code>Throwable</code>.
* @return The nested stack trace, with the root cause first.
* @param throwable the <code>Throwable</code> 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; i<ts.length; i++) {
Throwable[] ts = getThrowables(throwable);
for (int i = 0; i < ts.length; i++) {
ts[i].printStackTrace(pw);
if(isNestedThrowable(ts[i])) {
if (isNestedThrowable(ts[i])) {
break;
}
}
return sw.getBuffer().toString();
}
/**
* <p>Returns whether a <code>Throwable</code> is considered nested
* or not.</p>
*
* @param throwable The <code>Throwable</code>.
* @return boolean <code>true</code> if nested otherwise <code>false</code>
*/
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; i<sz; i++) {
try {
Method method = throwable.getClass().getMethod(CAUSE_METHOD_NAMES[i], null);
if(method != null) {
return true;
}
} catch (NoSuchMethodException ignored) {
} catch (SecurityException ignored) {
}
}
try {
Field field = throwable.getClass().getField("detail");
if(field != null) {
return true;
}
} catch (NoSuchFieldException ignored) {
} catch (SecurityException ignored) {
}
return false;
}
//-----------------------------------------------------------------------
/**
* <p>Captures the stack trace associated with the specified
* <code>Throwable</code> object, decomposing it into a list of
* stack frames.</p>
*
* @param t The <code>Throwable</code>.
* @return An array of strings describing each stack frame.
* @param throwable the <code>Throwable</code> 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 @@ public class ExceptionUtils {
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 @@ public class ExceptionUtils {
return list;
}
private static Object getCauseMethod = null;
static {
try {
getCauseMethod = Throwable.class.getMethod("getCause", null);
} catch (Exception e) {
// ignore
}
}
/**
* <p>Checks if the Throwable class has a <code>getCause</code> method.</p>
*/
public static boolean isThrowableNested() {
return (getCauseMethod != null);
}
}

View File

@ -79,7 +79,7 @@ import java.util.List;
* @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 class NestableDelegate implements Serializable {
* 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;
}
/**