/* * Copyright 2013 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 com.google.gwt.core.shared; import com.google.gwt.core.shared.impl.ThrowableTypeResolver; /** * A serializable copy of a {@link Throwable}, including its causes and stack trace. It overrides * {@code #toString} to mimic original {@link Throwable#toString()} so that {@link #printStackTrace} * will work as if it is coming from the original exception. * * <p> * This class is especially useful for logging and testing as the emulated Throwable class does not * serialize recursively and does not serialize the stack trace. This class, as an alternative, can * be used to transfer the Throwable without losing any of these data, even if the underlying * Throwable is not serializable. * <p> * Please note that, to get more useful stack traces from client side, this class needs to be used * in conjunction with {@link com.google.gwt.core.server.StackTraceDeobfuscator}. * <p> * NOTE: Does not serialize suppressed exceptions to remain compatible with Java 6 and below. */ public final class SerializableThrowable extends Throwable { /** * Create a new {@link SerializableThrowable} from a provided throwable and its causes * recursively. * * @return a new SerializableThrowable or the passed object itself if it is {@code null} or * already a SerializableThrowable. */ public static SerializableThrowable fromThrowable(Throwable throwable) { if (throwable instanceof SerializableThrowable) { return (SerializableThrowable) throwable; } else if (throwable != null) { return createSerializable(throwable); } else { return null; } } private String typeName; private boolean exactTypeKnown; private transient Throwable originalThrowable; private StackTraceElement[] dummyFieldToIncludeTheTypeInSerialization; /** * Constructs a new SerializableThrowable with the specified detail message. */ public SerializableThrowable(String designatedType, String message) { super(message); this.typeName = designatedType; } @Override public Throwable fillInStackTrace() { // This is a no-op for optimization as we don't need stack traces to be auto-filled. // TODO(goktug): Java 7 let's you disable this by constructor flag return this; } /** * Sets the designated Throwable's type name. * * @param typeName the class name of the underlying designated throwable. * @param isExactType {@code false} if provided type name is not the exact type. * * @see #isExactDesignatedTypeKnown() */ public void setDesignatedType(String typeName, boolean isExactType) { this.typeName = typeName; this.exactTypeKnown = isExactType; } /** * Returns the designated throwable's type name. * * @see #isExactDesignatedTypeKnown() */ public String getDesignatedType() { return typeName; } /** * Return {@code true} if provided type name is the exact type of the throwable that is designated * by this instance. This can return {@code false} if the class metadata is not available in the * runtime. In that case {@link #getDesignatedType()} will return the type resolved by best-effort * and may not be the exact type; instead it can be one of the ancestors of the real type that * this instance designates. */ public boolean isExactDesignatedTypeKnown() { return exactTypeKnown; } /** * Initializes the cause of this throwable. * <p> * This method will convert the cause to {@link SerializableThrowable} if it is not already. */ @Override public Throwable initCause(Throwable cause) { return super.initCause(fromThrowable(cause)); } /** * Returns the original throwable that this serializable throwable is derived from. Note that the * original throwable is kept in a transient field; that is; it will not be transferred to server * side. In that case this method will return {@code null}. */ public Throwable getOriginalThrowable() { return originalThrowable; } @Override public String toString() { String type = exactTypeKnown ? typeName : (typeName + "(EXACT TYPE UNKNOWN)"); String msg = getMessage(); return msg == null ? type : (type + ": " + msg); } private static SerializableThrowable createSerializable(Throwable t) { SerializableThrowable throwable = new SerializableThrowable(null, t.getMessage()); throwable.setStackTrace(t.getStackTrace()); throwable.initCause(t.getCause()); throwable.originalThrowable = t; ThrowableTypeResolver.resolveDesignatedType(throwable, t); return throwable; } }