zcommon/src/main/java/org/zkoss/lang/Exceptions.java
/* Exceptions.java
Purpose: Utilities for Exceptions
Description:
History:
2001/4/22, Tom M. Yeh: Created.
Copyright (C) 2001 Potix Corporation. All Rights Reserved.
{{IS_RIGHT
This program is distributed under LGPL Version 2.1 in the hope that
it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.lang;
import java.io.StringWriter;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.UndeclaredThrowableException;
import java.sql.SQLException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zkoss.mesg.MCommon;
import org.zkoss.mesg.Messages;
/**
* Utilities for Exceptions.
*
* @author tomyeh
*/
public class Exceptions {
private static final Logger log = LoggerFactory.getLogger(Exceptions.class);
/**
* Finds the causes of an exception, ex, to see whether
* any of them is the given type.
*
* @return the cause if found; null if not found
*/
public static final Throwable findCause(Throwable ex, Class<?> cause) {
while (ex != null) {
if (cause.isInstance(ex))
return ex;
ex = getCause(ex);
}
return null;
}
/** Returns the cause of the given throwable. It is the same as
* t.getCause, but it solves the compatibility of J2EE that might not
* support JDK 1.4.
*/
public static final Throwable getCause(Throwable ex) {
Throwable t = ex.getCause();
if (t == null)
try {
if (ex instanceof java.rmi.RemoteException) {
return ((java.rmi.RemoteException)ex).detail;
} else if (ex instanceof org.xml.sax.SAXException) {
return ((org.xml.sax.SAXException)ex).getException();
} else if (ex instanceof javax.servlet.ServletException) {
return ((javax.servlet.ServletException)ex).getRootCause();
} else if (ex instanceof bsh.TargetError) {
return ((bsh.TargetError)ex).getTarget();
} else if (ex instanceof bsh.UtilTargetError) {
return ((bsh.UtilTargetError)ex).t;
//Remove the dependence on EJB
// } else if (ex instanceof javax.ejb.EJBException) {
// return ((javax.ejb.EJBException)ex).getCausedByException();
//Remove the dependence on JSP
// } else if (ex instanceof javax.servlet.jsp.JspException) {
// return ((javax.servlet.jsp.JspException)ex).getRootCause();
//Remove the dependence on EL
// } else if (ex instanceof javax.servlet.jsp.el.ELException) {
// return ((javax.servlet.jsp.el.ELException)ex).getRootCause();
}
} catch (Throwable e2) {
if (log.isDebugEnabled()) log.debug("Ignored: unable to resolve " + ex.getClass());
}
return t;
}
/**
* Unveils the real cause. A throwable object is called a real cause,
* if it doesn't have any cause (or called chained throwable).
*
* @param ex the throwable
* @return the real cause; ex itself if it is already the real cause
* @see #wrap
*/
public static final Throwable getRealCause(Throwable ex) {
while (true) {
Throwable cause = getCause(ex);
if (cause == null)
return ex;
ex = cause;
}
}
/**
* Converts an exception to the specified class. If any of causes
* is the specified class, the cause is returned. If the giving exception
* is RuntimeException or error, it is re-thrown directly. In other words,
* Unlike {@link #findCause}, it is designed to be used in the
* <code>throw</code> statement as follows.
*
* <p>Usage 1: one kind of exception is examined.
* <pre><code>
* try {
* ...
* } catch (Exception ex) {
* throw (MyException)Exceptions.wrap(ex, MyException.class);
* }</code></pre>
*
* <p>Usage 2: two kinds of exceptions are examined.
* <pre><code>
* try {
* ...
* } catch (Exception ex) {
* Throwable t = Exceptions.findCause(ex, AnotherException.class);
* if (t != null)
* throw (AnotherException)t;
* throw (MyException)Exceptions.wrap(ex, MyException.class);
* }</code></pre>
*
* <p>Usage 3: two kinds of exceptions are examined.
* <pre><code>
* try {
* ...
* } catch (AnotherException ex) {
* throw ex;
* } catch (Exception ex) {
* throw (MyException)Exceptions.wrap(ex, MyException.class);
* }</code></pre>
*
* <p>If you already know the exception, you don't need this method
* -- AnotherException won't never be RuntimeException or MyException.
* <pre><code>
* try {
* ...
* } catch (AnotherException ex) {
* throw new MyException(ex);
* }</code></pre>
*
* <p>Note: It assumes the exception has the constructor:
* <code>MyException(Throwable cause);</code>
*
* @param targetExceptCls the class of exception to be converted to
* @param ex the exception being caught (and to be converted)
* @see #getRealCause
* @see SystemException.Aide#wrap
*/
public static final
Throwable wrap(Throwable ex, Class<? extends Throwable> targetExceptCls) {
ex = myToAnother(ex, targetExceptCls);
if (targetExceptCls.isInstance(ex))
return ex;
try {
return (Throwable)Classes.newInstance(targetExceptCls,
new Class[] {Throwable.class}, new Object[] {ex});
} catch (Exception e2) {
log.warn("Unable to wrap an exception in " + targetExceptCls, e2);
throw new SystemException(ex);
//avoid dead-loop; don't use SystemException.Aide.wrap
}
}
private static final Throwable
myToAnother(Throwable ex, Class<? extends Throwable> targetExceptCls) {
if (ex instanceof InvocationTargetException)
ex = ex.getCause(); //might returns UndeclaredThrowableException
if (ex instanceof UndeclaredThrowableException)
ex = ex.getCause();
//NOTE: In EJB, runtime exceptions means rolls back, so (1) some app
//might intercept them with non-runtime, (2) you don't want runtime
//being wrapped here unexpectedly.
if (ex instanceof RuntimeException)
throw (RuntimeException)ex;
if (ex instanceof Error)
throw (Error)ex;
for (Throwable t = ex; t != null; t = getCause(t))
if (targetExceptCls.isInstance(t))
return t;
return ex;
}
/**
* Converts an exception to the specified class plus a message.
* It is similar to {@link #wrap(Throwable, Class)} but with
* an additional message. Thus, the target exception class must has
* the constructor: <code>MyException(String msg, Throwable cause);</code>
*/
public static final
Throwable wrap(Throwable ex, Class<? extends Throwable> targetExceptCls, String msg) {
ex = myToAnother(ex, targetExceptCls);
if (targetExceptCls.isInstance(ex))
return ex;
try {
return (Throwable)Classes.newInstance(targetExceptCls,
new Class[] {String.class, Throwable.class},
new Object[] {msg, ex});
} catch (Exception e2) {
log.warn("Unable to wrap an exception in " + targetExceptCls, e2);
throw new SystemException(ex);
//avoid dead-loop; don't use SystemException.Aide.wrap
}
}
/**
* Converts an exception to the specified class plus a message.
* It is similar to {@link #wrap(Throwable, Class)} but with
* an additional message code. Thus, the target exception class must has
* the constructor:
* <code>MyException(int code, Object[] fmtArgs, Throwable cause);</code>
*/
public static final Throwable
wrap(Throwable ex, Class<? extends Throwable> targetExceptCls, int code, Object[] fmtArgs) {
ex = myToAnother(ex, targetExceptCls);
if (targetExceptCls.isInstance(ex))
return ex;
try {
return (Throwable)Classes.newInstance(targetExceptCls,
new Class[] {int.class, Object[].class, Throwable.class},
new Object[] {new Integer(code), fmtArgs, ex});
} catch (Exception e2) {
log.warn("Unable to wrap an exception in " + targetExceptCls, e2);
throw new SystemException(ex);
//avoid dead-loop; don't use SystemException.Aide.wrap
}
}
/**
* Converts an exception to the specified class plus a message.
* It is similar to {@link #wrap(Throwable, Class)} but with
* an additional message code. Thus, the target exception class must has
* the constructor:
* <code>MyException(int code, Object fmtArgs, Throwable cause);</code>
*/
public static final Throwable
wrap(Throwable ex, Class<? extends Throwable> targetExceptCls, int code, Object fmtArg) {
ex = myToAnother(ex, targetExceptCls);
if (targetExceptCls.isInstance(ex))
return ex;
try {
return (Throwable)Classes.newInstance(targetExceptCls,
new Class[] {int.class, Object.class, Throwable.class},
new Object[] {new Integer(code), fmtArg, ex});
} catch (Exception e2) {
log.warn("Unable to wrap an exception in " + targetExceptCls, e2);
throw new SystemException(ex);
//avoid dead-loop; don't use SystemException.Aide.wrap
}
}
/**
* Converts an exception to the specified class plus a message.
* It is similar to {@link #wrap(Throwable, Class)} but with
* an additional message code. Thus, the target exception class must has
* the constructor:
* <code>MyException(int code, Throwable cause);</code>
*/
public static final Throwable
wrap(Throwable ex, Class<? extends Throwable> targetExceptCls, int code) {
ex = myToAnother(ex, targetExceptCls);
if (targetExceptCls.isInstance(ex))
return ex;
try {
return (Throwable)Classes.newInstance(targetExceptCls,
new Class[] {int.class, Throwable.class},
new Object[] {new Integer(code), ex});
} catch (Exception e2) {
log.warn("Unable to wrap an exception in " + targetExceptCls, e2);
throw new SystemException(ex);
//avoid dead-loop; don't use SystemException.Aide.wrap
}
}
/** Unwraps an exception if the enclosing one is InvocationTargetException
* or UndeclaredThrowableException.
* <p>Use it if you are catching exceptions thrown by Method.invoke().
*/
public static final Throwable unwrap(Throwable ex) {
for (;;) {
if (ex instanceof InvocationTargetException)
ex = ex.getCause(); //might returns UndeclaredThrowableException
else if (ex instanceof UndeclaredThrowableException)
ex = ex.getCause();
else {
try {
if (ex instanceof bsh.TargetError) {
final Throwable t = ((bsh.TargetError)ex).getTarget();
if (t != null) ex = t;
} else if (ex instanceof bsh.UtilTargetError) {
final Throwable t = ((bsh.UtilTargetError)ex).t;
if (t != null) ex = t;
}
} catch (Throwable e2) {
if (log.isDebugEnabled()) log.debug("Ignored: unable to resolve " + ex.getClass());
}
//Remove the dependence on EL
/* try {
if (ex instanceof javax.servlet.jsp.el.ELException) {
final Throwable t =
((javax.servlet.jsp.el.ELException)ex).getRootCause();
if (t != null) ex = t;
}
} catch (Throwable e2) {
if (log.isDebugEnabled()) log.debug("Ignored: unable to resolve " + ex.getClass());
}*/
return ex;
}
assert ex != null: "null cause";
}
}
/**
* Returns the extra message, or null if no extra.
*
* <p>Currently, it handles only SQLException
*/
public static final String getExtraMessage(Throwable ex) {
ex = findCause(ex, SQLException.class);
if (ex != null) {
SQLException e = (SQLException)ex;
return "[SQL: " + e.getErrorCode() + ", " + e.getSQLState() + ']';
}
return null;
}
/** Returns a message of the exception.
*/
public static final String getMessage(Throwable ex) {
String s;
for (Throwable t = ex;;) {
s = t.getMessage();
if (s != null && s.length() > 0)
break; //found
t = getCause(t);
if (t == null) {
s = Messages.get(MCommon.UNKNOWN_EXCEPTION, ex.getClass().getName());
break; //failed
}
}
final String s2 = getExtraMessage(ex);
return s2 != null ? s + s2: s;
}
/**
* Gets the message for an exception's getMessage method.
*
* <p>It actually delegates to Messages, and then
* appends an error code.
*/
public static final String getMessage(int code, Object[] fmtArgs) {
return Messages.get(code, fmtArgs);
}
/**
* Gets the message for an exception with one format-argument.
*/
public static final String getMessage(int code, Object fmtArg) {
return getMessage(code, new Object[] {fmtArg});
}
/**
* Gets the message for an exception without format-argument.
*/
public static final String getMessage(int code) {
return getMessage(code, (Object[])null);
}
/** Returns the first few lines of the stack trace.
* It is useful to make the cause exception's stack trace to be
* part of the message of the exception thrown to the user.
*/
public static final String getBriefStackTrace(Throwable t) {
return formatStackTrace(null, t, ">>", 6).toString();
}
/** Formats the stack trace and returns the result.
* Currently, it only adds the prefix to each line.
*
* @param prefix the prefix shown in front of each line of the stack trace;
* null to denote empty
*/
public static final String formatStackTrace(Throwable t, String prefix) {
return formatStackTrace(null, t, prefix).toString();
}
/** Formats the stack trace and appends it to the specified string buffer.
* @param sb the string buffer to append the stack trace. A string buffer
* will be created if null.
* @param prefix the prefix shown in front of each line of the stack trace;
* null to denote empty
*/
public static final StringBuffer
formatStackTrace(StringBuffer sb, Throwable t, String prefix) {
return formatStackTrace(sb, t, prefix, 0);
}
/** Formats the stack trace and appends it to the specified string buffer,
* but only display at most maxcnt lines.
*
* <p>The maximal allowed number of lines is controlled by
* maxcnt. Note: a stack frame is not counted, if it belongs
* to java.*, javax.* or sun.*.
*
* @param sb the string buffer to append the stack trace. A string buffer
* will be created if null.
* @param prefix the prefix shown in front of each line of the stack trace;
* null to denote empty
* @param maxcnt the maximal allowed number of lines to dump ({@code <=0}: no limit)
*/
public static final StringBuffer
formatStackTrace(StringBuffer sb, Throwable t, String prefix, int maxcnt) {
final StringWriter sw = new StringWriter();
t.printStackTrace(new PrintWriter(sw));
final StringBuffer trace = sw.getBuffer();
if (prefix == null) prefix = "";
if (maxcnt > 0 || prefix.length() > 0) {
final int len = trace.length();
if (sb == null)
sb = new StringBuffer(len + 256);
if (maxcnt <= 0)
maxcnt = Integer.MAX_VALUE;
boolean ignoreCount = false;
for (int j = 0; j < len;) { //for each line
if (!ignoreCount && --maxcnt < 0) {
sb.append(prefix).append("...");
break;
}
//StringBuffer has no indexOf(char,j), so...
int k = j;
while (k < len && trace.charAt(k++) != '\n')
; //point k to the char after \n
String frame = trace.substring(j, k);
sb.append(prefix).append(frame);
j = k;
ignoreCount = inStack(frame, "java.")
|| inStack(frame, "javax.") || inStack(frame, "sun.")
|| inStack(frame, "bsh.");
}
} else {
if (sb == null)
return trace;
sb.append(trace);
}
return sb;
}
private static boolean inStack(String frame, String sub) {
final int j = frame.indexOf(sub);
if (j < 0) return false;
if (j == 0) return true;
final char cc = frame.charAt(j - 1);
return (cc < 'a' || cc > 'z') && (cc < 'A' || cc > 'Z');
}
}