blob: 856776e4cf2a01dad3c5d511bbaf26edd2571895 [file] [log] [blame]
/*
* Copyright 2008 Google Inc.
*
* Licensed 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 static javaemul.internal.InternalPreconditions.checkCriticalArgument;
import static javaemul.internal.InternalPreconditions.checkNotNull;
import static javaemul.internal.InternalPreconditions.checkState;
import java.io.PrintStream;
import java.io.Serializable;
import javax.annotation.Nonnull;
import javaemul.internal.JsUtils;
import javaemul.internal.annotations.DoNotInline;
import jsinterop.annotations.JsMethod;
import jsinterop.annotations.JsProperty;
import jsinterop.annotations.JsType;
/**
* See <a
* href="http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Throwable.html">the
* official Java API doc</a> for details.
*/
public class Throwable implements Serializable {
private static final Object UNINITIALIZED = "__noinit__";
/*
* NOTE: We cannot use custom field serializers because we need the client and
* server to use different serialization strategies to deal with this type.
* The client uses the generated field serializers which can use JSNI. That
* leaves the server free to special case Throwable so that only the
* detailMessage field is serialized.
* See SerializabilityUtil.
*/
private String detailMessage;
private transient Throwable cause;
private transient Throwable[] suppressedExceptions;
private transient StackTraceElement[] stackTrace = new StackTraceElement[0];
private transient boolean disableSuppression;
private transient boolean writetableStackTrace = true;
@JsProperty
private transient Object backingJsObject = UNINITIALIZED;
public Throwable() {
fillInStackTrace();
initializeBackingError();
}
public Throwable(String message) {
this.detailMessage = message;
fillInStackTrace();
initializeBackingError();
}
public Throwable(String message, Throwable cause) {
this.cause = cause;
this.detailMessage = message;
fillInStackTrace();
initializeBackingError();
}
public Throwable(Throwable cause) {
this.detailMessage = (cause == null) ? null : cause.toString();
this.cause = cause;
fillInStackTrace();
initializeBackingError();
}
/**
* Constructor that allows subclasses disabling exception suppression and stack traces.
* Those features should only be disabled in very specific cases.
*/
protected Throwable(String message, Throwable cause, boolean enableSuppression,
boolean writetableStackTrace) {
this.cause = cause;
this.detailMessage = message;
this.writetableStackTrace = writetableStackTrace;
this.disableSuppression = !enableSuppression;
if (writetableStackTrace) {
fillInStackTrace();
}
initializeBackingError();
}
Throwable(Object backingJsObject) {
fillInStackTrace();
setBackingJsObject(backingJsObject);
detailMessage = String.valueOf(backingJsObject);
}
private void initializeBackingError() {
// Replace newlines with spaces so that we don't confuse the parser
// below which splits on newlines, and will otherwise try to parse
// the error message as part of the stack trace.
String message = detailMessage == null ? null : detailMessage.nativeReplaceAll("\n", " ");
String errorMessage = toString(message);
setBackingJsObject(fixIE(createError(errorMessage)));
captureStackTrace();
}
// TODO(goktug): set 'name' property to class name and 'message' to detailMessage instead when
// they are respected by dev tools logging.
Object createError(String msg) {
return new NativeError(msg);
}
@SuppressWarnings("unusable-by-js")
private static native Object fixIE(Object e) /*-{
// In IE -unlike every other browser-, the stack property is not defined until you throw it.
if (!("stack" in e)) {
try { throw e; } catch(ignored) {}
}
return e;
}-*/;
@SuppressWarnings("unusable-by-js")
private native void captureStackTrace() /*-{
@com.google.gwt.core.client.impl.StackTraceCreator::captureStackTrace(*)(this);
}-*/;
public Object getBackingJsObject() {
return backingJsObject;
}
private void setBackingJsObject(Object backingJsObject) {
this.backingJsObject = backingJsObject;
linkBack(backingJsObject);
}
private void linkBack(Object error) {
if (error != null) {
JsUtils.setPropertySafe(error, "__java$exception", this);
}
}
/**
* Call to add an exception that was suppressed. Used by try-with-resources.
*/
public final void addSuppressed(Throwable exception) {
checkNotNull(exception, "Cannot suppress a null exception.");
checkCriticalArgument(exception != this, "Exception can not suppress itself.");
if (disableSuppression) {
return;
}
if (suppressedExceptions == null) {
suppressedExceptions = new Throwable[] { exception };
} else {
// TRICK: This is not correct Java (would give an OOBE, but it works in JS and
// this code will only be executed in JS.
suppressedExceptions[suppressedExceptions.length] = exception;
}
}
/**
* Populates the stack trace information for the Throwable.
*
* @return this
*/
@DoNotInline
public Throwable fillInStackTrace() {
if (writetableStackTrace) {
// If this is the first run, let constructor initialize it.
// (We need to initialize the backingJsObject from constructor as our own implementation of
// fillInStackTrace is not guaranteed to be executed.)
if (backingJsObject != UNINITIALIZED) {
initializeBackingError();
}
// Invalidate the cached trace
this.stackTrace = null;
}
return this;
}
public Throwable getCause() {
return cause;
}
public String getLocalizedMessage() {
return getMessage();
}
public String getMessage() {
return detailMessage;
}
/**
* Returns the stack trace for the Throwable if it is available.
* <p> Availability of stack traces in script mode depends on module properties and browser.
* See: https://code.google.com/p/google-web-toolkit/wiki/WebModeExceptions#Emulated_Stack_Data
*/
public StackTraceElement[] getStackTrace() {
if (stackTrace == null) {
stackTrace = constructJavaStackTrace();
}
return stackTrace;
}
@SuppressWarnings("unusable-by-js")
private native StackTraceElement[] constructJavaStackTrace() /*-{
return @com.google.gwt.core.client.impl.StackTraceCreator::constructJavaStackTrace(*)(this);
}-*/;
/**
* Returns the array of Exception that this one suppressedExceptions.
*/
public final Throwable[] getSuppressed() {
if (suppressedExceptions == null) {
suppressedExceptions = new Throwable[0];
}
return suppressedExceptions;
}
public Throwable initCause(Throwable cause) {
checkState(this.cause == null, "Can't overwrite cause");
checkCriticalArgument(cause != this, "Self-causation not permitted");
this.cause = cause;
return this;
}
public void printStackTrace() {
printStackTrace(System.err);
}
public void printStackTrace(PrintStream out) {
printStackTraceImpl(out, "", "");
}
private void printStackTraceImpl(PrintStream out, String prefix, String ident) {
out.println(ident + prefix + this);
printStackTraceItems(out, ident);
for (Throwable t : getSuppressed()) {
t.printStackTraceImpl(out, "Suppressed: ", "\t" + ident);
}
Throwable theCause = getCause();
if (theCause != null) {
theCause.printStackTraceImpl(out, "Caused by: ", ident);
}
}
private void printStackTraceItems(PrintStream out, String ident) {
for (StackTraceElement element : getStackTrace()) {
out.println(ident + "\tat " + element);
}
}
public void setStackTrace(StackTraceElement[] stackTrace) {
int length = stackTrace.length;
StackTraceElement[] copy = new StackTraceElement[length];
for (int i = 0; i < length; ++i) {
copy[i] = checkNotNull(stackTrace[i]);
}
this.stackTrace = copy;
}
@Override
public String toString() {
return toString(getLocalizedMessage());
}
// A private method to avoid polymorphic calls from constructor.
private String toString(String message) {
String className = getClass().getName();
return message == null ? className : className + ": " + message;
}
@JsMethod
@Nonnull
public static Throwable of(Object e) {
// If the JS error is already mapped to a Java Exception, use it.
if (e != null) {
Throwable throwable = JsUtils.getProperty(e, "__java$exception");
if (throwable != null) {
return throwable;
}
}
// If the JS error is being seen for the first time, map it best corresponding Java exception.
return e instanceof NativeTypeError ? new NullPointerException(e) : new JsException(e);
}
@JsType(isNative = true, name = "Error", namespace = "<window>")
private static class NativeError {
NativeError(String msg) { }
}
@JsType(isNative = true, name = "TypeError", namespace = "<window>")
private static class NativeTypeError { }
}