Introduces a common SerializableThrowable as public API.

Change-Id: I98ae52858d87b6ac4a0c844464b25540be454942
Review-Link: https://gwt-review.googlesource.com/#/c/2160/

Review by: skybrian@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@11552 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/core/client/impl/SerializableThrowable.java b/user/src/com/google/gwt/core/client/impl/SerializableThrowable.java
index f5b993d..509679a 100644
--- a/user/src/com/google/gwt/core/client/impl/SerializableThrowable.java
+++ b/user/src/com/google/gwt/core/client/impl/SerializableThrowable.java
@@ -1,30 +1,26 @@
 /*
  * Copyright 2010 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.client.impl;
 
 import java.io.Serializable;
 
 /**
- * The emulated Throwable class does not serialize Throwables recursively and
- * does not serialize the stack trace.  This class is an alternative, and
- * can be used by writing a custom serializer for the class which contains a
- * Throwable. See {@link LogRecord_CustomFieldSerializer} as an example.
- *
+ * @deprecated use {@link com.google.gwt.core.shared.SerializableThrowable} instead.
  */
+@Deprecated
 public class SerializableThrowable implements Serializable {
   private SerializableThrowable cause = null;
   private String message = null;
@@ -32,8 +28,9 @@
   private String typeName = null;
 
   /**
-   * A subclass of Throwable that contains the serialized exception class type.
+   * @deprecated use com.google.gwt.core.client.SerializableThrowable instead.
    */
+  @Deprecated
   public static class ThrowableWithClassName extends Throwable {
 
     private String typeName;
diff --git a/user/src/com/google/gwt/core/shared/SerializableThrowable.java b/user/src/com/google/gwt/core/shared/SerializableThrowable.java
new file mode 100644
index 0000000..a44fb04
--- /dev/null
+++ b/user/src/com/google/gwt/core/shared/SerializableThrowable.java
@@ -0,0 +1,168 @@
+/*
+ * 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.client.JavaScriptException;
+
+/**
+ * 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}.
+ */
+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 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.
+    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 designed
+   * 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));
+  }
+
+  @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());
+    if (isClassMetadataAvailable()) {
+      throwable.setDesignatedType(t.getClass().getName(), true);
+    } else {
+      resolveDesignatedType(throwable, t);
+    }
+    return throwable;
+  }
+
+  // TODO(goktug): Replace when availability of class metadata can be checked in compile-time so
+  // that #resolveDesignatedType will be compiled out.
+  private static boolean isClassMetadataAvailable() {
+    return !GWT.isScript()
+        || SerializableThrowable.class.getName().endsWith(".SerializableThrowable");
+  }
+
+  /**
+   * Resolves best effort class name by checking against some common exception types.
+   */
+  private static void resolveDesignatedType(SerializableThrowable t, Throwable designatedType) {
+    String resolvedName;
+    Class<?> resolvedType;
+    try {
+      throw designatedType;
+    } catch (NullPointerException e) {
+      resolvedName = "java.lang.NullPointerException";
+      resolvedType = NullPointerException.class;
+    } catch (JavaScriptException e) {
+      resolvedName = "com.google.gwt.core.client.JavaScriptException";
+      resolvedType = JavaScriptException.class;
+    } catch (RuntimeException e) {
+      resolvedName = "java.lang.RuntimeException";
+      resolvedType = RuntimeException.class;
+    } catch (Exception e) {
+      resolvedName = "java.lang.Exception";
+      resolvedType = Exception.class;
+    } catch (Error e) {
+      resolvedName = "java.lang.Error";
+      resolvedType = Error.class;
+    } catch (Throwable e) {
+      resolvedName = "java.lang.Throwable";
+      resolvedType = Throwable.class;
+    }
+    t.setDesignatedType(resolvedName, resolvedType == designatedType.getClass());
+  }
+}
diff --git a/user/src/com/google/gwt/user/client/rpc/core/com/google/gwt/core/shared/SerializableThrowable_CustomFieldSerializer.java b/user/src/com/google/gwt/user/client/rpc/core/com/google/gwt/core/shared/SerializableThrowable_CustomFieldSerializer.java
new file mode 100644
index 0000000..e37d21d
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/rpc/core/com/google/gwt/core/shared/SerializableThrowable_CustomFieldSerializer.java
@@ -0,0 +1,79 @@
+/*
+ * 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.user.client.rpc.core.com.google.gwt.core.shared;
+
+import com.google.gwt.core.shared.SerializableThrowable;
+import com.google.gwt.user.client.rpc.CustomFieldSerializer;
+import com.google.gwt.user.client.rpc.SerializationException;
+import com.google.gwt.user.client.rpc.SerializationStreamReader;
+import com.google.gwt.user.client.rpc.SerializationStreamWriter;
+
+/**
+ * Custom field serializer for SerializableThrowable.
+ */
+public final class SerializableThrowable_CustomFieldSerializer
+    extends CustomFieldSerializer<SerializableThrowable> {
+
+  public static SerializableThrowable instantiate(SerializationStreamReader streamReader)
+      throws SerializationException {
+    return new SerializableThrowable(null, streamReader.readString());
+  }
+
+  public static void deserialize(
+      SerializationStreamReader streamReader, SerializableThrowable instance)
+      throws SerializationException {
+    String type = streamReader.readString();
+    boolean typeIsExact = streamReader.readBoolean();
+    instance.setDesignatedType(type, typeIsExact);
+    instance.setStackTrace((StackTraceElement[]) streamReader.readObject());
+    instance.initCause((Throwable) streamReader.readObject());
+  }
+
+  public static void serialize(
+      SerializationStreamWriter streamWriter, SerializableThrowable instance)
+      throws SerializationException {
+    streamWriter.writeString(instance.getMessage());
+    streamWriter.writeString(instance.getDesignatedType());
+    streamWriter.writeBoolean(instance.isExactDesignatedTypeKnown());
+    streamWriter.writeObject(instance.getStackTrace());
+    streamWriter.writeObject(instance.getCause());
+  }
+
+  @Override
+  public boolean hasCustomInstantiateInstance() {
+    return true;
+  }
+
+  @Override
+  public SerializableThrowable instantiateInstance(SerializationStreamReader streamReader)
+      throws SerializationException {
+    return instantiate(streamReader);
+  }
+
+  @Override
+  public void deserializeInstance(
+      SerializationStreamReader streamReader, SerializableThrowable instance)
+      throws SerializationException {
+    deserialize(streamReader, instance);
+  }
+
+  @Override
+  public void serializeInstance(
+      SerializationStreamWriter streamWriter, SerializableThrowable instance)
+      throws SerializationException {
+    serialize(streamWriter, instance);
+  }
+}
diff --git a/user/test/com/google/gwt/core/CoreSuite.java b/user/test/com/google/gwt/core/CoreSuite.java
index 39cb829..d1f6f2b 100644
--- a/user/test/com/google/gwt/core/CoreSuite.java
+++ b/user/test/com/google/gwt/core/CoreSuite.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -28,6 +28,7 @@
 import com.google.gwt.core.client.impl.SchedulerImplTest;
 import com.google.gwt.core.client.impl.StackTraceCreatorTest;
 import com.google.gwt.core.client.prefetch.RunAsyncCodeTest;
+import com.google.gwt.core.shared.SerializableThrowableTest;
 import com.google.gwt.dev.StrictModeTest;
 import com.google.gwt.junit.tools.GWTTestSuite;
 
@@ -52,6 +53,7 @@
     suite.addTestSuite(SchedulerImplTest.class);
     suite.addTestSuite(SchedulerTest.class);
     suite.addTestSuite(ScriptInjectorTest.class);
+    suite.addTestSuite(SerializableThrowableTest.class);
     suite.addTestSuite(StackTraceCreatorTest.class);
     suite.addTestSuite(StrictModeTest.class);
     suite.addTestSuite(RunAsyncCodeTest.class);
diff --git a/user/test/com/google/gwt/core/shared/SerializableThrowableTest.java b/user/test/com/google/gwt/core/shared/SerializableThrowableTest.java
new file mode 100644
index 0000000..476b50d
--- /dev/null
+++ b/user/test/com/google/gwt/core/shared/SerializableThrowableTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.junit.client.GWTTestCase;
+
+/**
+ * Tests for the {@link SerializableThrowable} class.
+ *
+ * @see com.google.gwt.user.client.rpc.ExceptionsTest for serialization tests.
+ */
+public class SerializableThrowableTest extends GWTTestCase {
+
+  private static final String TEST_CLASS_NAME =
+      "com.google.gwt.core.shared.SerializableThrowableTest";
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.core.Core";
+  }
+
+  public void testToString() throws Exception {
+    SerializableThrowable t = SerializableThrowable.fromThrowable(new RuntimeException("msg"));
+    t.setDesignatedType("a.A", true);
+    assertEquals("a.A: msg", t.toString());
+    t.setDesignatedType("a.A", false);
+    assertEquals("a.A(EXACT TYPE UNKNOWN): msg", t.toString());
+  }
+
+  public void testFromThrowable() throws Exception {
+    RuntimeException exception = new RuntimeException("msg");
+    exception.initCause(new RuntimeException("cause"));
+
+    SerializableThrowable serializableThrowable = SerializableThrowable.fromThrowable(exception);
+    assertEquals("msg", serializableThrowable.getMessage());
+    assertEquals(exception.getStackTrace().length, serializableThrowable.getStackTrace().length);
+    assertEquals("java.lang.RuntimeException: msg", serializableThrowable.toString());
+
+    SerializableThrowable cause = (SerializableThrowable) serializableThrowable.getCause();
+    assertEquals("cause", cause.getMessage());
+    assertEquals("java.lang.RuntimeException: cause", cause.toString());
+  }
+
+  public void testFromThrowable_alreadySerializable() {
+    SerializableThrowable expected = new SerializableThrowable(null, "msg");
+    assertSame(expected, SerializableThrowable.fromThrowable(expected));
+  }
+
+  public void testFromThrowable_null() {
+    assertNull(SerializableThrowable.fromThrowable(null));
+  }
+
+  private static class MyException extends Exception { }
+  private static class MyRuntimeException extends RuntimeException { }
+  private static class MyNullPointerException extends NullPointerException { }
+
+  public void testDesignatedType() throws Exception {
+    SerializableThrowable t = SerializableThrowable.fromThrowable(new RuntimeException());
+    assertEquals("java.lang.RuntimeException", t.getDesignatedType());
+    assertTrue(t.isExactDesignatedTypeKnown());
+  }
+
+  public void testDesignatedType_withClassMetadata() throws Exception {
+    if (!isClassMetadataAvailable()) {
+      return;
+    }
+    SerializableThrowable t;
+
+    t = SerializableThrowable.fromThrowable(new MyException());
+    assertEquals(TEST_CLASS_NAME + "$MyException", t.getDesignatedType());
+    assertTrue(t.isExactDesignatedTypeKnown());
+
+    t = SerializableThrowable.fromThrowable(new MyRuntimeException());
+    assertEquals(TEST_CLASS_NAME + "$MyRuntimeException", t.getDesignatedType());
+    assertTrue(t.isExactDesignatedTypeKnown());
+
+    t = SerializableThrowable.fromThrowable(new MyNullPointerException());
+    assertEquals(TEST_CLASS_NAME + "$MyNullPointerException", t.getDesignatedType());
+    assertTrue(t.isExactDesignatedTypeKnown());
+  }
+
+  public void testDesignatedType_withoutClassMetadata() throws Exception {
+    if (isClassMetadataAvailable()) {
+      return;
+    }
+    SerializableThrowable t;
+
+    t = SerializableThrowable.fromThrowable(new MyException());
+    assertEquals("java.lang.Exception", t.getDesignatedType());
+    assertFalse(t.isExactDesignatedTypeKnown());
+
+    t = SerializableThrowable.fromThrowable(new MyRuntimeException());
+    assertEquals("java.lang.RuntimeException", t.getDesignatedType());
+    assertFalse(t.isExactDesignatedTypeKnown());
+
+    t = SerializableThrowable.fromThrowable(new MyNullPointerException());
+    assertEquals("java.lang.NullPointerException", t.getDesignatedType());
+    assertFalse(t.isExactDesignatedTypeKnown());
+  }
+
+  private static boolean isClassMetadataAvailable() {
+    return SerializableThrowableTest.class.getName().endsWith(".SerializableThrowableTest");
+  }
+}
diff --git a/user/test/com/google/gwt/user/client/rpc/ExceptionsTest.java b/user/test/com/google/gwt/user/client/rpc/ExceptionsTest.java
index 600b5c4..69ad74f 100644
--- a/user/test/com/google/gwt/user/client/rpc/ExceptionsTest.java
+++ b/user/test/com/google/gwt/user/client/rpc/ExceptionsTest.java
@@ -16,6 +16,7 @@
 package com.google.gwt.user.client.rpc;
 
 import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.shared.SerializableThrowable;
 import com.google.gwt.event.shared.UmbrellaException;
 
 /**
@@ -26,21 +27,46 @@
   private ExceptionsTestServiceAsync exceptionsTestService;
 
   public void testUmbrellaException() {
-    ExceptionsTestServiceAsync service = getServiceAsync();
-    delayTestFinishForRpc();
     final UmbrellaException expected = TestSetFactory.createUmbrellaException();
-    service.echo(expected,
-        new AsyncCallback<UmbrellaException>() {
-          public void onFailure(Throwable caught) {
-            TestSetValidator.rethrowException(caught);
-          }
+    checkException(expected, new AsyncCallback<UmbrellaException>() {
+      public void onFailure(Throwable caught) {
+        TestSetValidator.rethrowException(caught);
+      }
 
-          public void onSuccess(UmbrellaException result) {
-            assertNotNull(result);
-            assertTrue(TestSetValidator.isValid(expected, result));
-            finishTest();
-          }
-        });
+      public void onSuccess(UmbrellaException result) {
+        assertNotNull(result);
+        assertTrue(TestSetValidator.isValid(expected, result));
+        finishTest();
+      }
+    });
+  }
+
+  public void testSerializableThrowable() {
+    SerializableThrowable expected = new SerializableThrowable(null, "msg");
+    expected.setDesignatedType("x", true);
+    expected.setStackTrace(new StackTraceElement[] {new StackTraceElement("c", "m", "f", 42)});
+    expected.initCause(new SerializableThrowable(null, "cause"));
+
+    checkException(expected, new AsyncCallback<SerializableThrowable>() {
+      public void onFailure(Throwable caught) {
+        TestSetValidator.rethrowException(caught);
+      }
+
+      public void onSuccess(SerializableThrowable result) {
+        assertNotNull(result);
+        assertEquals("msg", result.getMessage());
+        assertEquals("x", result.getDesignatedType());
+        assertTrue(result.isExactDesignatedTypeKnown());
+        assertEquals("c.m(f:42)", result.getStackTrace()[0].toString());
+        assertEquals("cause", ((SerializableThrowable) result.getCause()).getMessage());
+        finishTest();
+      }
+    });
+  }
+
+  private <T extends Throwable> void checkException(T expected, AsyncCallback<T> callback) {
+    delayTestFinishForRpc();
+    getServiceAsync().echo(expected, callback);
   }
 
   private ExceptionsTestServiceAsync getServiceAsync() {
diff --git a/user/test/com/google/gwt/user/client/rpc/ExceptionsTestService.java b/user/test/com/google/gwt/user/client/rpc/ExceptionsTestService.java
index fc258d7..551f0ac 100644
--- a/user/test/com/google/gwt/user/client/rpc/ExceptionsTestService.java
+++ b/user/test/com/google/gwt/user/client/rpc/ExceptionsTestService.java
@@ -15,26 +15,10 @@
  */
 package com.google.gwt.user.client.rpc;
 
-import com.google.gwt.event.shared.UmbrellaException;
-
 /**
  * Remote Service for testing the serialization of GWT Exception classes.
  */
 @RemoteServiceRelativePath("exceptions")
 public interface ExceptionsTestService extends RemoteService {
-
-  /**
-   * Exception specific to testing of GWT exception serialization.
-   */
-  final class ExceptionsTestServiceException extends Exception {
-    public ExceptionsTestServiceException() {
-    }
-
-    public ExceptionsTestServiceException(String msg) {
-      super(msg);
-    }
-  }
-
-  UmbrellaException echo(UmbrellaException exception)
-      throws ExceptionsTestServiceException;
+  <T extends Throwable> T echo(T throwable);
 }
diff --git a/user/test/com/google/gwt/user/client/rpc/ExceptionsTestServiceAsync.java b/user/test/com/google/gwt/user/client/rpc/ExceptionsTestServiceAsync.java
index a19197d..e347c31 100644
--- a/user/test/com/google/gwt/user/client/rpc/ExceptionsTestServiceAsync.java
+++ b/user/test/com/google/gwt/user/client/rpc/ExceptionsTestServiceAsync.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2011 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
@@ -15,11 +15,10 @@
  */
 package com.google.gwt.user.client.rpc;
 
-import com.google.gwt.event.shared.UmbrellaException;
 
 /**
  * Async remote service class for testing GWT Exception serialization.
  */
 public interface ExceptionsTestServiceAsync {
-  void echo(UmbrellaException exception, AsyncCallback<UmbrellaException> callback);
+  <T extends Throwable> void echo(T exception, AsyncCallback<T> callback);
 }
diff --git a/user/test/com/google/gwt/user/server/rpc/ExceptionsTestServiceImpl.java b/user/test/com/google/gwt/user/server/rpc/ExceptionsTestServiceImpl.java
index ed8bdd4..3006c79 100644
--- a/user/test/com/google/gwt/user/server/rpc/ExceptionsTestServiceImpl.java
+++ b/user/test/com/google/gwt/user/server/rpc/ExceptionsTestServiceImpl.java
@@ -15,10 +15,7 @@
  */
 package com.google.gwt.user.server.rpc;
 
-import com.google.gwt.event.shared.UmbrellaException;
 import com.google.gwt.user.client.rpc.ExceptionsTestService;
-import com.google.gwt.user.client.rpc.TestSetFactory;
-import com.google.gwt.user.client.rpc.TestSetValidator;
 
 /**
  * Remote Service Implementation for Exception serialization tests.
@@ -26,14 +23,9 @@
 public class ExceptionsTestServiceImpl extends HybridServiceServlet implements
     ExceptionsTestService {
 
-  public UmbrellaException echo(UmbrellaException exception)
-      throws ExceptionsTestServiceException {
-    UmbrellaException expected = TestSetFactory.createUmbrellaException();
-    if (!TestSetValidator.isValid(expected, exception)) {
-      throw new ExceptionsTestServiceException();
-    }
-
-    return exception;
+  @Override
+  public <T extends Throwable> T echo(T throwable) {
+    return throwable;
   }
 
 }