Improves Dev Mode reporting of JavaScriptException to include method
name and args.

Review at http://gwt-code-reviews.appspot.com/1570803

Review by: tobyr@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10704 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/shell/BrowserChannelServer.java b/dev/core/src/com/google/gwt/dev/shell/BrowserChannelServer.java
index 63ad7ea..871e6db 100644
--- a/dev/core/src/com/google/gwt/dev/shell/BrowserChannelServer.java
+++ b/dev/core/src/com/google/gwt/dev/shell/BrowserChannelServer.java
@@ -28,6 +28,7 @@
 import java.net.MalformedURLException;
 import java.net.Socket;
 import java.net.URL;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
@@ -241,8 +242,8 @@
           // JSException
           exceptionValue = returnValue.getValue().toString();
         }
-        RuntimeException exception = ModuleSpace.createJavaScriptException(
-            ccl, exceptionValue);
+        RuntimeException exception = ModuleSpace.createJavaScriptException(ccl,
+            exceptionValue, methodName + "(" + Arrays.toString(args) + ")");
         // reset the stack trace to here to minimize GWT infrastructure in
         // the stack trace
         exception.fillInStackTrace();
diff --git a/dev/core/src/com/google/gwt/dev/shell/ModuleSpace.java b/dev/core/src/com/google/gwt/dev/shell/ModuleSpace.java
index ecb8ff6..9a11514 100644
--- a/dev/core/src/com/google/gwt/dev/shell/ModuleSpace.java
+++ b/dev/core/src/com/google/gwt/dev/shell/ModuleSpace.java
@@ -55,19 +55,28 @@
   }
 
   /**
+   * Equivalent to {@link #createJavaScriptException(ClassLoader,Object,String)
+   * createJavaScriptException(cl, exception, "")}.
+   */
+  protected static RuntimeException createJavaScriptException(ClassLoader cl,
+      Object exception) {
+    return createJavaScriptException(cl, exception, "");
+  }
+
+  /**
    * Create a JavaScriptException object. This must be done reflectively, since
    * this class will have been loaded from a ClassLoader other than the
    * session's thread.
    */
   protected static RuntimeException createJavaScriptException(ClassLoader cl,
-      Object exception) {
+      Object exception, String message) {
     Exception caught;
     try {
       Class<?> javaScriptExceptionClass = Class.forName(
           "com.google.gwt.core.client.JavaScriptException", true, cl);
       Constructor<?> ctor = javaScriptExceptionClass.getDeclaredConstructor(
-          Object.class);
-      return (RuntimeException) ctor.newInstance(new Object[] {exception});
+          Object.class, String.class);
+      return (RuntimeException) ctor.newInstance(new Object[] {exception, message});
     } catch (InstantiationException e) {
       caught = e;
     } catch (IllegalAccessException e) {
diff --git a/user/src/com/google/gwt/core/client/JavaScriptException.java b/user/src/com/google/gwt/core/client/JavaScriptException.java
index 36efebc..5c44f48 100644
--- a/user/src/com/google/gwt/core/client/JavaScriptException.java
+++ b/user/src/com/google/gwt/core/client/JavaScriptException.java
@@ -45,23 +45,23 @@
  */
 public final class JavaScriptException extends RuntimeException {
 
-  private static String getDescription(Object e) {
+  private static String getExceptionDescription(Object e) {
     if (e instanceof JavaScriptObject) {
-      return getDescription0((JavaScriptObject) e);
+      return getExceptionDescription0((JavaScriptObject) e);
     } else {
       return e + "";
     }
   }
 
-  private static native String getDescription0(JavaScriptObject e) /*-{
+  private static native String getExceptionDescription0(JavaScriptObject e) /*-{
     return (e == null) ? null : e.message;
   }-*/;
 
-  private static String getName(Object e) {
+  private static String getExceptionName(Object e) {
     if (e == null) {
       return "null";
     } else if (e instanceof JavaScriptObject) {
-      return getName0((JavaScriptObject) e);
+      return getExceptionName0((JavaScriptObject) e);
     } else if (e instanceof String) {
       return "String";
     } else {
@@ -69,11 +69,11 @@
     }
   }
 
-  private static native String getName0(JavaScriptObject e) /*-{
+  private static native String getExceptionName0(JavaScriptObject e) /*-{
     return (e == null) ? null : e.name;
   }-*/;
 
-  private static String getProperties(Object e) {
+  private static String getExceptionProperties(Object e) {
     return (GWT.isScript() && e instanceof JavaScriptObject)
         ? StackTraceCreator.getProperties((JavaScriptObject) e) : "";
   }
@@ -82,7 +82,7 @@
    * The original description of the JavaScript exception this class wraps,
    * initialized as <code>e.message</code>.
    */
-  private String description;
+  private String description = "";
 
   /**
    * The underlying exception this class wraps.
@@ -104,7 +104,18 @@
    * @param e the object caught in JavaScript that triggered the exception
    */
   public JavaScriptException(Object e) {
+    this(e, "");
+  }
+
+  /**
+   * @param e the object caught in JavaScript that triggered the exception
+   * @param description to include in getMessage(), e.g. at the top of a stack
+   *          trace
+   */
+  public JavaScriptException(Object e, String description) {
     this.e = e;
+    this.description = description;
+
     /*
      * In Development Mode, JavaScriptExceptions are created exactly when the
      * native method returns and their stack traces are fixed-up by the
@@ -117,7 +128,7 @@
       StackTraceCreator.createStackTrace(this);
     }
   }
-
+  
   public JavaScriptException(String name, String description) {
     this.message = "JavaScript " + name + " exception: " + description;
     this.name = name;
@@ -176,9 +187,9 @@
   }
 
   private void init() {
-    name = getName(e);
-    description = getDescription(e);
-    message = "(" + name + "): " + description + getProperties(e);
+    name = getExceptionName(e);
+    description = description + ": " + getExceptionDescription(e);
+    message = "(" + name + ") " + getExceptionProperties(e) + description;
   }
 
 }
diff --git a/user/test/com/google/gwt/core/client/JavaScriptExceptionTest.java b/user/test/com/google/gwt/core/client/JavaScriptExceptionTest.java
index 54d2eed..d4b193e 100644
--- a/user/test/com/google/gwt/core/client/JavaScriptExceptionTest.java
+++ b/user/test/com/google/gwt/core/client/JavaScriptExceptionTest.java
@@ -62,10 +62,10 @@
       fail();
     } catch (JavaScriptException e) {
       assertEquals("myName", e.getName());
-      assertEquals("myDescription", e.getDescription());
+      assertDescription(e, "myDescription");
       assertSame(jso, e.getException());
       assertTrue(e.getMessage().contains("myName"));
-      assertTrue(e.getMessage().contains("myDescription"));
+      assertTrue(e.getMessage().contains(e.getDescription()));
       if (extraPropertiesShouldBePresent) {
         assertTrue(
             "message does not contain 'extraField', but should: "
@@ -115,7 +115,7 @@
       assertSame(e, t);
     }
   }
-  
+
   @WithProperties({
     @Property(name = "compiler.stackMode", value = "emulated")
   })
@@ -142,7 +142,7 @@
      */
     assertJsoProperties(GWT.isScript());
   }
-
+  
   @WithProperties({
     @Property(name = "compiler.stackMode", value = "strip")
   })
@@ -163,7 +163,7 @@
       fail();
     } catch (JavaScriptException e) {
       assertEquals("null", e.getName());
-      assertEquals("null", e.getDescription());
+      assertDescription(e, "null");
       assertEquals(null, e.getException());
       assertTrue(e.getMessage().contains("null"));
     }
@@ -181,10 +181,10 @@
       fail();
     } catch (JavaScriptException e) {
       assertEquals(o.getClass().getName(), e.getName());
-      assertEquals("myLameObject", e.getDescription());
+      assertDescription(e, "myLameObject");
       assertEquals(null, e.getException());
       assertTrue(e.getMessage().contains(o.getClass().getName()));
-      assertTrue(e.getMessage().contains("myLameObject"));
+      assertTrue(e.getMessage().contains(e.getDescription()));
     }
   }
 
@@ -194,9 +194,20 @@
       fail();
     } catch (JavaScriptException e) {
       assertEquals("String", e.getName());
-      assertEquals("foobarbaz", e.getDescription());
+      assertDescription(e, "foobarbaz");
       assertEquals(null, e.getException());
-      assertTrue(e.getMessage().contains("foobarbaz"));
+      assertTrue(e.getMessage().contains(e.getDescription()));
     }
   }
+
+  private void assertDescription(JavaScriptException e, String description) {
+    if (!GWT.isScript()) {
+      assertTrue("Should start with method name",
+          e.getDescription().startsWith(
+              "@com.google.gwt.core.client.JavaScriptExceptionTest::"
+                  + "throwNative(Ljava/lang/Object;)"));
+    }
+    assertTrue("Should end with " + description,
+        e.getDescription().endsWith(description));
+  }
 }