/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (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.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Rhino code, released * May 6, 1999. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1997-1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Patrick Beard * Norris Boyd * Igor Bukanov * Mike McCabe * Matthias Radestock * Andi Vajda * Andrew Wason * Kemal Bayram * * Alternatively, the contents of this file may be used under the terms of * the GNU General Public License Version 2 or later (the "GPL"), in which * case the provisions of the GPL are applicable instead of those above. If * you wish to allow use of your version of this file only under the terms of * the GPL and not to allow others to use your version of this file under the * MPL, indicate your decision by deleting the provisions above and replacing * them with the notice and other provisions required by the GPL. If you do * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. * * ***** END LICENSE BLOCK ***** */ package org.mozilla.javascript; import org.mozilla.classfile.*; import java.lang.reflect.*; import java.io.*; import java.security.*; import java.util.*; public final class JavaAdapter implements IdFunctionCall { /** * Provides a key with which to distinguish previously generated * adapter classes stored in a hash table. */ static class JavaAdapterSignature { Class<?> superClass; Class<?>[] interfaces; ObjToIntMap names; JavaAdapterSignature(Class<?> superClass, Class<?>[] interfaces, ObjToIntMap names) { this.superClass = superClass; this.interfaces = interfaces; this.names = names; } @Override public boolean equals(Object obj) { if (!(obj instanceof JavaAdapterSignature)) return false; JavaAdapterSignature sig = (JavaAdapterSignature) obj; if (superClass != sig.superClass) return false; if (interfaces != sig.interfaces) { if (interfaces.length != sig.interfaces.length) return false; for (int i=0; i < interfaces.length; i++) if (interfaces[i] != sig.interfaces[i]) return false; } if (names.size() != sig.names.size()) return false; ObjToIntMap.Iterator iter = new ObjToIntMap.Iterator(names); for (iter.start(); !iter.done(); iter.next()) { String name = (String)iter.getKey(); int arity = iter.getValue(); if (arity != sig.names.get(name, arity + 1)) return false; } return true; } @Override public int hashCode() { return (superClass.hashCode() + Arrays.hashCode(interfaces)) ^ names.size(); } } public static void init(Context cx, Scriptable scope, boolean sealed) { JavaAdapter obj = new JavaAdapter(); IdFunctionObject ctor = new IdFunctionObject(obj, FTAG, Id_JavaAdapter, "JavaAdapter", 1, scope); ctor.markAsConstructor(null); if (sealed) { ctor.sealObject(); } ctor.exportAsScopeProperty(); } public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { if (f.hasTag(FTAG)) { if (f.methodId() == Id_JavaAdapter) { return js_createAdapter(cx, scope, args); } } throw f.unknown(); } public static Object convertResult(Object result, Class<?> c) { if (result == Undefined.instance && (c != ScriptRuntime.ObjectClass && c != ScriptRuntime.StringClass)) { // Avoid an error for an undefined value; return null instead. return null; } return Context.jsToJava(result, c); } public static Scriptable createAdapterWrapper(Scriptable obj, Object adapter) { Scriptable scope = ScriptableObject.getTopLevelScope(obj); NativeJavaObject res = new NativeJavaObject(scope, adapter, null, true); res.setPrototype(obj); return res; } public static Object getAdapterSelf(Class<?> adapterClass, Object adapter) throws NoSuchFieldException, IllegalAccessException { Field self = adapterClass.getDeclaredField("self"); return self.get(adapter); } static Object js_createAdapter(Context cx, Scriptable scope, Object[] args) { int N = args.length; if (N == 0) { throw ScriptRuntime.typeError0("msg.adapter.zero.args"); } Class<?> superClass = null; Class<?>[] intfs = new Class[N - 1]; int interfaceCount = 0; for (int i = 0; i != N - 1; ++i) { Object arg = args[i]; if (!(arg instanceof NativeJavaClass)) { throw ScriptRuntime.typeError2("msg.not.java.class.arg", String.valueOf(i), ScriptRuntime.toString(arg)); } Class<?> c = ((NativeJavaClass) arg).getClassObject(); if (!c.isInterface()) { if (superClass != null) { throw ScriptRuntime.typeError2("msg.only.one.super", superClass.getName(), c.getName()); } superClass = c; } else { intfs[interfaceCount++] = c; } } if (superClass == null) superClass = ScriptRuntime.ObjectClass; Class<?>[] interfaces = new Class[interfaceCount]; System.arraycopy(intfs, 0, interfaces, 0, interfaceCount); Scriptable obj = ScriptRuntime.toObject(cx, scope, args[N - 1]); Class<?> adapterClass = getAdapterClass(scope, superClass, interfaces, obj); Class<?>[] ctorParms = { ScriptRuntime.ContextFactoryClass, ScriptRuntime.ScriptableClass }; Object[] ctorArgs = { cx.getFactory(), obj }; try { Object adapter = adapterClass.getConstructor(ctorParms). newInstance(ctorArgs); Object self = getAdapterSelf(adapterClass, adapter); // Return unwrapped JavaAdapter if it implements Scriptable if (self instanceof Wrapper) { Object unwrapped = ((Wrapper) self).unwrap(); if (unwrapped instanceof Scriptable) { if (unwrapped instanceof ScriptableObject) { ScriptRuntime.setObjectProtoAndParent( (ScriptableObject)unwrapped, scope); } return unwrapped; } } return self; } catch (Exception ex) { throw Context.throwAsScriptRuntimeEx(ex); } } // Needed by NativeJavaObject serializer public static void writeAdapterObject(Object javaObject, ObjectOutputStream out) throws IOException { Class<?> cl = javaObject.getClass(); out.writeObject(cl.getSuperclass().getName()); Class<?>[] interfaces = cl.getInterfaces(); String[] interfaceNames = new String[interfaces.length]; for (int i=0; i < interfaces.length; i++) interfaceNames[i] = interfaces[i].getName(); out.writeObject(interfaceNames); try { Object delegee = cl.getField("delegee").get(javaObject); out.writeObject(delegee); return; } catch (IllegalAccessException e) { } catch (NoSuchFieldException e) { } throw new IOException(); } // Needed by NativeJavaObject de-serializer public static Object readAdapterObject(Scriptable self, ObjectInputStream in) throws IOException, ClassNotFoundException { ContextFactory factory; Context cx = Context.getCurrentContext(); if (cx != null) { factory = cx.getFactory(); } else { factory = null; } Class<?> superClass = Class.forName((String)in.readObject()); String[] interfaceNames = (String[])in.readObject(); Class<?>[] interfaces = new Class[interfaceNames.length]; for (int i=0; i < interfaceNames.length; i++) interfaces[i] = Class.forName(interfaceNames[i]); Scriptable delegee = (Scriptable)in.readObject(); Class<?> adapterClass = getAdapterClass(self, superClass, interfaces, delegee); Class<?>[] ctorParms = { ScriptRuntime.ContextFactoryClass, ScriptRuntime.ScriptableClass, ScriptRuntime.ScriptableClass }; Object[] ctorArgs = { factory, delegee, self }; try { return adapterClass.getConstructor(ctorParms).newInstance(ctorArgs); } catch(InstantiationException e) { } catch(IllegalAccessException e) { } catch(InvocationTargetException e) { } catch(NoSuchMethodException e) { } throw new ClassNotFoundException("adapter"); } private static ObjToIntMap getObjectFunctionNames(Scriptable obj) { Object[] ids = ScriptableObject.getPropertyIds(obj); ObjToIntMap map = new ObjToIntMap(ids.length); for (int i = 0; i != ids.length; ++i) { if (!(ids[i] instanceof String)) continue; String id = (String) ids[i]; Object value = ScriptableObject.getProperty(obj, id); if (value instanceof Function) { Function f = (Function)value; int length = ScriptRuntime.toInt32( ScriptableObject.getProperty(f, "length")); if (length < 0) { length = 0; } map.put(id, length); } } return map; } private static Class<?> getAdapterClass(Scriptable scope, Class<?> superClass, Class<?>[] interfaces, Scriptable obj) { ClassCache cache = ClassCache.get(scope); Map<JavaAdapterSignature,Class<?>> generated = cache.getInterfaceAdapterCacheMap(); ObjToIntMap names = getObjectFunctionNames(obj); JavaAdapterSignature sig; sig = new JavaAdapterSignature(superClass, interfaces, names); Class<?> adapterClass = generated.get(sig); if (adapterClass == null) { String adapterName = "adapter" + cache.newClassSerialNumber(); byte[] code = createAdapterCode(names, adapterName, superClass, interfaces, null); adapterClass = loadAdapterClass(adapterName, code); if (cache.isCachingEnabled()) { generated.put(sig, adapterClass); } } return adapterClass; } public static byte[] createAdapterCode(ObjToIntMap functionNames, String adapterName, Class<?> superClass, Class<?>[] interfaces, String scriptClassName) { ClassFileWriter cfw = new ClassFileWriter(adapterName, superClass.getName(), "<adapter>"); cfw.addField("factory", "Lorg/mozilla/javascript/ContextFactory;", (short) (ClassFileWriter.ACC_PUBLIC | ClassFileWriter.ACC_FINAL)); cfw.addField("delegee", "Lorg/mozilla/javascript/Scriptable;", (short) (ClassFileWriter.ACC_PUBLIC | ClassFileWriter.ACC_FINAL)); cfw.addField("self", "Lorg/mozilla/javascript/Scriptable;", (short) (ClassFileWriter.ACC_PUBLIC | ClassFileWriter.ACC_FINAL)); int interfacesCount = interfaces == null ? 0 : interfaces.length; for (int i=0; i < interfacesCount; i++) { if (interfaces[i] != null) cfw.addInterface(interfaces[i].getName()); } String superName = superClass.getName().replace('.', '/'); generateCtor(cfw, adapterName, superName); generateSerialCtor(cfw, adapterName, superName); if (scriptClassName != null) generateEmptyCtor(cfw, adapterName, superName, scriptClassName); ObjToIntMap generatedOverrides = new ObjToIntMap(); ObjToIntMap generatedMethods = new ObjToIntMap(); // generate methods to satisfy all specified interfaces. for (int i = 0; i < interfacesCount; i++) { Method[] methods = interfaces[i].getMethods(); for (int j = 0; j < methods.length; j++) { Method method = methods[j]; int mods = method.getModifiers(); if (Modifier.isStatic(mods) || Modifier.isFinal(mods)) { continue; } String methodName = method.getName(); Class<?>[] argTypes = method.getParameterTypes(); if (!functionNames.has(methodName)) { try { superClass.getMethod(methodName, argTypes); // The class we're extending implements this method and // the JavaScript object doesn't have an override. See // bug 61226. continue; } catch (NoSuchMethodException e) { // Not implemented by superclass; fall through } } // make sure to generate only one instance of a particular // method/signature. String methodSignature = getMethodSignature(method, argTypes); String methodKey = methodName + methodSignature; if (! generatedOverrides.has(methodKey)) { generateMethod(cfw, adapterName, methodName, argTypes, method.getReturnType()); generatedOverrides.put(methodKey, 0); generatedMethods.put(methodName, 0); } } } // Now, go through the superclass's methods, checking for abstract // methods or additional methods to override. // generate any additional overrides that the object might contain. Method[] methods = getOverridableMethods(superClass); for (int j = 0; j < methods.length; j++) { Method method = methods[j]; int mods = method.getModifiers(); // if a method is marked abstract, must implement it or the // resulting class won't be instantiable. otherwise, if the object // has a property of the same name, then an override is intended. boolean isAbstractMethod = Modifier.isAbstract(mods); String methodName = method.getName(); if (isAbstractMethod || functionNames.has(methodName)) { // make sure to generate only one instance of a particular // method/signature. Class<?>[] argTypes = method.getParameterTypes(); String methodSignature = getMethodSignature(method, argTypes); String methodKey = methodName + methodSignature; if (! generatedOverrides.has(methodKey)) { generateMethod(cfw, adapterName, methodName, argTypes, method.getReturnType()); generatedOverrides.put(methodKey, 0); generatedMethods.put(methodName, 0); // if a method was overridden, generate a "super$method" // which lets the delegate call the superclass' version. if (!isAbstractMethod) { generateSuper(cfw, adapterName, superName, methodName, methodSignature, argTypes, method.getReturnType()); } } } } // Generate Java methods for remaining properties that are not // overrides. ObjToIntMap.Iterator iter = new ObjToIntMap.Iterator(functionNames); for (iter.start(); !iter.done(); iter.next()) { String functionName = (String)iter.getKey(); if (generatedMethods.has(functionName)) continue; int length = iter.getValue(); Class<?>[] parms = new Class[length]; for (int k=0; k < length; k++) parms[k] = ScriptRuntime.ObjectClass; generateMethod(cfw, adapterName, functionName, parms, ScriptRuntime.ObjectClass); } return cfw.toByteArray(); } static Method[] getOverridableMethods(Class<?> clazz) { ArrayList<Method> list = new ArrayList<Method>(); HashSet<String> skip = new HashSet<String>(); // Check superclasses before interfaces so we always choose // implemented methods over abstract ones, even if a subclass // re-implements an interface already implemented in a superclass // (e.g. java.util.ArrayList) for (Class<?> c = clazz; c != null; c = c.getSuperclass()) { appendOverridableMethods(c, list, skip); } for (Class<?> c = clazz; c != null; c = c.getSuperclass()) { for (Class<?> intf: c.getInterfaces()) appendOverridableMethods(intf, list, skip); } return list.toArray(new Method[list.size()]); } private static void appendOverridableMethods(Class<?> c, ArrayList<Method> list, HashSet<String> skip) { Method[] methods = c.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) { String methodKey = methods[i].getName() + getMethodSignature(methods[i], methods[i].getParameterTypes()); if (skip.contains(methodKey)) continue; // skip this method int mods = methods[i].getModifiers(); if (Modifier.isStatic(mods)) continue; if (Modifier.isFinal(mods)) { // Make sure we don't add a final method to the list // of overridable methods. skip.add(methodKey); continue; } if (Modifier.isPublic(mods) || Modifier.isProtected(mods)) { list.add(methods[i]); skip.add(methodKey); } } } static Class<?> loadAdapterClass(String className, byte[] classBytes) { Object staticDomain; Class<?> domainClass = SecurityController.getStaticSecurityDomainClass(); if(domainClass == CodeSource.class || domainClass == ProtectionDomain.class) { // use the calling script's security domain if available ProtectionDomain protectionDomain = SecurityUtilities.getScriptProtectionDomain(); if (protectionDomain == null) { protectionDomain = JavaAdapter.class.getProtectionDomain(); } if(domainClass == CodeSource.class) { staticDomain = protectionDomain == null ? null : protectionDomain.getCodeSource(); } else { staticDomain = protectionDomain; } } else { staticDomain = null; } GeneratedClassLoader loader = SecurityController.createLoader(null, staticDomain); Class<?> result = loader.defineClass(className, classBytes); loader.linkClass(result); return result; } public static Function getFunction(Scriptable obj, String functionName) { Object x = ScriptableObject.getProperty(obj, functionName); if (x == Scriptable.NOT_FOUND) { // This method used to swallow the exception from calling // an undefined method. People have come to depend on this // somewhat dubious behavior. It allows people to avoid // implementing listener methods that they don't care about, // for instance. return null; } if (!(x instanceof Function)) throw ScriptRuntime.notFunctionError(x, functionName); return (Function)x; } /** * Utility method which dynamically binds a Context to the current thread, * if none already exists. */ public static Object callMethod(ContextFactory factory, final Scriptable thisObj, final Function f, final Object[] args, final long argsToWrap) { if (f == null) { // See comments in getFunction return Undefined.instance; } if (factory == null) { factory = ContextFactory.getGlobal(); } final Scriptable scope = f.getParentScope(); if (argsToWrap == 0) { return Context.call(factory, f, scope, thisObj, args); } Context cx = Context.getCurrentContext(); if (cx != null) { return doCall(cx, scope, thisObj, f, args, argsToWrap); } else { return factory.call(new ContextAction() { public Object run(Context cx) { return doCall(cx, scope, thisObj, f, args, argsToWrap); } }); } } private static Object doCall(Context cx, Scriptable scope, Scriptable thisObj, Function f, Object[] args, long argsToWrap) { // Wrap the rest of objects for (int i = 0; i != args.length; ++i) { if (0 != (argsToWrap & (1 << i))) { Object arg = args[i]; if (!(arg instanceof Scriptable)) { args[i] = cx.getWrapFactory().wrap(cx, scope, arg, null); } } } return f.call(cx, scope, thisObj, args); } public static Scriptable runScript(final Script script) { return (Scriptable)ContextFactory.getGlobal().call( new ContextAction() { public Object run(Context cx) { ScriptableObject global = ScriptRuntime.getGlobal(cx); script.exec(cx, global); return global; } }); } 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 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); // second 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 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 } /** * 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); } } /** * 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; } /** * 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); } } private static void generateMethod(ClassFileWriter cfw, String genName, String methodName, Class<?>[] parms, Class<?> returnType) { StringBuffer sb = new StringBuffer(); 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, true); cfw.stopMethod((short)paramsEnd); } /** * Generates code to push typed parameters onto the operand stack * prior to a direct Java method call. */ private static int generatePushParam(ClassFileWriter cfw, int paramOffset, Class<?> paramType) { if (!paramType.isPrimitive()) { cfw.addALoad(paramOffset); return 1; } String typeName = paramType.getName(); switch (typeName.charAt(0)) { case 'z': case 'b': case 'c': case 's': case 'i': // load an int value, convert to double. cfw.addILoad(paramOffset); return 1; case 'l': // load a long, convert to double. cfw.addLLoad(paramOffset); return 2; case 'f': // load a float, convert to double. cfw.addFLoad(paramOffset); return 1; case 'd': cfw.addDLoad(paramOffset); return 2; } throw Kit.codeBug(); } /** * 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 (int i = 0; i < parms.length; i++) { paramOffset += generatePushParam(cfw, paramOffset, parms[i]); } // 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)); } /** * Returns a fully qualified method name concatenated with its signature. */ private static String getMethodSignature(Method method, Class<?>[] argTypes) { StringBuffer sb = new StringBuffer(); appendMethodSignature(argTypes, method.getReturnType(), sb); return sb.toString(); } static int appendMethodSignature(Class<?>[] argTypes, Class<?> returnType, StringBuffer sb) { sb.append('('); int firstLocal = 1 + argTypes.length; // includes this. for (int i = 0; i < argTypes.length; i++) { Class<?> type = argTypes[i]; appendTypeString(sb, type); if (type == Long.TYPE || type == Double.TYPE) { // adjust for duble slot ++firstLocal; } } sb.append(')'); appendTypeString(sb, returnType); return firstLocal; } private static StringBuffer appendTypeString(StringBuffer sb, Class<?> type) { while (type.isArray()) { sb.append('['); type = type.getComponentType(); } if (type.isPrimitive()) { char typeLetter; if (type == Boolean.TYPE) { typeLetter = 'Z'; } else if (type == Long.TYPE) { typeLetter = 'J'; } else { String typeName = type.getName(); typeLetter = Character.toUpperCase(typeName.charAt(0)); } sb.append(typeLetter); } else { sb.append('L'); sb.append(type.getName().replace('.', '/')); sb.append(';'); } return sb; } static int[] getArgsToConvert(Class<?>[] argTypes) { int count = 0; for (int i = 0; i != argTypes.length; ++i) { if (!argTypes[i].isPrimitive()) ++count; } if (count == 0) return null; int[] array = new int[count]; count = 0; for (int i = 0; i != argTypes.length; ++i) { if (!argTypes[i].isPrimitive()) array[count++] = i; } return array; } private static final Object FTAG = "JavaAdapter"; private static final int Id_JavaAdapter = 1; }