- Native methods are now changed to non-native thunks via bytecode rewriting, as part of other rewrites.
- JsniInjector no longer performs this function at the source level.
- JsniInjector still parses out the actual method bodies and changes them into class-level annotations.
- This change is en route to do away with source code rewriting altogether.

Patch by: tobyr (+me minor cleanup)
Review by: me


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2652 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
index 9dfc01e..2359e09 100644
--- a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
+++ b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
@@ -33,12 +33,15 @@
 import org.apache.commons.collections.map.ReferenceIdentityMap;
 import org.apache.commons.collections.map.ReferenceMap;
 
+import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -341,12 +344,43 @@
   private static final Class<?>[] BRIDGE_CLASSES = new Class<?>[] {
       ShellJavaScriptHost.class, JsniMethods.class, JsniMethod.class};
 
+  private static final boolean CLASS_DUMP = false;
+
+  private static final String CLASS_DUMP_PATH = "rewritten-classes";
+
   static {
     for (Class<?> c : BRIDGE_CLASSES) {
       BRIDGE_CLASS_NAMES.put(c.getName(), c);
     }
   }
 
+  private static void classDump(String name, byte[] bytes) {
+    String packageName, className;
+    int pos = name.lastIndexOf('.');
+    if (pos < 0) {
+      packageName = "";
+      className = name;
+    } else {
+      packageName = name.substring(0, pos);
+      className = name.substring(pos + 1);
+    }
+
+    File dir = new File(CLASS_DUMP_PATH + File.separator
+        + packageName.replace('.', File.separatorChar));
+    if (!dir.exists()) {
+      dir.mkdirs();
+    }
+
+    File file = new File(dir, className + ".class");
+    try {
+      FileOutputStream fileOutput = new FileOutputStream(file);
+      fileOutput.write(bytes);
+      fileOutput.close();
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+  }
+
   private final HostedModeClassRewriter classRewriter;
 
   private final ByteCodeCompiler compiler;
@@ -554,7 +588,13 @@
         }
         classBytes = compiler.getClassBytes(logger, lookupClassName);
         if (classRewriter != null) {
-          classBytes = classRewriter.rewrite(className, classBytes);
+          byte[] newBytes = classRewriter.rewrite(className, classBytes);
+          if (CLASS_DUMP) {
+            if (!Arrays.equals(classBytes, newBytes)) {
+              classDump(className, classBytes);
+            }
+          }
+          classBytes = newBytes;
         }
       }
       Class<?> newClass = defineClass(className, classBytes, 0,
diff --git a/dev/core/src/com/google/gwt/dev/shell/JsniInjector.java b/dev/core/src/com/google/gwt/dev/shell/JsniInjector.java
index bd1c3dd..34d2917 100644
--- a/dev/core/src/com/google/gwt/dev/shell/JsniInjector.java
+++ b/dev/core/src/com/google/gwt/dev/shell/JsniInjector.java
@@ -21,8 +21,6 @@
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.JMethod;
 import com.google.gwt.core.ext.typeinfo.JParameter;
-import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
-import com.google.gwt.core.ext.typeinfo.JType;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.dev.jdt.CompilationUnitProviderWithAlternateSource;
 import com.google.gwt.dev.js.ast.JsBlock;
@@ -84,8 +82,6 @@
   private final Map<JClassType, List<JsniMethod>> jsniMethodMap = new IdentityHashMap<JClassType, List<JsniMethod>>();
   private final TypeOracle oracle;
 
-  private final Map<JMethod, JsBlock> parsedJsByMethod = new IdentityHashMap<JMethod, JsBlock>();
-
   public JsniInjector(TypeOracle oracle) {
     this.oracle = oracle;
   }
@@ -135,8 +131,45 @@
     }
   }
 
-  private JsniMethod createJsniMethod(JMethod method, final String file,
-      char[] source) {
+  private void collectJsniMethods(TreeLogger logger, char[] source,
+      JClassType type) throws UnableToCompleteException {
+
+    // Locate the nearest non-local type; don't try to annotate local types.
+    JClassType targetType = type;
+    while (targetType.isLocalType()) {
+      targetType = targetType.getEnclosingType();
+    }
+    List<JsniMethod> jsniMethods = jsniMethodMap.get(targetType);
+    String loc = type.getCompilationUnit().getLocation();
+
+    for (JMethod method : type.getMethods()) {
+      if (!method.isNative()) {
+        continue;
+      }
+      Jsni.Interval interval = Jsni.findJsniSource(method);
+      if (interval == null) {
+        String msg = "No JavaScript body found for native method '" + method
+            + "' in type '" + type + "'";
+        logger.log(TreeLogger.ERROR, msg, null);
+        throw new UnableToCompleteException();
+      }
+      // Parse it.
+      String js = String.valueOf(source, interval.start, interval.end
+          - interval.start);
+      int startLine = Jsni.countNewlines(source, 0, interval.start) + 1;
+      JsBlock body = Jsni.parseAsFunctionBody(logger, js, loc, startLine);
+
+      // Add JsniMethod annotations to the target type.
+      if (jsniMethods == null) {
+        jsniMethods = new ArrayList<JsniMethod>();
+        jsniMethodMap.put(targetType, jsniMethods);
+      }
+      jsniMethods.add(createJsniMethod(method, body, loc, source));
+    }
+  }
+
+  private JsniMethod createJsniMethod(JMethod method, JsBlock jsniBody,
+      final String file, char[] source) {
 
     final int line = Jsni.countNewlines(source, 0, method.getBodyStart()) + 1;
 
@@ -154,8 +187,6 @@
      * itself will print curly braces, so we don't need them around the
      * try/catch.
      */
-    JsBlock jsniBody = parsedJsByMethod.get(method);
-    assert (jsniBody != null);
     String jsTry = "try ";
     String jsCatch = " catch (e) {\n  __static[\"@" + Jsni.JAVASCRIPTHOST_NAME
         + "::exceptionCaught(Ljava/lang/Object;)\"](e == null ? null : e);\n"
@@ -248,206 +279,26 @@
     return sb.toString().toCharArray();
   }
 
-  /**
-   * Create a legal Java method call that will result in a JSNI invocation.
-   * 
-   * @param method
-   * @param expectedHeaderLines
-   * @param expectedBodyLines
-   * @param prettyPrint true if the output should be prettier
-   * @return a String of the Java code to call a JSNI method, using
-   *         JavaScriptHost.invokeNative*
-   */
-  private String genNonNativeVersionOfJsniMethod(JMethod method,
-      int expectedHeaderLines, int expectedBodyLines, boolean pretty) {
-    StringBuffer sb = new StringBuffer();
-    String nl = pretty ? "\n " : "";
-
-    // Add extra lines at the start to match comments + declaration
-    if (!pretty) {
-      for (int i = 0; i < expectedHeaderLines; ++i) {
-        sb.append('\n');
-      }
-    }
-
-    String methodDecl = method.getReadableDeclaration(false, true, false,
-        false, false);
-
-    sb.append(methodDecl + " {" + nl);
-    // wrap the call in a try-catch block
-    sb.append("try {" + nl);
-
-    // Write the Java call to the property invoke method, adding
-    // downcasts where necessary.
-    JType returnType = method.getReturnType();
-    JPrimitiveType primType = returnType.isPrimitive();
-    if (primType != null) {
-      // Primitives have special overloads.
-      char[] primTypeSuffix = primType.getSimpleSourceName().toCharArray();
-      primTypeSuffix[0] = Character.toUpperCase(primTypeSuffix[0]);
-      String invokeMethodName = "invokeNative" + String.valueOf(primTypeSuffix);
-      if (primType != JPrimitiveType.VOID) {
-        sb.append("return ");
-      }
-      sb.append(Jsni.JAVASCRIPTHOST_NAME);
-      sb.append(".");
-      sb.append(invokeMethodName);
-    } else {
-      // Some reference type.
-      // We need to add a downcast to the originally-declared type.
-      String returnTypeName = returnType.getParameterizedQualifiedSourceName();
-      sb.append("return (");
-      sb.append(returnTypeName);
-      sb.append(")");
-      sb.append(Jsni.JAVASCRIPTHOST_NAME);
-      sb.append(".invokeNativeObject");
-    }
-
-    // Write the argument list for the invoke call.
-    sb.append("(\"@");
-    String jsniSig = Jsni.getJsniSignature(method);
-    sb.append(jsniSig);
-    if (method.isStatic()) {
-      sb.append("\", null, ");
-    } else {
-      sb.append("\", this, ");
-    }
-
-    // Build an array of classes that tells the invoker how to adapt the
-    // incoming arguments for calling into JavaScript.
-    sb.append(Jsni.buildTypeList(method));
-    sb.append(',');
-
-    // Build an array containing the arguments based on the names of the
-    // parameters.
-    sb.append(Jsni.buildArgList(method));
-    sb.append(");" + nl);
-
-    // Catch exceptions; rethrow if the exception is RTE or declared.
-    sb.append("} catch (java.lang.Throwable __gwt_exception) {" + nl);
-    sb.append("if (__gwt_exception instanceof java.lang.RuntimeException) throw (java.lang.RuntimeException) __gwt_exception;"
-        + nl);
-    sb.append("if (__gwt_exception instanceof java.lang.Error) throw (java.lang.Error) __gwt_exception;"
-        + nl);
-    JType[] throwTypes = method.getThrows();
-    for (int i = 0; i < throwTypes.length; ++i) {
-      String typeName = throwTypes[i].getQualifiedSourceName();
-      sb.append("if (__gwt_exception instanceof " + typeName + ") throw ("
-          + typeName + ") __gwt_exception;" + nl);
-    }
-    sb.append("throw new java.lang.RuntimeException(\"Undeclared checked exception thrown out of JavaScript; web mode behavior may differ.\", __gwt_exception);"
-        + nl);
-    sb.append("}" + nl);
-
-    sb.append("}" + nl);
-
-    // Add extra lines at the end to match JSNI body.
-    if (!pretty) {
-      for (int i = 0; i < expectedBodyLines; ++i) {
-        sb.append('\n');
-      }
-    }
-
-    return sb.toString();
-  }
-
   private void rewriteCompilationUnit(TreeLogger logger, char[] source,
       List<Replacement> changes, CompilationUnitProvider cup, boolean pretty)
       throws UnableToCompleteException {
 
-    // First create replacements for all native methods.
+    // Collect all JSNI methods in the compilation unit.
     JClassType[] types = oracle.getTypesInCompilationUnit(cup);
     for (JClassType type : types) {
       if (!type.getQualifiedSourceName().startsWith("java.")) {
-        rewriteType(logger, source, changes, type, pretty);
+        collectJsniMethods(logger, source, type);
       }
     }
 
-    // Then annotate the appropriate types with JsniMethod annotations.
+    // Annotate the appropriate types with JsniMethod annotations.
     for (JClassType type : types) {
       List<JsniMethod> jsniMethods = jsniMethodMap.get(type);
-      if (jsniMethods != null) {
+      if (jsniMethods != null && jsniMethods.size() > 0) {
         char[] annotation = genJsniMethodsAnnotation(jsniMethods, pretty);
         int declStart = type.getDeclStart();
         changes.add(new Replacement(declStart, declStart, annotation));
       }
     }
   }
-
-  private void rewriteType(TreeLogger logger, char[] source,
-      List<Replacement> changes, JClassType type, boolean pretty)
-      throws UnableToCompleteException {
-
-    String loc = type.getCompilationUnit().getLocation();
-
-    // Examine each method for JSNIness.
-    List<JMethod> patchedMethods = new ArrayList<JMethod>();
-    JMethod[] methods = type.getMethods();
-    for (int i = 0; i < methods.length; i++) {
-      JMethod method = methods[i];
-      if (method.isNative()) {
-        Jsni.Interval interval = Jsni.findJsniSource(method);
-        if (interval != null) {
-          // The method itself needs to be replaced.
-
-          // Parse it.
-          String js = String.valueOf(source, interval.start, interval.end
-              - interval.start);
-          int startLine = Jsni.countNewlines(source, 0, interval.start) + 1;
-          JsBlock body = Jsni.parseAsFunctionBody(logger, js, loc, startLine);
-
-          // Remember this as being a valid JSNI method.
-          parsedJsByMethod.put(method, body);
-
-          // Replace the method.
-          final int declStart = method.getDeclStart();
-          final int declEnd = method.getDeclEnd();
-
-          int expectedHeaderLines = Jsni.countNewlines(source, declStart,
-              interval.start);
-          int expectedBodyLines = Jsni.countNewlines(source, interval.start,
-              interval.end);
-          String newDecl = genNonNativeVersionOfJsniMethod(method,
-              expectedHeaderLines, expectedBodyLines, pretty);
-
-          final char[] newSource = newDecl.toCharArray();
-          changes.add(new Replacement(declStart, declEnd, newSource));
-          patchedMethods.add(method);
-        } else {
-          // report error
-          String msg = "No JavaScript body found for native method '" + method
-              + "' in type '" + type + "'";
-          logger.log(TreeLogger.ERROR, msg, null);
-          throw new UnableToCompleteException();
-        }
-      }
-    }
-
-    if (!patchedMethods.isEmpty()) {
-      JMethod[] patched = new JMethod[patchedMethods.size()];
-      patched = patchedMethods.toArray(patched);
-
-      TreeLogger branch = logger.branch(TreeLogger.SPAM, "Patched methods in '"
-          + type.getQualifiedSourceName() + "'", null);
-
-      for (int i = 0; i < patched.length; i++) {
-        branch.log(TreeLogger.SPAM, patched[i].getReadableDeclaration(), null);
-      }
-
-      // Locate the nearest non-local type.
-      while (type.isLocalType()) {
-        type = type.getEnclosingType();
-      }
-
-      // Add JsniMethod infos to the nearest non-inner type for each method.
-      List<JsniMethod> jsniMethods = jsniMethodMap.get(type);
-      if (jsniMethods == null) {
-        jsniMethods = new ArrayList<JsniMethod>();
-        jsniMethodMap.put(type, jsniMethods);
-      }
-      for (JMethod m : patched) {
-        jsniMethods.add(createJsniMethod(m, loc, source));
-      }
-    }
-  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/HostedModeClassRewriter.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/HostedModeClassRewriter.java
index 46cba56..0530026 100644
--- a/dev/core/src/com/google/gwt/dev/shell/rewrite/HostedModeClassRewriter.java
+++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/HostedModeClassRewriter.java
@@ -19,10 +19,8 @@
 import com.google.gwt.dev.asm.ClassVisitor;
 import com.google.gwt.dev.asm.ClassWriter;
 import com.google.gwt.dev.asm.Opcodes;
-import com.google.gwt.dev.asm.util.TraceClassVisitor;
 import com.google.gwt.dev.shell.JsValueGlue;
 
-import java.io.PrintWriter;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -33,6 +31,8 @@
  * This class performs any and all byte code rewriting needed to make hosted
  * mode work. Currently, it performs the following rewrites:
  * <ol>
+ * <li>Rewrites all native methods into non-native thunks to call JSNI via
+ * {@link com.google.gwt.dev.shell.JavaScriptHost}.</li>
  * <li>Rewrites all JSO types into an interface type (which retains the
  * original name) and an implementation type (which has a $ appended).</li>
  * <li>All JSO interface types are empty and mirror the original type
@@ -46,6 +46,7 @@
  * instantiable type.</li>
  * </ol>
  * 
+ * @see RewriteJsniMethods
  * @see RewriteRefsToJsoClasses
  * @see WriteJsoInterface
  * @see WriteJsoImpl
@@ -80,13 +81,13 @@
     String findOriginalDeclaringClass(String declaredClass, String signature);
   }
 
-  static final String REFERENCE_FIELD = JsValueGlue.HOSTED_MODE_REFERENCE;
-
   static final String JAVASCRIPTOBJECT_DESC = JsValueGlue.JSO_CLASS.replace(
       '.', '/');
 
   static final String JAVASCRIPTOBJECT_IMPL_DESC = JsValueGlue.JSO_IMPL_CLASS.replace(
-      '.', '/');;
+      '.', '/');
+
+  static final String REFERENCE_FIELD = JsValueGlue.HOSTED_MODE_REFERENCE;;
 
   static String addSyntheticThisParam(String owner, String methodDescriptor) {
     return "(L" + owner + ";" + methodDescriptor.substring(1);
@@ -109,16 +110,16 @@
   private final Set<String> jsoIntfDescs;
 
   /**
-   * Maps methods to the class in which they are declared.
-   */
-  private InstanceMethodOracle mapper;
-
-  /**
    * Records the superclass of every JSO for generating empty JSO interfaces.
    */
   private final Map<String, String> jsoSuperDescs;
 
   /**
+   * Maps methods to the class in which they are declared.
+   */
+  private InstanceMethodOracle mapper;
+
+  /**
    * Creates a new {@link HostedModeClassRewriter} for a specified set of
    * subclasses of JavaScriptObject.
    * 
@@ -184,6 +185,8 @@
       v = new WriteJsoImpl(v, jsoIntfDescs, mapper);
     }
 
+    v = new RewriteJsniMethods(v);
+
     new ClassReader(classBytes).accept(v, 0);
     return writer.toByteArray();
   }
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsniMethods.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsniMethods.java
new file mode 100644
index 0000000..475a7d7
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsniMethods.java
@@ -0,0 +1,365 @@
+/*
+ * 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 com.google.gwt.dev.shell.rewrite;
+
+import com.google.gwt.dev.asm.ClassAdapter;
+import com.google.gwt.dev.asm.ClassVisitor;
+import com.google.gwt.dev.asm.MethodVisitor;
+import com.google.gwt.dev.asm.Opcodes;
+import com.google.gwt.dev.asm.Type;
+import com.google.gwt.dev.asm.commons.GeneratorAdapter;
+import com.google.gwt.dev.asm.commons.Method;
+import com.google.gwt.dev.shell.JavaScriptHost;
+
+import java.lang.reflect.Modifier;
+
+/**
+ * Turns native method declarations into normal Java functions which perform the
+ * corresponding JSNI dispatch.
+ */
+public class RewriteJsniMethods extends ClassAdapter {
+
+  /**
+   * Fast way to look up boxing methods.
+   */
+  private static class Boxing {
+
+    /**
+     * A matching list of boxed types for each primitive type.
+     */
+    private static final Type[] BOXED_TYPES = new Type[] {
+        VOID_TYPE, BOOLEAN_TYPE, CHARACTER_TYPE, BYTE_TYPE, SHORT_TYPE,
+        INTEGER_TYPE, FLOAT_TYPE, LONG_TYPE, DOUBLE_TYPE,};
+
+    /**
+     * The list of primitive types.
+     */
+    private static final Type[] PRIMITIVE_TYPES = new Type[] {
+        Type.VOID_TYPE, Type.BOOLEAN_TYPE, Type.CHAR_TYPE, Type.BYTE_TYPE,
+        Type.SHORT_TYPE, Type.INT_TYPE, Type.FLOAT_TYPE, Type.LONG_TYPE,
+        Type.DOUBLE_TYPE,};
+
+    /**
+     * A map of Type.sort() to valueOf Method. There are 11 possible results
+     * from Type.sort(), 0 through 10.
+     */
+    private static final Method[] SORT_MAP = new Method[11];
+
+    static {
+      assert PRIMITIVE_TYPES.length == BOXED_TYPES.length;
+      for (int i = 0; i < PRIMITIVE_TYPES.length; ++i) {
+        Type primitive = PRIMITIVE_TYPES[i];
+        Type boxed = BOXED_TYPES[i];
+        if (boxed != null) {
+          SORT_MAP[i] = new Method("valueOf", boxed, new Type[] {primitive});
+        }
+      }
+    }
+
+    public static Method getBoxMethod(Type type) {
+      int sortType = type.getSort();
+      assert (sortType >= 0 && sortType < SORT_MAP.length) : "Unexpected JavaScriptHostInfo.get index - "
+          + sortType;
+      return SORT_MAP[sortType];
+    }
+  }
+
+  /**
+   * Oracle for info regarding {@link JavaScriptHost}.
+   */
+  private static class JavaScriptHostInfo {
+
+    /**
+     * The {@link JavaScriptHost} type.
+     */
+    public static final Type TYPE = Type.getType(JavaScriptHost.class);
+
+    /**
+     * The parameter signature of {@link JavaScriptHost}'s invoke* methods, in
+     * classes.
+     */
+    private static final Class<?>[] INVOKE_NATIVE_PARAM_CLASSES = new Class[] {
+        String.class, Object.class, Class[].class, Object[].class,};
+
+    /**
+     * The parameter signature of {@link JavaScriptHost}'s invoke* methods, in
+     * types.
+     */
+    private static final Type[] INVOKE_NATIVE_PARAM_TYPES;
+
+    /**
+     * A map of Type.sort() to JavaScriptHostInfo.
+     */
+    private static final JavaScriptHostInfo[] SORT_MAP = new JavaScriptHostInfo[11];
+
+    static {
+      INVOKE_NATIVE_PARAM_TYPES = new Type[INVOKE_NATIVE_PARAM_CLASSES.length];
+      for (int i = 0; i < INVOKE_NATIVE_PARAM_TYPES.length; ++i) {
+        INVOKE_NATIVE_PARAM_TYPES[i] = Type.getType(INVOKE_NATIVE_PARAM_CLASSES[i]);
+      }
+
+      Class<?>[] primitives = {
+          void.class, boolean.class, byte.class, char.class, short.class,
+          int.class, long.class, float.class, double.class};
+      for (Class<?> c : primitives) {
+        Type type = Type.getType(c);
+        String typeName = type.getClassName();
+        typeName = Character.toUpperCase(typeName.charAt(0))
+            + typeName.substring(1);
+        SORT_MAP[type.getSort()] = new JavaScriptHostInfo(type, "invokeNative"
+            + typeName);
+      }
+      JavaScriptHostInfo objectType = new JavaScriptHostInfo(
+          Type.getType(Object.class), "invokeNativeObject", true);
+      SORT_MAP[Type.ARRAY] = objectType;
+      SORT_MAP[Type.OBJECT] = objectType;
+      assert noNulls(SORT_MAP) : "Did not fully fill in JavaScriptHostInfo.SORT_MAP";
+    }
+
+    public static JavaScriptHostInfo get(int sortType) {
+      assert (sortType >= 0 && sortType < SORT_MAP.length) : "Unexpected JavaScriptHostInfo.get index - "
+          + sortType;
+      return SORT_MAP[sortType];
+    }
+
+    /**
+     * Validate our model against the real JavaScriptHost class.
+     */
+    private static boolean matchesRealMethod(String methodName, Type returnType) {
+      try {
+        java.lang.reflect.Method method = JavaScriptHost.class.getDeclaredMethod(
+            methodName, INVOKE_NATIVE_PARAM_CLASSES);
+        assert (method.getModifiers() & Modifier.STATIC) != 0 : "Was expecting method '"
+            + method + "' to be static";
+        Type realReturnType = Type.getType(method.getReturnType());
+        return realReturnType.getDescriptor().equals(returnType.getDescriptor());
+      } catch (SecurityException e) {
+      } catch (NoSuchMethodException e) {
+      }
+      return false;
+    }
+
+    private static boolean noNulls(Object[] array) {
+      for (Object element : array) {
+        if (element == null) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    private final Method method;
+
+    private final boolean requiresCast;
+
+    private JavaScriptHostInfo(Type returnType, String methodName) {
+      this(returnType, methodName, false);
+    }
+
+    private JavaScriptHostInfo(Type returnType, String methodName,
+        boolean requiresCast) {
+      this.requiresCast = requiresCast;
+      this.method = new Method(methodName, returnType,
+          INVOKE_NATIVE_PARAM_TYPES);
+      assert matchesRealMethod(methodName, returnType) : "JavaScriptHostInfo for '"
+          + this + "' does not match real method";
+    }
+
+    public Method getMethod() {
+      return method;
+    }
+
+    public boolean requiresCast() {
+      return requiresCast;
+    }
+
+    @Override
+    public String toString() {
+      return method.toString();
+    }
+  }
+
+  /**
+   * Rewrites native Java methods to dispatch as JSNI.
+   */
+  private class MyMethodAdapter extends GeneratorAdapter {
+
+    private String descriptor;
+    private boolean isStatic;
+    private String name;
+
+    public MyMethodAdapter(MethodVisitor mv, int access, String name,
+        String desc) {
+      super(mv, access, name, desc);
+      this.descriptor = desc;
+      this.name = name;
+      isStatic = (access & Opcodes.ACC_STATIC) != 0;
+    }
+
+    /**
+     * Replacement for {@link GeneratorAdapter#box(Type)}, which always calls,
+     * for example, {@code new Boolean} instead of using
+     * {@link Boolean#valueOf(boolean)}.
+     */
+    @Override
+    public void box(Type type) {
+      Method method = Boxing.getBoxMethod(type);
+      if (method != null) {
+        invokeStatic(method.getReturnType(), method);
+      }
+    }
+
+    /**
+     * Does all of the work necessary to do the dispatch to the appropriate
+     * variant of
+     * {@link JavaScriptHost#invokeNativeVoid JavaScriptHost.invokeNative*}.
+     * And example output:
+     * 
+     * <pre>
+     * return JavaScriptHost.invokeNativeInt(
+     *     "@com.google.gwt.sample.hello.client.Hello::echo(I)", null,
+     *     new Class[] {int.class,}, new Object[] {x,});
+     * </pre>
+     */
+    public void visitCode() {
+      super.visitCode();
+
+      // First argument - JSNI signature
+      String jsniTarget = getJsniSignature(name, descriptor);
+      visitLdcInsn(jsniTarget);
+      // Stack is at 1
+
+      // Second argument - target; "null" if static, otherwise "this".
+      if (isStatic) {
+        visitInsn(Opcodes.ACONST_NULL);
+      } else {
+        loadThis();
+      }
+      // Stack is at 2
+
+      // Third argument - a Class[] describing the types of this method
+      loadClassArray();
+      // Stack is at 3; reaches 6 internally
+
+      // Fourth argument - all the arguments boxed into an Object[]
+      loadArgArray();
+      // Stack is at 4; reaches 7 or 8 internally (long/double takes 2)
+
+      // Invoke the matching JavaScriptHost.invokeNative* method
+      Type returnType = Type.getReturnType(descriptor);
+      JavaScriptHostInfo info = JavaScriptHostInfo.get(returnType.getSort());
+      invokeStatic(JavaScriptHostInfo.TYPE, info.getMethod());
+      // Stack is at 1
+      if (info.requiresCast()) {
+        checkCast(returnType);
+      }
+      returnValue();
+      // Stack is at 0
+    }
+
+    @Override
+    public void visitEnd() {
+      // Force code to be visited; required since this was a native method.
+      visitCode();
+
+      /*
+       * For speed, we don't ask ASM to COMPUTE_MAXS. We manually calculated a
+       * max depth of 8.
+       */
+      int maxStack = 8;
+      int maxLocals = 0; // Computed by GeneratorAdapter superclass.
+      super.visitMaxs(maxStack, maxLocals);
+      super.visitEnd();
+    }
+
+    private void loadClassArray() {
+      Type[] argTypes = Type.getArgumentTypes(descriptor);
+      push(argTypes.length);
+      newArray(CLASS_TYPE);
+      // Stack is at 3
+      for (int i = 0; i < argTypes.length; ++i) {
+        dup();
+        push(i);
+        push(argTypes[i]);
+        arrayStore(CLASS_TYPE);
+      }
+    }
+  }
+
+  private static final Type BOOLEAN_TYPE = Type.getObjectType("java/lang/Boolean");
+  private static final Type BYTE_TYPE = Type.getObjectType("java/lang/Byte");
+  private static final Type CHARACTER_TYPE = Type.getObjectType("java/lang/Character");
+  private static final Type CLASS_TYPE = Type.getObjectType("java/lang/Class");
+  private static final Type DOUBLE_TYPE = Type.getObjectType("java/lang/Double");
+  private static final Type FLOAT_TYPE = Type.getObjectType("java/lang/Float");
+  private static final Type INTEGER_TYPE = Type.getObjectType("java/lang/Integer");
+  private static final Type LONG_TYPE = Type.getObjectType("java/lang/Long");
+  private static final Type SHORT_TYPE = Type.getObjectType("java/lang/Short");
+  private static final Type VOID_TYPE = Type.getObjectType("java/lang/Void");
+
+  /**
+   * The name of the class we're operating on.
+   */
+  private String classDesc;
+
+  public RewriteJsniMethods(ClassVisitor v) {
+    super(v);
+  }
+
+  @Override
+  public void visit(final int version, final int access, final String name,
+      final String signature, final String superName, final String[] interfaces) {
+    this.classDesc = name;
+    super.visit(version, access, name, signature, superName, interfaces);
+  }
+
+  @Override
+  public MethodVisitor visitMethod(int access, String name, String desc,
+      String signature, String[] exceptions) {
+
+    boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
+    access &= ~Opcodes.ACC_NATIVE;
+
+    MethodVisitor mv = super.visitMethod(access, name, desc, signature,
+        exceptions);
+
+    if (isNative) {
+      mv = new MyMethodAdapter(mv, access, name, desc);
+    }
+
+    return mv;
+  }
+
+  /**
+   * Returns the JSNI signature describing the method.
+   * 
+   * @param name the name of the method; for example {@code "echo"}
+   * @param descriptor the descriptor for the method; for example {@code "(I)I"}
+   * @return the JSNI signature for the method; for example,
+   *         {@code "@com.google.gwt.sample.hello.client.Hello::echo(I)"}
+   */
+  private String getJsniSignature(String name, String descriptor) {
+    int argsIndexBegin = descriptor.indexOf('(');
+    int argsIndexEnd = descriptor.indexOf(')');
+    assert argsIndexBegin != -1 && argsIndexEnd != -1
+        && argsIndexBegin < argsIndexEnd : "Could not find the arguments in the descriptor, "
+        + descriptor;
+    String argsDescriptor = descriptor.substring(argsIndexBegin,
+        argsIndexEnd + 1);
+    String sourceName = classDesc.replace('/', '.').replace('$', '.');
+    return "@" + sourceName + "::" + name + argsDescriptor;
+  }
+}