~ubuntu-branches/ubuntu/karmic/rhino/karmic

« back to all changes in this revision

Viewing changes to src/org/mozilla/javascript/JavaAdapter.java

  • Committer: Bazaar Package Importer
  • Author(s): Jerry Haltom
  • Date: 2005-03-19 16:56:07 UTC
  • mto: (11.1.1 squeeze)
  • mto: This revision was merged to the branch mainline in revision 3.
  • Revision ID: james.westby@ubuntu.com-20050319165607-geu3j3fnqlkpqkh1
Tags: upstream-1.6.R1
ImportĀ upstreamĀ versionĀ 1.6.R1

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2
 
 *
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/
7
 
 *
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.
12
 
 *
13
 
 * The Original Code is Rhino code, released
14
 
 * May 6, 1999.
15
 
 *
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
19
 
 * Rights Reserved.
20
 
 *
21
 
 * Contributor(s): 
22
 
 * Patrick Beard
23
 
 * Norris Boyd
24
 
 * Mike McCabe
25
 
 * Matthias Radestock
26
 
 * Andi Vajda
27
 
 * Andrew Wason
28
 
 *
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.
39
 
 */
40
 
 
41
 
package org.mozilla.javascript;
42
 
 
43
 
import org.mozilla.classfile.*;
44
 
import java.lang.reflect.*;
45
 
import java.io.*;
46
 
import java.util.*;
47
 
 
48
 
public class JavaAdapter extends ScriptableObject {
49
 
    public boolean equals(Object obj) {
50
 
        return super.equals(obj);
51
 
    }
52
 
    
53
 
    public String getClassName() {
54
 
        return "JavaAdapter";
55
 
    }
56
 
 
57
 
    public static Object convertResult(Object result, String classname)
58
 
        throws ClassNotFoundException 
59
 
    {
60
 
        Class c = ScriptRuntime.loadClassName(classname);
61
 
        if (result == Undefined.instance && 
62
 
            (c != ScriptRuntime.ObjectClass && 
63
 
             c != ScriptRuntime.StringClass))
64
 
        {
65
 
            // Avoid an error for an undefined value; return null instead.
66
 
            return null;
67
 
        }
68
 
        return NativeJavaObject.coerceType(c, result);
69
 
    }
70
 
 
71
 
    public static Scriptable setAdapterProto(Scriptable obj, Object adapter) {
72
 
        Scriptable res = ScriptRuntime.toObject(ScriptableObject.getTopLevelScope(obj),adapter);
73
 
        res.setPrototype(obj);
74
 
        return res;
75
 
    }
76
 
 
77
 
    public static Object getAdapterSelf(Class adapterClass, Object adapter)
78
 
        throws NoSuchFieldException, IllegalAccessException
79
 
    {
80
 
        Field self = adapterClass.getDeclaredField("self");
81
 
        return self.get(adapter);
82
 
    }
83
 
 
84
 
    public static Object jsConstructor(Context cx, Object[] args, 
85
 
                                       Function ctorObj, boolean inNewExpr)
86
 
        throws InstantiationException, NoSuchMethodException, 
87
 
               IllegalAccessException, InvocationTargetException,
88
 
               ClassNotFoundException, NoSuchFieldException
89
 
    {
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);
97
 
            }
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, 
105
 
                                                      ctorObj);
106
 
                }
107
 
                superClass = c;
108
 
            } else {
109
 
                intfs[interfaceCount++] = c;
110
 
            }
111
 
        }
112
 
        
113
 
        if (superClass == null)
114
 
            superClass = Object.class;
115
 
        
116
 
        Class[] interfaces = new Class[interfaceCount];
117
 
        System.arraycopy(intfs, 0, interfaces, 0, interfaceCount);
118
 
        Scriptable obj = (Scriptable) args[args.length - 1];
119
 
        
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, 
126
 
                                              null, null);
127
 
            generatedClasses.put(sig, adapterClass);
128
 
        }
129
 
        
130
 
        Class[] ctorParms = { Scriptable.class };
131
 
        Object[] ctorArgs = { obj };
132
 
        Object adapter = adapterClass.getConstructor(ctorParms).newInstance(ctorArgs);
133
 
        return getAdapterSelf(adapterClass, adapter);
134
 
    }
135
 
 
136
 
    public static Class createAdapterClass(Context cx, Scriptable jsObj,
137
 
                                           String adapterName, Class superClass, 
138
 
                                           Class[] interfaces, 
139
 
                                           String scriptClassName,
140
 
                                           ClassNameHelper nameHelper)
141
 
        throws ClassNotFoundException
142
 
    {
143
 
        ClassFileWriter cfw = new ClassFileWriter(adapterName, 
144
 
                                                  superClass.getName(), 
145
 
                                                  "<adapter>");
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());
156
 
        }
157
 
        
158
 
        String superName = superClass.getName().replace('.', '/');
159
 
        generateCtor(cfw, adapterName, superName);
160
 
        if (scriptClassName != null)
161
 
            generateEmptyCtor(cfw, adapterName, superName, scriptClassName);
162
 
        
163
 
        Hashtable generatedOverrides = new Hashtable();
164
 
        Hashtable generatedMethods = new Hashtable();
165
 
        
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) ||
173
 
                    jsObj == null)
174
 
                {
175
 
                    continue;
176
 
                }
177
 
                if (!ScriptableObject.hasProperty(jsObj, method.getName())) {
178
 
                    try {
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
183
 
                        // bug 61226.
184
 
                        continue;
185
 
                    } catch (NoSuchMethodException e) {
186
 
                        // Not implemented by superclass; fall through
187
 
                    }
188
 
                }
189
 
                // make sure to generate only one instance of a particular 
190
 
                // method/signature.
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);
199
 
                }
200
 
            }
201
 
        }
202
 
 
203
 
        // Now, go through the superclasses methods, checking for abstract 
204
 
        // methods or additional methods to override.
205
 
 
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))
212
 
                continue;
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())))
219
 
            {
220
 
                // make sure to generate only one instance of a particular 
221
 
                // method/signature.
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);
231
 
                }
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());
239
 
                }
240
 
            }
241
 
        }
242
 
        
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)) 
249
 
                    continue;
250
 
                String id = (String) ids[j];
251
 
                if (generatedMethods.containsKey(id))
252
 
                    continue;
253
 
                Object f = o.get(id, o);
254
 
                int length;
255
 
                if (f instanceof Scriptable) {
256
 
                    Scriptable p = (Scriptable) f;
257
 
                    if (!(p instanceof Function))
258
 
                        continue;
259
 
                    length = (int) Context.toNumber(
260
 
                                ScriptableObject.getProperty(p, "length"));
261
 
                } else if (f instanceof FunctionNode) {
262
 
                    length = ((FunctionNode) f).getVariableTable()
263
 
                                .getParameterCount();
264
 
                } else {
265
 
                    continue;
266
 
                }
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);
271
 
            }  
272
 
        }
273
 
        ByteArrayOutputStream out = new ByteArrayOutputStream(512);
274
 
        try {
275
 
            cfw.write(out);
276
 
        }
277
 
        catch (IOException ioe) {
278
 
            throw new RuntimeException("unexpected IOException");
279
 
        }
280
 
        byte[] bytes = out.toByteArray();
281
 
        
282
 
        if (nameHelper != null) {
283
 
            if (nameHelper.getGeneratingDirectory() != null) {
284
 
                try {
285
 
                    int lastDot = adapterName.lastIndexOf('.');
286
 
                    if (lastDot != -1)
287
 
                        adapterName = adapterName.substring(lastDot+1);
288
 
                    String filename = nameHelper.getTargetClassFileName(adapterName);
289
 
                    FileOutputStream file = new FileOutputStream(filename);
290
 
                    file.write(bytes);
291
 
                    file.close();
292
 
                }
293
 
                catch (IOException iox) {
294
 
                    throw WrappedException.wrapException(iox);
295
 
                }
296
 
                return null;
297
 
            } else {
298
 
                try {
299
 
                    ClassOutput classOutput = nameHelper.getClassOutput();
300
 
                    if (classOutput != null) {
301
 
                        OutputStream cOut =
302
 
                            classOutput.getOutputStream(adapterName, true);
303
 
                        cOut.write(bytes);
304
 
                        cOut.close();
305
 
                    }
306
 
                } catch (IOException iox) {
307
 
                    throw WrappedException.wrapException(iox);
308
 
                }
309
 
            }
310
 
        }
311
 
            
312
 
        SecuritySupport ss = cx.getSecuritySupport();
313
 
        if (ss != null) {
314
 
            Object securityDomain = cx.getSecurityDomainForStackDepth(-1);
315
 
            Class result = ss.defineClass(adapterName, bytes, securityDomain);
316
 
            if (result != null)
317
 
                return result;
318
 
        } 
319
 
        if (classLoader == null)
320
 
            classLoader = new DefiningClassLoader();
321
 
        classLoader.defineClass(adapterName, bytes);
322
 
        return classLoader.loadClass(adapterName, true);
323
 
    }
324
 
    
325
 
    /**
326
 
     * Utility method which dynamically binds a Context to the current thread, 
327
 
     * if none already exists.
328
 
     */
329
 
    public static Object callMethod(Scriptable object, Object thisObj,
330
 
                                    String methodId, Object[] args) 
331
 
    {
332
 
        try {
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,
340
 
                // for instance.
341
 
                return Undefined.instance;
342
 
            }
343
 
            return ScriptRuntime.call(cx, fun, thisObj, args, object);
344
 
        } catch (JavaScriptException ex) {
345
 
            throw WrappedException.wrapException(ex);
346
 
        } finally {
347
 
            Context.exit();
348
 
        }
349
 
    }
350
 
    
351
 
    public static Scriptable toObject(Object value, Scriptable scope,
352
 
                                      Class staticType)
353
 
    {
354
 
        Context.enter();
355
 
        try {
356
 
            return Context.toObject(value, scope, staticType);
357
 
        } finally {
358
 
            Context.exit();
359
 
        }
360
 
    }
361
 
    
362
 
    private static void generateCtor(ClassFileWriter cfw, String adapterName, 
363
 
                                     String superName) 
364
 
    {
365
 
        cfw.startMethod("<init>", 
366
 
                        "(Lorg/mozilla/javascript/Scriptable;)V",
367
 
                        ClassFileWriter.ACC_PUBLIC);
368
 
        
369
 
        // Invoke base class constructor
370
 
        cfw.add(ByteCode.ALOAD_0);  // this
371
 
        cfw.add(ByteCode.INVOKESPECIAL, superName, "<init>", "()", "V");
372
 
        
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;");
378
 
 
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",
384
 
                "setAdapterProto",
385
 
                "(Lorg/mozilla/javascript/Scriptable;" +
386
 
                 "Ljava/lang/Object;)",
387
 
                "Lorg/mozilla/javascript/Scriptable;");
388
 
        
389
 
        // save the wrapper
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;");
395
 
 
396
 
        cfw.add(ByteCode.RETURN);
397
 
        cfw.stopMethod((short)20, null); // TODO: magic number "20"
398
 
    }
399
 
 
400
 
    private static void generateEmptyCtor(ClassFileWriter cfw, String adapterName, 
401
 
                                          String superName, String scriptClassName) 
402
 
    {
403
 
        cfw.startMethod("<init>", "()V", ClassFileWriter.ACC_PUBLIC);
404
 
        
405
 
        // Invoke base class constructor
406
 
        cfw.add(ByteCode.ALOAD_0);  // this
407
 
        cfw.add(ByteCode.INVOKESPECIAL, superName, "<init>", "()", "V");
408
 
        
409
 
        // Load script class
410
 
        cfw.add(ByteCode.NEW, scriptClassName);
411
 
        cfw.add(ByteCode.DUP);
412
 
        cfw.add(ByteCode.INVOKESPECIAL, scriptClassName, "<init>", "()", "V");
413
 
        
414
 
        // Run script and save resulting scope
415
 
        cfw.add(ByteCode.INVOKESTATIC,
416
 
                "org/mozilla/javascript/ScriptRuntime",
417
 
                "runScript",
418
 
                "(Lorg/mozilla/javascript/Script;)",
419
 
                "Lorg/mozilla/javascript/Scriptable;");
420
 
        cfw.add(ByteCode.ASTORE_1);
421
 
                
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;");
427
 
        
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",
433
 
                "setAdapterProto",
434
 
                "(Lorg/mozilla/javascript/Scriptable;" +
435
 
                 "Ljava/lang/Object;)",
436
 
                "Lorg/mozilla/javascript/Scriptable;");
437
 
        //save the wrapper
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;");
443
 
 
444
 
        cfw.add(ByteCode.RETURN);
445
 
        cfw.stopMethod((short)20, null); // TODO: magic number "20"
446
 
    }
447
 
 
448
 
    /**
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.
452
 
     */
453
 
    private static int generateWrapParam(ClassFileWriter cfw, int paramOffset, 
454
 
                                         Class paramType) 
455
 
    {
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");
463
 
        } else
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");
476
 
            } else {
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)) {
482
 
                case 'b':
483
 
                case 's':
484
 
                case 'i':
485
 
                    // load an int value, convert to double.
486
 
                    cfw.add(ByteCode.ILOAD, paramOffset++);
487
 
                    cfw.add(ByteCode.I2D);
488
 
                    break;
489
 
                case 'l':
490
 
                    // load a long, convert to double.
491
 
                    cfw.add(ByteCode.LLOAD, paramOffset);
492
 
                    cfw.add(ByteCode.L2D);
493
 
                    paramOffset += 2;
494
 
                    break;
495
 
                case 'f':
496
 
                    // load a float, convert to double.
497
 
                    cfw.add(ByteCode.FLOAD, paramOffset++);
498
 
                    cfw.add(ByteCode.F2D);
499
 
                    break;
500
 
                case 'd':
501
 
                    cfw.add(ByteCode.DLOAD, paramOffset);
502
 
                    paramOffset += 2;
503
 
                    break;
504
 
                }
505
 
                cfw.add(ByteCode.INVOKESPECIAL, "java/lang/Double", 
506
 
                        "<init>", "(D)", "V");
507
 
            }
508
 
        return paramOffset;
509
 
    }
510
 
 
511
 
    /**
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.
516
 
     */
517
 
    private static void generateReturnResult(ClassFileWriter cfw, 
518
 
                                             Class retType) 
519
 
    {
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;)", 
526
 
                    "Z");
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", 
538
 
                        "(I)", "C");
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;)", 
544
 
                    "D");
545
 
            String typeName = retType.getName();
546
 
            switch (typeName.charAt(0)) {
547
 
            case 'b':
548
 
            case 's':
549
 
            case 'i':
550
 
                cfw.add(ByteCode.D2I);
551
 
                cfw.add(ByteCode.IRETURN);
552
 
                break;
553
 
            case 'l':
554
 
                cfw.add(ByteCode.D2L);
555
 
                cfw.add(ByteCode.LRETURN);
556
 
                break;
557
 
            case 'f':
558
 
                cfw.add(ByteCode.D2F);
559
 
                cfw.add(ByteCode.FRETURN);
560
 
                break;
561
 
            case 'd':
562
 
                cfw.add(ByteCode.DRETURN);
563
 
                break;
564
 
            default:
565
 
                throw new RuntimeException("Unexpected return type " + 
566
 
                                           retType.toString());
567
 
            }
568
 
        } else {
569
 
            String retTypeStr = retType.getName();
570
 
            cfw.addLoadConstant(retTypeStr);
571
 
            cfw.add(ByteCode.INVOKESTATIC, 
572
 
                    "org/mozilla/javascript/JavaAdapter", 
573
 
                    "convertResult",
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);
580
 
        }
581
 
    }
582
 
 
583
 
    private static void generateMethod(ClassFileWriter cfw, String genName, 
584
 
                                       String methodName, Class[] parms,
585
 
                                       Class returnType) 
586
 
    {
587
 
        StringBuffer sb = new StringBuffer();
588
 
        sb.append('(');
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))
594
 
                arrayLocal += 2;
595
 
            else
596
 
                arrayLocal += 1;
597
 
        }
598
 
        sb.append(')');
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);
608
 
        
609
 
        // allocate a local variable to store the scope used to wrap native objects.
610
 
        short scopeLocal = (short) (arrayLocal + 1);
611
 
        boolean loadedScope = false;
612
 
 
613
 
        int paramOffset = 1;
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]);
619
 
            } else {
620
 
                // An arbitary Java object; call Context.toObject to wrap in 
621
 
                // a Scriptable object
622
 
                cfw.add(ByteCode.ALOAD, paramOffset++);
623
 
                if (! loadedScope) {
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);
630
 
                    loadedScope = true;
631
 
                }
632
 
                cfw.add(ByteCode.ALOAD, scopeLocal);
633
 
 
634
 
                // Get argument Class 
635
 
                cfw.addLoadConstant(parms[i].getName());
636
 
                cfw.add(ByteCode.INVOKESTATIC, 
637
 
                        "org/mozilla/javascript/ScriptRuntime", 
638
 
                        "loadClassName", 
639
 
                        "(Ljava/lang/String;)",                        
640
 
                        "Ljava/lang/Class;");
641
 
      
642
 
                cfw.add(ByteCode.INVOKESTATIC, 
643
 
                        "org/mozilla/javascript/JavaAdapter", 
644
 
                        "toObject", 
645
 
                        "(Ljava/lang/Object;" +
646
 
                         "Lorg/mozilla/javascript/Scriptable;" +
647
 
                         "Ljava/lang/Class;)",                         
648
 
                        "Lorg/mozilla/javascript/Scriptable;");
649
 
            }
650
 
            cfw.add(ByteCode.AASTORE);
651
 
        }
652
 
        
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);
661
 
        
662
 
        // go through utility method, which creates a Context to run the 
663
 
        // method in.
664
 
        cfw.add(ByteCode.INVOKESTATIC,
665
 
                "org/mozilla/javascript/JavaAdapter",
666
 
                "callMethod",
667
 
                "(Lorg/mozilla/javascript/Scriptable;" +
668
 
                 "Ljava/lang/Object;Ljava/lang/String;[Ljava/lang/Object;)",
669
 
                "Ljava/lang/Object;");
670
 
 
671
 
        if (returnType.equals(Void.TYPE)) {
672
 
            cfw.add(ByteCode.POP);
673
 
            cfw.add(ByteCode.RETURN);
674
 
        } else {
675
 
            generateReturnResult(cfw, returnType);
676
 
        }
677
 
        cfw.stopMethod((short)(scopeLocal + 3), null);
678
 
    }
679
 
 
680
 
    /**
681
 
     * Generates code to push typed parameters onto the operand stack
682
 
     * prior to a direct Java method call.
683
 
     */
684
 
    private static int generatePushParam(ClassFileWriter cfw, int paramOffset, 
685
 
                                         Class paramType) 
686
 
    {
687
 
        String typeName = paramType.getName();
688
 
        switch (typeName.charAt(0)) {
689
 
        case 'z':
690
 
        case 'b':
691
 
        case 'c':
692
 
        case 's':
693
 
        case 'i':
694
 
            // load an int value, convert to double.
695
 
            cfw.add(ByteCode.ILOAD, paramOffset++);
696
 
            break;
697
 
        case 'l':
698
 
            // load a long, convert to double.
699
 
            cfw.add(ByteCode.LLOAD, paramOffset);
700
 
            paramOffset += 2;
701
 
            break;
702
 
        case 'f':
703
 
            // load a float, convert to double.
704
 
            cfw.add(ByteCode.FLOAD, paramOffset++);
705
 
            break;
706
 
        case 'd':
707
 
            cfw.add(ByteCode.DLOAD, paramOffset);
708
 
            paramOffset += 2;
709
 
            break;
710
 
        }
711
 
        return paramOffset;
712
 
    }
713
 
 
714
 
    /**
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.
718
 
     */
719
 
    private static void generatePopResult(ClassFileWriter cfw, 
720
 
                                          Class retType) 
721
 
    {
722
 
        if (retType.isPrimitive()) {
723
 
            String typeName = retType.getName();
724
 
            switch (typeName.charAt(0)) {
725
 
            case 'b':
726
 
            case 'c':
727
 
            case 's':
728
 
            case 'i':
729
 
            case 'z':
730
 
                cfw.add(ByteCode.IRETURN);
731
 
                break;
732
 
            case 'l':
733
 
                cfw.add(ByteCode.LRETURN);
734
 
                break;
735
 
            case 'f':
736
 
                cfw.add(ByteCode.FRETURN);
737
 
                break;
738
 
            case 'd':
739
 
                cfw.add(ByteCode.DRETURN);
740
 
                break;
741
 
            }
742
 
        } else {
743
 
            cfw.add(ByteCode.ARETURN);
744
 
        }
745
 
    }
746
 
 
747
 
    /**
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.
751
 
     */
752
 
    private static void generateSuper(ClassFileWriter cfw,
753
 
                                      String genName, String superName,
754
 
                                      String methodName, String methodSignature,
755
 
                                      Class[] parms, Class returnType)
756
 
    {
757
 
        cfw.startMethod("super$" + methodName, methodSignature, 
758
 
                        ClassFileWriter.ACC_PUBLIC);
759
 
        
760
 
        // push "this"
761
 
        cfw.add(ByteCode.ALOAD, 0);
762
 
 
763
 
        // push the rest of the parameters.
764
 
        int paramOffset = 1;
765
 
        for (int i = 0; i < parms.length; i++) {
766
 
            if (parms[i].isPrimitive()) {
767
 
                paramOffset = generatePushParam(cfw, paramOffset, parms[i]);
768
 
            } else {
769
 
                cfw.add(ByteCode.ALOAD, paramOffset++);
770
 
            }
771
 
        }
772
 
        
773
 
        // split the method signature at the right parentheses.
774
 
        int rightParen = methodSignature.indexOf(')');
775
 
        
776
 
        // call the superclass implementation of the method.
777
 
        cfw.add(ByteCode.INVOKESPECIAL,
778
 
                superName,
779
 
                methodName,
780
 
                methodSignature.substring(0, rightParen + 1),
781
 
                methodSignature.substring(rightParen + 1));
782
 
 
783
 
        // now, handle the return type appropriately.        
784
 
        Class retType = returnType;
785
 
        if (!retType.equals(Void.TYPE)) {
786
 
            generatePopResult(cfw, retType);
787
 
        } else {
788
 
            cfw.add(ByteCode.RETURN);
789
 
        }
790
 
        cfw.stopMethod((short)(paramOffset + 1), null);
791
 
    }
792
 
    
793
 
    /**
794
 
     * Returns a fully qualified method name concatenated with its signature.
795
 
     */
796
 
    private static String getMethodSignature(Method method) {
797
 
        Class[] parms = method.getParameterTypes();
798
 
        StringBuffer sb = new StringBuffer();
799
 
        sb.append('(');
800
 
        for (int i = 0; i < parms.length; i++) {
801
 
            Class type = parms[i];
802
 
            appendTypeString(sb, type);
803
 
        }
804
 
        sb.append(')');
805
 
        appendTypeString(sb, method.getReturnType());
806
 
        return sb.toString();
807
 
    }
808
 
    
809
 
    private static StringBuffer appendTypeString(StringBuffer sb, Class type) 
810
 
    {
811
 
        while (type.isArray()) {
812
 
            sb.append('[');
813
 
            type = type.getComponentType();
814
 
        }
815
 
        if (type.isPrimitive()) {
816
 
            if (type.equals(Boolean.TYPE)) {
817
 
                sb.append('Z');
818
 
            } else
819
 
                if (type.equals(Long.TYPE)) {
820
 
                    sb.append('J');
821
 
                } else {
822
 
                    String typeName = type.getName();
823
 
                    sb.append(Character.toUpperCase(typeName.charAt(0)));
824
 
                }
825
 
        } else {
826
 
            sb.append('L');
827
 
            sb.append(type.getName().replace('.', '/'));
828
 
            sb.append(';');
829
 
        }
830
 
        return sb;
831
 
    }
832
 
    
833
 
    /**
834
 
     * Provides a key with which to distinguish previously generated
835
 
     * adapter classes stored in a hash table.
836
 
     */
837
 
    static class ClassSignature {
838
 
        Class mSuperClass;
839
 
        Class[] mInterfaces;
840
 
        Object[] mProperties;    // JDK1.2: Use HashSet
841
 
    
842
 
        ClassSignature(Class superClass, Class[] interfaces, Scriptable jsObj) {
843
 
            mSuperClass = superClass;
844
 
            mInterfaces = interfaces;
845
 
            mProperties = ScriptableObject.getPropertyIds(jsObj);
846
 
        }
847
 
            
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)
855
 
                            return false;
856
 
                        if (mInterfaces.length != interfaces.length)
857
 
                            return false;
858
 
                        for (int i=0; i < interfaces.length; i++)
859
 
                            if (mInterfaces[i] != interfaces[i])
860
 
                                return false;
861
 
                    }
862
 
                    if (mProperties.length != sig.mProperties.length)
863
 
                        return false;
864
 
                    for (int i=0; i < mProperties.length; i++) {
865
 
                        if (!mProperties[i].equals(sig.mProperties[i]))
866
 
                            return false;
867
 
                    }
868
 
                    return true;
869
 
                }
870
 
            }
871
 
            return false;
872
 
        }
873
 
            
874
 
        public int hashCode() {
875
 
            return mSuperClass.hashCode();
876
 
        }
877
 
    }
878
 
    
879
 
    private static int serial;
880
 
    private static DefiningClassLoader classLoader;
881
 
    private static Hashtable generatedClasses = new Hashtable(7);
882
 
}
 
1
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 
2
 *
 
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/
 
7
 *
 
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.
 
12
 *
 
13
 * The Original Code is Rhino code, released
 
14
 * May 6, 1999.
 
15
 *
 
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
 
19
 * Rights Reserved.
 
20
 *
 
21
 * Contributor(s):
 
22
 * Patrick Beard
 
23
 * Norris Boyd
 
24
 * Igor Bukanov
 
25
 * Mike McCabe
 
26
 * Matthias Radestock
 
27
 * Andi Vajda
 
28
 * Andrew Wason
 
29
 * Kemal Bayram
 
30
 *
 
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.
 
41
 */
 
42
 
 
43
package org.mozilla.javascript;
 
44
 
 
45
import org.mozilla.classfile.*;
 
46
import java.lang.reflect.*;
 
47
import java.io.*;
 
48
import java.util.*;
 
49
 
 
50
public final class JavaAdapter implements IdFunctionCall
 
51
{
 
52
    /**
 
53
     * Provides a key with which to distinguish previously generated
 
54
     * adapter classes stored in a hash table.
 
55
     */
 
56
    static class JavaAdapterSignature
 
57
    {
 
58
        Class superClass;
 
59
        Class[] interfaces;
 
60
        ObjToIntMap names;
 
61
 
 
62
        JavaAdapterSignature(Class superClass, Class[] interfaces,
 
63
                             ObjToIntMap names)
 
64
        {
 
65
            this.superClass = superClass;
 
66
            this.interfaces = interfaces;
 
67
            this.names = names;;
 
68
        }
 
69
 
 
70
        public boolean equals(Object obj)
 
71
        {
 
72
            if (!(obj instanceof JavaAdapterSignature))
 
73
                return false;
 
74
            JavaAdapterSignature sig = (JavaAdapterSignature) obj;
 
75
            if (superClass != sig.superClass)
 
76
                return false;
 
77
            if (interfaces != sig.interfaces) {
 
78
                if (interfaces.length != sig.interfaces.length)
 
79
                    return false;
 
80
                for (int i=0; i < interfaces.length; i++)
 
81
                    if (interfaces[i] != sig.interfaces[i])
 
82
                        return false;
 
83
            }
 
84
            if (names.size() != sig.names.size())
 
85
                return false;
 
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))
 
91
                    return false;
 
92
            }
 
93
            return true;
 
94
        }
 
95
 
 
96
        public int hashCode()
 
97
        {
 
98
            return superClass.hashCode()
 
99
                | (0x9e3779b9 * (names.size() | (interfaces.length << 16)));
 
100
        }
 
101
    }
 
102
 
 
103
    public static void init(Context cx, Scriptable scope, boolean sealed)
 
104
    {
 
105
        JavaAdapter obj = new JavaAdapter();
 
106
        IdFunctionObject ctor = new IdFunctionObject(obj, FTAG, Id_JavaAdapter,
 
107
                                                     "JavaAdapter", 1, scope);
 
108
        ctor.markAsConstructor(null);
 
109
        if (sealed) {
 
110
            ctor.sealObject();
 
111
        }
 
112
        ctor.exportAsScopeProperty();
 
113
    }
 
114
 
 
115
    public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
 
116
                             Scriptable thisObj, Object[] args)
 
117
    {
 
118
        if (f.hasTag(FTAG)) {
 
119
            if (f.methodId() == Id_JavaAdapter) {
 
120
                return js_createAdpter(cx, scope, args);
 
121
            }
 
122
        }
 
123
        throw f.unknown();
 
124
    }
 
125
 
 
126
    public static Object convertResult(Object result, Class c)
 
127
    {
 
128
        if (result == Undefined.instance &&
 
129
            (c != ScriptRuntime.ObjectClass &&
 
130
             c != ScriptRuntime.StringClass))
 
131
        {
 
132
            // Avoid an error for an undefined value; return null instead.
 
133
            return null;
 
134
        }
 
135
        return NativeJavaObject.coerceType(c, result, true);
 
136
    }
 
137
 
 
138
    public static Scriptable createAdapterWrapper(Scriptable obj,
 
139
                                                  Object adapter)
 
140
    {
 
141
        Scriptable scope = ScriptableObject.getTopLevelScope(obj);
 
142
        NativeJavaObject res = new NativeJavaObject(scope, adapter, null);
 
143
        res.setPrototype(obj);
 
144
        return res;
 
145
    }
 
146
 
 
147
    public static Object getAdapterSelf(Class adapterClass, Object adapter)
 
148
        throws NoSuchFieldException, IllegalAccessException
 
149
    {
 
150
        Field self = adapterClass.getDeclaredField("self");
 
151
        return self.get(adapter);
 
152
    }
 
153
 
 
154
    static Object js_createAdpter(Context cx, Scriptable scope, Object[] args)
 
155
    {
 
156
        int N = args.length;
 
157
        if (N == 0) {
 
158
            throw ScriptRuntime.typeError0("msg.adapter.zero.args");
 
159
        }
 
160
 
 
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",
 
168
                                               String.valueOf(i),
 
169
                                               ScriptRuntime.toString(arg));
 
170
            }
 
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());
 
176
                }
 
177
                superClass = c;
 
178
            } else {
 
179
                intfs[interfaceCount++] = c;
 
180
            }
 
181
        }
 
182
 
 
183
        if (superClass == null)
 
184
            superClass = ScriptRuntime.ObjectClass;
 
185
 
 
186
        Class[] interfaces = new Class[interfaceCount];
 
187
        System.arraycopy(intfs, 0, interfaces, 0, interfaceCount);
 
188
        Scriptable obj = ScriptRuntime.toObject(cx, scope, args[N - 1]);
 
189
 
 
190
        Class adapterClass = getAdapterClass(scope, superClass, interfaces,
 
191
                                             obj);
 
192
 
 
193
        Class[] ctorParms = { ScriptRuntime.ScriptableClass };
 
194
        Object[] ctorArgs = { obj };
 
195
        try {
 
196
            Object adapter = adapterClass.getConstructor(ctorParms).
 
197
                                 newInstance(ctorArgs);
 
198
            return getAdapterSelf(adapterClass, adapter);
 
199
        } catch (Exception ex) {
 
200
            throw Context.throwAsScriptRuntimeEx(ex);
 
201
        }
 
202
    }
 
203
 
 
204
    // Needed by NativeJavaObject serializer
 
205
    public static void writeAdapterObject(Object javaObject,
 
206
                                          ObjectOutputStream out)
 
207
        throws IOException
 
208
    {
 
209
        Class cl = javaObject.getClass();
 
210
        out.writeObject(cl.getSuperclass().getName());
 
211
 
 
212
        Class[] interfaces = cl.getInterfaces();
 
213
        String[] interfaceNames = new String[interfaces.length];
 
214
 
 
215
        for (int i=0; i < interfaces.length; i++)
 
216
            interfaceNames[i] = interfaces[i].getName();
 
217
 
 
218
        out.writeObject(interfaceNames);
 
219
 
 
220
        try {
 
221
            Object delegee = cl.getField("delegee").get(javaObject);
 
222
            out.writeObject(delegee);
 
223
            return;
 
224
        } catch (IllegalAccessException e) {
 
225
        } catch (NoSuchFieldException e) {
 
226
        }
 
227
        throw new IOException();
 
228
    }
 
229
 
 
230
    // Needed by NativeJavaObject de-serializer
 
231
    public static Object readAdapterObject(Scriptable self,
 
232
                                           ObjectInputStream in)
 
233
        throws IOException, ClassNotFoundException
 
234
    {
 
235
        Class superClass = Class.forName((String)in.readObject());
 
236
 
 
237
        String[] interfaceNames = (String[])in.readObject();
 
238
        Class[] interfaces = new Class[interfaceNames.length];
 
239
 
 
240
        for (int i=0; i < interfaceNames.length; i++)
 
241
            interfaces[i] = Class.forName(interfaceNames[i]);
 
242
 
 
243
        Scriptable delegee = (Scriptable)in.readObject();
 
244
 
 
245
        Class adapterClass = getAdapterClass(self, superClass, interfaces,
 
246
                                             delegee);
 
247
 
 
248
        Class[] ctorParms = {
 
249
            ScriptRuntime.ScriptableClass,
 
250
            ScriptRuntime.ScriptableClass
 
251
        };
 
252
        Object[] ctorArgs = { delegee, self };
 
253
        try {
 
254
            return adapterClass.getConstructor(ctorParms).newInstance(ctorArgs);
 
255
        } catch(InstantiationException e) {
 
256
        } catch(IllegalAccessException e) {
 
257
        } catch(InvocationTargetException e) {
 
258
        } catch(NoSuchMethodException e) {
 
259
        }
 
260
 
 
261
        throw new ClassNotFoundException("adapter");
 
262
    }
 
263
 
 
264
    private static ObjToIntMap getObjectFunctionNames(Scriptable obj)
 
265
    {
 
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))
 
270
                continue;
 
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"));
 
277
                if (length < 0) {
 
278
                    length = 0;
 
279
                }
 
280
                map.put(id, length);
 
281
            }
 
282
        }
 
283
        return map;
 
284
    }
 
285
 
 
286
    private static Class getAdapterClass(Scriptable scope, Class superClass,
 
287
                                         Class[] interfaces, Scriptable obj)
 
288
    {
 
289
        ClassCache cache = ClassCache.get(scope);
 
290
        Hashtable generated = cache.javaAdapterGeneratedClasses;
 
291
 
 
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);
 
301
 
 
302
            adapterClass = loadAdapterClass(adapterName, code);
 
303
            if (cache.isCachingEnabled()) {
 
304
                generated.put(sig, adapterClass);
 
305
            }
 
306
        }
 
307
        return adapterClass;
 
308
    }
 
309
 
 
310
    public static byte[] createAdapterCode(ObjToIntMap functionNames,
 
311
                                           String adapterName,
 
312
                                           Class superClass,
 
313
                                           Class[] interfaces,
 
314
                                           String scriptClassName)
 
315
    {
 
316
        ClassFileWriter cfw = new ClassFileWriter(adapterName,
 
317
                                                  superClass.getName(),
 
318
                                                  "<adapter>");
 
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());
 
329
        }
 
330
 
 
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);
 
336
 
 
337
        ObjToIntMap generatedOverrides = new ObjToIntMap();
 
338
        ObjToIntMap generatedMethods = new ObjToIntMap();
 
339
 
 
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)) {
 
347
                    continue;
 
348
                }
 
349
                String methodName = method.getName();
 
350
                Class[] argTypes = method.getParameterTypes();
 
351
                if (!functionNames.has(methodName)) {
 
352
                    try {
 
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
 
356
                        // bug 61226.
 
357
                        continue;
 
358
                    } catch (NoSuchMethodException e) {
 
359
                        // Not implemented by superclass; fall through
 
360
                    }
 
361
                }
 
362
                // make sure to generate only one instance of a particular
 
363
                // method/signature.
 
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);
 
371
                }
 
372
            }
 
373
        }
 
374
 
 
375
        // Now, go through the superclasses methods, checking for abstract
 
376
        // methods or additional methods to override.
 
377
 
 
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))
 
384
                continue;
 
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
 
392
                // method/signature.
 
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);
 
401
                }
 
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());
 
408
                }
 
409
            }
 
410
        }
 
411
 
 
412
        // Generate Java methods for remaining properties that are not
 
413
        // overrides.
 
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))
 
418
                continue;
 
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);
 
425
        }
 
426
        return cfw.toByteArray();
 
427
    }
 
428
 
 
429
    static Class loadAdapterClass(String className, byte[] classBytes)
 
430
    {
 
431
        GeneratedClassLoader loader
 
432
            = SecurityController.createLoader(null, null);
 
433
        Class result = loader.defineClass(className, classBytes);
 
434
        loader.linkClass(result);
 
435
        return result;
 
436
    }
 
437
 
 
438
    public static Function getFunction(Scriptable obj, String functionName)
 
439
    {
 
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,
 
446
            // for instance.
 
447
            return null;
 
448
        }
 
449
        if (!(x instanceof Function))
 
450
            throw ScriptRuntime.notFunctionError(x, functionName);
 
451
 
 
452
        return (Function)x;
 
453
    }
 
454
 
 
455
    /**
 
456
     * Utility method which dynamically binds a Context to the current thread,
 
457
     * if none already exists.
 
458
     */
 
459
    public static Object callMethod(final Scriptable thisObj,
 
460
                                    final Function f, final Object[] args,
 
461
                                    final long argsToWrap)
 
462
    {
 
463
        if (f == null) {
 
464
            // See comments in getFunction
 
465
            return Undefined.instance;
 
466
        }
 
467
        final Scriptable scope = f.getParentScope();
 
468
        if (argsToWrap == 0) {
 
469
            return Context.call(null, f, scope, thisObj, args);
 
470
        }
 
471
 
 
472
        Context cx = Context.getCurrentContext();
 
473
        if (cx != null) {
 
474
            return doCall(cx, scope, thisObj, f, args, argsToWrap);
 
475
        } else {
 
476
            ContextFactory factory = ScriptRuntime.getContextFactory(scope);
 
477
            return factory.call(new ContextAction() {
 
478
                public Object run(Context cx)
 
479
                {
 
480
                    return doCall(cx, scope, thisObj, f, args, argsToWrap);
 
481
                }
 
482
            });
 
483
        }
 
484
    }
 
485
 
 
486
    private static Object doCall(Context cx, Scriptable scope,
 
487
                                 Scriptable thisObj, Function f,
 
488
                                 Object[] args, long argsToWrap)
 
489
    {
 
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,
 
496
                                                       null);
 
497
                }
 
498
            }
 
499
        }
 
500
        return f.call(cx, scope, thisObj, args);
 
501
    }
 
502
 
 
503
    public static Scriptable runScript(final Script script)
 
504
    {
 
505
        return (Scriptable)Context.call(new ContextAction() {
 
506
            public Object run(Context cx)
 
507
            {
 
508
                ScriptableObject global = ScriptRuntime.getGlobal(cx);
 
509
                script.exec(cx, global);
 
510
                return global;
 
511
            }
 
512
        });
 
513
    }
 
514
 
 
515
    private static void generateCtor(ClassFileWriter cfw, String adapterName,
 
516
                                     String superName)
 
517
    {
 
518
        cfw.startMethod("<init>",
 
519
                        "(Lorg/mozilla/javascript/Scriptable;)V",
 
520
                        ClassFileWriter.ACC_PUBLIC);
 
521
 
 
522
        // Invoke base class constructor
 
523
        cfw.add(ByteCode.ALOAD_0);  // this
 
524
        cfw.addInvoke(ByteCode.INVOKESPECIAL, superName, "<init>", "()V");
 
525
 
 
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;");
 
531
 
 
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;");
 
544
 
 
545
        cfw.add(ByteCode.RETURN);
 
546
        cfw.stopMethod((short)3); // 2: this + delegee
 
547
    }
 
548
 
 
549
    private static void generateSerialCtor(ClassFileWriter cfw,
 
550
                                           String adapterName,
 
551
                                           String superName)
 
552
    {
 
553
        cfw.startMethod("<init>",
 
554
                        "(Lorg/mozilla/javascript/Scriptable;"
 
555
                        +"Lorg/mozilla/javascript/Scriptable;"
 
556
                        +")V",
 
557
                        ClassFileWriter.ACC_PUBLIC);
 
558
 
 
559
        // Invoke base class constructor
 
560
        cfw.add(ByteCode.ALOAD_0);  // this
 
561
        cfw.addInvoke(ByteCode.INVOKESPECIAL, superName, "<init>", "()V");
 
562
 
 
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;");
 
568
 
 
569
        // save self
 
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;");
 
574
 
 
575
        cfw.add(ByteCode.RETURN);
 
576
        cfw.stopMethod((short)20); // TODO: magic number "20"
 
577
    }
 
578
 
 
579
    private static void generateEmptyCtor(ClassFileWriter cfw,
 
580
                                          String adapterName,
 
581
                                          String superName,
 
582
                                          String scriptClassName)
 
583
    {
 
584
        cfw.startMethod("<init>", "()V", ClassFileWriter.ACC_PUBLIC);
 
585
 
 
586
        // Invoke base class constructor
 
587
        cfw.add(ByteCode.ALOAD_0);  // this
 
588
        cfw.addInvoke(ByteCode.INVOKESPECIAL, superName, "<init>", "()V");
 
589
 
 
590
        // Load script class
 
591
        cfw.add(ByteCode.NEW, scriptClassName);
 
592
        cfw.add(ByteCode.DUP);
 
593
        cfw.addInvoke(ByteCode.INVOKESPECIAL, scriptClassName, "<init>", "()V");
 
594
 
 
595
        // Run script and save resulting scope
 
596
        cfw.addInvoke(ByteCode.INVOKESTATIC,
 
597
                      "org/mozilla/javascript/JavaAdapter",
 
598
                      "runScript",
 
599
                      "(Lorg/mozilla/javascript/Script;"
 
600
                      +")Lorg/mozilla/javascript/Scriptable;");
 
601
        cfw.add(ByteCode.ASTORE_1);
 
602
 
 
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;");
 
608
 
 
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;");
 
621
 
 
622
        cfw.add(ByteCode.RETURN);
 
623
        cfw.stopMethod((short)2); // this + delegee
 
624
    }
 
625
 
 
626
    /**
 
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.
 
630
     */
 
631
    static void generatePushWrappedArgs(ClassFileWriter cfw,
 
632
                                        Class[] argTypes,
 
633
                                        int arrayLength)
 
634
    {
 
635
        // push arguments
 
636
        cfw.addPush(arrayLength);
 
637
        cfw.add(ByteCode.ANEWARRAY, "java/lang/Object");
 
638
        int paramOffset = 1;
 
639
        for (int i = 0; i != argTypes.length; ++i) {
 
640
            cfw.add(ByteCode.DUP); // duplicate array reference
 
641
            cfw.addPush(i);
 
642
            paramOffset += generateWrapArg(cfw, paramOffset, argTypes[i]);
 
643
            cfw.add(ByteCode.AASTORE);
 
644
        }
 
645
    }
 
646
 
 
647
    /**
 
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.
 
651
     */
 
652
    private static int generateWrapArg(ClassFileWriter cfw, int paramOffset,
 
653
                                       Class argType)
 
654
    {
 
655
        int size = 1;
 
656
        if (!argType.isPrimitive()) {
 
657
            cfw.add(ByteCode.ALOAD, paramOffset);
 
658
 
 
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",
 
665
                          "<init>", "(Z)V");
 
666
 
 
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;");
 
672
 
 
673
        } else {
 
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)) {
 
679
            case 'b':
 
680
            case 's':
 
681
            case 'i':
 
682
                // load an int value, convert to double.
 
683
                cfw.add(ByteCode.ILOAD, paramOffset);
 
684
                cfw.add(ByteCode.I2D);
 
685
                break;
 
686
            case 'l':
 
687
                // load a long, convert to double.
 
688
                cfw.add(ByteCode.LLOAD, paramOffset);
 
689
                cfw.add(ByteCode.L2D);
 
690
                size = 2;
 
691
                break;
 
692
            case 'f':
 
693
                // load a float, convert to double.
 
694
                cfw.add(ByteCode.FLOAD, paramOffset);
 
695
                cfw.add(ByteCode.F2D);
 
696
                break;
 
697
            case 'd':
 
698
                cfw.add(ByteCode.DLOAD, paramOffset);
 
699
                size = 2;
 
700
                break;
 
701
            }
 
702
            cfw.addInvoke(ByteCode.INVOKESPECIAL, "java/lang/Double",
 
703
                          "<init>", "(D)V");
 
704
        }
 
705
        return size;
 
706
    }
 
707
 
 
708
    /**
 
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.
 
712
     */
 
713
    static void generateReturnResult(ClassFileWriter cfw, Class retType,
 
714
                                     boolean callConvertResult)
 
715
    {
 
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);
 
721
 
 
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);
 
727
 
 
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",
 
734
                          "toString",
 
735
                          "(Ljava/lang/Object;)Ljava/lang/String;");
 
736
            cfw.add(ByteCode.ICONST_0);
 
737
            cfw.addInvoke(ByteCode.INVOKEVIRTUAL, "java/lang/String",
 
738
                          "charAt", "(I)C");
 
739
            cfw.add(ByteCode.IRETURN);
 
740
 
 
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)) {
 
747
            case 'b':
 
748
            case 's':
 
749
            case 'i':
 
750
                cfw.add(ByteCode.D2I);
 
751
                cfw.add(ByteCode.IRETURN);
 
752
                break;
 
753
            case 'l':
 
754
                cfw.add(ByteCode.D2L);
 
755
                cfw.add(ByteCode.LRETURN);
 
756
                break;
 
757
            case 'f':
 
758
                cfw.add(ByteCode.D2F);
 
759
                cfw.add(ByteCode.FRETURN);
 
760
                break;
 
761
            case 'd':
 
762
                cfw.add(ByteCode.DRETURN);
 
763
                break;
 
764
            default:
 
765
                throw new RuntimeException("Unexpected return type " +
 
766
                                           retType.toString());
 
767
            }
 
768
 
 
769
        } else {
 
770
            String retTypeStr = retType.getName();
 
771
            if (callConvertResult) {
 
772
                cfw.addLoadConstant(retTypeStr);
 
773
                cfw.addInvoke(ByteCode.INVOKESTATIC,
 
774
                              "java/lang/Class",
 
775
                              "forName",
 
776
                              "(Ljava/lang/String;)Ljava/lang/Class;");
 
777
 
 
778
                cfw.addInvoke(ByteCode.INVOKESTATIC,
 
779
                              "org/mozilla/javascript/JavaAdapter",
 
780
                              "convertResult",
 
781
                              "(Ljava/lang/Object;"
 
782
                              +"Ljava/lang/Class;"
 
783
                              +")Ljava/lang/Object;");
 
784
            }
 
785
            // Now cast to return type
 
786
            cfw.add(ByteCode.CHECKCAST, retTypeStr);
 
787
            cfw.add(ByteCode.ARETURN);
 
788
        }
 
789
    }
 
790
 
 
791
    private static void generateMethod(ClassFileWriter cfw, String genName,
 
792
                                       String methodName, Class[] parms,
 
793
                                       Class returnType)
 
794
    {
 
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);
 
800
 
 
801
        int FUNCTION = firstLocal;
 
802
        int LOCALS_END = firstLocal + 1;
 
803
 
 
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",
 
810
                      "getFunction",
 
811
                      "(Lorg/mozilla/javascript/Scriptable;"
 
812
                      +"Ljava/lang/String;"
 
813
                      +")Lorg/mozilla/javascript/Function;");
 
814
        cfw.add(ByteCode.DUP);
 
815
        cfw.addAStore(FUNCTION);
 
816
 
 
817
        // Prepare stack to call calMethod
 
818
        // push thisObj
 
819
        cfw.add(ByteCode.ALOAD_0);
 
820
        cfw.add(ByteCode.GETFIELD, genName, "self",
 
821
                "Lorg/mozilla/javascript/Scriptable;");
 
822
        // push function
 
823
        cfw.addALoad(FUNCTION);
 
824
 
 
825
        // push arguments
 
826
        generatePushWrappedArgs(cfw, parms, parms.length);
 
827
 
 
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"
 
834
                +" 64 arguments.");
 
835
        }
 
836
        long convertionMask = 0;
 
837
        for (int i = 0; i != parms.length; ++i) {
 
838
            if (!parms[i].isPrimitive()) {
 
839
                convertionMask |= (1 << i);
 
840
            }
 
841
        }
 
842
        cfw.addPush(convertionMask);
 
843
 
 
844
        // go through utility method, which creates a Context to run the
 
845
        // method in.
 
846
        cfw.addInvoke(ByteCode.INVOKESTATIC,
 
847
                      "org/mozilla/javascript/JavaAdapter",
 
848
                      "callMethod",
 
849
                      "(Lorg/mozilla/javascript/Scriptable;"
 
850
                      +"Lorg/mozilla/javascript/Function;"
 
851
                      +"[Ljava/lang/Object;"
 
852
                      +"J"
 
853
                      +")Ljava/lang/Object;");
 
854
 
 
855
        generateReturnResult(cfw, returnType, true);
 
856
 
 
857
        cfw.stopMethod((short)LOCALS_END);
 
858
    }
 
859
 
 
860
    /**
 
861
     * Generates code to push typed parameters onto the operand stack
 
862
     * prior to a direct Java method call.
 
863
     */
 
864
    private static int generatePushParam(ClassFileWriter cfw, int paramOffset,
 
865
                                         Class paramType)
 
866
    {
 
867
        if (!paramType.isPrimitive()) {
 
868
            cfw.addALoad(paramOffset);
 
869
            return 1;
 
870
        }
 
871
        String typeName = paramType.getName();
 
872
        switch (typeName.charAt(0)) {
 
873
        case 'z':
 
874
        case 'b':
 
875
        case 'c':
 
876
        case 's':
 
877
        case 'i':
 
878
            // load an int value, convert to double.
 
879
            cfw.addILoad(paramOffset);
 
880
            return 1;
 
881
        case 'l':
 
882
            // load a long, convert to double.
 
883
            cfw.addLLoad(paramOffset);
 
884
            return 2;
 
885
        case 'f':
 
886
            // load a float, convert to double.
 
887
            cfw.addFLoad(paramOffset);
 
888
            return 1;
 
889
        case 'd':
 
890
            cfw.addDLoad(paramOffset);
 
891
            return 2;
 
892
        }
 
893
        throw Kit.codeBug();
 
894
    }
 
895
 
 
896
    /**
 
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.
 
900
     */
 
901
    private static void generatePopResult(ClassFileWriter cfw,
 
902
                                          Class retType)
 
903
    {
 
904
        if (retType.isPrimitive()) {
 
905
            String typeName = retType.getName();
 
906
            switch (typeName.charAt(0)) {
 
907
            case 'b':
 
908
            case 'c':
 
909
            case 's':
 
910
            case 'i':
 
911
            case 'z':
 
912
                cfw.add(ByteCode.IRETURN);
 
913
                break;
 
914
            case 'l':
 
915
                cfw.add(ByteCode.LRETURN);
 
916
                break;
 
917
            case 'f':
 
918
                cfw.add(ByteCode.FRETURN);
 
919
                break;
 
920
            case 'd':
 
921
                cfw.add(ByteCode.DRETURN);
 
922
                break;
 
923
            }
 
924
        } else {
 
925
            cfw.add(ByteCode.ARETURN);
 
926
        }
 
927
    }
 
928
 
 
929
    /**
 
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.
 
933
     */
 
934
    private static void generateSuper(ClassFileWriter cfw,
 
935
                                      String genName, String superName,
 
936
                                      String methodName, String methodSignature,
 
937
                                      Class[] parms, Class returnType)
 
938
    {
 
939
        cfw.startMethod("super$" + methodName, methodSignature,
 
940
                        ClassFileWriter.ACC_PUBLIC);
 
941
 
 
942
        // push "this"
 
943
        cfw.add(ByteCode.ALOAD, 0);
 
944
 
 
945
        // push the rest of the parameters.
 
946
        int paramOffset = 1;
 
947
        for (int i = 0; i < parms.length; i++) {
 
948
            paramOffset += generatePushParam(cfw, paramOffset, parms[i]);
 
949
        }
 
950
 
 
951
        // call the superclass implementation of the method.
 
952
        cfw.addInvoke(ByteCode.INVOKESPECIAL,
 
953
                      superName,
 
954
                      methodName,
 
955
                      methodSignature);
 
956
 
 
957
        // now, handle the return type appropriately.
 
958
        Class retType = returnType;
 
959
        if (!retType.equals(Void.TYPE)) {
 
960
            generatePopResult(cfw, retType);
 
961
        } else {
 
962
            cfw.add(ByteCode.RETURN);
 
963
        }
 
964
        cfw.stopMethod((short)(paramOffset + 1));
 
965
    }
 
966
 
 
967
    /**
 
968
     * Returns a fully qualified method name concatenated with its signature.
 
969
     */
 
970
    private static String getMethodSignature(Method method, Class[] argTypes)
 
971
    {
 
972
        StringBuffer sb = new StringBuffer();
 
973
        appendMethodSignature(argTypes, method.getReturnType(), sb);
 
974
        return sb.toString();
 
975
    }
 
976
 
 
977
    static int appendMethodSignature(Class[] argTypes,
 
978
                                     Class returnType,
 
979
                                     StringBuffer sb)
 
980
    {
 
981
        sb.append('(');
 
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
 
988
                ++firstLocal;
 
989
            }
 
990
        }
 
991
        sb.append(')');
 
992
        appendTypeString(sb, returnType);
 
993
        return firstLocal;
 
994
    }
 
995
 
 
996
    private static StringBuffer appendTypeString(StringBuffer sb, Class type)
 
997
    {
 
998
        while (type.isArray()) {
 
999
            sb.append('[');
 
1000
            type = type.getComponentType();
 
1001
        }
 
1002
        if (type.isPrimitive()) {
 
1003
            char typeLetter;
 
1004
            if (type == Boolean.TYPE) {
 
1005
                typeLetter = 'Z';
 
1006
            } else if (type == Long.TYPE) {
 
1007
                typeLetter = 'J';
 
1008
            } else {
 
1009
                String typeName = type.getName();
 
1010
                typeLetter = Character.toUpperCase(typeName.charAt(0));
 
1011
            }
 
1012
            sb.append(typeLetter);
 
1013
        } else {
 
1014
            sb.append('L');
 
1015
            sb.append(type.getName().replace('.', '/'));
 
1016
            sb.append(';');
 
1017
        }
 
1018
        return sb;
 
1019
    }
 
1020
 
 
1021
    static int[] getArgsToConvert(Class[] argTypes)
 
1022
    {
 
1023
        int count = 0;
 
1024
        for (int i = 0; i != argTypes.length; ++i) {
 
1025
            if (!argTypes[i].isPrimitive())
 
1026
                ++count;
 
1027
        }
 
1028
        if (count == 0)
 
1029
            return null;
 
1030
        int[] array = new int[count];
 
1031
        count = 0;
 
1032
        for (int i = 0; i != argTypes.length; ++i) {
 
1033
            if (!argTypes[i].isPrimitive())
 
1034
                array[count++] = i;
 
1035
        }
 
1036
        return array;
 
1037
    }
 
1038
 
 
1039
    private static final Object FTAG = new Object();
 
1040
    private static final int Id_JavaAdapter = 1;
 
1041
}