- 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;
+ }
+}