1
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
3
* The contents of this file are subject to the Netscape Public
4
* License Version 1.1 (the "License"); you may not use this file
5
* except in compliance with the License. You may obtain a copy of
6
* the License at http://www.mozilla.org/NPL/
8
* Software distributed under the License is distributed on an "AS
9
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr
10
* implied. See the License for the specific language governing
11
* rights and limitations under the License.
13
* The Original Code is Rhino code, released
16
* The Initial Developer of the Original Code is Netscape
17
* Communications Corporation. Portions created by Netscape are
18
* Copyright (C) 1997-1999 Netscape Communications Corporation. All
29
* Alternatively, the contents of this file may be used under the
30
* terms of the GNU Public License (the "GPL"), in which case the
31
* provisions of the GPL are applicable instead of those above.
32
* If you wish to allow use of your version of this file only
33
* under the terms of the GPL and not to allow others to use your
34
* version of this file under the NPL, indicate your decision by
35
* deleting the provisions above and replace them with the notice
36
* and other provisions required by the GPL. If you do not delete
37
* the provisions above, a recipient may use your version of this
38
* file under either the NPL or the GPL.
41
package org.mozilla.javascript;
43
import org.mozilla.classfile.*;
44
import java.lang.reflect.*;
48
public class JavaAdapter extends ScriptableObject {
49
public boolean equals(Object obj) {
50
return super.equals(obj);
53
public String getClassName() {
57
public static Object convertResult(Object result, String classname)
58
throws ClassNotFoundException
60
Class c = ScriptRuntime.loadClassName(classname);
61
if (result == Undefined.instance &&
62
(c != ScriptRuntime.ObjectClass &&
63
c != ScriptRuntime.StringClass))
65
// Avoid an error for an undefined value; return null instead.
68
return NativeJavaObject.coerceType(c, result);
71
public static Scriptable setAdapterProto(Scriptable obj, Object adapter) {
72
Scriptable res = ScriptRuntime.toObject(ScriptableObject.getTopLevelScope(obj),adapter);
73
res.setPrototype(obj);
77
public static Object getAdapterSelf(Class adapterClass, Object adapter)
78
throws NoSuchFieldException, IllegalAccessException
80
Field self = adapterClass.getDeclaredField("self");
81
return self.get(adapter);
84
public static Object jsConstructor(Context cx, Object[] args,
85
Function ctorObj, boolean inNewExpr)
86
throws InstantiationException, NoSuchMethodException,
87
IllegalAccessException, InvocationTargetException,
88
ClassNotFoundException, NoSuchFieldException
90
Class superClass = null;
91
Class[] intfs = new Class[args.length-1];
92
int interfaceCount = 0;
93
for (int i=0; i < args.length-1; i++) {
94
if (!(args[i] instanceof NativeJavaClass)) {
95
throw NativeGlobal.constructError(cx, "TypeError",
96
"expected java class object", ctorObj);
98
Class c = ((NativeJavaClass) args[i]).getClassObject();
99
if (!c.isInterface()) {
100
if (superClass != null) {
101
String msg = "Only one class may be extended by a " +
102
"JavaAdapter. Had " + superClass.getName() +
103
" and " + c.getName();
104
throw NativeGlobal.constructError(cx, "TypeError", msg,
109
intfs[interfaceCount++] = c;
113
if (superClass == null)
114
superClass = Object.class;
116
Class[] interfaces = new Class[interfaceCount];
117
System.arraycopy(intfs, 0, interfaces, 0, interfaceCount);
118
Scriptable obj = (Scriptable) args[args.length - 1];
120
ClassSignature sig = new ClassSignature(superClass, interfaces, obj);
121
Class adapterClass = (Class) generatedClasses.get(sig);
122
if (adapterClass == null) {
123
String adapterName = "adapter" + serial++;
124
adapterClass = createAdapterClass(cx, obj, adapterName,
125
superClass, interfaces,
127
generatedClasses.put(sig, adapterClass);
130
Class[] ctorParms = { Scriptable.class };
131
Object[] ctorArgs = { obj };
132
Object adapter = adapterClass.getConstructor(ctorParms).newInstance(ctorArgs);
133
return getAdapterSelf(adapterClass, adapter);
136
public static Class createAdapterClass(Context cx, Scriptable jsObj,
137
String adapterName, Class superClass,
139
String scriptClassName,
140
ClassNameHelper nameHelper)
141
throws ClassNotFoundException
143
ClassFileWriter cfw = new ClassFileWriter(adapterName,
144
superClass.getName(),
146
cfw.addField("delegee", "Lorg/mozilla/javascript/Scriptable;",
147
(short) (ClassFileWriter.ACC_PUBLIC |
148
ClassFileWriter.ACC_FINAL));
149
cfw.addField("self", "Lorg/mozilla/javascript/Scriptable;",
150
(short) (ClassFileWriter.ACC_PUBLIC |
151
ClassFileWriter.ACC_FINAL));
152
int interfacesCount = interfaces == null ? 0 : interfaces.length;
153
for (int i=0; i < interfacesCount; i++) {
154
if (interfaces[i] != null)
155
cfw.addInterface(interfaces[i].getName());
158
String superName = superClass.getName().replace('.', '/');
159
generateCtor(cfw, adapterName, superName);
160
if (scriptClassName != null)
161
generateEmptyCtor(cfw, adapterName, superName, scriptClassName);
163
Hashtable generatedOverrides = new Hashtable();
164
Hashtable generatedMethods = new Hashtable();
166
// generate methods to satisfy all specified interfaces.
167
for (int i = 0; i < interfacesCount; i++) {
168
Method[] methods = interfaces[i].getMethods();
169
for (int j = 0; j < methods.length; j++) {
170
Method method = methods[j];
171
int mods = method.getModifiers();
172
if (Modifier.isStatic(mods) || Modifier.isFinal(mods) ||
177
if (!ScriptableObject.hasProperty(jsObj, method.getName())) {
179
superClass.getMethod(method.getName(),
180
method.getParameterTypes());
181
// The class we're extending implements this method and
182
// the JavaScript object doesn't have an override. See
185
} catch (NoSuchMethodException e) {
186
// Not implemented by superclass; fall through
189
// make sure to generate only one instance of a particular
191
String methodName = method.getName();
192
String methodKey = methodName + getMethodSignature(method);
193
if (! generatedOverrides.containsKey(methodKey)) {
194
generateMethod(cfw, adapterName, methodName,
195
method.getParameterTypes(),
196
method.getReturnType());
197
generatedOverrides.put(methodKey, Boolean.TRUE);
198
generatedMethods.put(methodName, Boolean.TRUE);
203
// Now, go through the superclasses methods, checking for abstract
204
// methods or additional methods to override.
206
// generate any additional overrides that the object might contain.
207
Method[] methods = superClass.getMethods();
208
for (int j = 0; j < methods.length; j++) {
209
Method method = methods[j];
210
int mods = method.getModifiers();
211
if (Modifier.isStatic(mods) || Modifier.isFinal(mods))
213
// if a method is marked abstract, must implement it or the
214
// resulting class won't be instantiable. otherwise, if the object
215
// has a property of the same name, then an override is intended.
216
boolean isAbstractMethod = Modifier.isAbstract(mods);
217
if (isAbstractMethod ||
218
(jsObj != null && ScriptableObject.hasProperty(jsObj,method.getName())))
220
// make sure to generate only one instance of a particular
222
String methodName = method.getName();
223
String methodSignature = getMethodSignature(method);
224
String methodKey = methodName + methodSignature;
225
if (! generatedOverrides.containsKey(methodKey)) {
226
generateMethod(cfw, adapterName, methodName,
227
method.getParameterTypes(),
228
method.getReturnType());
229
generatedOverrides.put(methodKey, Boolean.TRUE);
230
generatedMethods.put(methodName, Boolean.TRUE);
232
// if a method was overridden, generate a "super$method"
233
// which lets the delegate call the superclass' version.
234
if (!isAbstractMethod) {
235
generateSuper(cfw, adapterName, superName,
236
methodName, methodSignature,
237
method.getParameterTypes(),
238
method.getReturnType());
243
// Generate Java methods, fields for remaining properties that
244
// are not overrides.
245
for (Scriptable o = jsObj; o != null; o = (Scriptable)o.getPrototype()) {
246
Object[] ids = jsObj.getIds();
247
for (int j=0; j < ids.length; j++) {
248
if (!(ids[j] instanceof String))
250
String id = (String) ids[j];
251
if (generatedMethods.containsKey(id))
253
Object f = o.get(id, o);
255
if (f instanceof Scriptable) {
256
Scriptable p = (Scriptable) f;
257
if (!(p instanceof Function))
259
length = (int) Context.toNumber(
260
ScriptableObject.getProperty(p, "length"));
261
} else if (f instanceof FunctionNode) {
262
length = ((FunctionNode) f).getVariableTable()
263
.getParameterCount();
267
Class[] parms = new Class[length];
268
for (int k=0; k < length; k++)
269
parms[k] = Object.class;
270
generateMethod(cfw, adapterName, id, parms, Object.class);
273
ByteArrayOutputStream out = new ByteArrayOutputStream(512);
277
catch (IOException ioe) {
278
throw new RuntimeException("unexpected IOException");
280
byte[] bytes = out.toByteArray();
282
if (nameHelper != null) {
283
if (nameHelper.getGeneratingDirectory() != null) {
285
int lastDot = adapterName.lastIndexOf('.');
287
adapterName = adapterName.substring(lastDot+1);
288
String filename = nameHelper.getTargetClassFileName(adapterName);
289
FileOutputStream file = new FileOutputStream(filename);
293
catch (IOException iox) {
294
throw WrappedException.wrapException(iox);
299
ClassOutput classOutput = nameHelper.getClassOutput();
300
if (classOutput != null) {
302
classOutput.getOutputStream(adapterName, true);
306
} catch (IOException iox) {
307
throw WrappedException.wrapException(iox);
312
SecuritySupport ss = cx.getSecuritySupport();
314
Object securityDomain = cx.getSecurityDomainForStackDepth(-1);
315
Class result = ss.defineClass(adapterName, bytes, securityDomain);
319
if (classLoader == null)
320
classLoader = new DefiningClassLoader();
321
classLoader.defineClass(adapterName, bytes);
322
return classLoader.loadClass(adapterName, true);
326
* Utility method which dynamically binds a Context to the current thread,
327
* if none already exists.
329
public static Object callMethod(Scriptable object, Object thisObj,
330
String methodId, Object[] args)
333
Context cx = Context.enter();
334
Object fun = ScriptableObject.getProperty(object,methodId);
335
if (fun == Scriptable.NOT_FOUND) {
336
// This method used to swallow the exception from calling
337
// an undefined method. People have come to depend on this
338
// somewhat dubious behavior. It allows people to avoid
339
// implementing listener methods that they don't care about,
341
return Undefined.instance;
343
return ScriptRuntime.call(cx, fun, thisObj, args, object);
344
} catch (JavaScriptException ex) {
345
throw WrappedException.wrapException(ex);
351
public static Scriptable toObject(Object value, Scriptable scope,
356
return Context.toObject(value, scope, staticType);
362
private static void generateCtor(ClassFileWriter cfw, String adapterName,
365
cfw.startMethod("<init>",
366
"(Lorg/mozilla/javascript/Scriptable;)V",
367
ClassFileWriter.ACC_PUBLIC);
369
// Invoke base class constructor
370
cfw.add(ByteCode.ALOAD_0); // this
371
cfw.add(ByteCode.INVOKESPECIAL, superName, "<init>", "()", "V");
373
// Save parameter in instance variable "delegee"
374
cfw.add(ByteCode.ALOAD_0); // this
375
cfw.add(ByteCode.ALOAD_1); // first arg
376
cfw.add(ByteCode.PUTFIELD, adapterName, "delegee",
377
"Lorg/mozilla/javascript/Scriptable;");
379
// create a wrapper object to be used as "this" in method calls
380
cfw.add(ByteCode.ALOAD_1); // the Scriptable
381
cfw.add(ByteCode.ALOAD_0); // this
382
cfw.add(ByteCode.INVOKESTATIC,
383
"org/mozilla/javascript/JavaAdapter",
385
"(Lorg/mozilla/javascript/Scriptable;" +
386
"Ljava/lang/Object;)",
387
"Lorg/mozilla/javascript/Scriptable;");
390
cfw.add(ByteCode.ASTORE_1);
391
cfw.add(ByteCode.ALOAD_0); // this
392
cfw.add(ByteCode.ALOAD_1); // first arg
393
cfw.add(ByteCode.PUTFIELD, adapterName, "self",
394
"Lorg/mozilla/javascript/Scriptable;");
396
cfw.add(ByteCode.RETURN);
397
cfw.stopMethod((short)20, null); // TODO: magic number "20"
400
private static void generateEmptyCtor(ClassFileWriter cfw, String adapterName,
401
String superName, String scriptClassName)
403
cfw.startMethod("<init>", "()V", ClassFileWriter.ACC_PUBLIC);
405
// Invoke base class constructor
406
cfw.add(ByteCode.ALOAD_0); // this
407
cfw.add(ByteCode.INVOKESPECIAL, superName, "<init>", "()", "V");
410
cfw.add(ByteCode.NEW, scriptClassName);
411
cfw.add(ByteCode.DUP);
412
cfw.add(ByteCode.INVOKESPECIAL, scriptClassName, "<init>", "()", "V");
414
// Run script and save resulting scope
415
cfw.add(ByteCode.INVOKESTATIC,
416
"org/mozilla/javascript/ScriptRuntime",
418
"(Lorg/mozilla/javascript/Script;)",
419
"Lorg/mozilla/javascript/Scriptable;");
420
cfw.add(ByteCode.ASTORE_1);
422
// Save the Scriptable in instance variable "delegee"
423
cfw.add(ByteCode.ALOAD_0); // this
424
cfw.add(ByteCode.ALOAD_1); // the Scriptable
425
cfw.add(ByteCode.PUTFIELD, adapterName, "delegee",
426
"Lorg/mozilla/javascript/Scriptable;");
428
// create a wrapper object to be used as "this" in method calls
429
cfw.add(ByteCode.ALOAD_1); // the Scriptable
430
cfw.add(ByteCode.ALOAD_0); // this
431
cfw.add(ByteCode.INVOKESTATIC,
432
"org/mozilla/javascript/JavaAdapter",
434
"(Lorg/mozilla/javascript/Scriptable;" +
435
"Ljava/lang/Object;)",
436
"Lorg/mozilla/javascript/Scriptable;");
438
cfw.add(ByteCode.ASTORE_1);
439
cfw.add(ByteCode.ALOAD_0); // this
440
cfw.add(ByteCode.ALOAD_1); // first arg
441
cfw.add(ByteCode.PUTFIELD, adapterName, "self",
442
"Lorg/mozilla/javascript/Scriptable;");
444
cfw.add(ByteCode.RETURN);
445
cfw.stopMethod((short)20, null); // TODO: magic number "20"
449
* Generates code to create a java.lang.Boolean, java.lang.Character or a
450
* java.lang.Double to wrap the specified primitive parameter. Leaves the
451
* wrapper object on the top of the stack.
453
private static int generateWrapParam(ClassFileWriter cfw, int paramOffset,
456
if (paramType.equals(Boolean.TYPE)) {
457
// wrap boolean values with java.lang.Boolean.
458
cfw.add(ByteCode.NEW, "java/lang/Boolean");
459
cfw.add(ByteCode.DUP);
460
cfw.add(ByteCode.ILOAD, paramOffset++);
461
cfw.add(ByteCode.INVOKESPECIAL, "java/lang/Boolean",
462
"<init>", "(Z)", "V");
464
if (paramType.equals(Character.TYPE)) {
465
// Create a string of length 1 using the character parameter.
466
cfw.add(ByteCode.NEW, "java/lang/String");
467
cfw.add(ByteCode.DUP);
468
cfw.add(ByteCode.ICONST_1);
469
cfw.add(ByteCode.NEWARRAY, ByteCode.T_CHAR);
470
cfw.add(ByteCode.DUP);
471
cfw.add(ByteCode.ICONST_0);
472
cfw.add(ByteCode.ILOAD, paramOffset++);
473
cfw.add(ByteCode.CASTORE);
474
cfw.add(ByteCode.INVOKESPECIAL, "java/lang/String",
475
"<init>", "([C)", "V");
477
// convert all numeric values to java.lang.Double.
478
cfw.add(ByteCode.NEW, "java/lang/Double");
479
cfw.add(ByteCode.DUP);
480
String typeName = paramType.getName();
481
switch (typeName.charAt(0)) {
485
// load an int value, convert to double.
486
cfw.add(ByteCode.ILOAD, paramOffset++);
487
cfw.add(ByteCode.I2D);
490
// load a long, convert to double.
491
cfw.add(ByteCode.LLOAD, paramOffset);
492
cfw.add(ByteCode.L2D);
496
// load a float, convert to double.
497
cfw.add(ByteCode.FLOAD, paramOffset++);
498
cfw.add(ByteCode.F2D);
501
cfw.add(ByteCode.DLOAD, paramOffset);
505
cfw.add(ByteCode.INVOKESPECIAL, "java/lang/Double",
506
"<init>", "(D)", "V");
512
* Generates code to convert a wrapped value type to a primitive type.
513
* Handles unwrapping java.lang.Boolean, and java.lang.Number types.
514
* May need to map between char and java.lang.String as well.
515
* Generates the appropriate RETURN bytecode.
517
private static void generateReturnResult(ClassFileWriter cfw,
520
// wrap boolean values with java.lang.Boolean, convert all other
521
// primitive values to java.lang.Double.
522
if (retType.equals(Boolean.TYPE)) {
523
cfw.add(ByteCode.INVOKESTATIC,
524
"org/mozilla/javascript/Context",
525
"toBoolean", "(Ljava/lang/Object;)",
527
cfw.add(ByteCode.IRETURN);
528
} else if (retType.equals(Character.TYPE)) {
529
// characters are represented as strings in JavaScript.
530
// return the first character.
531
// first convert the value to a string if possible.
532
cfw.add(ByteCode.INVOKESTATIC,
533
"org/mozilla/javascript/Context",
534
"toString", "(Ljava/lang/Object;)",
535
"Ljava/lang/String;");
536
cfw.add(ByteCode.ICONST_0);
537
cfw.add(ByteCode.INVOKEVIRTUAL, "java/lang/String", "charAt",
539
cfw.add(ByteCode.IRETURN);
540
} else if (retType.isPrimitive()) {
541
cfw.add(ByteCode.INVOKESTATIC,
542
"org/mozilla/javascript/Context",
543
"toNumber", "(Ljava/lang/Object;)",
545
String typeName = retType.getName();
546
switch (typeName.charAt(0)) {
550
cfw.add(ByteCode.D2I);
551
cfw.add(ByteCode.IRETURN);
554
cfw.add(ByteCode.D2L);
555
cfw.add(ByteCode.LRETURN);
558
cfw.add(ByteCode.D2F);
559
cfw.add(ByteCode.FRETURN);
562
cfw.add(ByteCode.DRETURN);
565
throw new RuntimeException("Unexpected return type " +
569
String retTypeStr = retType.getName();
570
cfw.addLoadConstant(retTypeStr);
571
cfw.add(ByteCode.INVOKESTATIC,
572
"org/mozilla/javascript/JavaAdapter",
574
"(Ljava/lang/Object;" +
575
"Ljava/lang/String;)",
576
"Ljava/lang/Object;");
577
// Now cast to return type
578
cfw.add(ByteCode.CHECKCAST, retTypeStr.replace('.', '/'));
579
cfw.add(ByteCode.ARETURN);
583
private static void generateMethod(ClassFileWriter cfw, String genName,
584
String methodName, Class[] parms,
587
StringBuffer sb = new StringBuffer();
589
short arrayLocal = 1; // includes this.
590
for (int i = 0; i < parms.length; i++) {
591
Class type = parms[i];
592
appendTypeString(sb, type);
593
if (type.equals(Long.TYPE) || type.equals(Double.TYPE))
599
appendTypeString(sb, returnType);
600
String methodSignature = sb.toString();
601
// System.out.println("generating " + m.getName() + methodSignature);
602
// System.out.flush();
603
cfw.startMethod(methodName, methodSignature,
604
ClassFileWriter.ACC_PUBLIC);
605
cfw.add(ByteCode.BIPUSH, (byte) parms.length); // > 255 parms?
606
cfw.add(ByteCode.ANEWARRAY, "java/lang/Object");
607
cfw.add(ByteCode.ASTORE, arrayLocal);
609
// allocate a local variable to store the scope used to wrap native objects.
610
short scopeLocal = (short) (arrayLocal + 1);
611
boolean loadedScope = false;
614
for (int i = 0; i < parms.length; i++) {
615
cfw.add(ByteCode.ALOAD, arrayLocal);
616
cfw.add(ByteCode.BIPUSH, i);
617
if (parms[i].isPrimitive()) {
618
paramOffset = generateWrapParam(cfw, paramOffset, parms[i]);
620
// An arbitary Java object; call Context.toObject to wrap in
621
// a Scriptable object
622
cfw.add(ByteCode.ALOAD, paramOffset++);
624
// load this.self into a local the first time it's needed.
625
// it will provide the scope needed by Context.toObject().
626
cfw.add(ByteCode.ALOAD_0);
627
cfw.add(ByteCode.GETFIELD, genName, "delegee",
628
"Lorg/mozilla/javascript/Scriptable;");
629
cfw.add(ByteCode.ASTORE, scopeLocal);
632
cfw.add(ByteCode.ALOAD, scopeLocal);
634
// Get argument Class
635
cfw.addLoadConstant(parms[i].getName());
636
cfw.add(ByteCode.INVOKESTATIC,
637
"org/mozilla/javascript/ScriptRuntime",
639
"(Ljava/lang/String;)",
640
"Ljava/lang/Class;");
642
cfw.add(ByteCode.INVOKESTATIC,
643
"org/mozilla/javascript/JavaAdapter",
645
"(Ljava/lang/Object;" +
646
"Lorg/mozilla/javascript/Scriptable;" +
647
"Ljava/lang/Class;)",
648
"Lorg/mozilla/javascript/Scriptable;");
650
cfw.add(ByteCode.AASTORE);
653
cfw.add(ByteCode.ALOAD_0);
654
cfw.add(ByteCode.GETFIELD, genName, "delegee",
655
"Lorg/mozilla/javascript/Scriptable;");
656
cfw.add(ByteCode.ALOAD_0);
657
cfw.add(ByteCode.GETFIELD, genName, "self",
658
"Lorg/mozilla/javascript/Scriptable;");
659
cfw.addLoadConstant(methodName);
660
cfw.add(ByteCode.ALOAD, arrayLocal);
662
// go through utility method, which creates a Context to run the
664
cfw.add(ByteCode.INVOKESTATIC,
665
"org/mozilla/javascript/JavaAdapter",
667
"(Lorg/mozilla/javascript/Scriptable;" +
668
"Ljava/lang/Object;Ljava/lang/String;[Ljava/lang/Object;)",
669
"Ljava/lang/Object;");
671
if (returnType.equals(Void.TYPE)) {
672
cfw.add(ByteCode.POP);
673
cfw.add(ByteCode.RETURN);
675
generateReturnResult(cfw, returnType);
677
cfw.stopMethod((short)(scopeLocal + 3), null);
681
* Generates code to push typed parameters onto the operand stack
682
* prior to a direct Java method call.
684
private static int generatePushParam(ClassFileWriter cfw, int paramOffset,
687
String typeName = paramType.getName();
688
switch (typeName.charAt(0)) {
694
// load an int value, convert to double.
695
cfw.add(ByteCode.ILOAD, paramOffset++);
698
// load a long, convert to double.
699
cfw.add(ByteCode.LLOAD, paramOffset);
703
// load a float, convert to double.
704
cfw.add(ByteCode.FLOAD, paramOffset++);
707
cfw.add(ByteCode.DLOAD, paramOffset);
715
* Generates code to return a Java type, after calling a Java method
716
* that returns the same type.
717
* Generates the appropriate RETURN bytecode.
719
private static void generatePopResult(ClassFileWriter cfw,
722
if (retType.isPrimitive()) {
723
String typeName = retType.getName();
724
switch (typeName.charAt(0)) {
730
cfw.add(ByteCode.IRETURN);
733
cfw.add(ByteCode.LRETURN);
736
cfw.add(ByteCode.FRETURN);
739
cfw.add(ByteCode.DRETURN);
743
cfw.add(ByteCode.ARETURN);
748
* Generates a method called "super$methodName()" which can be called
749
* from JavaScript that is equivalent to calling "super.methodName()"
750
* from Java. Eventually, this may be supported directly in JavaScript.
752
private static void generateSuper(ClassFileWriter cfw,
753
String genName, String superName,
754
String methodName, String methodSignature,
755
Class[] parms, Class returnType)
757
cfw.startMethod("super$" + methodName, methodSignature,
758
ClassFileWriter.ACC_PUBLIC);
761
cfw.add(ByteCode.ALOAD, 0);
763
// push the rest of the parameters.
765
for (int i = 0; i < parms.length; i++) {
766
if (parms[i].isPrimitive()) {
767
paramOffset = generatePushParam(cfw, paramOffset, parms[i]);
769
cfw.add(ByteCode.ALOAD, paramOffset++);
773
// split the method signature at the right parentheses.
774
int rightParen = methodSignature.indexOf(')');
776
// call the superclass implementation of the method.
777
cfw.add(ByteCode.INVOKESPECIAL,
780
methodSignature.substring(0, rightParen + 1),
781
methodSignature.substring(rightParen + 1));
783
// now, handle the return type appropriately.
784
Class retType = returnType;
785
if (!retType.equals(Void.TYPE)) {
786
generatePopResult(cfw, retType);
788
cfw.add(ByteCode.RETURN);
790
cfw.stopMethod((short)(paramOffset + 1), null);
794
* Returns a fully qualified method name concatenated with its signature.
796
private static String getMethodSignature(Method method) {
797
Class[] parms = method.getParameterTypes();
798
StringBuffer sb = new StringBuffer();
800
for (int i = 0; i < parms.length; i++) {
801
Class type = parms[i];
802
appendTypeString(sb, type);
805
appendTypeString(sb, method.getReturnType());
806
return sb.toString();
809
private static StringBuffer appendTypeString(StringBuffer sb, Class type)
811
while (type.isArray()) {
813
type = type.getComponentType();
815
if (type.isPrimitive()) {
816
if (type.equals(Boolean.TYPE)) {
819
if (type.equals(Long.TYPE)) {
822
String typeName = type.getName();
823
sb.append(Character.toUpperCase(typeName.charAt(0)));
827
sb.append(type.getName().replace('.', '/'));
834
* Provides a key with which to distinguish previously generated
835
* adapter classes stored in a hash table.
837
static class ClassSignature {
840
Object[] mProperties; // JDK1.2: Use HashSet
842
ClassSignature(Class superClass, Class[] interfaces, Scriptable jsObj) {
843
mSuperClass = superClass;
844
mInterfaces = interfaces;
845
mProperties = ScriptableObject.getPropertyIds(jsObj);
848
public boolean equals(Object obj) {
849
if (obj instanceof ClassSignature) {
850
ClassSignature sig = (ClassSignature) obj;
851
if (mSuperClass == sig.mSuperClass) {
852
Class[] interfaces = sig.mInterfaces;
853
if (mInterfaces != interfaces) {
854
if (mInterfaces == null || interfaces == null)
856
if (mInterfaces.length != interfaces.length)
858
for (int i=0; i < interfaces.length; i++)
859
if (mInterfaces[i] != interfaces[i])
862
if (mProperties.length != sig.mProperties.length)
864
for (int i=0; i < mProperties.length; i++) {
865
if (!mProperties[i].equals(sig.mProperties[i]))
874
public int hashCode() {
875
return mSuperClass.hashCode();
879
private static int serial;
880
private static DefiningClassLoader classLoader;
881
private static Hashtable generatedClasses = new Hashtable(7);
1
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
3
* The contents of this file are subject to the Netscape Public
4
* License Version 1.1 (the "License"); you may not use this file
5
* except in compliance with the License. You may obtain a copy of
6
* the License at http://www.mozilla.org/NPL/
8
* Software distributed under the License is distributed on an "AS
9
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
10
* implied. See the License for the specific language governing
11
* rights and limitations under the License.
13
* The Original Code is Rhino code, released
16
* The Initial Developer of the Original Code is Netscape
17
* Communications Corporation. Portions created by Netscape are
18
* Copyright (C) 1997-1999 Netscape Communications Corporation. All
31
* Alternatively, the contents of this file may be used under the
32
* terms of the GNU Public License (the "GPL"), in which case the
33
* provisions of the GPL are applicable instead of those above.
34
* If you wish to allow use of your version of this file only
35
* under the terms of the GPL and not to allow others to use your
36
* version of this file under the NPL, indicate your decision by
37
* deleting the provisions above and replace them with the notice
38
* and other provisions required by the GPL. If you do not delete
39
* the provisions above, a recipient may use your version of this
40
* file under either the NPL or the GPL.
43
package org.mozilla.javascript;
45
import org.mozilla.classfile.*;
46
import java.lang.reflect.*;
50
public final class JavaAdapter implements IdFunctionCall
53
* Provides a key with which to distinguish previously generated
54
* adapter classes stored in a hash table.
56
static class JavaAdapterSignature
62
JavaAdapterSignature(Class superClass, Class[] interfaces,
65
this.superClass = superClass;
66
this.interfaces = interfaces;
70
public boolean equals(Object obj)
72
if (!(obj instanceof JavaAdapterSignature))
74
JavaAdapterSignature sig = (JavaAdapterSignature) obj;
75
if (superClass != sig.superClass)
77
if (interfaces != sig.interfaces) {
78
if (interfaces.length != sig.interfaces.length)
80
for (int i=0; i < interfaces.length; i++)
81
if (interfaces[i] != sig.interfaces[i])
84
if (names.size() != sig.names.size())
86
ObjToIntMap.Iterator iter = new ObjToIntMap.Iterator(names);
87
for (iter.start(); !iter.done(); iter.next()) {
88
String name = (String)iter.getKey();
89
int arity = iter.getValue();
90
if (arity != names.get(name, arity + 1))
98
return superClass.hashCode()
99
| (0x9e3779b9 * (names.size() | (interfaces.length << 16)));
103
public static void init(Context cx, Scriptable scope, boolean sealed)
105
JavaAdapter obj = new JavaAdapter();
106
IdFunctionObject ctor = new IdFunctionObject(obj, FTAG, Id_JavaAdapter,
107
"JavaAdapter", 1, scope);
108
ctor.markAsConstructor(null);
112
ctor.exportAsScopeProperty();
115
public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
116
Scriptable thisObj, Object[] args)
118
if (f.hasTag(FTAG)) {
119
if (f.methodId() == Id_JavaAdapter) {
120
return js_createAdpter(cx, scope, args);
126
public static Object convertResult(Object result, Class c)
128
if (result == Undefined.instance &&
129
(c != ScriptRuntime.ObjectClass &&
130
c != ScriptRuntime.StringClass))
132
// Avoid an error for an undefined value; return null instead.
135
return NativeJavaObject.coerceType(c, result, true);
138
public static Scriptable createAdapterWrapper(Scriptable obj,
141
Scriptable scope = ScriptableObject.getTopLevelScope(obj);
142
NativeJavaObject res = new NativeJavaObject(scope, adapter, null);
143
res.setPrototype(obj);
147
public static Object getAdapterSelf(Class adapterClass, Object adapter)
148
throws NoSuchFieldException, IllegalAccessException
150
Field self = adapterClass.getDeclaredField("self");
151
return self.get(adapter);
154
static Object js_createAdpter(Context cx, Scriptable scope, Object[] args)
158
throw ScriptRuntime.typeError0("msg.adapter.zero.args");
161
Class superClass = null;
162
Class[] intfs = new Class[N - 1];
163
int interfaceCount = 0;
164
for (int i = 0; i != N - 1; ++i) {
165
Object arg = args[i];
166
if (!(arg instanceof NativeJavaClass)) {
167
throw ScriptRuntime.typeError2("msg.not.java.class.arg",
169
ScriptRuntime.toString(arg));
171
Class c = ((NativeJavaClass) arg).getClassObject();
172
if (!c.isInterface()) {
173
if (superClass != null) {
174
throw ScriptRuntime.typeError2("msg.only.one.super",
175
superClass.getName(), c.getName());
179
intfs[interfaceCount++] = c;
183
if (superClass == null)
184
superClass = ScriptRuntime.ObjectClass;
186
Class[] interfaces = new Class[interfaceCount];
187
System.arraycopy(intfs, 0, interfaces, 0, interfaceCount);
188
Scriptable obj = ScriptRuntime.toObject(cx, scope, args[N - 1]);
190
Class adapterClass = getAdapterClass(scope, superClass, interfaces,
193
Class[] ctorParms = { ScriptRuntime.ScriptableClass };
194
Object[] ctorArgs = { obj };
196
Object adapter = adapterClass.getConstructor(ctorParms).
197
newInstance(ctorArgs);
198
return getAdapterSelf(adapterClass, adapter);
199
} catch (Exception ex) {
200
throw Context.throwAsScriptRuntimeEx(ex);
204
// Needed by NativeJavaObject serializer
205
public static void writeAdapterObject(Object javaObject,
206
ObjectOutputStream out)
209
Class cl = javaObject.getClass();
210
out.writeObject(cl.getSuperclass().getName());
212
Class[] interfaces = cl.getInterfaces();
213
String[] interfaceNames = new String[interfaces.length];
215
for (int i=0; i < interfaces.length; i++)
216
interfaceNames[i] = interfaces[i].getName();
218
out.writeObject(interfaceNames);
221
Object delegee = cl.getField("delegee").get(javaObject);
222
out.writeObject(delegee);
224
} catch (IllegalAccessException e) {
225
} catch (NoSuchFieldException e) {
227
throw new IOException();
230
// Needed by NativeJavaObject de-serializer
231
public static Object readAdapterObject(Scriptable self,
232
ObjectInputStream in)
233
throws IOException, ClassNotFoundException
235
Class superClass = Class.forName((String)in.readObject());
237
String[] interfaceNames = (String[])in.readObject();
238
Class[] interfaces = new Class[interfaceNames.length];
240
for (int i=0; i < interfaceNames.length; i++)
241
interfaces[i] = Class.forName(interfaceNames[i]);
243
Scriptable delegee = (Scriptable)in.readObject();
245
Class adapterClass = getAdapterClass(self, superClass, interfaces,
248
Class[] ctorParms = {
249
ScriptRuntime.ScriptableClass,
250
ScriptRuntime.ScriptableClass
252
Object[] ctorArgs = { delegee, self };
254
return adapterClass.getConstructor(ctorParms).newInstance(ctorArgs);
255
} catch(InstantiationException e) {
256
} catch(IllegalAccessException e) {
257
} catch(InvocationTargetException e) {
258
} catch(NoSuchMethodException e) {
261
throw new ClassNotFoundException("adapter");
264
private static ObjToIntMap getObjectFunctionNames(Scriptable obj)
266
Object[] ids = ScriptableObject.getPropertyIds(obj);
267
ObjToIntMap map = new ObjToIntMap(ids.length);
268
for (int i = 0; i != ids.length; ++i) {
269
if (!(ids[i] instanceof String))
271
String id = (String) ids[i];
272
Object value = ScriptableObject.getProperty(obj, id);
273
if (value instanceof Function) {
274
Function f = (Function)value;
275
int length = ScriptRuntime.toInt32(
276
ScriptableObject.getProperty(f, "length"));
286
private static Class getAdapterClass(Scriptable scope, Class superClass,
287
Class[] interfaces, Scriptable obj)
289
ClassCache cache = ClassCache.get(scope);
290
Hashtable generated = cache.javaAdapterGeneratedClasses;
292
ObjToIntMap names = getObjectFunctionNames(obj);
293
JavaAdapterSignature sig;
294
sig = new JavaAdapterSignature(superClass, interfaces, names);
295
Class adapterClass = (Class) generated.get(sig);
296
if (adapterClass == null) {
297
String adapterName = "adapter"
298
+ cache.newClassSerialNumber();
299
byte[] code = createAdapterCode(names, adapterName,
300
superClass, interfaces, null);
302
adapterClass = loadAdapterClass(adapterName, code);
303
if (cache.isCachingEnabled()) {
304
generated.put(sig, adapterClass);
310
public static byte[] createAdapterCode(ObjToIntMap functionNames,
314
String scriptClassName)
316
ClassFileWriter cfw = new ClassFileWriter(adapterName,
317
superClass.getName(),
319
cfw.addField("delegee", "Lorg/mozilla/javascript/Scriptable;",
320
(short) (ClassFileWriter.ACC_PUBLIC |
321
ClassFileWriter.ACC_FINAL));
322
cfw.addField("self", "Lorg/mozilla/javascript/Scriptable;",
323
(short) (ClassFileWriter.ACC_PUBLIC |
324
ClassFileWriter.ACC_FINAL));
325
int interfacesCount = interfaces == null ? 0 : interfaces.length;
326
for (int i=0; i < interfacesCount; i++) {
327
if (interfaces[i] != null)
328
cfw.addInterface(interfaces[i].getName());
331
String superName = superClass.getName().replace('.', '/');
332
generateCtor(cfw, adapterName, superName);
333
generateSerialCtor(cfw, adapterName, superName);
334
if (scriptClassName != null)
335
generateEmptyCtor(cfw, adapterName, superName, scriptClassName);
337
ObjToIntMap generatedOverrides = new ObjToIntMap();
338
ObjToIntMap generatedMethods = new ObjToIntMap();
340
// generate methods to satisfy all specified interfaces.
341
for (int i = 0; i < interfacesCount; i++) {
342
Method[] methods = interfaces[i].getMethods();
343
for (int j = 0; j < methods.length; j++) {
344
Method method = methods[j];
345
int mods = method.getModifiers();
346
if (Modifier.isStatic(mods) || Modifier.isFinal(mods)) {
349
String methodName = method.getName();
350
Class[] argTypes = method.getParameterTypes();
351
if (!functionNames.has(methodName)) {
353
superClass.getMethod(methodName, argTypes);
354
// The class we're extending implements this method and
355
// the JavaScript object doesn't have an override. See
358
} catch (NoSuchMethodException e) {
359
// Not implemented by superclass; fall through
362
// make sure to generate only one instance of a particular
364
String methodSignature = getMethodSignature(method, argTypes);
365
String methodKey = methodName + methodSignature;
366
if (! generatedOverrides.has(methodKey)) {
367
generateMethod(cfw, adapterName, methodName,
368
argTypes, method.getReturnType());
369
generatedOverrides.put(methodKey, 0);
370
generatedMethods.put(methodName, 0);
375
// Now, go through the superclasses methods, checking for abstract
376
// methods or additional methods to override.
378
// generate any additional overrides that the object might contain.
379
Method[] methods = superClass.getMethods();
380
for (int j = 0; j < methods.length; j++) {
381
Method method = methods[j];
382
int mods = method.getModifiers();
383
if (Modifier.isStatic(mods) || Modifier.isFinal(mods))
385
// if a method is marked abstract, must implement it or the
386
// resulting class won't be instantiable. otherwise, if the object
387
// has a property of the same name, then an override is intended.
388
boolean isAbstractMethod = Modifier.isAbstract(mods);
389
String methodName = method.getName();
390
if (isAbstractMethod || functionNames.has(methodName)) {
391
// make sure to generate only one instance of a particular
393
Class[] argTypes = method.getParameterTypes();
394
String methodSignature = getMethodSignature(method, argTypes);
395
String methodKey = methodName + methodSignature;
396
if (! generatedOverrides.has(methodKey)) {
397
generateMethod(cfw, adapterName, methodName,
398
argTypes, method.getReturnType());
399
generatedOverrides.put(methodKey, 0);
400
generatedMethods.put(methodName, 0);
402
// if a method was overridden, generate a "super$method"
403
// which lets the delegate call the superclass' version.
404
if (!isAbstractMethod) {
405
generateSuper(cfw, adapterName, superName,
406
methodName, methodSignature,
407
argTypes, method.getReturnType());
412
// Generate Java methods for remaining properties that are not
414
ObjToIntMap.Iterator iter = new ObjToIntMap.Iterator(functionNames);
415
for (iter.start(); !iter.done(); iter.next()) {
416
String functionName = (String)iter.getKey();
417
if (generatedMethods.has(functionName))
419
int length = iter.getValue();
420
Class[] parms = new Class[length];
421
for (int k=0; k < length; k++)
422
parms[k] = ScriptRuntime.ObjectClass;
423
generateMethod(cfw, adapterName, functionName, parms,
424
ScriptRuntime.ObjectClass);
426
return cfw.toByteArray();
429
static Class loadAdapterClass(String className, byte[] classBytes)
431
GeneratedClassLoader loader
432
= SecurityController.createLoader(null, null);
433
Class result = loader.defineClass(className, classBytes);
434
loader.linkClass(result);
438
public static Function getFunction(Scriptable obj, String functionName)
440
Object x = ScriptableObject.getProperty(obj, functionName);
441
if (x == Scriptable.NOT_FOUND) {
442
// This method used to swallow the exception from calling
443
// an undefined method. People have come to depend on this
444
// somewhat dubious behavior. It allows people to avoid
445
// implementing listener methods that they don't care about,
449
if (!(x instanceof Function))
450
throw ScriptRuntime.notFunctionError(x, functionName);
456
* Utility method which dynamically binds a Context to the current thread,
457
* if none already exists.
459
public static Object callMethod(final Scriptable thisObj,
460
final Function f, final Object[] args,
461
final long argsToWrap)
464
// See comments in getFunction
465
return Undefined.instance;
467
final Scriptable scope = f.getParentScope();
468
if (argsToWrap == 0) {
469
return Context.call(null, f, scope, thisObj, args);
472
Context cx = Context.getCurrentContext();
474
return doCall(cx, scope, thisObj, f, args, argsToWrap);
476
ContextFactory factory = ScriptRuntime.getContextFactory(scope);
477
return factory.call(new ContextAction() {
478
public Object run(Context cx)
480
return doCall(cx, scope, thisObj, f, args, argsToWrap);
486
private static Object doCall(Context cx, Scriptable scope,
487
Scriptable thisObj, Function f,
488
Object[] args, long argsToWrap)
490
// Wrap the rest of objects
491
for (int i = 0; i != args.length; ++i) {
492
if (0 != (argsToWrap & (1 << i))) {
493
Object arg = args[i];
494
if (!(arg instanceof Scriptable)) {
495
args[i] = cx.getWrapFactory().wrap(cx, scope, arg,
500
return f.call(cx, scope, thisObj, args);
503
public static Scriptable runScript(final Script script)
505
return (Scriptable)Context.call(new ContextAction() {
506
public Object run(Context cx)
508
ScriptableObject global = ScriptRuntime.getGlobal(cx);
509
script.exec(cx, global);
515
private static void generateCtor(ClassFileWriter cfw, String adapterName,
518
cfw.startMethod("<init>",
519
"(Lorg/mozilla/javascript/Scriptable;)V",
520
ClassFileWriter.ACC_PUBLIC);
522
// Invoke base class constructor
523
cfw.add(ByteCode.ALOAD_0); // this
524
cfw.addInvoke(ByteCode.INVOKESPECIAL, superName, "<init>", "()V");
526
// Save parameter in instance variable "delegee"
527
cfw.add(ByteCode.ALOAD_0); // this
528
cfw.add(ByteCode.ALOAD_1); // first arg
529
cfw.add(ByteCode.PUTFIELD, adapterName, "delegee",
530
"Lorg/mozilla/javascript/Scriptable;");
532
cfw.add(ByteCode.ALOAD_0); // this for the following PUTFIELD for self
533
// create a wrapper object to be used as "this" in method calls
534
cfw.add(ByteCode.ALOAD_1); // the Scriptable
535
cfw.add(ByteCode.ALOAD_0); // this
536
cfw.addInvoke(ByteCode.INVOKESTATIC,
537
"org/mozilla/javascript/JavaAdapter",
538
"createAdapterWrapper",
539
"(Lorg/mozilla/javascript/Scriptable;"
540
+"Ljava/lang/Object;"
541
+")Lorg/mozilla/javascript/Scriptable;");
542
cfw.add(ByteCode.PUTFIELD, adapterName, "self",
543
"Lorg/mozilla/javascript/Scriptable;");
545
cfw.add(ByteCode.RETURN);
546
cfw.stopMethod((short)3); // 2: this + delegee
549
private static void generateSerialCtor(ClassFileWriter cfw,
553
cfw.startMethod("<init>",
554
"(Lorg/mozilla/javascript/Scriptable;"
555
+"Lorg/mozilla/javascript/Scriptable;"
557
ClassFileWriter.ACC_PUBLIC);
559
// Invoke base class constructor
560
cfw.add(ByteCode.ALOAD_0); // this
561
cfw.addInvoke(ByteCode.INVOKESPECIAL, superName, "<init>", "()V");
563
// Save parameter in instance variable "delegee"
564
cfw.add(ByteCode.ALOAD_0); // this
565
cfw.add(ByteCode.ALOAD_1); // first arg
566
cfw.add(ByteCode.PUTFIELD, adapterName, "delegee",
567
"Lorg/mozilla/javascript/Scriptable;");
570
cfw.add(ByteCode.ALOAD_0); // this
571
cfw.add(ByteCode.ALOAD_2); // second arg
572
cfw.add(ByteCode.PUTFIELD, adapterName, "self",
573
"Lorg/mozilla/javascript/Scriptable;");
575
cfw.add(ByteCode.RETURN);
576
cfw.stopMethod((short)20); // TODO: magic number "20"
579
private static void generateEmptyCtor(ClassFileWriter cfw,
582
String scriptClassName)
584
cfw.startMethod("<init>", "()V", ClassFileWriter.ACC_PUBLIC);
586
// Invoke base class constructor
587
cfw.add(ByteCode.ALOAD_0); // this
588
cfw.addInvoke(ByteCode.INVOKESPECIAL, superName, "<init>", "()V");
591
cfw.add(ByteCode.NEW, scriptClassName);
592
cfw.add(ByteCode.DUP);
593
cfw.addInvoke(ByteCode.INVOKESPECIAL, scriptClassName, "<init>", "()V");
595
// Run script and save resulting scope
596
cfw.addInvoke(ByteCode.INVOKESTATIC,
597
"org/mozilla/javascript/JavaAdapter",
599
"(Lorg/mozilla/javascript/Script;"
600
+")Lorg/mozilla/javascript/Scriptable;");
601
cfw.add(ByteCode.ASTORE_1);
603
// Save the Scriptable in instance variable "delegee"
604
cfw.add(ByteCode.ALOAD_0); // this
605
cfw.add(ByteCode.ALOAD_1); // the Scriptable
606
cfw.add(ByteCode.PUTFIELD, adapterName, "delegee",
607
"Lorg/mozilla/javascript/Scriptable;");
609
cfw.add(ByteCode.ALOAD_0); // this for the following PUTFIELD for self
610
// create a wrapper object to be used as "this" in method calls
611
cfw.add(ByteCode.ALOAD_1); // the Scriptable
612
cfw.add(ByteCode.ALOAD_0); // this
613
cfw.addInvoke(ByteCode.INVOKESTATIC,
614
"org/mozilla/javascript/JavaAdapter",
615
"createAdapterWrapper",
616
"(Lorg/mozilla/javascript/Scriptable;"
617
+"Ljava/lang/Object;"
618
+")Lorg/mozilla/javascript/Scriptable;");
619
cfw.add(ByteCode.PUTFIELD, adapterName, "self",
620
"Lorg/mozilla/javascript/Scriptable;");
622
cfw.add(ByteCode.RETURN);
623
cfw.stopMethod((short)2); // this + delegee
627
* Generates code to wrap Java arguments into Object[].
628
* Non-primitive Java types are left as is pending convertion
629
* in the helper method. Leaves the array object on the top of the stack.
631
static void generatePushWrappedArgs(ClassFileWriter cfw,
636
cfw.addPush(arrayLength);
637
cfw.add(ByteCode.ANEWARRAY, "java/lang/Object");
639
for (int i = 0; i != argTypes.length; ++i) {
640
cfw.add(ByteCode.DUP); // duplicate array reference
642
paramOffset += generateWrapArg(cfw, paramOffset, argTypes[i]);
643
cfw.add(ByteCode.AASTORE);
648
* Generates code to wrap Java argument into Object.
649
* Non-primitive Java types are left unconverted pending convertion
650
* in the helper method. Leaves the wrapper object on the top of the stack.
652
private static int generateWrapArg(ClassFileWriter cfw, int paramOffset,
656
if (!argType.isPrimitive()) {
657
cfw.add(ByteCode.ALOAD, paramOffset);
659
} else if (argType == Boolean.TYPE) {
660
// wrap boolean values with java.lang.Boolean.
661
cfw.add(ByteCode.NEW, "java/lang/Boolean");
662
cfw.add(ByteCode.DUP);
663
cfw.add(ByteCode.ILOAD, paramOffset);
664
cfw.addInvoke(ByteCode.INVOKESPECIAL, "java/lang/Boolean",
667
} else if (argType == Character.TYPE) {
668
// Create a string of length 1 using the character parameter.
669
cfw.add(ByteCode.ILOAD, paramOffset);
670
cfw.addInvoke(ByteCode.INVOKESTATIC, "java/lang/String",
671
"valueOf", "(C)Ljava/lang/String;");
674
// convert all numeric values to java.lang.Double.
675
cfw.add(ByteCode.NEW, "java/lang/Double");
676
cfw.add(ByteCode.DUP);
677
String typeName = argType.getName();
678
switch (typeName.charAt(0)) {
682
// load an int value, convert to double.
683
cfw.add(ByteCode.ILOAD, paramOffset);
684
cfw.add(ByteCode.I2D);
687
// load a long, convert to double.
688
cfw.add(ByteCode.LLOAD, paramOffset);
689
cfw.add(ByteCode.L2D);
693
// load a float, convert to double.
694
cfw.add(ByteCode.FLOAD, paramOffset);
695
cfw.add(ByteCode.F2D);
698
cfw.add(ByteCode.DLOAD, paramOffset);
702
cfw.addInvoke(ByteCode.INVOKESPECIAL, "java/lang/Double",
709
* Generates code to convert a wrapped value type to a primitive type.
710
* Handles unwrapping java.lang.Boolean, and java.lang.Number types.
711
* Generates the appropriate RETURN bytecode.
713
static void generateReturnResult(ClassFileWriter cfw, Class retType,
714
boolean callConvertResult)
716
// wrap boolean values with java.lang.Boolean, convert all other
717
// primitive values to java.lang.Double.
718
if (retType == Void.TYPE) {
719
cfw.add(ByteCode.POP);
720
cfw.add(ByteCode.RETURN);
722
} else if (retType == Boolean.TYPE) {
723
cfw.addInvoke(ByteCode.INVOKESTATIC,
724
"org/mozilla/javascript/Context",
725
"toBoolean", "(Ljava/lang/Object;)Z");
726
cfw.add(ByteCode.IRETURN);
728
} else if (retType == Character.TYPE) {
729
// characters are represented as strings in JavaScript.
730
// return the first character.
731
// first convert the value to a string if possible.
732
cfw.addInvoke(ByteCode.INVOKESTATIC,
733
"org/mozilla/javascript/Context",
735
"(Ljava/lang/Object;)Ljava/lang/String;");
736
cfw.add(ByteCode.ICONST_0);
737
cfw.addInvoke(ByteCode.INVOKEVIRTUAL, "java/lang/String",
739
cfw.add(ByteCode.IRETURN);
741
} else if (retType.isPrimitive()) {
742
cfw.addInvoke(ByteCode.INVOKESTATIC,
743
"org/mozilla/javascript/Context",
744
"toNumber", "(Ljava/lang/Object;)D");
745
String typeName = retType.getName();
746
switch (typeName.charAt(0)) {
750
cfw.add(ByteCode.D2I);
751
cfw.add(ByteCode.IRETURN);
754
cfw.add(ByteCode.D2L);
755
cfw.add(ByteCode.LRETURN);
758
cfw.add(ByteCode.D2F);
759
cfw.add(ByteCode.FRETURN);
762
cfw.add(ByteCode.DRETURN);
765
throw new RuntimeException("Unexpected return type " +
770
String retTypeStr = retType.getName();
771
if (callConvertResult) {
772
cfw.addLoadConstant(retTypeStr);
773
cfw.addInvoke(ByteCode.INVOKESTATIC,
776
"(Ljava/lang/String;)Ljava/lang/Class;");
778
cfw.addInvoke(ByteCode.INVOKESTATIC,
779
"org/mozilla/javascript/JavaAdapter",
781
"(Ljava/lang/Object;"
783
+")Ljava/lang/Object;");
785
// Now cast to return type
786
cfw.add(ByteCode.CHECKCAST, retTypeStr);
787
cfw.add(ByteCode.ARETURN);
791
private static void generateMethod(ClassFileWriter cfw, String genName,
792
String methodName, Class[] parms,
795
StringBuffer sb = new StringBuffer();
796
int firstLocal = appendMethodSignature(parms, returnType, sb);
797
String methodSignature = sb.toString();
798
cfw.startMethod(methodName, methodSignature,
799
ClassFileWriter.ACC_PUBLIC);
801
int FUNCTION = firstLocal;
802
int LOCALS_END = firstLocal + 1;
804
cfw.add(ByteCode.ALOAD_0);
805
cfw.add(ByteCode.GETFIELD, genName, "delegee",
806
"Lorg/mozilla/javascript/Scriptable;");
807
cfw.addPush(methodName);
808
cfw.addInvoke(ByteCode.INVOKESTATIC,
809
"org/mozilla/javascript/JavaAdapter",
811
"(Lorg/mozilla/javascript/Scriptable;"
812
+"Ljava/lang/String;"
813
+")Lorg/mozilla/javascript/Function;");
814
cfw.add(ByteCode.DUP);
815
cfw.addAStore(FUNCTION);
817
// Prepare stack to call calMethod
819
cfw.add(ByteCode.ALOAD_0);
820
cfw.add(ByteCode.GETFIELD, genName, "self",
821
"Lorg/mozilla/javascript/Scriptable;");
823
cfw.addALoad(FUNCTION);
826
generatePushWrappedArgs(cfw, parms, parms.length);
828
// push bits to indicate which parameters should be wrapped
829
if (parms.length > 64) {
830
// If it will be an issue, then passing a static boolean array
831
// can be an option, but for now using simple bitmask
832
throw Context.reportRuntimeError0(
833
"JavaAdapter can not subclass methods with more then"
836
long convertionMask = 0;
837
for (int i = 0; i != parms.length; ++i) {
838
if (!parms[i].isPrimitive()) {
839
convertionMask |= (1 << i);
842
cfw.addPush(convertionMask);
844
// go through utility method, which creates a Context to run the
846
cfw.addInvoke(ByteCode.INVOKESTATIC,
847
"org/mozilla/javascript/JavaAdapter",
849
"(Lorg/mozilla/javascript/Scriptable;"
850
+"Lorg/mozilla/javascript/Function;"
851
+"[Ljava/lang/Object;"
853
+")Ljava/lang/Object;");
855
generateReturnResult(cfw, returnType, true);
857
cfw.stopMethod((short)LOCALS_END);
861
* Generates code to push typed parameters onto the operand stack
862
* prior to a direct Java method call.
864
private static int generatePushParam(ClassFileWriter cfw, int paramOffset,
867
if (!paramType.isPrimitive()) {
868
cfw.addALoad(paramOffset);
871
String typeName = paramType.getName();
872
switch (typeName.charAt(0)) {
878
// load an int value, convert to double.
879
cfw.addILoad(paramOffset);
882
// load a long, convert to double.
883
cfw.addLLoad(paramOffset);
886
// load a float, convert to double.
887
cfw.addFLoad(paramOffset);
890
cfw.addDLoad(paramOffset);
897
* Generates code to return a Java type, after calling a Java method
898
* that returns the same type.
899
* Generates the appropriate RETURN bytecode.
901
private static void generatePopResult(ClassFileWriter cfw,
904
if (retType.isPrimitive()) {
905
String typeName = retType.getName();
906
switch (typeName.charAt(0)) {
912
cfw.add(ByteCode.IRETURN);
915
cfw.add(ByteCode.LRETURN);
918
cfw.add(ByteCode.FRETURN);
921
cfw.add(ByteCode.DRETURN);
925
cfw.add(ByteCode.ARETURN);
930
* Generates a method called "super$methodName()" which can be called
931
* from JavaScript that is equivalent to calling "super.methodName()"
932
* from Java. Eventually, this may be supported directly in JavaScript.
934
private static void generateSuper(ClassFileWriter cfw,
935
String genName, String superName,
936
String methodName, String methodSignature,
937
Class[] parms, Class returnType)
939
cfw.startMethod("super$" + methodName, methodSignature,
940
ClassFileWriter.ACC_PUBLIC);
943
cfw.add(ByteCode.ALOAD, 0);
945
// push the rest of the parameters.
947
for (int i = 0; i < parms.length; i++) {
948
paramOffset += generatePushParam(cfw, paramOffset, parms[i]);
951
// call the superclass implementation of the method.
952
cfw.addInvoke(ByteCode.INVOKESPECIAL,
957
// now, handle the return type appropriately.
958
Class retType = returnType;
959
if (!retType.equals(Void.TYPE)) {
960
generatePopResult(cfw, retType);
962
cfw.add(ByteCode.RETURN);
964
cfw.stopMethod((short)(paramOffset + 1));
968
* Returns a fully qualified method name concatenated with its signature.
970
private static String getMethodSignature(Method method, Class[] argTypes)
972
StringBuffer sb = new StringBuffer();
973
appendMethodSignature(argTypes, method.getReturnType(), sb);
974
return sb.toString();
977
static int appendMethodSignature(Class[] argTypes,
982
int firstLocal = 1 + argTypes.length; // includes this.
983
for (int i = 0; i < argTypes.length; i++) {
984
Class type = argTypes[i];
985
appendTypeString(sb, type);
986
if (type == Long.TYPE || type == Double.TYPE) {
987
// adjust for duble slot
992
appendTypeString(sb, returnType);
996
private static StringBuffer appendTypeString(StringBuffer sb, Class type)
998
while (type.isArray()) {
1000
type = type.getComponentType();
1002
if (type.isPrimitive()) {
1004
if (type == Boolean.TYPE) {
1006
} else if (type == Long.TYPE) {
1009
String typeName = type.getName();
1010
typeLetter = Character.toUpperCase(typeName.charAt(0));
1012
sb.append(typeLetter);
1015
sb.append(type.getName().replace('.', '/'));
1021
static int[] getArgsToConvert(Class[] argTypes)
1024
for (int i = 0; i != argTypes.length; ++i) {
1025
if (!argTypes[i].isPrimitive())
1030
int[] array = new int[count];
1032
for (int i = 0; i != argTypes.length; ++i) {
1033
if (!argTypes[i].isPrimitive())
1039
private static final Object FTAG = new Object();
1040
private static final int Id_JavaAdapter = 1;