001: /* Copyright 2004 The JA-SIG Collaborative. All rights reserved.
002: * See license distributed with this file and
003: * available online at http://www.uportal.org/license.html
004: */
005:
006: package org.jasig.portal.lang;
007:
008: import java.io.PrintStream;
009: import java.io.PrintWriter;
010: import java.lang.reflect.InvocationTargetException;
011: import java.lang.reflect.Method;
012:
013: /**
014: * The <code>ChainedError</code> class has facilities for error
015: * chaining. Throwables which extend this class inherit these
016: * facilities by implementing appropriate constructors.
017: *
018: * @author <a href="mailto:jnielsen@sct.com">Jan Nielsen</a>
019: *
020: * @version "$Revision: 34761 $"
021: **/
022: public class ChainedError extends Error implements ChainedThrowable {
023: /** The chained throwable which is the cause. */
024: private Throwable mCause = null;
025:
026: /** Cache of the stack trace for the throwable. */
027: private StackTrace[] mStackTrace = null;
028:
029: /**
030: * Constructs an exception with no message and no cause.
031: **/
032: public ChainedError() {
033: initCause();
034: }
035:
036: /**
037: * Constructs an exception with a message but no cause. The
038: * message should be an internationalized message.
039: *
040: * @param message exception message
041: **/
042: public ChainedError(String message) {
043: super (message);
044: initCause();
045: }
046:
047: /**
048: * Constructs a new exception instance with no message but a
049: * cause.
050: *
051: * @param cause underlying cause of the exception
052: **/
053: public ChainedError(Throwable cause) {
054: super (null == cause ? null : cause.getMessage());
055: initCause();
056: mCause = cause;
057: }
058:
059: /**
060: * Constructs a new exception instance with a message and a
061: * cause. The message should be an internationalized message.
062: *
063: * @param message exception message
064: *
065: * @param cause underlying cause of the exception
066: **/
067: public ChainedError(String message, Throwable cause) {
068: super (message);
069: initCause();
070: mCause = cause;
071: }
072:
073: /**
074: * Returns the cause for this <code>Throwable</code> or
075: * <code>null</code>. The cause is mean for diagnostic purposes
076: * only; Clients should not try to use the cause for additional
077: * error handling since that would build an inappropriate
078: * subsystem dependency.
079: *
080: * @return throwable associated with this object, or
081: * <code>null</code> if not set
082: **/
083: public Throwable getCause() {
084: return mCause;
085: }
086:
087: /**
088: * Translate the error message to a locale specific error
089: * message.
090: *
091: * @return localized error message
092: **/
093: public String getLocalizedMessage() {
094: return ThrowableHelper.getLocalizedMessage(getMessage());
095: }
096:
097: /**
098: * Prints this throwable and its backtrace to the standard
099: * error stream. This method prints a stack trace for this
100: * <code>Throwable</code> object on the error output stream that
101: * is the value of the field <code>System.err</code>. The first
102: * line of output contains the result of the {@link #toString()}
103: * method for this object. Remaining lines represent data
104: * previously recorded by the method {@link
105: * #fillInStackTrace()}. The format of this information depends on
106: * the implementation, but the following example may be regarded
107: * as typical:<p/>
108: *
109: * <blockquote><pre>
110: * java.lang.NullPointerException
111: * at MyClass.mash(MyClass.java:9)
112: * at MyClass.crunch(MyClass.java:6)
113: * at MyClass.main(MyClass.java:3)
114: * </pre></blockquote>
115: *
116: * This example was produced by running the program:<p/>
117: *
118: * <pre>
119: * class MyClass {
120: * public static void main(String[] args) {
121: * crunch(null);
122: * }
123: * static void crunch(int[] a) {
124: * mash(a);
125: * }
126: * static void mash(int[] b) {
127: * System.out.println(b[0]);
128: * }
129: * }
130: * </pre>
131: *
132: * The backtrace for a throwable with an initialized, non-null
133: * cause should generally include the backtrace for the cause.
134: * The format of this information depends on the implementation,
135: * but the following example may be regarded as typical:<p/>
136: *
137: * <pre>
138: * HighLevelException: MidLevelException: LowLevelException
139: * at Junk.a(Junk.java:13)
140: * at Junk.main(Junk.java:4)
141: * Caused by: MidLevelException: LowLevelException
142: * at Junk.c(Junk.java:23)
143: * at Junk.b(Junk.java:17)
144: * at Junk.a(Junk.java:11)
145: * ... 1 more
146: * Caused by: LowLevelException
147: * at Junk.e(Junk.java:30)
148: * at Junk.d(Junk.java:27)
149: * at Junk.c(Junk.java:21)
150: * ... 3 more
151: * </pre>
152: *
153: * Note the presence of lines containing the characters
154: * <tt>"..."</tt>. These lines indicate that the remainder of the
155: * stack trace for this exception matches the indicated number of
156: * frames from the bottom of the stack trace of the exception that
157: * was caused by this exception (the "enclosing" exception). This
158: * shorthand can greatly reduce the length of the output in the
159: * common case where a wrapped exception is thrown from same
160: * method as the "causative exception" is caught. The above
161: * example was produced by running the program:<p/>
162: *
163: * <pre>
164: * public class Junk {
165: * public static void main(String args[]) {
166: * try {
167: * a();
168: * } catch(HighLevelException e) {
169: * e.printStackTrace();
170: * }
171: * }
172: * static void a() throws HighLevelException {
173: * try {
174: * b();
175: * } catch(MidLevelException e) {
176: * throw new HighLevelException(e);
177: * }
178: * }
179: * static void b() throws MidLevelException {
180: * c();
181: * }
182: * static void c() throws MidLevelException {
183: * try {
184: * d();
185: * } catch(LowLevelException e) {
186: * throw new MidLevelException(e);
187: * }
188: * }
189: * static void d() throws LowLevelException {
190: * e();
191: * }
192: * static void e() throws LowLevelException {
193: * throw new LowLevelException();
194: * }
195: * }
196: *
197: * class HighLevelException extends Exception {
198: * HighLevelException(Throwable cause) { super(cause); }
199: * }
200: *
201: * class MidLevelException extends Exception {
202: * MidLevelException(Throwable cause) { super(cause); }
203: * }
204: *
205: * class LowLevelException extends Exception {
206: * }
207: * </pre>
208: **/
209: public void printStackTrace() {
210: printStackTrace(System.err);
211: }
212:
213: /**
214: * Prints this throwable and its backtrace to the specified
215: * print stream.
216: *
217: * @param stream <code>PrintStream</code> to use for output
218: *
219: * @throws NullPointerException if stream is <code>null</code>
220: **/
221: public void printStackTrace(PrintStream stream) {
222: synchronized (stream) {
223: stream.println(this );
224:
225: StackTrace[] trace = getOurStackTrace0();
226:
227: for (int i = 0; i < trace.length; i++) {
228: stream.println(Resources.getString(
229: ChainedException.class, "at",
230: new String[] { trace[i].toString() }));
231: }
232:
233: Throwable ourCause = getCause();
234:
235: if (null != ourCause) {
236: if (ourCause instanceof ChainedException) {
237: ((ChainedError) ourCause).printStackTraceAsCause(
238: stream, trace);
239: } else {
240: stream.println(Resources.getString(
241: ChainedException.class, "caused_by",
242: new String[] { ourCause.toString() }));
243:
244: ourCause.printStackTrace(stream);
245: }
246: }
247: }
248: }
249:
250: /**
251: * Prints this throwable and its backtrace to the specified
252: * print writer.
253: *
254: * @param stream <code>PrintWriter</code> to use for output
255: *
256: * @throws NullPointerException if stream is <code>null</code>
257: **/
258: public void printStackTrace(PrintWriter stream) {
259: // In a pre-1.4 JVM
260: synchronized (stream) {
261: stream.println(this );
262:
263: StackTrace[] trace = getOurStackTrace0();
264:
265: for (int i = 0; i < trace.length; i++) {
266: stream.println(Resources.getString(
267: ChainedException.class, "at",
268: new String[] { trace[i].toString() }));
269: }
270:
271: Throwable ourCause = getCause();
272:
273: if (null != ourCause) {
274: if (ourCause instanceof ChainedException) {
275: ((ChainedError) ourCause).printStackTraceAsCause(
276: stream, trace);
277: } else {
278: stream.println(Resources.getString(
279: ChainedException.class, "caused_by",
280: new String[] { ourCause.toString() }));
281:
282: ourCause.printStackTrace(stream);
283: }
284: }
285: }
286: }
287:
288: /**
289: * Returns an array of stack trace objects representing the current
290: * stack.
291: *
292: * @return array of stack trace elements
293: **/
294: private StackTrace[] getOurStackTrace0() {
295: if (null == mStackTrace) {
296: try {
297: Method getStackTrace = getClass().getMethod(
298: "getStackTrace", (Class[]) null);
299:
300: mStackTrace = StackTrace
301: .convertStackTrace((Object[]) getStackTrace
302: .invoke(this , (Object[]) null));
303: } catch (InvocationTargetException x) {
304: // In a 1.4 JVM but a problem occurred during invocation, so
305: // generate a pre 1.4 trace. The trace will not be correct.
306: mStackTrace = getOurStackTraceInPre1_4();
307: } catch (NoSuchMethodException x) {
308: // In a pre-1.4 JVM
309: mStackTrace = getOurStackTraceInPre1_4();
310: } catch (IllegalAccessException x) {
311: // In a pre-1.4 JVM.
312: mStackTrace = getOurStackTraceInPre1_4();
313: }
314: }
315: return mStackTrace;
316: }
317:
318: /**
319: * In a pre-1.4 JVM, create the stack trace elements by first
320: * constructing the normal error message and then removing the
321: * error message from the stream.
322: *
323: * @return array of stack trace elements
324: **/
325: private StackTrace[] getOurStackTraceInPre1_4() {
326: // In a pre-1.4 JVM, create the stack trace as one string
327: // object by using the super class mechanism.
328: java.io.StringWriter stringWriter = new java.io.StringWriter();
329: java.io.PrintWriter printWriter = null;
330:
331: StackTrace[] elements = null;
332:
333: try {
334: printWriter = new java.io.PrintWriter(stringWriter);
335:
336: super .printStackTrace(printWriter);
337:
338: elements = StackTrace.getStackTrace(this , stringWriter
339: .toString());
340: } finally {
341: if (null != printWriter) {
342: printWriter.close();
343: }
344: }
345: return elements;
346: }
347:
348: /**
349: * Always <code>null</code> out the 1.4 cause to ensure serialized
350: * exceptions are properly reconstructed.
351: **/
352: private void initCause() {
353: try {
354: Method initCause = this .getClass().getMethod("initCause",
355: new Class[] { Throwable.class });
356:
357: initCause.invoke(this , new Object[] { null });
358: } catch (NoSuchMethodException x) {
359: ;
360: // This is not a JDK 1.4 environment.
361: } catch (InvocationTargetException x) {
362: ;
363: // Something bad happened when initCause was executed.
364: } catch (IllegalAccessException x) {
365: ;
366: // This is not a JDK 1.4 environment, but the object has defined
367: // an initCause method which is not accesssible.
368: }
369: }
370:
371: /**
372: * Print our stack trace as a cause for the specified stack
373: * trace.
374: *
375: * @param stream stream to which stack trace should be written
376: *
377: * @param causedTrace stack trace to write to stream
378: **/
379: private void printStackTraceAsCause(PrintStream stream,
380: StackTrace[] causedTrace) {
381: StackTrace[] trace = getOurStackTrace0();
382:
383: int m = trace.length - 1;
384: int n = causedTrace.length - 1;
385:
386: while (0 <= m && 0 <= n && trace[m].equals(causedTrace[n])) {
387: m--;
388: n--;
389: }
390:
391: stream.println(Resources.getString(ChainedException.class,
392: "caused_by", new String[] { this .toString() }));
393:
394: for (int i = 0; i <= m; i++) {
395: stream.println(Resources.getString(ChainedException.class,
396: "at", new String[] { trace[i].toString() }));
397: }
398:
399: int framesInCommon = trace.length - 1 - m;
400:
401: if (0 != framesInCommon) {
402: stream.println(Resources.getString(ChainedException.class,
403: "more", new String[] { TypeConverter
404: .toString(framesInCommon) }));
405: }
406:
407: Throwable ourCause = getCause();
408:
409: if (null != ourCause) {
410: if (ourCause instanceof ChainedException) {
411: ((ChainedError) ourCause).printStackTraceAsCause(
412: stream, trace);
413: }
414: }
415: }
416:
417: /**
418: * Print our stack trace as a cause for the specified stack trace.
419: *
420: * @param stream stream to which stack trace should be written
421: *
422: * @param causedTrace stack trace to write to stream
423: **/
424: private void printStackTraceAsCause(PrintWriter stream,
425: StackTrace[] causedTrace) {
426: StackTrace[] trace = getOurStackTrace0();
427:
428: int m = trace.length - 1;
429: int n = causedTrace.length - 1;
430:
431: while (0 <= m && 0 <= n && trace[m].equals(causedTrace[n])) {
432: m--;
433: n--;
434: }
435:
436: stream.println(Resources.getString(ChainedException.class,
437: "caused_by", new String[] { this .toString() }));
438:
439: for (int i = 0; i <= m; i++) {
440: stream.println(Resources.getString(ChainedException.class,
441: "at", new String[] { trace[i].toString() }));
442: }
443:
444: int framesInCommon = trace.length - 1 - m;
445:
446: if (0 != framesInCommon) {
447: stream.println(Resources.getString(ChainedException.class,
448: "more", new String[] { TypeConverter
449: .toString(framesInCommon) }));
450: }
451:
452: Throwable ourCause = getCause();
453:
454: if (null != ourCause) {
455: if (ourCause instanceof ChainedThrowable) {
456: ((ChainedError) ourCause).printStackTraceAsCause(
457: stream, trace);
458: }
459: }
460: }
461: }
|