001: /* Exceptions.java
002:
003: {{IS_NOTE
004:
005: Purpose: Utilities for Exceptions
006: Description:
007: History:
008: 2001/4/22, Tom M. Yeh: Created.
009:
010: }}IS_NOTE
011:
012: Copyright (C) 2001 Potix Corporation. All Rights Reserved.
013:
014: {{IS_RIGHT
015: This program is distributed under GPL Version 2.0 in the hope that
016: it will be useful, but WITHOUT ANY WARRANTY.
017: }}IS_RIGHT
018: */
019: package org.zkoss.lang;
020:
021: import java.io.StringWriter;
022: import java.io.PrintWriter;
023: import java.lang.reflect.InvocationTargetException;
024: import java.lang.reflect.UndeclaredThrowableException;
025: import java.sql.SQLException;
026:
027: import org.zkoss.lang.D;
028: import org.zkoss.mesg.MCommon;
029: import org.zkoss.mesg.Messages;
030: import org.zkoss.util.logging.Log;
031:
032: /**
033: * Utilities for Exceptions.
034: *
035: * @author tomyeh
036: */
037: public class Exceptions {
038: private static final Log log = Log.lookup(Exceptions.class);
039:
040: /**
041: * Finds the causes of an exception, ex, to see whether
042: * any of them is the givinge type.
043: *
044: * @return the cause if found; null if not found
045: */
046: public static final Throwable findCause(Throwable ex, Class cause) {
047: while (ex != null) {
048: if (cause.isInstance(ex))
049: return ex;
050:
051: ex = getCause(ex);
052: }
053: return null;
054: }
055:
056: /** Returns the cause of the given throwable. It is the same as
057: * t.getCause, but it solves the compatibility of J2EE that might not
058: * support JDK 1.4.
059: */
060: public static final Throwable getCause(Throwable ex) {
061: Throwable t = ex.getCause();
062: if (t == null)
063: try {
064: if (ex instanceof java.rmi.RemoteException) {
065: return ((java.rmi.RemoteException) ex).detail;
066: } else if (ex instanceof org.xml.sax.SAXException) {
067: return ((org.xml.sax.SAXException) ex)
068: .getException();
069: } else if (ex instanceof javax.servlet.ServletException) {
070: return ((javax.servlet.ServletException) ex)
071: .getRootCause();
072: } else if (ex instanceof bsh.TargetError) {
073: return ((bsh.TargetError) ex).getTarget();
074: } else if (ex instanceof bsh.UtilTargetError) {
075: return ((bsh.UtilTargetError) ex).t;
076: //Remove the dependence on EJB
077: // } else if (ex instanceof javax.ejb.EJBException) {
078: // return ((javax.ejb.EJBException)ex).getCausedByException();
079: //Remove the dependence on JSP
080: // } else if (ex instanceof javax.servlet.jsp.JspException) {
081: // return ((javax.servlet.jsp.JspException)ex).getRootCause();
082: //Remove the dependence on EL
083: // } else if (ex instanceof javax.servlet.jsp.el.ELException) {
084: // return ((javax.servlet.jsp.el.ELException)ex).getRootCause();
085: }
086: } catch (Throwable e2) {
087: if (log.debugable())
088: log.debug("Ignored: unable to resolve "
089: + ex.getClass());
090: }
091: return t;
092: }
093:
094: /**
095: * Unveils the real cause. A throwable object is called a real cause,
096: * if it doesn't have any cause (or called chained throwable).
097: *
098: * @param ex the throwable
099: * @return the real cause; ex itself if it is already the real cause
100: * @see #wrap
101: */
102: public static final Throwable getRealCause(Throwable ex) {
103: while (true) {
104: Throwable cause = getCause(ex);
105: if (cause == null)
106: return ex;
107: ex = cause;
108: }
109: }
110:
111: /**
112: * Converts an exception to the sepecified class. If any of causes
113: * is the specified class, the cause is returned. If the giving exception
114: * is RuntimeException or error, it is re-thrown directly. In other words,
115: * Unlike {@link #findCause}, it is designed to be used in the
116: * <code>throw</code> statement as follows.
117: *
118: * <p>Usage 1: one kind of exception is examined.
119: * <pre><code>
120: * try {
121: * ...
122: * } catch (Exception ex) {
123: * throw (MyException)Exceptions.wrap(ex, MyException.class);
124: * }</code></pre>
125: *
126: * <p>Usage 2: two kinds of exceptions are examined.
127: * <pre><code>
128: * try {
129: * ...
130: * } catch (Exception ex) {
131: * Throwable t = Exceptions.findCause(ex, AnotherException.class);
132: * if (t != null)
133: * throw (AnotherException)t;
134: * throw (MyException)Exceptions.wrap(ex, MyException.class);
135: * }</code></pre>
136: *
137: * <p>Usage 3: two kinds of exceptions are examined.
138: * <pre><code>
139: * try {
140: * ...
141: * } catch (AnotherException ex) {
142: * throw ex;
143: * } catch (Exception ex) {
144: * throw (MyException)Exceptions.wrap(ex, MyException.class);
145: * }</code></pre>
146: *
147: * <p>If you already know the exception, you don't need this method
148: * -- AnotherException won't never be RuntimeException or MyException.
149: * <pre><code>
150: * try {
151: * ...
152: * } catch (AnotherException ex) {
153: * throw new MyException(ex);
154: * }</code></pre>
155: *
156: * <p>Note: It assumes the exception has the constructor:
157: * <code>MyException(Throwable cause);</code>
158: *
159: * @param targetExceptCls the class of exception to be converted to
160: * @param ex the exception being caught (and to be converted)
161: * @see #getRealCause
162: * @see SystemException.Aide#wrap
163: */
164: public static final Throwable wrap(Throwable ex,
165: Class targetExceptCls) {
166: ex = myToAnother(ex, targetExceptCls);
167: if (targetExceptCls.isInstance(ex))
168: return ex;
169:
170: try {
171: return (Throwable) Classes.newInstance(targetExceptCls,
172: new Class[] { Throwable.class },
173: new Object[] { ex });
174: } catch (Exception e2) {
175: log.warning("Unable to wrap an exception in "
176: + targetExceptCls, e2);
177: throw new SystemException(ex);
178: //avoid dead-loop; don't use SystemException.Aide.wrap
179: }
180: }
181:
182: private static final Throwable myToAnother(Throwable ex,
183: Class targetExceptCls) {
184: assert D.OFF || ex != null : "null exception";
185:
186: if (ex instanceof InvocationTargetException)
187: ex = ex.getCause(); //might returns UndeclaredThrowableException
188: if (ex instanceof UndeclaredThrowableException)
189: ex = ex.getCause();
190:
191: //NOTE: In EJB, runtime exceptions means rolls back, so (1) some app
192: //might intercept them with non-runtime, (2) you don't want runtime
193: //being wrapped here unexpectedly.
194: if (ex instanceof RuntimeException)
195: throw (RuntimeException) ex;
196: if (ex instanceof Error)
197: throw (Error) ex;
198:
199: for (Throwable t = ex; t != null; t = getCause(t))
200: if (targetExceptCls.isInstance(t))
201: return t;
202:
203: return ex;
204: }
205:
206: /**
207: * Converts an exception to the sepecified class plus a message.
208: * It is similar to {@link #wrap(Throwable, Class)} but with
209: * an additional message. Thus, the target exception class must has
210: * the constructor: <code>MyException(String msg, Throwable cause);</code>
211: */
212: public static final Throwable wrap(Throwable ex,
213: Class targetExceptCls, String msg) {
214: ex = myToAnother(ex, targetExceptCls);
215: if (targetExceptCls.isInstance(ex))
216: return ex;
217:
218: try {
219: return (Throwable) Classes.newInstance(targetExceptCls,
220: new Class[] { String.class, Throwable.class },
221: new Object[] { msg, ex });
222: } catch (Exception e2) {
223: log.warning("Unable to wrap an exception in "
224: + targetExceptCls, e2);
225: throw new SystemException(ex);
226: //avoid dead-loop; don't use SystemException.Aide.wrap
227: }
228: }
229:
230: /**
231: * Converts an exception to the sepecified class plus a message.
232: * It is similar to {@link #wrap(Throwable, Class)} but with
233: * an additional message code. Thus, the target exception class must has
234: * the constructor:
235: * <code>MyException(int code, Object[] fmtArgs, Throwable cause);</code>
236: */
237: public static final Throwable wrap(Throwable ex,
238: Class targetExceptCls, int code, Object[] fmtArgs) {
239: ex = myToAnother(ex, targetExceptCls);
240: if (targetExceptCls.isInstance(ex))
241: return ex;
242:
243: try {
244: return (Throwable) Classes.newInstance(targetExceptCls,
245: new Class[] { int.class, Object[].class,
246: Throwable.class }, new Object[] {
247: new Integer(code), fmtArgs, ex });
248: } catch (Exception e2) {
249: log.warning("Unable to wrap an exception in "
250: + targetExceptCls, e2);
251: throw new SystemException(ex);
252: //avoid dead-loop; don't use SystemException.Aide.wrap
253: }
254: }
255:
256: /**
257: * Converts an exception to the sepecified class plus a message.
258: * It is similar to {@link #wrap(Throwable, Class)} but with
259: * an additional message code. Thus, the target exception class must has
260: * the constructor:
261: * <code>MyException(int code, Object fmtArgs, Throwable cause);</code>
262: */
263: public static final Throwable wrap(Throwable ex,
264: Class targetExceptCls, int code, Object fmtArg) {
265: ex = myToAnother(ex, targetExceptCls);
266: if (targetExceptCls.isInstance(ex))
267: return ex;
268:
269: try {
270: return (Throwable) Classes.newInstance(targetExceptCls,
271: new Class[] { int.class, Object.class,
272: Throwable.class }, new Object[] {
273: new Integer(code), fmtArg, ex });
274: } catch (Exception e2) {
275: log.warning("Unable to wrap an exception in "
276: + targetExceptCls, e2);
277: throw new SystemException(ex);
278: //avoid dead-loop; don't use SystemException.Aide.wrap
279: }
280: }
281:
282: /**
283: * Converts an exception to the sepecified class plus a message.
284: * It is similar to {@link #wrap(Throwable, Class)} but with
285: * an additional message code. Thus, the target exception class must has
286: * the constructor:
287: * <code>MyException(int code, Throwable cause);</code>
288: */
289: public static final Throwable wrap(Throwable ex,
290: Class targetExceptCls, int code) {
291: ex = myToAnother(ex, targetExceptCls);
292: if (targetExceptCls.isInstance(ex))
293: return ex;
294:
295: try {
296: return (Throwable) Classes.newInstance(targetExceptCls,
297: new Class[] { int.class, Throwable.class },
298: new Object[] { new Integer(code), ex });
299: } catch (Exception e2) {
300: log.warning("Unable to wrap an exception in "
301: + targetExceptCls, e2);
302: throw new SystemException(ex);
303: //avoid dead-loop; don't use SystemException.Aide.wrap
304: }
305: }
306:
307: /** Unwraps an exception if the enclosing one is InvocationTargetException
308: * or UndeclaredThrowableException.
309: * <p>Use it if you are catching exceptions thrown by Method.invoke().
310: */
311: public static final Throwable unwrap(Throwable ex) {
312: for (;;) {
313: if (ex instanceof InvocationTargetException)
314: ex = ex.getCause(); //might returns UndeclaredThrowableException
315: else if (ex instanceof UndeclaredThrowableException)
316: ex = ex.getCause();
317: else {
318: try {
319: if (ex instanceof bsh.TargetError) {
320: final Throwable t = ((bsh.TargetError) ex)
321: .getTarget();
322: if (t != null)
323: ex = t;
324: } else if (ex instanceof bsh.UtilTargetError) {
325: final Throwable t = ((bsh.UtilTargetError) ex).t;
326: if (t != null)
327: ex = t;
328: }
329: } catch (Throwable e2) {
330: if (log.debugable())
331: log.debug("Ignored: unable to resolve "
332: + ex.getClass());
333: }
334: //Remove the dependence on EL
335: /* try {
336: if (ex instanceof javax.servlet.jsp.el.ELException) {
337: final Throwable t =
338: ((javax.servlet.jsp.el.ELException)ex).getRootCause();
339: if (t != null) ex = t;
340: }
341: } catch (Throwable e2) {
342: if (log.debugable()) log.debug("Ignored: unable to resolve " + ex.getClass());
343: }*/
344: return ex;
345: }
346: assert D.OFF || ex != null : "null cause";
347: }
348: }
349:
350: /**
351: * Returns the extra message, or null if no extra.
352: *
353: * <p>Currently, it handles only SQLException
354: */
355: public static final String getExtraMessage(Throwable ex) {
356: ex = findCause(ex, SQLException.class);
357: if (ex != null) {
358: SQLException e = (SQLException) ex;
359: return "[SQL: " + e.getErrorCode() + ", " + e.getSQLState()
360: + ']';
361: }
362: return null;
363: }
364:
365: /** Returns a message of the exception.
366: */
367: public static final String getMessage(Throwable ex) {
368: String s;
369: for (Throwable t = ex;;) {
370: s = t.getMessage();
371: if (s != null && s.length() > 0)
372: break; //found
373:
374: t = getCause(t);
375: if (t == null) {
376: s = Messages.get(MCommon.UNKNOWN_EXCEPTION, ex
377: .getClass().getName());
378: break; //failed
379: }
380: }
381: final String s2 = getExtraMessage(ex);
382: return s2 != null ? s + s2 : s;
383: }
384:
385: /**
386: * Gets the message for an exception's getMessage method.
387: *
388: * <p>It actually delegates to Messages, and then
389: * appends an error code.
390: */
391: public static final String getMessage(int code, Object[] fmtArgs) {
392: return Messages.get(code, fmtArgs);
393: }
394:
395: /**
396: * Gets the message for an exception with one format-argument.
397: */
398: public static final String getMessage(int code, Object fmtArg) {
399: return getMessage(code, new Object[] { fmtArg });
400: }
401:
402: /**
403: * Gets the message for an exception without format-argument.
404: */
405: public static final String getMessage(int code) {
406: return getMessage(code, (Object[]) null);
407: }
408:
409: /** Returns the first few lines of the stack trace.
410: * It is useful to make the cause exception's stacktrace to be
411: * part of the message of the exception thrown to the user.
412: */
413: public static final String getBriefStackTrace(Throwable t) {
414: return formatStackTrace(null, t, ">>", 6).toString();
415: }
416:
417: /** Formats the stack trace and returns the result.
418: * Currently, it only adds the prefix to each line.
419: *
420: * @param prefix the prefix shown in front of each line of the stack trace;
421: * null to denote empty
422: * @see org.zkoss.util.logging.Log#realCause
423: */
424: public static final String formatStackTrace(Throwable t,
425: String prefix) {
426: return formatStackTrace(null, t, prefix).toString();
427: }
428:
429: /** Formats the stack trace and appends it to the specified string buffer.
430: * @param sb the string buffer to append the stack trace. A string buffer
431: * will be created if null.
432: * @param prefix the prefix shown in front of each line of the stack trace;
433: * null to denote empty
434: */
435: public static final StringBuffer formatStackTrace(StringBuffer sb,
436: Throwable t, String prefix) {
437: return formatStackTrace(sb, t, prefix, 0);
438: }
439:
440: /** Formats the stack trace and appends it to the specified string buffer,
441: * but only display at most maxcnt lines.
442: *
443: * <p>The maximal allowed number of lines is controlled by
444: * maxcnt. Note: a stack frame is not counted, if it belongs
445: * to java.*, javax.* or sun.*.
446: *
447: * @param sb the string buffer to append the stack trace. A string buffer
448: * will be created if null.
449: * @param prefix the prefix shown in front of each line of the stack trace;
450: * null to denote empty
451: * @param maxcnt the maximal allowed number of lines to dump (<=0: no limit)
452: */
453: public static final StringBuffer formatStackTrace(StringBuffer sb,
454: Throwable t, String prefix, int maxcnt) {
455: final StringWriter sw = new StringWriter();
456: t.printStackTrace(new PrintWriter(sw));
457: final StringBuffer trace = sw.getBuffer();
458:
459: if (prefix == null)
460: prefix = "";
461: if (maxcnt > 0 || prefix.length() > 0) {
462: final int len = trace.length();
463: if (sb == null)
464: sb = new StringBuffer(len + 256);
465: if (maxcnt <= 0)
466: maxcnt = Integer.MAX_VALUE;
467: boolean ignoreCount = false;
468: for (int j = 0; j < len;) { //for each line
469: if (!ignoreCount && --maxcnt < 0) {
470: sb.append(prefix).append("...");
471: break;
472: }
473:
474: //StringBuffer has no indexOf(char,j), so...
475: int k = j;
476: while (k < len && trace.charAt(k++) != '\n')
477: ; //point k to the char after \n
478:
479: String frame = trace.substring(j, k);
480: sb.append(prefix).append(frame);
481: j = k;
482:
483: ignoreCount = inStack(frame, "java.")
484: || inStack(frame, "javax.")
485: || inStack(frame, "sun.");
486: }
487: } else {
488: if (sb == null)
489: return trace;
490: sb.append(trace);
491: }
492: return sb;
493: }
494:
495: private static boolean inStack(String frame, String sub) {
496: final int j = frame.indexOf(sub);
497: if (j < 0)
498: return false;
499: if (j == 0)
500: return true;
501:
502: final char cc = frame.charAt(j - 1);
503: return (cc < 'a' || cc > 'z') && (cc < 'A' || cc > 'Z');
504: }
505: }
|