summaryrefslogtreecommitdiffstats
path: root/infrastructure/rhino1_7R1/src/org/mozilla/javascript/JavaAdapter.java
diff options
context:
space:
mode:
Diffstat (limited to 'infrastructure/rhino1_7R1/src/org/mozilla/javascript/JavaAdapter.java')
-rw-r--r--infrastructure/rhino1_7R1/src/org/mozilla/javascript/JavaAdapter.java1129
1 files changed, 1129 insertions, 0 deletions
diff --git a/infrastructure/rhino1_7R1/src/org/mozilla/javascript/JavaAdapter.java b/infrastructure/rhino1_7R1/src/org/mozilla/javascript/JavaAdapter.java
new file mode 100644
index 0000000..6e0a827
--- /dev/null
+++ b/infrastructure/rhino1_7R1/src/org/mozilla/javascript/JavaAdapter.java
@@ -0,0 +1,1129 @@
+/* -*- 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;
+ }
+
+ 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 != names.get(name, arity + 1))
+ return false;
+ }
+ return true;
+ }
+
+ public int hashCode()
+ {
+ return superClass.hashCode()
+ | (0x9e3779b9 * (names.size() | (interfaces.length << 16)));
+ }
+ }
+
+ 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);
+ return getAdapterSelf(adapterClass, adapter);
+ } 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 c)
+ {
+ ArrayList<Method> list = new ArrayList<Method>();
+ HashSet<String> skip = new HashSet<String>();
+ while (c != null) {
+ 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);
+ }
+ }
+ c = c.getSuperclass();
+ }
+ return list.toArray(new Method[list.size()]);
+ }
+
+ static Class loadAdapterClass(String className, byte[] classBytes)
+ {
+ Object staticDomain;
+ Class domainClass = SecurityController.getStaticSecurityDomainClass();
+ if(domainClass == CodeSource.class || domainClass == ProtectionDomain.class) {
+ ProtectionDomain 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)Context.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 = new Object();
+ private static final int Id_JavaAdapter = 1;
+}