Simplifies GwtRunner and adds a java reflection based test accessor.

This patch will remove the need for JSNI and code generation used by GWTTestCase
while running in dev mode.
Although this doesn't show much performance improvements, the stack traces in Dev Mode
are much less cluttered as no JSNI is involved on test method call.

Change-Id: I27198232174520c039448e3aa836b8014d0cd3cc
Review-Link: https://gwt-review.googlesource.com/#/c/4322/
diff --git a/tools/api-checker/config/gwt25_26userApi.conf b/tools/api-checker/config/gwt25_26userApi.conf
index 91c09d1..a95d12e 100644
--- a/tools/api-checker/config/gwt25_26userApi.conf
+++ b/tools/api-checker/config/gwt25_26userApi.conf
@@ -103,6 +103,7 @@
 :user/src/com/google/gwt/junit/*.java\
 :user/src/com/google/gwt/junit/client/GWTTestCase.java\
 :user/src/com/google/gwt/junit/client/impl/GWTRunner.java\
+:user/src/com/google/gwt/junit/client/impl/GWTTestAccessor.java\
 :user/src/com/google/gwt/junit/remote/**\
 :user/src/com/google/gwt/resources/css/**\
 :user/src/com/google/gwt/resources/ext/**\
diff --git a/user/src/com/google/gwt/junit/JUnit.gwt.xml b/user/src/com/google/gwt/junit/JUnit.gwt.xml
index cfc2a90..d1034c7 100644
--- a/user/src/com/google/gwt/junit/JUnit.gwt.xml
+++ b/user/src/com/google/gwt/junit/JUnit.gwt.xml
@@ -24,8 +24,8 @@
 
   <source path="client"/>
 
-  <generate-with class="com.google.gwt.junit.rebind.GWTRunnerProxyGenerator">
-    <when-type-is class="com.google.gwt.junit.client.impl.GWTRunnerProxy"/>
+  <generate-with class="com.google.gwt.junit.rebind.GWTTestMetadataGenerator">
+    <when-type-is class="com.google.gwt.junit.client.impl.GWTTestMetadata"/>
   </generate-with>
 
   <!-- We want to provide consistent stack traces across all browsers. -->
diff --git a/user/src/com/google/gwt/junit/client/impl/GWTRunnerProxy.java b/user/src/com/google/gwt/junit/client/impl/GWTRunnerProxy.java
deleted file mode 100644
index 942fed7..0000000
--- a/user/src/com/google/gwt/junit/client/impl/GWTRunnerProxy.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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.junit.client.impl;
-
-import com.google.gwt.core.client.JavaScriptObject;
-import com.google.gwt.core.client.SingleJsoImpl;
-import com.google.gwt.junit.client.GWTTestCase;
-
-/**
- * A proxy generated by code generator to provide access to user agent property and reflective
- * capabilities for GWTTestCase.
- */
-public interface GWTRunnerProxy {
-
-  /**
-   * A helper to provide create/execute functionality over GWTTestCase using names.
-   */
-  @SingleJsoImpl(JsniTestAccessor.class)
-  interface TestAccessor {
-    GWTTestCase newInstance(String className) throws Throwable;
-    Object invoke(GWTTestCase object, String className, String methodName) throws Throwable;
-  }
-
-  /**
-   * Creates a new {@link TestAccessor} for the module under test.
-   */
-  TestAccessor createTestAccessor();
-
-  /**
-   * Return the user agent property.
-   */
-  String getUserAgentProperty();
-
-
-  /**
-   * A JSNI + codegen based {@link TestAccessor} implementation.
-   */
-  class JsniTestAccessor extends JavaScriptObject implements TestAccessor {
-
-    protected JsniTestAccessor() { /* empty */}
-
-    @Override
-    public final GWTTestCase newInstance(String className) {
-      return (GWTTestCase) invoke(null, className, "new");
-    }
-
-    @Override
-    public final native Object invoke(GWTTestCase o, String className, String methodName) /*-{
-      return this[className][methodName](o);
-    }-*/;
-  }
-}
diff --git a/user/src/com/google/gwt/junit/client/impl/GWTTestAccessor.java b/user/src/com/google/gwt/junit/client/impl/GWTTestAccessor.java
new file mode 100644
index 0000000..3b4eb48
--- /dev/null
+++ b/user/src/com/google/gwt/junit/client/impl/GWTTestAccessor.java
@@ -0,0 +1,44 @@
+/*
+ * 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.junit.client.impl;
+
+import com.google.gwt.junit.client.GWTTestCase;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * A helper to provide create/execute functionality over GWTTestCase using class and method names
+ * via reflection. This class is super-sourced for production mode.
+ */
+public class GWTTestAccessor {
+
+  public GWTTestCase newInstance(String className) throws Throwable {
+    return (GWTTestCase) Class.forName(className).newInstance();
+  }
+
+  public Object invoke(GWTTestCase test, String className, String methodName) throws Throwable {
+    assert test.getClass().getName().equals(className);
+
+    Method m = test.getClass().getMethod(methodName);
+    m.setAccessible(true);
+    try {
+      return m.invoke(test);
+    } catch (InvocationTargetException e) {
+      throw e.getTargetException();
+    }
+  }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/junit/rebind/GWTRunnerProxyGenerator.java b/user/src/com/google/gwt/junit/rebind/GWTTestMetadataGenerator.java
similarity index 71%
rename from user/src/com/google/gwt/junit/rebind/GWTRunnerProxyGenerator.java
rename to user/src/com/google/gwt/junit/rebind/GWTTestMetadataGenerator.java
index 36e8c4f..06cf032 100644
--- a/user/src/com/google/gwt/junit/rebind/GWTRunnerProxyGenerator.java
+++ b/user/src/com/google/gwt/junit/rebind/GWTTestMetadataGenerator.java
@@ -15,11 +15,11 @@
  */
 package com.google.gwt.junit.rebind;
 
+import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.core.ext.BadPropertyValueException;
 import com.google.gwt.core.ext.ConfigurationProperty;
 import com.google.gwt.core.ext.Generator;
 import com.google.gwt.core.ext.GeneratorContext;
-import com.google.gwt.core.ext.SelectionProperty;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JAbstractMethod;
@@ -31,8 +31,6 @@
 import com.google.gwt.dev.util.collect.HashMap;
 import com.google.gwt.junit.client.GWTTestCase;
 import com.google.gwt.junit.client.GWTTestCase.TestModuleInfo;
-import com.google.gwt.junit.client.impl.GWTRunnerProxy;
-import com.google.gwt.junit.client.impl.GWTRunnerProxy.JsniTestAccessor;
 import com.google.gwt.junit.client.impl.JUnitHost.TestInfo;
 import com.google.gwt.junit.client.impl.MissingTestPlaceHolder;
 import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
@@ -44,19 +42,11 @@
 import java.util.Set;
 
 /**
- * This class generates a JSNI based {@link GWTRunnerProxy} implementation.
- * <p>
- * For each gwt module following classes will be generated:
- * <li>GwtRunnerProxyImpl abstract class that implements createTestAccessor using JSNI</li>
- * <li>GwtRunnerProxyImplXyz (e.g. GwtRunnerProxyImplSafari) that extends GwtRunnerProxyImpl and
- * implements getUserAgentPropery</li>
+ * A generator that generates {@code GWTTestMetadata}.
  */
-public class GWTRunnerProxyGenerator extends Generator {
+public class GWTTestMetadataGenerator extends Generator {
 
-  private static final String PROXY = GWTRunnerProxy.class.getCanonicalName();
-  private static final String JSNI_TEST_ACCESSOR = JsniTestAccessor.class.getCanonicalName();
-  private static final String MISSING_TEST = MissingTestPlaceHolder.class.getCanonicalName();
-
+  private static final String BASE_CLASS = "com.google.gwt.junit.client.impl.GWTTestMetadata";
   private static final JType[] NO_PARAMS = new JType[0];
 
   private static String getPackagePrefix(JClassType classType) {
@@ -70,8 +60,8 @@
   @Override
   public String generate(TreeLogger logger, GeneratorContext context, String typeName)
       throws UnableToCompleteException {
-    if (!PROXY.equals(typeName)) {
-      logger.log(TreeLogger.ERROR, "This generator may only be used with " + PROXY, null);
+    if (!BASE_CLASS.equals(typeName)) {
+      logger.log(TreeLogger.ERROR, "This generator may only be used with " + BASE_CLASS, null);
       throw new UnableToCompleteException();
     }
     JClassType requestedClass;
@@ -98,43 +88,21 @@
       throw new UnableToCompleteException();
     }
 
-    String userAgent;
-    try {
-      SelectionProperty prop = context.getPropertyOracle().getSelectionProperty(
-          logger, "user.agent");
-      userAgent = prop.getCurrentValue();
-    } catch (BadPropertyValueException e) {
-      logger.log(TreeLogger.ERROR, "Could not resolve user.agent property", e);
-      throw new UnableToCompleteException();
-    }
-
     String packageName = requestedClass.getPackage().getName();
+    String generatedClass = requestedClass.getName() + "Impl";
 
-    // Generate the base class shared across different permutations:
-    String generatedBaseClass = requestedClass.getName().replace('.', '_') + "Impl";
-    SourceWriter sourceWriter =
-        getSourceWriter(logger, context, packageName, generatedBaseClass, null, null);
+    SourceWriter sourceWriter = getSourceWriter(logger, context, packageName, generatedClass);
     if (sourceWriter != null) {
-      writeMethodCreateTestAccessor(sourceWriter, getTestClasses(logger, context, moduleName));
+      writeCreateMethod(sourceWriter, getTestClasses(logger, context, moduleName));
       sourceWriter.commit(logger);
     }
-
-    // Generate the actual class for each permutation"
-    String generatedClass = generatedBaseClass + userAgent;
-    sourceWriter =
-        getSourceWriter(logger, context, packageName, generatedClass, generatedBaseClass, PROXY);
-    if (sourceWriter != null) {
-      writeGetUserAgentPropertyMethod(userAgent, sourceWriter);
-      sourceWriter.commit(logger);
-    }
-
     return packageName + "." + generatedClass;
   }
 
   /**
    * Will generate following:
    * <pre>
-   * public native final JsniTestAccessor createTestAccessor() /*-{
+   * public native final JavaScriptObject get() /*-{
    *   return {
    *     'a.b.c.X' = {
    *       'new' : function(test) {
@@ -157,8 +125,8 @@
    * }-{@literal*}/;
    * </pre>
    */
-  private void writeMethodCreateTestAccessor(SourceWriter sw, Map<String, JClassType> testClasses) {
-    sw.println("public native final %s createTestAccessor() /*-{", JSNI_TEST_ACCESSOR);
+  private void writeCreateMethod(SourceWriter sw, Map<String, JClassType> testClasses) {
+    sw.println("public native final %s get() /*-{", JavaScriptObject.class.getCanonicalName());
     sw.indent();
     sw.println("return {");
     for (Map.Entry<String, JClassType> entry : testClasses.entrySet()) {
@@ -186,12 +154,6 @@
     sw.println("'%s' : function(test) { return %s(); },", methodName, call);
   }
 
-  private void writeGetUserAgentPropertyMethod(String userAgent, SourceWriter sw) {
-    sw.println("public final String getUserAgentProperty() {");
-    sw.indentln("return \"" + userAgent + "\";");
-    sw.println("}");
-  }
-
   private Map<String, JClassType> getTestClasses(
       TreeLogger logger, GeneratorContext context, String moduleName)
       throws UnableToCompleteException {
@@ -220,15 +182,12 @@
    */
   private JClassType getTestClass(TypeOracle typeOracle, String testClassName) {
     JClassType type = typeOracle.findType(testClassName);
-    return type != null ? type : typeOracle.findType(MISSING_TEST);
+    return type != null ? type
+        : typeOracle.findType(MissingTestPlaceHolder.class.getCanonicalName());
   }
 
-  private SourceWriter getSourceWriter(TreeLogger logger,
-      GeneratorContext ctx,
-      String packageName,
-      String className,
-      String superclassName,
-      String interfaceName) {
+  private SourceWriter getSourceWriter(
+      TreeLogger logger, GeneratorContext ctx, String packageName, String className) {
     PrintWriter printWriter = ctx.tryCreate(logger, packageName, className);
     if (printWriter == null) {
       return null;
@@ -236,12 +195,7 @@
 
     ClassSourceFileComposerFactory composerFactory =
         new ClassSourceFileComposerFactory(packageName, className);
-    if (superclassName != null) {
-      composerFactory.setSuperclass(superclassName);
-    }
-    if (interfaceName != null) {
-      composerFactory.addImplementedInterface(interfaceName);
-    }
+    composerFactory.setSuperclass(BASE_CLASS);
     return composerFactory.createSourceWriter(ctx, printWriter);
   }
 
diff --git a/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTRunner.java b/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTRunner.java
index fac71a8..e8bc935 100644
--- a/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTRunner.java
+++ b/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTRunner.java
@@ -20,7 +20,6 @@
 import com.google.gwt.core.client.Scheduler;
 import com.google.gwt.http.client.UrlBuilder;
 import com.google.gwt.junit.client.GWTTestCase;
-import com.google.gwt.junit.client.impl.GWTRunnerProxy.TestAccessor;
 import com.google.gwt.junit.client.impl.JUnitHost.ClientInfo;
 import com.google.gwt.junit.client.impl.JUnitHost.InitialResponse;
 import com.google.gwt.junit.client.impl.JUnitHost.TestBlock;
@@ -29,6 +28,7 @@
 import com.google.gwt.user.client.Window;
 import com.google.gwt.user.client.rpc.AsyncCallback;
 import com.google.gwt.user.client.rpc.ServiceDefTarget;
+import com.google.gwt.useragent.client.UserAgent;
 
 import java.util.HashMap;
 
@@ -193,7 +193,7 @@
    */
   private boolean serverless = false;
 
-  private TestAccessor testAccessor;
+  private GWTTestAccessor testAccessor;
 
   // TODO(FINDBUGS): can this be a private constructor to avoid multiple
   // instances?
@@ -210,11 +210,8 @@
   }
 
   public void onModuleLoad() {
-    GWTRunnerProxy proxy = GWT.create(GWTRunnerProxy.class);
-    testAccessor = proxy.createTestAccessor();
-
-    clientInfo = new ClientInfo(parseQueryParamInteger(
-        SESSIONID_QUERY_PARAM, -1), proxy.getUserAgentProperty());
+    testAccessor = new GWTTestAccessor();
+    clientInfo = new ClientInfo(parseQueryParamInteger(SESSIONID_QUERY_PARAM, -1), getUserAgent());
     maxRetryCount = parseQueryParamInteger(RETRYCOUNT_QUERY_PARAM, 3);
     currentBlock = checkForQueryParamTestToRun();
     if (currentBlock != null) {
@@ -232,6 +229,11 @@
     }
   }
 
+  private String getUserAgent() {
+    UserAgent userAgentProperty = GWT.create(UserAgent.class);
+    return userAgentProperty.getCompileTimeValue();
+  }
+
   public void reportResultsAndGetNextMethod(JUnitResult result) {
     if (serverless) {
       // That's it, we're done
diff --git a/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTTestAccessor.java b/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTTestAccessor.java
new file mode 100644
index 0000000..e6abd71
--- /dev/null
+++ b/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTTestAccessor.java
@@ -0,0 +1,37 @@
+/*
+ * 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.junit.client.impl;
+
+import com.google.gwt.core.client.GwtScriptOnly;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * A GWTTestAccessor implementation that utilizes generated test class metadata for script mode.
+ */
+@GwtScriptOnly
+public class GWTTestAccessor {
+
+  private final JavaScriptObject data = GWTTestMetadata.create().get();
+
+  public final GWTTestCase newInstance(String className) {
+    return (GWTTestCase) invoke(null, className, "new");
+  }
+
+  public final native Object invoke(GWTTestCase o, String className, String methodName) /*-{
+    return this.@com.google.gwt.junit.client.impl.GWTTestAccessor::data[className][methodName](o);
+  }-*/;
+}
diff --git a/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTTestMetadata.java b/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTTestMetadata.java
new file mode 100644
index 0000000..6eef85e
--- /dev/null
+++ b/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTTestMetadata.java
@@ -0,0 +1,36 @@
+/*
+ * 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.junit.client.impl;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.shared.GWT;
+
+/**
+ * Provides metadata of {@code GWTTestCase} to access its constructor and methods in the runtime.
+ */
+abstract class GWTTestMetadata {
+
+  static GWTTestMetadata create() {
+    return GWT.create(GWTTestMetadata.class);
+  }
+
+  /**
+   * Returns the metadata as a javascript map. See
+   * {@link com.google.gwt.junit.rebind.GWTTestMetadataGenerator#writeCreateMethod} for the format.
+   */
+  abstract JavaScriptObject get();
+}
+