/** * Generates code to return a Java type, after calling a Java method that returns the same type. * Generates the appropriate RETURN bytecode. */ private static void generatePopResult(ClassFileWriter cfw, Class<?> retType) { if (retType.isPrimitive()) { String typeName = retType.getName(); switch (typeName.charAt(0)) { case 'b': case 'c': case 's': case 'i': case 'z': cfw.add(ByteCode.IRETURN); break; case 'l': cfw.add(ByteCode.LRETURN); break; case 'f': cfw.add(ByteCode.FRETURN); break; case 'd': cfw.add(ByteCode.DRETURN); break; } } else { cfw.add(ByteCode.ARETURN); } }
/** * Generates a method called "super$methodName()" which can be called from JavaScript that is * equivalent to calling "super.methodName()" from Java. Eventually, this may be supported * directly in JavaScript. */ private static void generateSuper( ClassFileWriter cfw, String genName, String superName, String methodName, String methodSignature, Class<?>[] parms, Class<?> returnType) { cfw.startMethod("super$" + methodName, methodSignature, ClassFileWriter.ACC_PUBLIC); // push "this" cfw.add(ByteCode.ALOAD, 0); // push the rest of the parameters. int paramOffset = 1; for (Class<?> parm : parms) { paramOffset += generatePushParam(cfw, paramOffset, parm); } // call the superclass implementation of the method. cfw.addInvoke(ByteCode.INVOKESPECIAL, superName, methodName, methodSignature); // now, handle the return type appropriately. Class<?> retType = returnType; if (!retType.equals(Void.TYPE)) { generatePopResult(cfw, retType); } else { cfw.add(ByteCode.RETURN); } cfw.stopMethod((short) (paramOffset + 1)); }
/** * Generates code to wrap Java arguments into Object[]. Non-primitive Java types are left as-is * pending conversion in the helper method. Leaves the array object on the top of the stack. */ static void generatePushWrappedArgs(ClassFileWriter cfw, Class<?>[] argTypes, int arrayLength) { // push arguments cfw.addPush(arrayLength); cfw.add(ByteCode.ANEWARRAY, "java/lang/Object"); int paramOffset = 1; for (int i = 0; i != argTypes.length; ++i) { cfw.add(ByteCode.DUP); // duplicate array reference cfw.addPush(i); paramOffset += generateWrapArg(cfw, paramOffset, argTypes[i]); cfw.add(ByteCode.AASTORE); } }
private static void generateSerialCtor( ClassFileWriter cfw, String adapterName, String superName) { cfw.startMethod( "<init>", "(Lorg/mozilla/javascript/ContextFactory;" + "Lorg/mozilla/javascript/Scriptable;" + "Lorg/mozilla/javascript/Scriptable;" + ")V", ClassFileWriter.ACC_PUBLIC); // Invoke base class constructor cfw.add(ByteCode.ALOAD_0); // this cfw.addInvoke(ByteCode.INVOKESPECIAL, superName, "<init>", "()V"); // Save parameter in instance variable "factory" cfw.add(ByteCode.ALOAD_0); // this cfw.add(ByteCode.ALOAD_1); // first arg: ContextFactory instance cfw.add(ByteCode.PUTFIELD, adapterName, "factory", "Lorg/mozilla/javascript/ContextFactory;"); // Save parameter in instance variable "delegee" cfw.add(ByteCode.ALOAD_0); // this cfw.add(ByteCode.ALOAD_2); // second arg: Scriptable delegee cfw.add(ByteCode.PUTFIELD, adapterName, "delegee", "Lorg/mozilla/javascript/Scriptable;"); // save self cfw.add(ByteCode.ALOAD_0); // this cfw.add(ByteCode.ALOAD_3); // third arg: Scriptable self cfw.add(ByteCode.PUTFIELD, adapterName, "self", "Lorg/mozilla/javascript/Scriptable;"); cfw.add(ByteCode.RETURN); cfw.stopMethod((short) 4); // 4: this + factory + delegee + self }
private static void generateCtor(ClassFileWriter cfw, String adapterName, String superName) { cfw.startMethod( "<init>", "(Lorg/mozilla/javascript/ContextFactory;" + "Lorg/mozilla/javascript/Scriptable;)V", ClassFileWriter.ACC_PUBLIC); // Invoke base class constructor cfw.add(ByteCode.ALOAD_0); // this cfw.addInvoke(ByteCode.INVOKESPECIAL, superName, "<init>", "()V"); // Save parameter in instance variable "factory" cfw.add(ByteCode.ALOAD_0); // this cfw.add(ByteCode.ALOAD_1); // first arg: ContextFactory instance cfw.add(ByteCode.PUTFIELD, adapterName, "factory", "Lorg/mozilla/javascript/ContextFactory;"); // Save parameter in instance variable "delegee" cfw.add(ByteCode.ALOAD_0); // this cfw.add(ByteCode.ALOAD_2); // second arg: Scriptable delegee cfw.add(ByteCode.PUTFIELD, adapterName, "delegee", "Lorg/mozilla/javascript/Scriptable;"); cfw.add(ByteCode.ALOAD_0); // this for the following PUTFIELD for self // create a wrapper object to be used as "this" in method calls cfw.add(ByteCode.ALOAD_2); // the Scriptable delegee cfw.add(ByteCode.ALOAD_0); // this cfw.addInvoke( ByteCode.INVOKESTATIC, "org/mozilla/javascript/JavaAdapter", "createAdapterWrapper", "(Lorg/mozilla/javascript/Scriptable;" + "Ljava/lang/Object;" + ")Lorg/mozilla/javascript/Scriptable;"); cfw.add(ByteCode.PUTFIELD, adapterName, "self", "Lorg/mozilla/javascript/Scriptable;"); cfw.add(ByteCode.RETURN); cfw.stopMethod((short) 3); // 3: this + factory + delegee }
private static void generateMethod( ClassFileWriter cfw, String genName, String methodName, Class<?>[] parms, Class<?> returnType, boolean convertResult) { StringBuilder sb = new StringBuilder(); int paramsEnd = appendMethodSignature(parms, returnType, sb); String methodSignature = sb.toString(); cfw.startMethod(methodName, methodSignature, ClassFileWriter.ACC_PUBLIC); // Prepare stack to call method // push factory cfw.add(ByteCode.ALOAD_0); cfw.add(ByteCode.GETFIELD, genName, "factory", "Lorg/mozilla/javascript/ContextFactory;"); // push self cfw.add(ByteCode.ALOAD_0); cfw.add(ByteCode.GETFIELD, genName, "self", "Lorg/mozilla/javascript/Scriptable;"); // push function cfw.add(ByteCode.ALOAD_0); cfw.add(ByteCode.GETFIELD, genName, "delegee", "Lorg/mozilla/javascript/Scriptable;"); cfw.addPush(methodName); cfw.addInvoke( ByteCode.INVOKESTATIC, "org/mozilla/javascript/JavaAdapter", "getFunction", "(Lorg/mozilla/javascript/Scriptable;" + "Ljava/lang/String;" + ")Lorg/mozilla/javascript/Function;"); // push arguments generatePushWrappedArgs(cfw, parms, parms.length); // push bits to indicate which parameters should be wrapped if (parms.length > 64) { // If it will be an issue, then passing a static boolean array // can be an option, but for now using simple bitmask throw Context.reportRuntimeError0( "JavaAdapter can not subclass methods with more then" + " 64 arguments."); } long convertionMask = 0; for (int i = 0; i != parms.length; ++i) { if (!parms[i].isPrimitive()) { convertionMask |= (1 << i); } } cfw.addPush(convertionMask); // go through utility method, which creates a Context to run the // method in. cfw.addInvoke( ByteCode.INVOKESTATIC, "org/mozilla/javascript/JavaAdapter", "callMethod", "(Lorg/mozilla/javascript/ContextFactory;" + "Lorg/mozilla/javascript/Scriptable;" + "Lorg/mozilla/javascript/Function;" + "[Ljava/lang/Object;" + "J" + ")Ljava/lang/Object;"); generateReturnResult(cfw, returnType, convertResult); cfw.stopMethod((short) paramsEnd); }
/** * Generates code to convert a wrapped value type to a primitive type. Handles unwrapping * java.lang.Boolean, and java.lang.Number types. Generates the appropriate RETURN bytecode. */ static void generateReturnResult( ClassFileWriter cfw, Class<?> retType, boolean callConvertResult) { // wrap boolean values with java.lang.Boolean, convert all other // primitive values to java.lang.Double. if (retType == Void.TYPE) { cfw.add(ByteCode.POP); cfw.add(ByteCode.RETURN); } else if (retType == Boolean.TYPE) { cfw.addInvoke( ByteCode.INVOKESTATIC, "org/mozilla/javascript/Context", "toBoolean", "(Ljava/lang/Object;)Z"); cfw.add(ByteCode.IRETURN); } else if (retType == Character.TYPE) { // characters are represented as strings in JavaScript. // return the first character. // first convert the value to a string if possible. cfw.addInvoke( ByteCode.INVOKESTATIC, "org/mozilla/javascript/Context", "toString", "(Ljava/lang/Object;)Ljava/lang/String;"); cfw.add(ByteCode.ICONST_0); cfw.addInvoke(ByteCode.INVOKEVIRTUAL, "java/lang/String", "charAt", "(I)C"); cfw.add(ByteCode.IRETURN); } else if (retType.isPrimitive()) { cfw.addInvoke( ByteCode.INVOKESTATIC, "org/mozilla/javascript/Context", "toNumber", "(Ljava/lang/Object;)D"); String typeName = retType.getName(); switch (typeName.charAt(0)) { case 'b': case 's': case 'i': cfw.add(ByteCode.D2I); cfw.add(ByteCode.IRETURN); break; case 'l': cfw.add(ByteCode.D2L); cfw.add(ByteCode.LRETURN); break; case 'f': cfw.add(ByteCode.D2F); cfw.add(ByteCode.FRETURN); break; case 'd': cfw.add(ByteCode.DRETURN); break; default: throw new RuntimeException("Unexpected return type " + retType.toString()); } } else { String retTypeStr = retType.getName(); if (callConvertResult) { cfw.addLoadConstant(retTypeStr); cfw.addInvoke( ByteCode.INVOKESTATIC, "java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;"); cfw.addInvoke( ByteCode.INVOKESTATIC, "org/mozilla/javascript/JavaAdapter", "convertResult", "(Ljava/lang/Object;" + "Ljava/lang/Class;" + ")Ljava/lang/Object;"); } // Now cast to return type cfw.add(ByteCode.CHECKCAST, retTypeStr); cfw.add(ByteCode.ARETURN); } }
/** * Generates code to wrap Java argument into Object. Non-primitive Java types are left unconverted * pending conversion in the helper method. Leaves the wrapper object on the top of the stack. */ private static int generateWrapArg(ClassFileWriter cfw, int paramOffset, Class<?> argType) { int size = 1; if (!argType.isPrimitive()) { cfw.add(ByteCode.ALOAD, paramOffset); } else if (argType == Boolean.TYPE) { // wrap boolean values with java.lang.Boolean. cfw.add(ByteCode.NEW, "java/lang/Boolean"); cfw.add(ByteCode.DUP); cfw.add(ByteCode.ILOAD, paramOffset); cfw.addInvoke(ByteCode.INVOKESPECIAL, "java/lang/Boolean", "<init>", "(Z)V"); } else if (argType == Character.TYPE) { // Create a string of length 1 using the character parameter. cfw.add(ByteCode.ILOAD, paramOffset); cfw.addInvoke(ByteCode.INVOKESTATIC, "java/lang/String", "valueOf", "(C)Ljava/lang/String;"); } else { // convert all numeric values to java.lang.Double. cfw.add(ByteCode.NEW, "java/lang/Double"); cfw.add(ByteCode.DUP); String typeName = argType.getName(); switch (typeName.charAt(0)) { case 'b': case 's': case 'i': // load an int value, convert to double. cfw.add(ByteCode.ILOAD, paramOffset); cfw.add(ByteCode.I2D); break; case 'l': // load a long, convert to double. cfw.add(ByteCode.LLOAD, paramOffset); cfw.add(ByteCode.L2D); size = 2; break; case 'f': // load a float, convert to double. cfw.add(ByteCode.FLOAD, paramOffset); cfw.add(ByteCode.F2D); break; case 'd': cfw.add(ByteCode.DLOAD, paramOffset); size = 2; break; } cfw.addInvoke(ByteCode.INVOKESPECIAL, "java/lang/Double", "<init>", "(D)V"); } return size; }
private static void generateEmptyCtor( ClassFileWriter cfw, String adapterName, String superName, String scriptClassName) { cfw.startMethod("<init>", "()V", ClassFileWriter.ACC_PUBLIC); // Invoke base class constructor cfw.add(ByteCode.ALOAD_0); // this cfw.addInvoke(ByteCode.INVOKESPECIAL, superName, "<init>", "()V"); // Set factory to null to use current global when necessary cfw.add(ByteCode.ALOAD_0); cfw.add(ByteCode.ACONST_NULL); cfw.add(ByteCode.PUTFIELD, adapterName, "factory", "Lorg/mozilla/javascript/ContextFactory;"); // Load script class cfw.add(ByteCode.NEW, scriptClassName); cfw.add(ByteCode.DUP); cfw.addInvoke(ByteCode.INVOKESPECIAL, scriptClassName, "<init>", "()V"); // Run script and save resulting scope cfw.addInvoke( ByteCode.INVOKESTATIC, "org/mozilla/javascript/JavaAdapter", "runScript", "(Lorg/mozilla/javascript/Script;" + ")Lorg/mozilla/javascript/Scriptable;"); cfw.add(ByteCode.ASTORE_1); // Save the Scriptable in instance variable "delegee" cfw.add(ByteCode.ALOAD_0); // this cfw.add(ByteCode.ALOAD_1); // the Scriptable cfw.add(ByteCode.PUTFIELD, adapterName, "delegee", "Lorg/mozilla/javascript/Scriptable;"); cfw.add(ByteCode.ALOAD_0); // this for the following PUTFIELD for self // create a wrapper object to be used as "this" in method calls cfw.add(ByteCode.ALOAD_1); // the Scriptable cfw.add(ByteCode.ALOAD_0); // this cfw.addInvoke( ByteCode.INVOKESTATIC, "org/mozilla/javascript/JavaAdapter", "createAdapterWrapper", "(Lorg/mozilla/javascript/Scriptable;" + "Ljava/lang/Object;" + ")Lorg/mozilla/javascript/Scriptable;"); cfw.add(ByteCode.PUTFIELD, adapterName, "self", "Lorg/mozilla/javascript/Scriptable;"); cfw.add(ByteCode.RETURN); cfw.stopMethod((short) 2); // this + delegee }
private static void generateCtor( ClassFileWriter cfw, String adapterName, String superName, Constructor<?> superCtor) { short locals = 3; // this + factory + delegee Class<?>[] parameters = superCtor.getParameterTypes(); // Note that we swapped arguments in app-facing constructors to avoid // conflicting signatures with serial constructor defined below. if (parameters.length == 0) { cfw.startMethod( "<init>", "(Lorg/mozilla/javascript/Scriptable;" + "Lorg/mozilla/javascript/ContextFactory;)V", ClassFileWriter.ACC_PUBLIC); // Invoke base class constructor cfw.add(ByteCode.ALOAD_0); // this cfw.addInvoke(ByteCode.INVOKESPECIAL, superName, "<init>", "()V"); } else { StringBuilder sig = new StringBuilder( "(Lorg/mozilla/javascript/Scriptable;" + "Lorg/mozilla/javascript/ContextFactory;"); int marker = sig.length(); // lets us reuse buffer for super // signature for (Class<?> c : parameters) { appendTypeString(sig, c); } sig.append(")V"); cfw.startMethod("<init>", sig.toString(), ClassFileWriter.ACC_PUBLIC); // Invoke base class constructor cfw.add(ByteCode.ALOAD_0); // this short paramOffset = 3; for (Class<?> parameter : parameters) { paramOffset += generatePushParam(cfw, paramOffset, parameter); } locals = paramOffset; sig.delete(1, marker); cfw.addInvoke(ByteCode.INVOKESPECIAL, superName, "<init>", sig.toString()); } // Save parameter in instance variable "delegee" cfw.add(ByteCode.ALOAD_0); // this cfw.add(ByteCode.ALOAD_1); // first arg: Scriptable delegee cfw.add(ByteCode.PUTFIELD, adapterName, "delegee", "Lorg/mozilla/javascript/Scriptable;"); // Save parameter in instance variable "factory" cfw.add(ByteCode.ALOAD_0); // this cfw.add(ByteCode.ALOAD_2); // second arg: ContextFactory instance cfw.add(ByteCode.PUTFIELD, adapterName, "factory", "Lorg/mozilla/javascript/ContextFactory;"); cfw.add(ByteCode.ALOAD_0); // this for the following PUTFIELD for self // create a wrapper object to be used as "this" in method calls cfw.add(ByteCode.ALOAD_1); // the Scriptable delegee cfw.add(ByteCode.ALOAD_0); // this cfw.addInvoke( ByteCode.INVOKESTATIC, "org/mozilla/javascript/JavaAdapter", "createAdapterWrapper", "(Lorg/mozilla/javascript/Scriptable;" + "Ljava/lang/Object;" + ")Lorg/mozilla/javascript/Scriptable;"); cfw.add(ByteCode.PUTFIELD, adapterName, "self", "Lorg/mozilla/javascript/Scriptable;"); cfw.add(ByteCode.RETURN); cfw.stopMethod(locals); }