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.lang.reflect.Method;
009: import java.io.PrintWriter;
010: import java.io.StringWriter;
011:
012: /**
013: * The <code>StackTrace</code> object represents a single stack frame
014: * of information. The entire set of stack frames is represented as an
015: * array of <code>StackTrace</code> object.<p/>
016: *
017: * Note: The stack frame information is not always available because
018: * some JVMs strip some or all the required information.
019: *
020: * @author <a href="mailto:jnielsen@sct.com">Jan Nielsen</a>
021: *
022: * @version "$Revision: 35982 $"
023: **/
024: public final class StackTrace {
025: /** Default size of the stack trace array buffer. */
026: private static final int DEFAULT_STACK_TRACE_ARRAY_SIZE = TypeConverter
027: .toInt(Resources
028: .getString(StackTrace.class,
029: "getStackTraceElement.default_stack_trace_array_size"));
030:
031: /** Moniker for native method. */
032: private static final int NATIVE_METHOD = -2;
033:
034: /** Prime number used to calculate hash code. */
035: private static final int PRIME = 31;
036:
037: /**
038: * Returns the name of the source file. If the parser failed to
039: * find the source file name the value <code>"Unknown"</code> will
040: * be returned.
041: *
042: * @return name of the source file
043: **/
044: public final String getFileName() {
045: String fileName = mFileName;
046:
047: if (null == fileName) {
048: fileName = Resources.getString(StackTrace.class,
049: "unknown_source");
050: }
051:
052: return fileName;
053: }
054:
055: /**
056: * Returns the name of the class. The parser should always be
057: * able to identify the calling class.
058: *
059: * @return name of the class
060: **/
061: public final String getClassName() {
062: return mClassName;
063: }
064:
065: /**
066: * Returns the name of the method. The parser should alwas be
067: * able to identify the calling method name.
068: *
069: * @return name of the method
070: **/
071: public final String getMethodName() {
072: return mMethodName;
073: }
074:
075: /**
076: * Returns the line number in the source file. If the parser is
077: * unable to determine the line number the value <code>-1</code>
078: * will be returned. If the parser determines the method is a
079: * native, a -2 is returned.
080: *
081: * @return source code line number, or -1 or -2
082: **/
083: public final int getLineNumber() {
084: return mLineNumber;
085: }
086:
087: /**
088: * Returns <code>true</code> if method is implemented
089: * natively.
090: *
091: * @return <code>true</code> is native method; otherwise
092: * <code>false</code>
093: **/
094: public final boolean isNativeMethod() {
095: return NATIVE_METHOD == mLineNumber;
096: }
097:
098: /**
099: * Returns all <code>StackTrace</code>s in an array of the
100: * specified throwable.
101: *
102: *
103: * @param throwable throwable being parsed
104: *
105: * @return array of <code>StackTrace</code>s
106: *
107: * @throws NullPointerException if throwable is <code>null</code>
108: *
109: * @throws NullPointerException if stackTrace is <code>null</code>
110: *
111: * @throws IllegalArgumentException if stackTrace is an empty
112: * string
113: **/
114: public static final StackTrace[] getStackTrace(Throwable throwable) {
115: StringWriter stringWriter = new StringWriter();
116: PrintWriter printWriter = null;
117:
118: StackTrace[] elements = null;
119:
120: try {
121: printWriter = new PrintWriter(stringWriter);
122:
123: throwable.printStackTrace(printWriter);
124:
125: elements = StackTrace.getStackTrace(throwable, stringWriter
126: .toString());
127: } finally {
128: if (null != printWriter) {
129: printWriter.close();
130: }
131: }
132: return elements;
133: }
134:
135: /**
136: * Returns all <code>StackTrace</code>s in an array. This method
137: * is used by the <code>Chained*</code> concrete classes to avoid
138: * a circular dependency. The throwable, and its string output
139: * must both be specified. Both are required because the throwable
140: * could be a <code>com.sct.pipeline.lang.ChainedException</code>
141: * which would result in a circular dependency if a
142: * <code>printStackTrace</code> was used in this method to
143: * retrieve the trace information. Invoke this method in the
144: * following fashion:
145: *
146: * <code><pre>
147: * StringWriter stringWriter = new StringWriter();
148: *
149: * PrintWriter printWriter = new PrintWriter(
150: * stringWriter
151: * );
152: *
153: * Throwable t = new Throwable();
154: *
155: * t.printStackTrace(
156: * printWriter
157: * );
158: *
159: * printWriter.close();
160: *
161: * StackTrace[] frames = getStackTraceElements(
162: * t,
163: * stringWriter.toString()
164: * );
165: * </pre></code>
166: *
167: * @param throwable throwable being parsed
168: *
169: * @param stackTrace string output of
170: * <code>throwable.printStackTrace</code>
171: *
172: * @return array of <code>StackTrace</code>s
173: *
174: * @throws NullPointerException if throwable is <code>null</code>
175: *
176: * @throws NullPointerException if stackTrace is <code>null</code>
177: *
178: * @throws IllegalArgumentException if stackTrace is an empty string
179: **/
180: static final StackTrace[] getStackTrace(Throwable throwable,
181: String stackTrace) {
182: if (null == throwable) {
183: throw new NullPointerException(
184: ThrowableHelper
185: .getInternationalizedMessage(
186: StackTrace.class,
187: "getStackTraceElement.error.throwable_is_null"));
188: }
189:
190: if (null == stackTrace) {
191: throw new NullPointerException(
192: ThrowableHelper
193: .getInternationalizedMessage(
194: StackTrace.class,
195: "getStackTraceElement.error.stack_trace_is_null"));
196: }
197:
198: if (stackTrace.equals("")) {
199: throw new IllegalArgumentException(
200: ThrowableHelper
201: .getInternationalizedMessage(
202: StackTrace.class,
203: "getStackTraceElement.error.stack_trace_is_empty"));
204: }
205:
206: return parseStackTrace(throwable, stackTrace);
207: }
208:
209: /**
210: * Returns the stack trace elements of the client. The array of
211: * stack trace elements start with the client's invoking frame in
212: * the zeroth element.
213: *
214: * @return array of stack trace elements of the client
215: **/
216: public static final StackTrace[] getStackTrace() {
217: StringWriter stringWriter = new StringWriter();
218:
219: PrintWriter printWriter = null;
220: StackTrace[] clientFrames = null;
221:
222: try {
223:
224: printWriter = new PrintWriter(stringWriter);
225:
226: Throwable t = new Throwable();
227:
228: t.printStackTrace(printWriter);
229:
230: printWriter.close();
231:
232: StackTrace[] frames = getStackTrace(t, stringWriter
233: .toString());
234:
235: clientFrames = new StackTrace[frames.length - 1];
236:
237: // Skip the first frame (this method).
238: System.arraycopy(frames, 1, clientFrames, 0,
239: clientFrames.length);
240: } finally {
241: if (null != printWriter) {
242: printWriter.close();
243: }
244: }
245:
246: return clientFrames;
247: }
248:
249: /**
250: * Returns the specified stack trace element if it exists. If an
251: * invalid, either too large or too small, index is specified
252: * <code>null</code> is returned.
253: *
254: * @param index index of the stack trace element to return
255: *
256: * @return stack trace element at the index or <code>null</code>
257: **/
258: public static final StackTrace getStackTrace(int index) {
259: StackTrace element = null;
260: PrintWriter printWriter = null;
261:
262: try {
263:
264: if (!(0 > index)) {
265: String moniker = "dummy moniker";
266:
267: StringWriter stringWriter = new StringWriter();
268:
269: printWriter = new PrintWriter(stringWriter);
270:
271: Throwable throwable = new Throwable(moniker);
272:
273: throwable.printStackTrace(printWriter);
274:
275: StackTrace[] elements = getStackTrace(throwable,
276: stringWriter.toString());
277:
278: if ((0 < index + 1) && (elements.length > index + 1)) {
279: element = elements[index + 1];
280: }
281: }
282: } finally {
283: if (null != printWriter) {
284: printWriter.close();
285: }
286: }
287:
288: return element;
289: }
290:
291: /**
292: * Converts a JDK 1.4 stack trace to this object.
293: *
294: * @param stackTrace array of JDK 1.4 stack trace objects
295: *
296: * @return array of <code>StackTrace</code> objects
297: **/
298: static final StackTrace[] convertStackTrace(Object[] stackTrace) {
299: int size = stackTrace.length;
300:
301: StackTrace[] elements = new StackTrace[size];
302:
303: for (int i = 0; i < size; i++) {
304: elements[i] = convertElement(stackTrace[i]);
305: }
306: return elements;
307: }
308:
309: /**
310: * Converts a J2SDK 1.4 <code>StackTraceElement</code> object
311: * to a <code>StackTrace</code> object.
312: *
313: * @param element <code>StackTraceElement</code> object
314: *
315: * @return corresponding <code>StackTrace</code> object
316: **/
317: private static final StackTrace convertElement(Object element) {
318: StackTrace stackTraceElement = null;
319:
320: try {
321: Method getClassName = element.getClass().getMethod(
322: "getClassName", (Class[]) null);
323:
324: Method getMethodName = element.getClass().getMethod(
325: "getMethodName", (Class[]) null);
326:
327: Method getFileName = element.getClass().getMethod(
328: "getFileName", (Class[]) null);
329:
330: Method getLineNumber = element.getClass().getMethod(
331: "getLineNumber", (Class[]) null);
332:
333: stackTraceElement = new StackTrace((String) getClassName
334: .invoke(element, (Object[]) null),
335: (String) getMethodName.invoke(element,
336: (Object[]) null), (String) getFileName
337: .invoke(element, (Object[]) null),
338: ((Integer) getLineNumber.invoke(element,
339: (Object[]) null)).intValue());
340: } catch (Exception x) {
341: ThrowableHelper.handle(x);
342: }
343: return stackTraceElement;
344: }
345:
346: /**
347: * Returns an array of <code>StackTrace</code> objects representing
348: * the <code>Throwable</code> argument.
349: *
350: * @param throwable
351: *
352: * @param stackTrace
353: *
354: * @return array of <code>StackTrace</code> objects
355: **/
356: private static final StackTrace[] parseStackTrace(
357: Throwable throwable, String stackTrace) {
358: // Remove the standard header information in the stack trace.
359: try {
360: stackTrace = stackTrace
361: .substring(null != throwable.getLocalizedMessage() ? ((throwable
362: .getClass().getName()
363: + ":" + throwable.getLocalizedMessage())
364: .length() + 1)
365: : (throwable.getClass().getName().length() + 1));
366: } catch (IndexOutOfBoundsException x) {
367: ThrowableHelper.handle(x);
368: }
369:
370: Object[] stackTraceObjects = parseStackTrace(stackTrace);
371:
372: StackTrace[] stackTraceArray = new StackTrace[stackTraceObjects.length];
373:
374: if (null != stackTraceObjects) {
375: System.arraycopy(stackTraceObjects, 0, stackTraceArray, 0,
376: stackTraceArray.length);
377: }
378:
379: return stackTraceArray;
380: }
381:
382: /**
383: * Parses a stack trace into individual stack frames.
384: *
385: * @param stackTrace stack trace to parse
386: *
387: * @return array of stack trace objects
388: **/
389: private static final Object[] parseStackTrace(String stackTrace) {
390: final ParseState state = new ParseState(stackTrace);
391:
392: java.util.ArrayList stackTraceElements = new java.util.ArrayList(
393: DEFAULT_STACK_TRACE_ARRAY_SIZE);
394:
395: while ((state.mIndex + 3) < state.mSize) {
396: StackTrace frame = parseStackTrace(state);
397:
398: stackTraceElements.add(frame);
399: }
400:
401: return stackTraceElements.toArray();
402: }
403:
404: /**
405: * Returns a representation of the stack frame element. The
406: * format of the information may vary but the following examples
407: * are typical:
408: *
409: * <ul>
410: * <li><tt>mycompany.MyClass.snap(MyClass.java:9)</tt></li>
411: * <li><tt>mycompany.MyClass.snap(MyClass.java)</tt></li>
412: * <li><tt>mycompany.MyClass.snap(Unknown Source)</tt></li>
413: * <li><tt>mycompany.MyClass.snap(Native Method)</tt></li>
414: * </ul>
415: *
416: * @return string presentation of the stack frame.
417: **/
418: public final String toString() {
419: StringBuffer buffer = new StringBuffer(mClassName).append(".")
420: .append(mMethodName);
421:
422: if (isNativeMethod()) {
423: buffer.append("("
424: + Resources.getString(StackTrace.class,
425: "native_method") + ")");
426: } else if (null != mFileName && 0 <= mLineNumber) {
427: buffer.append("(").append(mFileName).append(":").append(
428: mLineNumber).append(")");
429: } else if (null != mFileName) {
430: buffer.append("(").append(mFileName).append(")");
431: } else {
432: buffer.append("("
433: + Resources.getString(StackTrace.class,
434: "unknown_source") + ")");
435: }
436:
437: return buffer.toString();
438: }
439:
440: /**
441: * Returns a string representation of the stack trace elements.
442: *
443: * @param elements stack trace elements to stringify
444: *
445: * @return string representation of the stack trace array
446: **/
447: public static final String toString(StackTrace[] elements) {
448: String sep = System.getProperty("line.separator");
449: StringBuffer buffer = new StringBuffer();
450:
451: for (int i = 0; i < elements.length; i++) {
452: buffer.append(elements[i].toString()).append(sep);
453: }
454:
455: return buffer.toString();
456: }
457:
458: /**
459: * Returns <code>true<code> if the specified object is another
460: * <tt>StackTrace</tt> instance representing the same
461: * execution point as this instance. Two stack trace elements
462: * <tt>a</tt> and <tt>b</tt> are equal if and only if:
463: *
464: * <pre><code>
465: * equals(a.getFileName(), b.getFileName()) &&
466: * a.getLineNumber() == b.getLineNumber()) &&
467: * equals(a.getClassName(), b.getClassName()) &&
468: * equals(a.getMethodName(), b.getMethodName())
469: * </code></pre>
470: *
471: * where <tt>equals</tt> is defined as:
472: *
473: * <pre><code>
474: * static boolean equals(Object a, Object b) {
475: * return a==b || (a != null && a.equals(b));
476: * }
477: * </code></pre>
478: *
479: * @param obj the object to be compared with this stack trace
480: * element
481: *
482: * @return <code>true</code> if the specified object is another
483: * <tt>StackTrace</tt> instance representing the same
484: * execution point as this instance; otherwise <code>false</code>
485: **/
486: public boolean equals(Object obj) {
487: boolean isEqual = false;
488:
489: if (this == obj) {
490: isEqual = true;
491: } else if (obj instanceof StackTrace) {
492: StackTrace e = (StackTrace) obj;
493:
494: isEqual = e.mClassName.equals(mClassName)
495: && e.mLineNumber == mLineNumber
496: && eq(mMethodName, e.mMethodName)
497: && eq(mFileName, e.mFileName);
498: }
499:
500: return isEqual;
501: }
502:
503: /**
504: * Returns <code>true</code> if the two objects are
505: * equal. Equality is defined as either object identity or if
506: * <code>a.equals(b)</code> if <code>a</code> is not
507: * <code>null</code>.
508: *
509: * @param a left hand side argument
510: *
511: * @param b right hand side argument
512: *
513: * @return <code>true</code> if the two are equal; otherwise
514: * <code>false</code>
515: **/
516: private static final boolean eq(Object a, Object b) {
517: return a == b || (null != a && a.equals(b));
518: }
519:
520: /**
521: * Returns a hash code value for this stack trace element.
522: *
523: * @return hash code value
524: **/
525: public int hashCode() {
526: int result = PRIME * mClassName.hashCode()
527: + mMethodName.hashCode();
528: result = PRIME * result
529: + (null == mFileName ? 0 : mFileName.hashCode());
530: result = PRIME * result + mLineNumber;
531:
532: return result;
533: }
534:
535: /**
536: * Hidden constructor for the <code>StackTrace</code> object.
537: *
538: * @param className class name in the stack frame
539: *
540: * @param methodName method name in the stack frame
541: *
542: * @param fileName file name in the stack frame
543: *
544: * @param lineNumber line number in the stack frame
545: **/
546: private StackTrace(String className, String methodName,
547: String fileName, int lineNumber) {
548: mClassName = className;
549: mMethodName = methodName;
550: mFileName = fileName;
551: mLineNumber = lineNumber;
552: }
553:
554: /**
555: * Parse the first class name from the specified stack trace
556: * starting at the specified index.
557: *
558: * @param state parsing state
559: *
560: * @return parsed stack trace element
561: **/
562: private static final StackTrace parseStackTrace(ParseState state) {
563: String className = null;
564: String methodName = null;
565: String fileName = null;
566: int lineNumber = -1;
567:
568: int classNameStart;
569: int classNameEnd;
570: int methodNameStart = 0;
571: int methodNameEnd = 0;
572: int fileNameStart;
573: int fileNameEnd;
574: int lineNumberStart;
575: int lineNumberEnd;
576:
577: // Move to beginning of class name.
578: while (state.mIndex + 2 < state.mSize) {
579: if ('a' == state.mStackTraceCharacters[state.mIndex]
580: && 't' == state.mStackTraceCharacters[state.mIndex + 1]
581: && ' ' == state.mStackTraceCharacters[state.mIndex + 2]) {
582: state.mIndex += 3;
583: break;
584: }
585: state.mIndex++;
586: }
587: classNameStart = state.mIndex;
588: classNameEnd = classNameStart;
589:
590: // Move the end of method name, saving the class name end and
591: // method name start along the way.
592: while (state.mIndex < state.mSize) {
593: if ('.' == state.mStackTraceCharacters[state.mIndex]) {
594: classNameEnd = state.mIndex;
595: methodNameStart = classNameEnd + 1;
596: } else if ('(' == state.mStackTraceCharacters[state.mIndex]) {
597: methodNameEnd = state.mIndex;
598: break;
599: }
600: state.mIndex++;
601: }
602:
603: // Define the class name.
604: try {
605: className = state.mStackTrace.substring(classNameStart,
606: classNameEnd);
607: } catch (IndexOutOfBoundsException x) {
608: ThrowableHelper.handle(x);
609: }
610:
611: try {
612: // Define the method name.
613: methodName = state.mStackTrace.substring(methodNameStart,
614: methodNameEnd);
615: } catch (IndexOutOfBoundsException x) {
616: ThrowableHelper.handle(x);
617: }
618:
619: // Move to after the parenthesis.
620: fileNameStart = state.mIndex + 1;
621:
622: if (state.mIndex + 14 < state.mSize
623: && 'U' == state.mStackTraceCharacters[state.mIndex + 1]
624: && 'n' == state.mStackTraceCharacters[state.mIndex + 2]
625: && 'k' == state.mStackTraceCharacters[state.mIndex + 3]
626: && 'n' == state.mStackTraceCharacters[state.mIndex + 4]
627: && 'o' == state.mStackTraceCharacters[state.mIndex + 5]
628: && 'w' == state.mStackTraceCharacters[state.mIndex + 6]
629: && 'n' == state.mStackTraceCharacters[state.mIndex + 7]
630: && ' ' == state.mStackTraceCharacters[state.mIndex + 8]
631: && 'S' == state.mStackTraceCharacters[state.mIndex + 9]
632: && 'o' == state.mStackTraceCharacters[state.mIndex + 10]
633: && 'u' == state.mStackTraceCharacters[state.mIndex + 11]
634: && 'r' == state.mStackTraceCharacters[state.mIndex + 12]
635: && 'c' == state.mStackTraceCharacters[state.mIndex + 13]
636: && 'e' == state.mStackTraceCharacters[state.mIndex + 14]) {
637: fileName = null;
638: lineNumber = -1;
639: } else if (state.mIndex + 13 < state.mSize
640: && 'N' == state.mStackTraceCharacters[state.mIndex + 1]
641: && 'a' == state.mStackTraceCharacters[state.mIndex + 2]
642: && 't' == state.mStackTraceCharacters[state.mIndex + 3]
643: && 'i' == state.mStackTraceCharacters[state.mIndex + 4]
644: && 'v' == state.mStackTraceCharacters[state.mIndex + 5]
645: && 'e' == state.mStackTraceCharacters[state.mIndex + 6]
646: && ' ' == state.mStackTraceCharacters[state.mIndex + 7]
647: && 'M' == state.mStackTraceCharacters[state.mIndex + 8]
648: && 'e' == state.mStackTraceCharacters[state.mIndex + 9]
649: && 't' == state.mStackTraceCharacters[state.mIndex + 10]
650: && 'h' == state.mStackTraceCharacters[state.mIndex + 11]
651: && 'o' == state.mStackTraceCharacters[state.mIndex + 12]
652: && 'd' == state.mStackTraceCharacters[state.mIndex + 13]) {
653: lineNumber = -2;
654: } else {
655: // Current character.
656: char ch;
657:
658: // Move to end of source file name by looking for the ':'.
659: while (state.mIndex < state.mSize) {
660: ch = state.mStackTraceCharacters[state.mIndex];
661:
662: if ('\n' == ch || '\r' == ch) {
663: // Failed to find ':' so leave the file name and line
664: // number as unknown.
665:
666: // In case this is a '\r\n' platform.
667: if ((state.mIndex + 1) < state.mSize
668: && '\n' == state.mStackTraceCharacters[state.mIndex + 1]) {
669: state.mIndex++;
670: }
671: break;
672: } else if ('.' == ch) {
673: // Move past the ".java".
674: fileNameEnd = state.mIndex + 5;
675:
676: // Move past the ":".
677: lineNumberStart = fileNameEnd + 1;
678:
679: // Define the file name.
680: fileName = state.mStackTrace.substring(
681: fileNameStart, fileNameEnd);
682:
683: // Move to the start of the line number.
684: state.mIndex = lineNumberStart;
685:
686: while (state.mIndex < state.mSize) {
687: ch = state.mStackTraceCharacters[state.mIndex];
688: if ('\n' == ch || '\r' == ch) {
689: // Failed to find the terminator, so leave
690: // line number unknown.
691:
692: // In case this is a '\r\n' platform.
693: if ((state.mIndex + 1) < state.mSize
694: && '\n' == state.mStackTraceCharacters[state.mIndex + 1]) {
695: state.mIndex++;
696: }
697: break;
698: } else if (')' == state.mStackTraceCharacters[state.mIndex]) {
699: lineNumberEnd = state.mIndex;
700:
701: // Define the line number.
702: lineNumber = TypeConverter
703: .toInt(state.mStackTrace.substring(
704: lineNumberStart,
705: lineNumberEnd));
706: // Don't break here in case there are spaces at
707: // the end of the stack trace.
708: }
709: state.mIndex++;
710: }
711: break;
712: }
713: state.mIndex++;
714: }
715: }
716:
717: StackTrace frame = new StackTrace(className, methodName,
718: fileName, lineNumber);
719:
720: return frame;
721: }
722:
723: /**
724: * The <code>ParseState</code> class holds the current parsing
725: * state.
726: *
727: * @author <a href="mailto:jnielsen@sct.com">Jan Nielsen</a>
728: *
729: * @version "$Revision: 35982 $"
730: *
731: * @invariant null != mStackTrace
732: **/
733: private static class ParseState {
734: /**
735: * Construct a parsing state object with the specified
736: * stack trace.
737: *
738: * @param stackTrace stack trace to be parsed
739: **/
740: private ParseState(String stackTrace) {
741: mStackTrace = stackTrace;
742: mStackTraceCharacters = mStackTrace.toCharArray();
743: mSize = mStackTraceCharacters.length;
744: mIndex = 0;
745: }
746:
747: /** Entire stack trace. */
748: private String mStackTrace;
749:
750: /** Entire stack trace in character array form. */
751: private char[] mStackTraceCharacters;
752:
753: /** Size of the entire stack trace. */
754: private int mSize;
755:
756: /** Current index in the stack trace array. */
757: private int mIndex;
758: }
759:
760: /**
761: * Holder for the source file name.
762: **/
763: private String mFileName;
764:
765: /**
766: * Holder for the class name.
767: **/
768: private String mClassName;
769:
770: /**
771: * Holder for the method name.
772: **/
773: private String mMethodName;
774:
775: /**
776: * Holder for the line number. The value (-2) is used to denote a
777: * native method.
778: **/
779: private int mLineNumber = -1;
780: }
|