/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package java.lang;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import org.jikesrvm.VM;
import org.jikesrvm.runtime.StackTrace;
import org.jikesrvm.scheduler.RVMThread;
/**
* This class must be implemented by the VM vendor, or the reference
* implementation can be used if the documented natives are implemented.
*
* This class is the superclass of all classes which can be thrown by the
* virtual machine. The two direct subclasses represent recoverable exceptions
* (Exception) and unrecoverable errors (Error). This class provides common
* methods for accessing a string message which provides extra information about
* the circumstances in which the throwable was created, and for filling in a
* walkback (i.e. a record of the call stack at a particular point in time)
* which can be printed later.
*
* @see Error
* @see Exception
* @see RuntimeException
*/
public class Throwable implements java.io.Serializable {
private static final long serialVersionUID = -3042686055658047285L;
/** The stack trace for this throwable */
private transient StackTrace vmStackTrace;
/**
* Zero length array of stack trace elements, returned when handling an OOM or
* an unexpected error
*/
private static final StackTraceElement[] zeroLengthStackTrace = new StackTraceElement[0];
/**
* The message provided when the exception was created.
*/
private String detailMessage;
/**
* The cause of this Throwable. Null when there is no cause.
*/
private Throwable cause = this;
private StackTraceElement[] stackTrace;
/**
* Constructs a new instance of this class with its walkback filled in.
*/
public Throwable() {
super();
fillInStackTrace();
}
/**
* Constructs a new instance of this class with its walkback and message
* filled in.
*
* @param detailMessage String The detail message for the exception.
*/
public Throwable(String detailMessage) {
this();
this.detailMessage = detailMessage;
}
/**
* Constructs a new instance of this class with its walkback, message and
* cause filled in.
*
* @param detailMessage String The detail message for the exception.
* @param throwable The cause of this Throwable
*/
public Throwable(String detailMessage, Throwable throwable) {
this();
this.detailMessage = detailMessage;
cause = throwable;
}
/**
* Constructs a new instance of this class with its walkback and cause
* filled in.
*
* @param throwable The cause of this Throwable
*/
public Throwable(Throwable throwable) {
this();
this.detailMessage = throwable == null ? null : throwable.toString();
cause = throwable;
}
/**
* Record in the receiver a walkback from the point where this message was
* sent. The message is public so that code which catches a throwable and
* then <em>re-throws</em> it can adjust the walkback to represent the
* location where the exception was re-thrown.
*
* @return the receiver
*/
public Throwable fillInStackTrace() {
if (VM.fullyBooted) {
if (RVMThread.getCurrentThread().isCollectorThread()) {
VM.sysWriteln("Exception in GC thread");
RVMThread.dumpVirtualMachine();
} else {
try {
vmStackTrace = new StackTrace();
} catch (OutOfMemoryError oome) {
// ignore
} catch (Throwable t) {
VM.sysFail("VMThrowable.fillInStackTrace(): Cannot fill in a stack trace; got a weird Throwable when I tried to");
}
}
} else {
// no stack trace available until fully booted.
}
return this;
}
/**
* Answers the extra information message which was provided when the
* throwable was created. If no message was provided at creation time, then
* answer null.
*
* @return String The receiver's message.
*/
public String getMessage() {
return detailMessage;
}
/**
* Answers the extra information message which was provided when the
* throwable was created. If no message was provided at creation time, then
* answer null. Subclasses may override this method to answer localized text
* for the message.
*
* @return String The receiver's message.
*/
public String getLocalizedMessage() {
return getMessage();
}
/**
* This native must be implemented to use the reference implementation of
* this class. The result of this native is cloned, and returned from the
* public API getStackTrace().
*
* Answers an array of StackTraceElement. Each StackTraceElement represents
* a entry on the stack.
*
* @return an array of StackTraceElement representing the stack
*/
private StackTraceElement[] getStackTraceImpl() {
if (vmStackTrace == null) {
return zeroLengthStackTrace;
} else if (RVMThread.getCurrentThread().isCollectorThread()) {
VM.sysWriteln("Throwable.getStackTrace called from GC thread: dumping stack using scheduler");
RVMThread.dumpStack();
return zeroLengthStackTrace;
}
StackTrace.Element[] vmElements;
try {
vmElements = vmStackTrace.getStackTrace(this);
} catch (Throwable t) {
VM.sysWriteln("Error calling StackTrace.getStackTrace: dumping stack using scheduler");
RVMThread.dumpStack();
return zeroLengthStackTrace;
}
if (vmElements == null) {
VM.sysWriteln("Error calling StackTrace.getStackTrace returned null");
RVMThread.dumpStack();
return zeroLengthStackTrace;
}
if (VM.fullyBooted) {
try {
StackTraceElement[] elements = new StackTraceElement[vmElements.length];
for (int i=0; i < vmElements.length; i++) {
StackTrace.Element vmElement = vmElements[i];
String fileName = vmElement.getFileName();
int lineNumber = vmElement.getLineNumber();
String className = vmElement.getClassName();
if (className == null) className = "";
String methodName = vmElement.getMethodName();
if (methodName == null) methodName = "";
boolean isNative = vmElement.isNative();
elements[i] = new StackTraceElement(className, methodName, fileName, lineNumber);
}
return elements;
} catch (Throwable t) {
VM.sysWriteln("Error constructing StackTraceElements: dumping stack");
}
} else {
VM.sysWriteln("Dumping stack using sysWrite in not fullyBooted VM");
}
for (StackTrace.Element vmElement : vmElements) {
if (vmElement == null) {
VM.sysWriteln("Error stack trace with null entry");
RVMThread.dumpStack();
return zeroLengthStackTrace;
}
String fileName = vmElement.getFileName();
int lineNumber = vmElement.getLineNumber();
String className = vmElement.getClassName();
String methodName = vmElement.getMethodName();
VM.sysWrite(" at ");
if (className != "") {
VM.sysWrite(className);
VM.sysWrite(".");
}
VM.sysWrite(methodName);
if (fileName != null) {
VM.sysWrite("(");
VM.sysWrite(fileName);
if (lineNumber > 0) {
VM.sysWrite(":");
VM.sysWrite(vmElement.getLineNumber());
}
VM.sysWrite(")");
}
VM.sysWriteln();
}
return zeroLengthStackTrace;
}
/**
* Answers an array of StackTraceElement. Each StackTraceElement represents
* a entry on the stack.
*
* @return an array of StackTraceElement representing the stack
*/
public StackTraceElement[] getStackTrace() {
return getInternalStackTrace().clone();
}
/**
* Sets the array of StackTraceElements. Each StackTraceElement represents a
* entry on the stack. A copy of this array will be returned by
* getStackTrace() and printed by printStackTrace().
*
* @param trace The array of StackTraceElement
*/
public void setStackTrace(StackTraceElement[] trace) {
StackTraceElement[] newTrace = trace.clone();
for (java.lang.StackTraceElement element : newTrace) {
if (element == null) {
throw new NullPointerException();
}
}
stackTrace = newTrace;
}
/**
* Outputs a printable representation of the receiver's walkback on the
* System.err stream.
*/
public void printStackTrace() {
printStackTrace(System.err);
}
/**
* Count the number of duplicate stack frames, starting from the end of the
* stack.
*
* @param currentStack a stack to compare
* @param parentStack a stack to compare
*
* @return the number of duplicate stack frames.
*/
private static int countDuplicates(StackTraceElement[] currentStack,
StackTraceElement[] parentStack) {
int duplicates = 0;
int parentIndex = parentStack.length;
for (int i = currentStack.length; --i >= 0 && --parentIndex >= 0;) {
StackTraceElement parentFrame = parentStack[parentIndex];
if (parentFrame.equals(currentStack[i])) {
duplicates++;
} else {
break;
}
}
return duplicates;
}
/**
* Answers an array of StackTraceElement. Each StackTraceElement represents
* a entry on the stack. Cache the stack trace in the stackTrace field,
* returning the cached field when it has already been initialized.
*
* @return an array of StackTraceElement representing the stack
*/
private StackTraceElement[] getInternalStackTrace() {
if (stackTrace == null) {
stackTrace = getStackTraceImpl();
}
return stackTrace;
}
/**
* Outputs a printable representation of the receiver's walkback on the
* stream specified by the argument.
*
* @param err PrintStream The stream to write the walkback on.
*/
public void printStackTrace(PrintStream err) {
err.println(toString());
// Don't use getStackTrace() as it calls clone()
// Get stackTrace, in case stackTrace is reassigned
StackTraceElement[] stack = getInternalStackTrace();
for (java.lang.StackTraceElement element : stack) {
err.println("\tat " + element);
}
StackTraceElement[] parentStack = stack;
Throwable throwable = getCause();
while (throwable != null) {
err.print("Caused by: ");
err.println(throwable);
StackTraceElement[] currentStack = throwable.getInternalStackTrace();
int duplicates = countDuplicates(currentStack, parentStack);
for (int i = 0; i < currentStack.length - duplicates; i++) {
err.println("\tat " + currentStack[i]);
}
if (duplicates > 0) {
err.println("\t... " + duplicates + " more");
}
parentStack = currentStack;
throwable = throwable.getCause();
}
}
/**
* Outputs a printable representation of the receiver's walkback on the
* writer specified by the argument.
*
* @param err PrintWriter The writer to write the walkback on.
*/
public void printStackTrace(PrintWriter err) {
err.println(toString());
// Don't use getStackTrace() as it calls clone()
// Get stackTrace, in case stackTrace is reassigned
StackTraceElement[] stack = getInternalStackTrace();
for (java.lang.StackTraceElement element : stack) {
err.println("\tat " + element);
}
StackTraceElement[] parentStack = stack;
Throwable throwable = getCause();
while (throwable != null) {
err.print("Caused by: ");
err.println(throwable);
StackTraceElement[] currentStack = throwable.getInternalStackTrace();
int duplicates = countDuplicates(currentStack, parentStack);
for (int i = 0; i < currentStack.length - duplicates; i++) {
err.println("\tat " + currentStack[i]);
}
if (duplicates > 0) {
err.println("\t... " + duplicates + " more");
}
parentStack = currentStack;
throwable = throwable.getCause();
}
}
/**
* Answers a string containing a concise, human-readable description of the
* receiver.
*
* @return String a printable representation for the receiver.
*/
@Override
public String toString() {
String msg = getLocalizedMessage();
String name = getClass().getName();
if (msg == null) {
return name;
}
return new StringBuffer(name.length() + 2 + msg.length()).append(name).append(": ")
.append(msg).toString();
}
/**
* Initialize the cause of the receiver. The cause cannot be reassigned.
*
* @param throwable The cause of this Throwable
*
* @exception IllegalArgumentException when the cause is the receiver
* @exception IllegalStateException when the cause has already been
* initialized
*
* @return the receiver.
*/
public synchronized Throwable initCause(Throwable throwable) {
if (cause == this) {
if (throwable != this) {
cause = throwable;
return this;
}
throw new IllegalArgumentException("Cause cannot be the receiver");
}
throw new IllegalStateException("Cause already initialized");
}
/**
* Answers the cause of this Throwable, or null if there is no cause.
*
* @return Throwable The receiver's cause.
*/
public Throwable getCause() {
if (cause == this) {
return null;
}
return cause;
}
private void writeObject(ObjectOutputStream s) throws IOException {
// ensure the stackTrace field is initialized
getInternalStackTrace();
s.defaultWriteObject();
}
}