/***************************************
* *
* 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
* Throwable.printStackTrace().
*
* @version $Revision: 1.1 $
* @author Jason Dillon
*/
public final class StackTrace
implements Serializable, Cloneable, Printable
{
/** Parse all entries */
public static final int UNLIMITED = 0;
/** Empty prefix constant */
private static final String EMPTY_PREFIX = "";
/** List of StackTrace.Entry elements */
protected final List stack;
/**
* Initialize a StackTrace.
*
* @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 StackTrace.
*
* @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 StackTrace.
*
* @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 StackTrace.
*
* @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 StackTrace.
*
* @param level Number of levels to go down into the trace.
*/
public StackTrace(final int level) {
this(new Throwable(), level + 1, UNLIMITED);
}
/**
* Construct a StackTrace.
*/
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 System.err.
*
* @param prefix Stack trace entry prefix.
*/
public void print(final String prefix) {
print(System.err, prefix);
}
/**
* Print this stack trace to System.err.
*/
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.
*
*
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. * *
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
{
/** Unknown element token */
public static final String UNKNOWN = " This is a macro for
* This is a macro for
* Classes.stripPackageName(entry.getClassName())entry.getClassName() + "." + entry.getMethodName()prefixclassName.methodName(sourceFileName:lineNumber)
* or prefixclassName.methodName(sourceFileName) 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
* className.methodName(sourceFileName:lineNumber)
*/
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 System.err.
*
* @param prefix Prefix for string conversion.
*/
public void print(final String prefix) {
print(System.err, prefix);
}
/**
* Print this stack trace entry to System.err.
*/
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