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