StackTrace.java |
/*************************************** * * * JBoss: The OpenSource J2EE WebOS * * * * Distributable under LGPL license. * * See terms of license at gnu.org. * * * ***************************************/ package org.jboss.util; import java.io.IOException; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.PrintWriter; import java.io.PrintStream; import java.io.InputStreamReader; import java.io.BufferedReader; import java.io.Serializable; import java.util.List; import java.util.Iterator; import java.util.ArrayList; import org.jboss.util.stream.Printable; /** * Provides access to the current stack trace by parsing the output of * <code>Throwable.printStackTrace()</code>. * * @version <tt>$Revision: 1.1.28.2 $</tt> * @author <a href="mailto:jason@planet57.com">Jason Dillon</a> */ public final class StackTrace implements Serializable, Cloneable, Printable { static final long serialVersionUID = -6077429788585907990L; /** Parse all entries */ public static final int UNLIMITED = 0; /** Empty prefix constant */ private static final String EMPTY_PREFIX = ""; /** List of <tt>StackTrace.Entry</tt> elements */ protected final List stack; /** * Initialize a <tt>StackTrace</tt>. * * @param detail Detail throwable to determine stack entries from. * @param level Number of levels to go down into the trace. * @param limit The maximum number of entries to parse (does not * include skipped levels or the description). * A value <= zero results in all entries being parsed. * * @throws IllegalArgumentException Invalid level or limit. * @throws NestedRuntimeException Failed to create Parser. * @throws NestedRuntimeException Failed to parse stack trace. */ public StackTrace(final Throwable detail, final int level, final int limit) { if (level < 0) throw new IllegalArgumentException("level < 0"); if (limit < 0) throw new IllegalArgumentException("limit < 0"); try { Parser parser = Parser.getInstance(); stack = parser.parse(detail, level, limit); } catch (InstantiationException e) { throw new NestedRuntimeException(e); } catch (IOException e) { throw new NestedRuntimeException(e); } } /** * Initialize a <tt>StackTrace</tt>. * * @param detail Detail throwable to determine stack entries from. * @param level Number of levels to go down into the trace. * * @throws IllegalArgumentException Invalid level. * @throws NestedRuntimeException Failed to create Parser. * @throws NestedRuntimeException Failed to parse stack trace. */ public StackTrace(final Throwable detail, final int level) { this(detail, level, 0); } /** * Initialize a <tt>StackTrace</tt>. * * @param detail Detail throwable to determine stack entries from. * @param level Number of levels to go down into the trace. * * @throws NestedRuntimeException Failed to create Parser. * @throws NestedRuntimeException Failed to parse stack trace. */ public StackTrace(final Throwable detail) { this(detail, 0, 0); } /** * Construct a <tt>StackTrace</tt>. * * @param level Number of levels to go down into the trace. * @param limit The maximum number of entries to parse (does not * include skipped levels or the description). * A value <= zero results in all entries being parsed. */ public StackTrace(final int level, final int limit) { this(new Throwable(), level + 1, limit); } /** * Construct a <tt>StackTrace</tt>. * * @param level Number of levels to go down into the trace. */ public StackTrace(final int level) { this(new Throwable(), level + 1, UNLIMITED); } /** * Construct a <tt>StackTrace</tt>. */ public StackTrace() { this(new Throwable(), 1, UNLIMITED); } /** * Sub-trace constructor. */ protected StackTrace(final List stack) { this.stack = stack; } /** * Check if the given object is equals to this. * * @param obj Object to test equality with. * @return True if object is equal to this. */ public boolean equals(final Object obj) { if (obj == this) return true; if (obj != null && obj.getClass() == getClass()) { return ((StackTrace)obj).stack.equals(stack); } return false; } /** * Returns a shallow cloned copy of this object. * * @return A shallow cloned copy of this object. */ public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(); } } /** * Returns the stack trace entry for the element at the given level. * * @param level Number of levels. * @return Stack trace entry. * * @throws IndexOutOfBoundsException Invalid level index. */ public Entry getEntry(final int level) { return (Entry)stack.get(level); } /** * Returns the stack trace entry for the calling method. * * @return Stack trace entry for calling method. */ public Entry getCallerEntry() { return getEntry(0); } /** * Return the root entry for this stack trace. * * @return Stack trace entry for the root calling method. */ public Entry getRootEntry() { return getEntry(stack.size() - 1); } /** * Returns a sub trace starting at the the given level. * * @param level Number of levels. * @return Sub-trace. */ public StackTrace getSubTrace(final int level) { return new StackTrace(stack.subList(level, stack.size())); } /** * Returns a sub trace starting at the the given level. * * @param level Number of levels. * @param limit Limit the sub-trace. If there are less entries * than the limit, the limit has no effect. * @return Sub-trace. */ public StackTrace getSubTrace(final int level, int limit) { if (limit > 0) { limit = Math.min(level + limit, stack.size()); } else { limit = stack.size(); } // limit is now the ending index of the stack to return return new StackTrace(stack.subList(level, limit)); } /** * Returns the stack trace starting at the calling method. * * @return Stack trace for calling method. */ public StackTrace getCallerTrace() { return getSubTrace(1); } /** * Print this stack trace. * * @param writer The writer to print to. * @param prefix Stack trace entry prefix. */ public void print(final PrintWriter writer, final String prefix) { Iterator iter = stack.iterator(); while (iter.hasNext()) { Entry entry = (Entry)iter.next(); entry.print(writer, prefix); } } /** * Print this stack trace. * * @param writer The writer to print to. */ public void print(final PrintWriter writer) { print(writer, EMPTY_PREFIX); } /** * Print this stack trace. * * @param stream The stream to print to. * @param prefix Stack trace entry prefix. */ public void print(final PrintStream stream, final String prefix) { Iterator iter = stack.iterator(); while (iter.hasNext()) { Entry entry = (Entry)iter.next(); entry.print(stream, prefix); } } /** * Print this stack trace. * * @param stream The stream to print to. */ public void print(final PrintStream stream) { print(stream, EMPTY_PREFIX); } /** * Print this stack trace to <code>System.err</code>. * * @param prefix Stack trace entry prefix. */ public void print(final String prefix) { print(System.err, prefix); } /** * Print this stack trace to <code>System.err</code>. */ public void print() { print(System.err); } /** * Returns an iterator over all of the entries in the stack trace. * * @return An iterator over all of the entries in the stack trace. */ public Iterator iterator() { return stack.iterator(); } /** * Returns the number of entries (or size) of the stack trace. * * @return The number of entries (or size) of the stack trace. */ public int size() { return stack.size(); } ///////////////////////////////////////////////////////////////////////// // Static Access // ///////////////////////////////////////////////////////////////////////// /** * Returns a stack trace entry for the current position in the stack. * * <p>The current entry refers to the method that has invoked * {@link #currentEntry()}. * * @return Current position in the stack. */ public static final Entry currentEntry() { return new StackTrace().getCallerEntry(); } /** * Returns a stack trace entry for the calling methods current position * in the stack. * * <p>Calling method in this case refers to the method that has called * the method which invoked {@link #callerEntry()}. * * @return Calling methods current position in the stack. * * @throws IndexOutOfBoundsException The current entry is at bottom * of the stack. */ public static final Entry callerEntry() { return new StackTrace(1).getCallerEntry(); } /** * Returns a stack trace entry for the root calling method of the current * execution thread. * * @return Stack trace entry for the root calling method. */ public static final Entry rootEntry() { return new StackTrace().getRootEntry(); } ///////////////////////////////////////////////////////////////////////// // StackTrace Entry // ///////////////////////////////////////////////////////////////////////// /** * A stack trace entry. */ public static final class Entry implements Cloneable, Serializable, Printable { static final long serialVersionUID = 7013023772762859280L; /** Unknown element token */ public static final String UNKNOWN = "<unknown>"; /** Default package token */ public static final String DEFAULT = "<default>"; /** The fully qualified class name for this entry */ protected String className = UNKNOWN; /** The method name for this entry */ protected String methodName = UNKNOWN; /** The source file name for this entry */ protected String sourceFileName = UNKNOWN; /** The source file line number for this entry */ protected String lineNumber = UNKNOWN; /** * Construct a new StackTrace entry. * * @param className Fully qualified class name. * @param methodName Method name. * @param sourceFileName Source file name. * @param lineNumber Source file line number. */ public Entry(final String className, final String methodName, final String sourceFileName, final String lineNumber) { this.className = className; this.methodName = methodName; this.sourceFileName = sourceFileName; this.lineNumber = lineNumber; } /** * Construct a new StackTrace entry. * * @param raw The raw stack trace entry. */ public Entry(final String raw) { // parse the raw string parse(raw); } /** * Parse a raw stack trace entry. * * @param raw Raw stack trace. */ protected void parse(final String raw) { // get the class and method names int j = raw.indexOf("at ") + 3; int i = raw.indexOf("("); if (j == -1 || i == -1) return; String temp = raw.substring(j, i); i = temp.lastIndexOf("."); if (i == -1) return; className = temp.substring(0, i); methodName = temp.substring(i + 1); // get the source file name j = raw.indexOf("(") + 1; i = raw.indexOf(":"); if (j == -1) return; if (i == -1) { i = raw.indexOf(")"); if (i == -1) return; sourceFileName = raw.substring(j, i); } else { sourceFileName = raw.substring(j, i); // get the line number j = i + 1; i = raw.lastIndexOf(")"); if (i != -1) lineNumber = raw.substring(j, i); else lineNumber = raw.substring(j); } } /** * Get the class name for this entry. * * @return The class name for this entry. */ public String getClassName() { return className; } /** * Get the short class name for this entry. * * <p>This is a macro for * <code>Classes.stripPackageName(entry.getClassName())</code></p> * * @return The short class name for this entry. */ public String getShortClassName() { return Classes.stripPackageName(className); } /** * Get the method name for this entry. * * @return The method name for this entry. */ public String getMethodName() { return methodName; } /** * Get the fully qualified method name for this entry. * * <p>This is a macro for * <code>entry.getClassName() + "." + entry.getMethodName()</code></p> * * @return The fully qualified method name for this entry. */ public String getFullMethodName() { return className + "." + methodName; } /** * Get the source file name for this entry. * * @return The source file name for this entry. */ public String getSourceFileName() { return sourceFileName; } /** * Get the source file line number for this entry. * * @return The source file line number for this entry. */ public String getLineNumber() { return lineNumber; } /** * Return a string representation of this with the given prefix. * * @param prefix Prefix for returned string. * @return A string in the format of * <code>prefixclassName.methodName(sourceFileName:lineNumber)</code> * or <code>prefixclassName.methodName(sourceFileName)</code> if there * is no line number. */ public String toString(final String prefix) { StringBuffer buff = new StringBuffer(); if (prefix != null) buff.append(prefix); buff.append(className).append(".").append(methodName) .append("(").append(sourceFileName); if (! lineNumber.equals(UNKNOWN)) buff.append(":").append(lineNumber); buff.append(")"); return buff.toString(); } /** * Return a string representation of this. * * @return A string in the format of * <code>className.methodName(sourceFileName:lineNumber)</code> */ public String toString() { return toString(EMPTY_PREFIX); } /** * Return the hash code of this object. * * @return The hash code of this object. */ public int hashCode() { return HashCode.generate(new String[] { className, methodName, sourceFileName, lineNumber, }); } /** * Check the equality of a given object with this. * * @param obj Object to test equality with. * @return True if the given object is equal to this. */ public boolean equals(final Object obj) { if (obj == this) return true; if (obj != null && obj.getClass() == getClass()) { Entry entry = (Entry)obj; return entry.className.equals(className) && entry.methodName.equals(methodName) && entry.sourceFileName.equals(sourceFileName) && entry.lineNumber.equals(lineNumber); } return false; } /** * Returns a shallow cloned copy of this object. * * @return A shallow cloned copy of this object. */ public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(); } } /** * Print this stack trace entry. * * @param writer The writer to print to. * @param prefix Prefix for string conversion. */ public void print(final PrintWriter writer, final String prefix) { writer.println(this.toString(prefix)); } /** * Print this stack trace entry. * * @param writer The writer to print to. */ public void print(final PrintWriter writer) { writer.println(this.toString()); } /** * Print this stack trace entry. * * @param stream The stream to print to. * @param prefix Prefix for string conversion. */ public void print(final PrintStream stream, final String prefix) { stream.println(this.toString(prefix)); } /** * Print this stack trace entry. * * @param stream The stream to print to. */ public void print(final PrintStream stream) { stream.println(this.toString()); } /** * Print this stack trace entry to <code>System.err<code>. * * @param prefix Prefix for string conversion. */ public void print(final String prefix) { print(System.err, prefix); } /** * Print this stack trace entry to <code>System.err<code>. */ public void print() { print(System.err); } } ///////////////////////////////////////////////////////////////////////// // StackTrace Parser // ///////////////////////////////////////////////////////////////////////// /** * A parser which takes a standard Throwable and produces * {@link StackTrace.Entry} objects. */ public static class Parser { /** * Skip the throwable description of the trace. * * @param reader Reader representing the trace. * * @throws IOException */ protected void skipDescription(final BufferedReader reader) throws IOException { reader.readLine(); } /** * Skip to the correct level of the stack (going down into the stack). * * @param reader Reader representing the stack trace. * @param level Number of levels to go down. * * @throws IOException */ protected void setLevel(final BufferedReader reader, final int level) throws IOException { for (int i=0; i<level; i++) { reader.readLine(); } } /** * Read a throwable stack trace as an array of bytes. * * @param detail Throwable to get trace from. * @return Throwable stack trace as an array of bytes. * * @throws IOException */ protected byte[] readBytes(final Throwable detail) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(baos); try { detail.printStackTrace(ps); } finally { ps.close(); } return baos.toByteArray(); } /** * Create a reader for the trace of the given Throwable. * * @param detail Thorwable to get trace from. * @return Reader for the throwable stack trace. * * @throws IOException */ protected BufferedReader createReader(final Throwable detail) throws IOException { byte bytes[] = readBytes(detail); ByteArrayInputStream bais = new ByteArrayInputStream(bytes); InputStreamReader reader = new InputStreamReader(bais); return new BufferedReader(reader); } /** * Parse a Throwable stack trace. * * @param detail Throwable to get trace from. * @param level Number of levels down to begin parsing. * @param limit The maximum number of entries to parse (does not * include skipped levels or the description). * A value <= zero results in all entries being parsed. * @return List of {@link StackTrace.Entry} objects. * * @throws IOException */ public List parse(final Throwable detail, final int level, final int limit) throws IOException { // create an reader the throwable BufferedReader reader = createReader(detail); // ignore throwable description skipDescription(reader); // set the stack level setLevel(reader, level); // read in the stack entrys List list = new ArrayList(); String raw; int count = 0; while ((raw = reader.readLine()) != null) { Entry entry = createEntry(raw); list.add(entry); // if we have reached the limit, then stop parsing if (limit > UNLIMITED && ++count >= limit) break; } return list; } /** * Create a stack trace entry for the given raw trace entry. * * @param raw Raw stack trace entry. * @return Stack trace entry. * * @throws IOException */ protected Entry createEntry(final String raw) throws IOException { return new Entry(raw); } ////////////////////////////////////////////////////////////////////// // Singleton Access // ////////////////////////////////////////////////////////////////////// /** The single instance of StackTrace.Parser */ private static Parser instance = null; /** * Get the stack trace parser for this virtual machine. * * @return Stack trace parser * * @throws InstantiationException */ public static final synchronized Parser getInstance() throws InstantiationException { if (instance == null) { // change to read parser class from a property instance = new Parser(); } return instance; } } }
StackTrace.java |