0001: /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
0002: *
0003: * ***** BEGIN LICENSE BLOCK *****
0004: * Version: MPL 1.1/GPL 2.0
0005: *
0006: * The contents of this file are subject to the Mozilla Public License Version
0007: * 1.1 (the "License"); you may not use this file except in compliance with
0008: * the License. You may obtain a copy of the License at
0009: * http://www.mozilla.org/MPL/
0010: *
0011: * Software distributed under the License is distributed on an "AS IS" basis,
0012: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
0013: * for the specific language governing rights and limitations under the
0014: * License.
0015: *
0016: * The Original Code is Rhino code, released
0017: * May 6, 1999.
0018: *
0019: * The Initial Developer of the Original Code is
0020: * Netscape Communications Corporation.
0021: * Portions created by the Initial Developer are Copyright (C) 1997-1999
0022: * the Initial Developer. All Rights Reserved.
0023: *
0024: * Contributor(s):
0025: * Patrick Beard
0026: * Norris Boyd
0027: * Igor Bukanov
0028: * Mike McCabe
0029: * Matthias Radestock
0030: * Andi Vajda
0031: * Andrew Wason
0032: * Kemal Bayram
0033: *
0034: * Alternatively, the contents of this file may be used under the terms of
0035: * the GNU General Public License Version 2 or later (the "GPL"), in which
0036: * case the provisions of the GPL are applicable instead of those above. If
0037: * you wish to allow use of your version of this file only under the terms of
0038: * the GPL and not to allow others to use your version of this file under the
0039: * MPL, indicate your decision by deleting the provisions above and replacing
0040: * them with the notice and other provisions required by the GPL. If you do
0041: * not delete the provisions above, a recipient may use your version of this
0042: * file under either the MPL or the GPL.
0043: *
0044: * ***** END LICENSE BLOCK ***** */
0045:
0046: package org.mozilla.javascript;
0047:
0048: import org.mozilla.classfile.*;
0049: import java.lang.reflect.*;
0050: import java.io.*;
0051: import java.security.*;
0052: import java.util.*;
0053:
0054: public final class JavaAdapter implements IdFunctionCall {
0055: /**
0056: * Provides a key with which to distinguish previously generated
0057: * adapter classes stored in a hash table.
0058: */
0059: static class JavaAdapterSignature {
0060: Class super Class;
0061: Class[] interfaces;
0062: ObjToIntMap names;
0063:
0064: JavaAdapterSignature(Class super Class, Class[] interfaces,
0065: ObjToIntMap names) {
0066: this .super Class = super Class;
0067: this .interfaces = interfaces;
0068: this .names = names;
0069: }
0070:
0071: public boolean equals(Object obj) {
0072: if (!(obj instanceof JavaAdapterSignature))
0073: return false;
0074: JavaAdapterSignature sig = (JavaAdapterSignature) obj;
0075: if (super Class != sig.super Class)
0076: return false;
0077: if (interfaces != sig.interfaces) {
0078: if (interfaces.length != sig.interfaces.length)
0079: return false;
0080: for (int i = 0; i < interfaces.length; i++)
0081: if (interfaces[i] != sig.interfaces[i])
0082: return false;
0083: }
0084: if (names.size() != sig.names.size())
0085: return false;
0086: ObjToIntMap.Iterator iter = new ObjToIntMap.Iterator(names);
0087: for (iter.start(); !iter.done(); iter.next()) {
0088: String name = (String) iter.getKey();
0089: int arity = iter.getValue();
0090: if (arity != names.get(name, arity + 1))
0091: return false;
0092: }
0093: return true;
0094: }
0095:
0096: public int hashCode() {
0097: return super Class.hashCode()
0098: | (0x9e3779b9 * (names.size() | (interfaces.length << 16)));
0099: }
0100: }
0101:
0102: public static void init(Context cx, Scriptable scope, boolean sealed) {
0103: JavaAdapter obj = new JavaAdapter();
0104: IdFunctionObject ctor = new IdFunctionObject(obj, FTAG,
0105: Id_JavaAdapter, "JavaAdapter", 1, scope);
0106: ctor.markAsConstructor(null);
0107: if (sealed) {
0108: ctor.sealObject();
0109: }
0110: ctor.exportAsScopeProperty();
0111: }
0112:
0113: public Object execIdCall(IdFunctionObject f, Context cx,
0114: Scriptable scope, Scriptable this Obj, Object[] args) {
0115: if (f.hasTag(FTAG)) {
0116: if (f.methodId() == Id_JavaAdapter) {
0117: return js_createAdapter(cx, scope, args);
0118: }
0119: }
0120: throw f.unknown();
0121: }
0122:
0123: public static Object convertResult(Object result, Class c) {
0124: if (result == Undefined.instance
0125: && (c != ScriptRuntime.ObjectClass && c != ScriptRuntime.StringClass)) {
0126: // Avoid an error for an undefined value; return null instead.
0127: return null;
0128: }
0129: return Context.jsToJava(result, c);
0130: }
0131:
0132: public static Scriptable createAdapterWrapper(Scriptable obj,
0133: Object adapter) {
0134: Scriptable scope = ScriptableObject.getTopLevelScope(obj);
0135: NativeJavaObject res = new NativeJavaObject(scope, adapter,
0136: null, true);
0137: res.setPrototype(obj);
0138: return res;
0139: }
0140:
0141: public static Object getAdapterSelf(Class adapterClass,
0142: Object adapter) throws NoSuchFieldException,
0143: IllegalAccessException {
0144: Field self = adapterClass.getDeclaredField("self");
0145: return self.get(adapter);
0146: }
0147:
0148: static Object js_createAdapter(Context cx, Scriptable scope,
0149: Object[] args) {
0150: int N = args.length;
0151: if (N == 0) {
0152: throw ScriptRuntime.typeError0("msg.adapter.zero.args");
0153: }
0154:
0155: Class super Class = null;
0156: Class[] intfs = new Class[N - 1];
0157: int interfaceCount = 0;
0158: for (int i = 0; i != N - 1; ++i) {
0159: Object arg = args[i];
0160: if (!(arg instanceof NativeJavaClass)) {
0161: throw ScriptRuntime.typeError2(
0162: "msg.not.java.class.arg", String.valueOf(i),
0163: ScriptRuntime.toString(arg));
0164: }
0165: Class c = ((NativeJavaClass) arg).getClassObject();
0166: if (!c.isInterface()) {
0167: if (super Class != null) {
0168: throw ScriptRuntime.typeError2(
0169: "msg.only.one.super", super Class.getName(),
0170: c.getName());
0171: }
0172: super Class = c;
0173: } else {
0174: intfs[interfaceCount++] = c;
0175: }
0176: }
0177:
0178: if (super Class == null)
0179: super Class = ScriptRuntime.ObjectClass;
0180:
0181: Class[] interfaces = new Class[interfaceCount];
0182: System.arraycopy(intfs, 0, interfaces, 0, interfaceCount);
0183: Scriptable obj = ScriptRuntime.toObject(cx, scope, args[N - 1]);
0184:
0185: Class adapterClass = getAdapterClass(scope, super Class,
0186: interfaces, obj);
0187:
0188: Class[] ctorParms = { ScriptRuntime.ContextFactoryClass,
0189: ScriptRuntime.ScriptableClass };
0190: Object[] ctorArgs = { cx.getFactory(), obj };
0191: try {
0192: Object adapter = adapterClass.getConstructor(ctorParms)
0193: .newInstance(ctorArgs);
0194: return getAdapterSelf(adapterClass, adapter);
0195: } catch (Exception ex) {
0196: throw Context.throwAsScriptRuntimeEx(ex);
0197: }
0198: }
0199:
0200: // Needed by NativeJavaObject serializer
0201: public static void writeAdapterObject(Object javaObject,
0202: ObjectOutputStream out) throws IOException {
0203: Class cl = javaObject.getClass();
0204: out.writeObject(cl.getSuperclass().getName());
0205:
0206: Class[] interfaces = cl.getInterfaces();
0207: String[] interfaceNames = new String[interfaces.length];
0208:
0209: for (int i = 0; i < interfaces.length; i++)
0210: interfaceNames[i] = interfaces[i].getName();
0211:
0212: out.writeObject(interfaceNames);
0213:
0214: try {
0215: Object delegee = cl.getField("delegee").get(javaObject);
0216: out.writeObject(delegee);
0217: return;
0218: } catch (IllegalAccessException e) {
0219: } catch (NoSuchFieldException e) {
0220: }
0221: throw new IOException();
0222: }
0223:
0224: // Needed by NativeJavaObject de-serializer
0225: public static Object readAdapterObject(Scriptable self,
0226: ObjectInputStream in) throws IOException,
0227: ClassNotFoundException {
0228: ContextFactory factory;
0229: Context cx = Context.getCurrentContext();
0230: if (cx != null) {
0231: factory = cx.getFactory();
0232: } else {
0233: factory = null;
0234: }
0235:
0236: Class super Class = Class.forName((String) in.readObject());
0237:
0238: String[] interfaceNames = (String[]) in.readObject();
0239: Class[] interfaces = new Class[interfaceNames.length];
0240:
0241: for (int i = 0; i < interfaceNames.length; i++)
0242: interfaces[i] = Class.forName(interfaceNames[i]);
0243:
0244: Scriptable delegee = (Scriptable) in.readObject();
0245:
0246: Class adapterClass = getAdapterClass(self, super Class,
0247: interfaces, delegee);
0248:
0249: Class[] ctorParms = { ScriptRuntime.ContextFactoryClass,
0250: ScriptRuntime.ScriptableClass,
0251: ScriptRuntime.ScriptableClass };
0252: Object[] ctorArgs = { factory, delegee, self };
0253: try {
0254: return adapterClass.getConstructor(ctorParms).newInstance(
0255: ctorArgs);
0256: } catch (InstantiationException e) {
0257: } catch (IllegalAccessException e) {
0258: } catch (InvocationTargetException e) {
0259: } catch (NoSuchMethodException e) {
0260: }
0261:
0262: throw new ClassNotFoundException("adapter");
0263: }
0264:
0265: private static ObjToIntMap getObjectFunctionNames(Scriptable obj) {
0266: Object[] ids = ScriptableObject.getPropertyIds(obj);
0267: ObjToIntMap map = new ObjToIntMap(ids.length);
0268: for (int i = 0; i != ids.length; ++i) {
0269: if (!(ids[i] instanceof String))
0270: continue;
0271: String id = (String) ids[i];
0272: Object value = ScriptableObject.getProperty(obj, id);
0273: if (value instanceof Function) {
0274: Function f = (Function) value;
0275: int length = ScriptRuntime.toInt32(ScriptableObject
0276: .getProperty(f, "length"));
0277: if (length < 0) {
0278: length = 0;
0279: }
0280: map.put(id, length);
0281: }
0282: }
0283: return map;
0284: }
0285:
0286: private static Class getAdapterClass(Scriptable scope,
0287: Class super Class, Class[] interfaces, Scriptable obj) {
0288: ClassCache cache = ClassCache.get(scope);
0289: Map<JavaAdapterSignature, Class<?>> generated = cache
0290: .getInterfaceAdapterCacheMap();
0291:
0292: ObjToIntMap names = getObjectFunctionNames(obj);
0293: JavaAdapterSignature sig;
0294: sig = new JavaAdapterSignature(super Class, interfaces, names);
0295: Class<?> adapterClass = generated.get(sig);
0296: if (adapterClass == null) {
0297: String adapterName = "adapter"
0298: + cache.newClassSerialNumber();
0299: byte[] code = createAdapterCode(names, adapterName,
0300: super Class, interfaces, null);
0301:
0302: adapterClass = loadAdapterClass(adapterName, code);
0303: if (cache.isCachingEnabled()) {
0304: generated.put(sig, adapterClass);
0305: }
0306: }
0307: return adapterClass;
0308: }
0309:
0310: public static byte[] createAdapterCode(ObjToIntMap functionNames,
0311: String adapterName, Class super Class, Class[] interfaces,
0312: String scriptClassName) {
0313: ClassFileWriter cfw = new ClassFileWriter(adapterName,
0314: super Class.getName(), "<adapter>");
0315: cfw
0316: .addField(
0317: "factory",
0318: "Lorg/mozilla/javascript/ContextFactory;",
0319: (short) (ClassFileWriter.ACC_PUBLIC | ClassFileWriter.ACC_FINAL));
0320: cfw
0321: .addField(
0322: "delegee",
0323: "Lorg/mozilla/javascript/Scriptable;",
0324: (short) (ClassFileWriter.ACC_PUBLIC | ClassFileWriter.ACC_FINAL));
0325: cfw
0326: .addField(
0327: "self",
0328: "Lorg/mozilla/javascript/Scriptable;",
0329: (short) (ClassFileWriter.ACC_PUBLIC | ClassFileWriter.ACC_FINAL));
0330: int interfacesCount = interfaces == null ? 0
0331: : interfaces.length;
0332: for (int i = 0; i < interfacesCount; i++) {
0333: if (interfaces[i] != null)
0334: cfw.addInterface(interfaces[i].getName());
0335: }
0336:
0337: String super Name = super Class.getName().replace('.', '/');
0338: generateCtor(cfw, adapterName, super Name);
0339: generateSerialCtor(cfw, adapterName, super Name);
0340: if (scriptClassName != null)
0341: generateEmptyCtor(cfw, adapterName, super Name,
0342: scriptClassName);
0343:
0344: ObjToIntMap generatedOverrides = new ObjToIntMap();
0345: ObjToIntMap generatedMethods = new ObjToIntMap();
0346:
0347: // generate methods to satisfy all specified interfaces.
0348: for (int i = 0; i < interfacesCount; i++) {
0349: Method[] methods = interfaces[i].getMethods();
0350: for (int j = 0; j < methods.length; j++) {
0351: Method method = methods[j];
0352: int mods = method.getModifiers();
0353: if (Modifier.isStatic(mods) || Modifier.isFinal(mods)) {
0354: continue;
0355: }
0356: String methodName = method.getName();
0357: Class[] argTypes = method.getParameterTypes();
0358: if (!functionNames.has(methodName)) {
0359: try {
0360: super Class.getMethod(methodName, argTypes);
0361: // The class we're extending implements this method and
0362: // the JavaScript object doesn't have an override. See
0363: // bug 61226.
0364: continue;
0365: } catch (NoSuchMethodException e) {
0366: // Not implemented by superclass; fall through
0367: }
0368: }
0369: // make sure to generate only one instance of a particular
0370: // method/signature.
0371: String methodSignature = getMethodSignature(method,
0372: argTypes);
0373: String methodKey = methodName + methodSignature;
0374: if (!generatedOverrides.has(methodKey)) {
0375: generateMethod(cfw, adapterName, methodName,
0376: argTypes, method.getReturnType());
0377: generatedOverrides.put(methodKey, 0);
0378: generatedMethods.put(methodName, 0);
0379: }
0380: }
0381: }
0382:
0383: // Now, go through the superclass's methods, checking for abstract
0384: // methods or additional methods to override.
0385:
0386: // generate any additional overrides that the object might contain.
0387: Method[] methods = getOverridableMethods(super Class);
0388: for (int j = 0; j < methods.length; j++) {
0389: Method method = methods[j];
0390: int mods = method.getModifiers();
0391: // if a method is marked abstract, must implement it or the
0392: // resulting class won't be instantiable. otherwise, if the object
0393: // has a property of the same name, then an override is intended.
0394: boolean isAbstractMethod = Modifier.isAbstract(mods);
0395: String methodName = method.getName();
0396: if (isAbstractMethod || functionNames.has(methodName)) {
0397: // make sure to generate only one instance of a particular
0398: // method/signature.
0399: Class[] argTypes = method.getParameterTypes();
0400: String methodSignature = getMethodSignature(method,
0401: argTypes);
0402: String methodKey = methodName + methodSignature;
0403: if (!generatedOverrides.has(methodKey)) {
0404: generateMethod(cfw, adapterName, methodName,
0405: argTypes, method.getReturnType());
0406: generatedOverrides.put(methodKey, 0);
0407: generatedMethods.put(methodName, 0);
0408:
0409: // if a method was overridden, generate a "super$method"
0410: // which lets the delegate call the superclass' version.
0411: if (!isAbstractMethod) {
0412: generateSuper(cfw, adapterName, super Name,
0413: methodName, methodSignature, argTypes,
0414: method.getReturnType());
0415: }
0416: }
0417: }
0418: }
0419:
0420: // Generate Java methods for remaining properties that are not
0421: // overrides.
0422: ObjToIntMap.Iterator iter = new ObjToIntMap.Iterator(
0423: functionNames);
0424: for (iter.start(); !iter.done(); iter.next()) {
0425: String functionName = (String) iter.getKey();
0426: if (generatedMethods.has(functionName))
0427: continue;
0428: int length = iter.getValue();
0429: Class[] parms = new Class[length];
0430: for (int k = 0; k < length; k++)
0431: parms[k] = ScriptRuntime.ObjectClass;
0432: generateMethod(cfw, adapterName, functionName, parms,
0433: ScriptRuntime.ObjectClass);
0434: }
0435: return cfw.toByteArray();
0436: }
0437:
0438: static Method[] getOverridableMethods(Class c) {
0439: ArrayList<Method> list = new ArrayList<Method>();
0440: HashSet<String> skip = new HashSet<String>();
0441: while (c != null) {
0442: Method[] methods = c.getDeclaredMethods();
0443: for (int i = 0; i < methods.length; i++) {
0444: String methodKey = methods[i].getName()
0445: + getMethodSignature(methods[i], methods[i]
0446: .getParameterTypes());
0447: if (skip.contains(methodKey))
0448: continue; // skip this method
0449: int mods = methods[i].getModifiers();
0450: if (Modifier.isStatic(mods))
0451: continue;
0452: if (Modifier.isFinal(mods)) {
0453: // Make sure we don't add a final method to the list
0454: // of overridable methods.
0455: skip.add(methodKey);
0456: continue;
0457: }
0458: if (Modifier.isPublic(mods)
0459: || Modifier.isProtected(mods)) {
0460: list.add(methods[i]);
0461: skip.add(methodKey);
0462: }
0463: }
0464: c = c.getSuperclass();
0465: }
0466: return list.toArray(new Method[list.size()]);
0467: }
0468:
0469: static Class loadAdapterClass(String className, byte[] classBytes) {
0470: Object staticDomain;
0471: Class domainClass = SecurityController
0472: .getStaticSecurityDomainClass();
0473: if (domainClass == CodeSource.class
0474: || domainClass == ProtectionDomain.class) {
0475: ProtectionDomain protectionDomain = JavaAdapter.class
0476: .getProtectionDomain();
0477: if (domainClass == CodeSource.class) {
0478: staticDomain = protectionDomain == null ? null
0479: : protectionDomain.getCodeSource();
0480: } else {
0481: staticDomain = protectionDomain;
0482: }
0483: } else {
0484: staticDomain = null;
0485: }
0486: GeneratedClassLoader loader = SecurityController.createLoader(
0487: null, staticDomain);
0488: Class result = loader.defineClass(className, classBytes);
0489: loader.linkClass(result);
0490: return result;
0491: }
0492:
0493: public static Function getFunction(Scriptable obj,
0494: String functionName) {
0495: Object x = ScriptableObject.getProperty(obj, functionName);
0496: if (x == Scriptable.NOT_FOUND) {
0497: // This method used to swallow the exception from calling
0498: // an undefined method. People have come to depend on this
0499: // somewhat dubious behavior. It allows people to avoid
0500: // implementing listener methods that they don't care about,
0501: // for instance.
0502: return null;
0503: }
0504: if (!(x instanceof Function))
0505: throw ScriptRuntime.notFunctionError(x, functionName);
0506:
0507: return (Function) x;
0508: }
0509:
0510: /**
0511: * Utility method which dynamically binds a Context to the current thread,
0512: * if none already exists.
0513: */
0514: public static Object callMethod(ContextFactory factory,
0515: final Scriptable this Obj, final Function f,
0516: final Object[] args, final long argsToWrap) {
0517: if (f == null) {
0518: // See comments in getFunction
0519: return Undefined.instance;
0520: }
0521: if (factory == null) {
0522: factory = ContextFactory.getGlobal();
0523: }
0524:
0525: final Scriptable scope = f.getParentScope();
0526: if (argsToWrap == 0) {
0527: return Context.call(factory, f, scope, this Obj, args);
0528: }
0529:
0530: Context cx = Context.getCurrentContext();
0531: if (cx != null) {
0532: return doCall(cx, scope, this Obj, f, args, argsToWrap);
0533: } else {
0534: return factory.call(new ContextAction() {
0535: public Object run(Context cx) {
0536: return doCall(cx, scope, this Obj, f, args,
0537: argsToWrap);
0538: }
0539: });
0540: }
0541: }
0542:
0543: private static Object doCall(Context cx, Scriptable scope,
0544: Scriptable this Obj, Function f, Object[] args,
0545: long argsToWrap) {
0546: // Wrap the rest of objects
0547: for (int i = 0; i != args.length; ++i) {
0548: if (0 != (argsToWrap & (1 << i))) {
0549: Object arg = args[i];
0550: if (!(arg instanceof Scriptable)) {
0551: args[i] = cx.getWrapFactory().wrap(cx, scope, arg,
0552: null);
0553: }
0554: }
0555: }
0556: return f.call(cx, scope, this Obj, args);
0557: }
0558:
0559: public static Scriptable runScript(final Script script) {
0560: return (Scriptable) Context.call(new ContextAction() {
0561: public Object run(Context cx) {
0562: ScriptableObject global = ScriptRuntime.getGlobal(cx);
0563: script.exec(cx, global);
0564: return global;
0565: }
0566: });
0567: }
0568:
0569: private static void generateCtor(ClassFileWriter cfw,
0570: String adapterName, String super Name) {
0571: cfw.startMethod("<init>",
0572: "(Lorg/mozilla/javascript/ContextFactory;"
0573: + "Lorg/mozilla/javascript/Scriptable;)V",
0574: ClassFileWriter.ACC_PUBLIC);
0575:
0576: // Invoke base class constructor
0577: cfw.add(ByteCode.ALOAD_0); // this
0578: cfw.addInvoke(ByteCode.INVOKESPECIAL, super Name, "<init>",
0579: "()V");
0580:
0581: // Save parameter in instance variable "factory"
0582: cfw.add(ByteCode.ALOAD_0); // this
0583: cfw.add(ByteCode.ALOAD_1); // first arg: ContextFactory instance
0584: cfw.add(ByteCode.PUTFIELD, adapterName, "factory",
0585: "Lorg/mozilla/javascript/ContextFactory;");
0586:
0587: // Save parameter in instance variable "delegee"
0588: cfw.add(ByteCode.ALOAD_0); // this
0589: cfw.add(ByteCode.ALOAD_2); // second arg: Scriptable delegee
0590: cfw.add(ByteCode.PUTFIELD, adapterName, "delegee",
0591: "Lorg/mozilla/javascript/Scriptable;");
0592:
0593: cfw.add(ByteCode.ALOAD_0); // this for the following PUTFIELD for self
0594: // create a wrapper object to be used as "this" in method calls
0595: cfw.add(ByteCode.ALOAD_2); // the Scriptable delegee
0596: cfw.add(ByteCode.ALOAD_0); // this
0597: cfw.addInvoke(ByteCode.INVOKESTATIC,
0598: "org/mozilla/javascript/JavaAdapter",
0599: "createAdapterWrapper",
0600: "(Lorg/mozilla/javascript/Scriptable;"
0601: + "Ljava/lang/Object;"
0602: + ")Lorg/mozilla/javascript/Scriptable;");
0603: cfw.add(ByteCode.PUTFIELD, adapterName, "self",
0604: "Lorg/mozilla/javascript/Scriptable;");
0605:
0606: cfw.add(ByteCode.RETURN);
0607: cfw.stopMethod((short) 3); // 3: this + factory + delegee
0608: }
0609:
0610: private static void generateSerialCtor(ClassFileWriter cfw,
0611: String adapterName, String super Name) {
0612: cfw.startMethod("<init>",
0613: "(Lorg/mozilla/javascript/ContextFactory;"
0614: + "Lorg/mozilla/javascript/Scriptable;"
0615: + "Lorg/mozilla/javascript/Scriptable;" + ")V",
0616: ClassFileWriter.ACC_PUBLIC);
0617:
0618: // Invoke base class constructor
0619: cfw.add(ByteCode.ALOAD_0); // this
0620: cfw.addInvoke(ByteCode.INVOKESPECIAL, super Name, "<init>",
0621: "()V");
0622:
0623: // Save parameter in instance variable "factory"
0624: cfw.add(ByteCode.ALOAD_0); // this
0625: cfw.add(ByteCode.ALOAD_1); // first arg: ContextFactory instance
0626: cfw.add(ByteCode.PUTFIELD, adapterName, "factory",
0627: "Lorg/mozilla/javascript/ContextFactory;");
0628:
0629: // Save parameter in instance variable "delegee"
0630: cfw.add(ByteCode.ALOAD_0); // this
0631: cfw.add(ByteCode.ALOAD_2); // second arg: Scriptable delegee
0632: cfw.add(ByteCode.PUTFIELD, adapterName, "delegee",
0633: "Lorg/mozilla/javascript/Scriptable;");
0634: // save self
0635: cfw.add(ByteCode.ALOAD_0); // this
0636: cfw.add(ByteCode.ALOAD_3); // second arg: Scriptable self
0637: cfw.add(ByteCode.PUTFIELD, adapterName, "self",
0638: "Lorg/mozilla/javascript/Scriptable;");
0639:
0640: cfw.add(ByteCode.RETURN);
0641: cfw.stopMethod((short) 4); // 4: this + factory + delegee + self
0642: }
0643:
0644: private static void generateEmptyCtor(ClassFileWriter cfw,
0645: String adapterName, String super Name, String scriptClassName) {
0646: cfw.startMethod("<init>", "()V", ClassFileWriter.ACC_PUBLIC);
0647:
0648: // Invoke base class constructor
0649: cfw.add(ByteCode.ALOAD_0); // this
0650: cfw.addInvoke(ByteCode.INVOKESPECIAL, super Name, "<init>",
0651: "()V");
0652:
0653: // Set factory to null to use current global when necessary
0654: cfw.add(ByteCode.ALOAD_0);
0655: cfw.add(ByteCode.ACONST_NULL);
0656: cfw.add(ByteCode.PUTFIELD, adapterName, "factory",
0657: "Lorg/mozilla/javascript/ContextFactory;");
0658:
0659: // Load script class
0660: cfw.add(ByteCode.NEW, scriptClassName);
0661: cfw.add(ByteCode.DUP);
0662: cfw.addInvoke(ByteCode.INVOKESPECIAL, scriptClassName,
0663: "<init>", "()V");
0664:
0665: // Run script and save resulting scope
0666: cfw.addInvoke(ByteCode.INVOKESTATIC,
0667: "org/mozilla/javascript/JavaAdapter", "runScript",
0668: "(Lorg/mozilla/javascript/Script;"
0669: + ")Lorg/mozilla/javascript/Scriptable;");
0670: cfw.add(ByteCode.ASTORE_1);
0671:
0672: // Save the Scriptable in instance variable "delegee"
0673: cfw.add(ByteCode.ALOAD_0); // this
0674: cfw.add(ByteCode.ALOAD_1); // the Scriptable
0675: cfw.add(ByteCode.PUTFIELD, adapterName, "delegee",
0676: "Lorg/mozilla/javascript/Scriptable;");
0677:
0678: cfw.add(ByteCode.ALOAD_0); // this for the following PUTFIELD for self
0679: // create a wrapper object to be used as "this" in method calls
0680: cfw.add(ByteCode.ALOAD_1); // the Scriptable
0681: cfw.add(ByteCode.ALOAD_0); // this
0682: cfw.addInvoke(ByteCode.INVOKESTATIC,
0683: "org/mozilla/javascript/JavaAdapter",
0684: "createAdapterWrapper",
0685: "(Lorg/mozilla/javascript/Scriptable;"
0686: + "Ljava/lang/Object;"
0687: + ")Lorg/mozilla/javascript/Scriptable;");
0688: cfw.add(ByteCode.PUTFIELD, adapterName, "self",
0689: "Lorg/mozilla/javascript/Scriptable;");
0690:
0691: cfw.add(ByteCode.RETURN);
0692: cfw.stopMethod((short) 2); // this + delegee
0693: }
0694:
0695: /**
0696: * Generates code to wrap Java arguments into Object[].
0697: * Non-primitive Java types are left as-is pending conversion
0698: * in the helper method. Leaves the array object on the top of the stack.
0699: */
0700: static void generatePushWrappedArgs(ClassFileWriter cfw,
0701: Class[] argTypes, int arrayLength) {
0702: // push arguments
0703: cfw.addPush(arrayLength);
0704: cfw.add(ByteCode.ANEWARRAY, "java/lang/Object");
0705: int paramOffset = 1;
0706: for (int i = 0; i != argTypes.length; ++i) {
0707: cfw.add(ByteCode.DUP); // duplicate array reference
0708: cfw.addPush(i);
0709: paramOffset += generateWrapArg(cfw, paramOffset,
0710: argTypes[i]);
0711: cfw.add(ByteCode.AASTORE);
0712: }
0713: }
0714:
0715: /**
0716: * Generates code to wrap Java argument into Object.
0717: * Non-primitive Java types are left unconverted pending conversion
0718: * in the helper method. Leaves the wrapper object on the top of the stack.
0719: */
0720: private static int generateWrapArg(ClassFileWriter cfw,
0721: int paramOffset, Class argType) {
0722: int size = 1;
0723: if (!argType.isPrimitive()) {
0724: cfw.add(ByteCode.ALOAD, paramOffset);
0725:
0726: } else if (argType == Boolean.TYPE) {
0727: // wrap boolean values with java.lang.Boolean.
0728: cfw.add(ByteCode.NEW, "java/lang/Boolean");
0729: cfw.add(ByteCode.DUP);
0730: cfw.add(ByteCode.ILOAD, paramOffset);
0731: cfw.addInvoke(ByteCode.INVOKESPECIAL, "java/lang/Boolean",
0732: "<init>", "(Z)V");
0733:
0734: } else if (argType == Character.TYPE) {
0735: // Create a string of length 1 using the character parameter.
0736: cfw.add(ByteCode.ILOAD, paramOffset);
0737: cfw.addInvoke(ByteCode.INVOKESTATIC, "java/lang/String",
0738: "valueOf", "(C)Ljava/lang/String;");
0739:
0740: } else {
0741: // convert all numeric values to java.lang.Double.
0742: cfw.add(ByteCode.NEW, "java/lang/Double");
0743: cfw.add(ByteCode.DUP);
0744: String typeName = argType.getName();
0745: switch (typeName.charAt(0)) {
0746: case 'b':
0747: case 's':
0748: case 'i':
0749: // load an int value, convert to double.
0750: cfw.add(ByteCode.ILOAD, paramOffset);
0751: cfw.add(ByteCode.I2D);
0752: break;
0753: case 'l':
0754: // load a long, convert to double.
0755: cfw.add(ByteCode.LLOAD, paramOffset);
0756: cfw.add(ByteCode.L2D);
0757: size = 2;
0758: break;
0759: case 'f':
0760: // load a float, convert to double.
0761: cfw.add(ByteCode.FLOAD, paramOffset);
0762: cfw.add(ByteCode.F2D);
0763: break;
0764: case 'd':
0765: cfw.add(ByteCode.DLOAD, paramOffset);
0766: size = 2;
0767: break;
0768: }
0769: cfw.addInvoke(ByteCode.INVOKESPECIAL, "java/lang/Double",
0770: "<init>", "(D)V");
0771: }
0772: return size;
0773: }
0774:
0775: /**
0776: * Generates code to convert a wrapped value type to a primitive type.
0777: * Handles unwrapping java.lang.Boolean, and java.lang.Number types.
0778: * Generates the appropriate RETURN bytecode.
0779: */
0780: static void generateReturnResult(ClassFileWriter cfw,
0781: Class retType, boolean callConvertResult) {
0782: // wrap boolean values with java.lang.Boolean, convert all other
0783: // primitive values to java.lang.Double.
0784: if (retType == Void.TYPE) {
0785: cfw.add(ByteCode.POP);
0786: cfw.add(ByteCode.RETURN);
0787:
0788: } else if (retType == Boolean.TYPE) {
0789: cfw.addInvoke(ByteCode.INVOKESTATIC,
0790: "org/mozilla/javascript/Context", "toBoolean",
0791: "(Ljava/lang/Object;)Z");
0792: cfw.add(ByteCode.IRETURN);
0793:
0794: } else if (retType == Character.TYPE) {
0795: // characters are represented as strings in JavaScript.
0796: // return the first character.
0797: // first convert the value to a string if possible.
0798: cfw.addInvoke(ByteCode.INVOKESTATIC,
0799: "org/mozilla/javascript/Context", "toString",
0800: "(Ljava/lang/Object;)Ljava/lang/String;");
0801: cfw.add(ByteCode.ICONST_0);
0802: cfw.addInvoke(ByteCode.INVOKEVIRTUAL, "java/lang/String",
0803: "charAt", "(I)C");
0804: cfw.add(ByteCode.IRETURN);
0805:
0806: } else if (retType.isPrimitive()) {
0807: cfw.addInvoke(ByteCode.INVOKESTATIC,
0808: "org/mozilla/javascript/Context", "toNumber",
0809: "(Ljava/lang/Object;)D");
0810: String typeName = retType.getName();
0811: switch (typeName.charAt(0)) {
0812: case 'b':
0813: case 's':
0814: case 'i':
0815: cfw.add(ByteCode.D2I);
0816: cfw.add(ByteCode.IRETURN);
0817: break;
0818: case 'l':
0819: cfw.add(ByteCode.D2L);
0820: cfw.add(ByteCode.LRETURN);
0821: break;
0822: case 'f':
0823: cfw.add(ByteCode.D2F);
0824: cfw.add(ByteCode.FRETURN);
0825: break;
0826: case 'd':
0827: cfw.add(ByteCode.DRETURN);
0828: break;
0829: default:
0830: throw new RuntimeException("Unexpected return type "
0831: + retType.toString());
0832: }
0833:
0834: } else {
0835: String retTypeStr = retType.getName();
0836: if (callConvertResult) {
0837: cfw.addLoadConstant(retTypeStr);
0838: cfw.addInvoke(ByteCode.INVOKESTATIC, "java/lang/Class",
0839: "forName",
0840: "(Ljava/lang/String;)Ljava/lang/Class;");
0841:
0842: cfw.addInvoke(ByteCode.INVOKESTATIC,
0843: "org/mozilla/javascript/JavaAdapter",
0844: "convertResult", "(Ljava/lang/Object;"
0845: + "Ljava/lang/Class;"
0846: + ")Ljava/lang/Object;");
0847: }
0848: // Now cast to return type
0849: cfw.add(ByteCode.CHECKCAST, retTypeStr);
0850: cfw.add(ByteCode.ARETURN);
0851: }
0852: }
0853:
0854: private static void generateMethod(ClassFileWriter cfw,
0855: String genName, String methodName, Class[] parms,
0856: Class returnType) {
0857: StringBuffer sb = new StringBuffer();
0858: int paramsEnd = appendMethodSignature(parms, returnType, sb);
0859: String methodSignature = sb.toString();
0860: cfw.startMethod(methodName, methodSignature,
0861: ClassFileWriter.ACC_PUBLIC);
0862:
0863: // Prepare stack to call method
0864:
0865: // push factory
0866: cfw.add(ByteCode.ALOAD_0);
0867: cfw.add(ByteCode.GETFIELD, genName, "factory",
0868: "Lorg/mozilla/javascript/ContextFactory;");
0869:
0870: // push self
0871: cfw.add(ByteCode.ALOAD_0);
0872: cfw.add(ByteCode.GETFIELD, genName, "self",
0873: "Lorg/mozilla/javascript/Scriptable;");
0874:
0875: // push function
0876: cfw.add(ByteCode.ALOAD_0);
0877: cfw.add(ByteCode.GETFIELD, genName, "delegee",
0878: "Lorg/mozilla/javascript/Scriptable;");
0879: cfw.addPush(methodName);
0880: cfw.addInvoke(ByteCode.INVOKESTATIC,
0881: "org/mozilla/javascript/JavaAdapter", "getFunction",
0882: "(Lorg/mozilla/javascript/Scriptable;"
0883: + "Ljava/lang/String;"
0884: + ")Lorg/mozilla/javascript/Function;");
0885:
0886: // push arguments
0887: generatePushWrappedArgs(cfw, parms, parms.length);
0888:
0889: // push bits to indicate which parameters should be wrapped
0890: if (parms.length > 64) {
0891: // If it will be an issue, then passing a static boolean array
0892: // can be an option, but for now using simple bitmask
0893: throw Context
0894: .reportRuntimeError0("JavaAdapter can not subclass methods with more then"
0895: + " 64 arguments.");
0896: }
0897: long convertionMask = 0;
0898: for (int i = 0; i != parms.length; ++i) {
0899: if (!parms[i].isPrimitive()) {
0900: convertionMask |= (1 << i);
0901: }
0902: }
0903: cfw.addPush(convertionMask);
0904:
0905: // go through utility method, which creates a Context to run the
0906: // method in.
0907: cfw.addInvoke(ByteCode.INVOKESTATIC,
0908: "org/mozilla/javascript/JavaAdapter", "callMethod",
0909: "(Lorg/mozilla/javascript/ContextFactory;"
0910: + "Lorg/mozilla/javascript/Scriptable;"
0911: + "Lorg/mozilla/javascript/Function;"
0912: + "[Ljava/lang/Object;" + "J"
0913: + ")Ljava/lang/Object;");
0914:
0915: generateReturnResult(cfw, returnType, true);
0916:
0917: cfw.stopMethod((short) paramsEnd);
0918: }
0919:
0920: /**
0921: * Generates code to push typed parameters onto the operand stack
0922: * prior to a direct Java method call.
0923: */
0924: private static int generatePushParam(ClassFileWriter cfw,
0925: int paramOffset, Class paramType) {
0926: if (!paramType.isPrimitive()) {
0927: cfw.addALoad(paramOffset);
0928: return 1;
0929: }
0930: String typeName = paramType.getName();
0931: switch (typeName.charAt(0)) {
0932: case 'z':
0933: case 'b':
0934: case 'c':
0935: case 's':
0936: case 'i':
0937: // load an int value, convert to double.
0938: cfw.addILoad(paramOffset);
0939: return 1;
0940: case 'l':
0941: // load a long, convert to double.
0942: cfw.addLLoad(paramOffset);
0943: return 2;
0944: case 'f':
0945: // load a float, convert to double.
0946: cfw.addFLoad(paramOffset);
0947: return 1;
0948: case 'd':
0949: cfw.addDLoad(paramOffset);
0950: return 2;
0951: }
0952: throw Kit.codeBug();
0953: }
0954:
0955: /**
0956: * Generates code to return a Java type, after calling a Java method
0957: * that returns the same type.
0958: * Generates the appropriate RETURN bytecode.
0959: */
0960: private static void generatePopResult(ClassFileWriter cfw,
0961: Class retType) {
0962: if (retType.isPrimitive()) {
0963: String typeName = retType.getName();
0964: switch (typeName.charAt(0)) {
0965: case 'b':
0966: case 'c':
0967: case 's':
0968: case 'i':
0969: case 'z':
0970: cfw.add(ByteCode.IRETURN);
0971: break;
0972: case 'l':
0973: cfw.add(ByteCode.LRETURN);
0974: break;
0975: case 'f':
0976: cfw.add(ByteCode.FRETURN);
0977: break;
0978: case 'd':
0979: cfw.add(ByteCode.DRETURN);
0980: break;
0981: }
0982: } else {
0983: cfw.add(ByteCode.ARETURN);
0984: }
0985: }
0986:
0987: /**
0988: * Generates a method called "super$methodName()" which can be called
0989: * from JavaScript that is equivalent to calling "super.methodName()"
0990: * from Java. Eventually, this may be supported directly in JavaScript.
0991: */
0992: private static void generateSuper(ClassFileWriter cfw,
0993: String genName, String super Name, String methodName,
0994: String methodSignature, Class[] parms, Class returnType) {
0995: cfw.startMethod("super$" + methodName, methodSignature,
0996: ClassFileWriter.ACC_PUBLIC);
0997:
0998: // push "this"
0999: cfw.add(ByteCode.ALOAD, 0);
1000:
1001: // push the rest of the parameters.
1002: int paramOffset = 1;
1003: for (int i = 0; i < parms.length; i++) {
1004: paramOffset += generatePushParam(cfw, paramOffset, parms[i]);
1005: }
1006:
1007: // call the superclass implementation of the method.
1008: cfw.addInvoke(ByteCode.INVOKESPECIAL, super Name, methodName,
1009: methodSignature);
1010:
1011: // now, handle the return type appropriately.
1012: Class retType = returnType;
1013: if (!retType.equals(Void.TYPE)) {
1014: generatePopResult(cfw, retType);
1015: } else {
1016: cfw.add(ByteCode.RETURN);
1017: }
1018: cfw.stopMethod((short) (paramOffset + 1));
1019: }
1020:
1021: /**
1022: * Returns a fully qualified method name concatenated with its signature.
1023: */
1024: private static String getMethodSignature(Method method,
1025: Class[] argTypes) {
1026: StringBuffer sb = new StringBuffer();
1027: appendMethodSignature(argTypes, method.getReturnType(), sb);
1028: return sb.toString();
1029: }
1030:
1031: static int appendMethodSignature(Class[] argTypes,
1032: Class returnType, StringBuffer sb) {
1033: sb.append('(');
1034: int firstLocal = 1 + argTypes.length; // includes this.
1035: for (int i = 0; i < argTypes.length; i++) {
1036: Class type = argTypes[i];
1037: appendTypeString(sb, type);
1038: if (type == Long.TYPE || type == Double.TYPE) {
1039: // adjust for duble slot
1040: ++firstLocal;
1041: }
1042: }
1043: sb.append(')');
1044: appendTypeString(sb, returnType);
1045: return firstLocal;
1046: }
1047:
1048: private static StringBuffer appendTypeString(StringBuffer sb,
1049: Class type) {
1050: while (type.isArray()) {
1051: sb.append('[');
1052: type = type.getComponentType();
1053: }
1054: if (type.isPrimitive()) {
1055: char typeLetter;
1056: if (type == Boolean.TYPE) {
1057: typeLetter = 'Z';
1058: } else if (type == Long.TYPE) {
1059: typeLetter = 'J';
1060: } else {
1061: String typeName = type.getName();
1062: typeLetter = Character.toUpperCase(typeName.charAt(0));
1063: }
1064: sb.append(typeLetter);
1065: } else {
1066: sb.append('L');
1067: sb.append(type.getName().replace('.', '/'));
1068: sb.append(';');
1069: }
1070: return sb;
1071: }
1072:
1073: static int[] getArgsToConvert(Class[] argTypes) {
1074: int count = 0;
1075: for (int i = 0; i != argTypes.length; ++i) {
1076: if (!argTypes[i].isPrimitive())
1077: ++count;
1078: }
1079: if (count == 0)
1080: return null;
1081: int[] array = new int[count];
1082: count = 0;
1083: for (int i = 0; i != argTypes.length; ++i) {
1084: if (!argTypes[i].isPrimitive())
1085: array[count++] = i;
1086: }
1087: return array;
1088: }
1089:
1090: private static final Object FTAG = new Object();
1091: private static final int Id_JavaAdapter = 1;
1092: }
|