NativeJSAdapter.java revision 1782:fc972ab7d939
1/*
2 * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package jdk.nashorn.internal.objects;
27
28import static jdk.nashorn.internal.lookup.Lookup.MH;
29import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
30import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
31import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
32
33import java.lang.invoke.MethodHandle;
34import java.lang.invoke.MethodHandles;
35import java.lang.invoke.MethodType;
36import java.util.ArrayList;
37import java.util.Iterator;
38import java.util.List;
39import jdk.dynalink.CallSiteDescriptor;
40import jdk.dynalink.StandardOperation;
41import jdk.dynalink.linker.GuardedInvocation;
42import jdk.dynalink.linker.LinkRequest;
43import jdk.nashorn.internal.lookup.Lookup;
44import jdk.nashorn.internal.objects.annotations.Constructor;
45import jdk.nashorn.internal.objects.annotations.ScriptClass;
46import jdk.nashorn.internal.runtime.FindProperty;
47import jdk.nashorn.internal.runtime.JSType;
48import jdk.nashorn.internal.runtime.PropertyMap;
49import jdk.nashorn.internal.runtime.ScriptFunction;
50import jdk.nashorn.internal.runtime.ScriptObject;
51import jdk.nashorn.internal.runtime.ScriptRuntime;
52import jdk.nashorn.internal.runtime.arrays.ArrayLikeIterator;
53import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
54import jdk.nashorn.internal.scripts.JO;
55
56/**
57 * This class is the implementation of the Nashorn-specific global object named {@code JSAdapter}. It can be thought of
58 * as the {@link java.lang.reflect.Proxy} equivalent for JavaScript. A {@code NativeJSAdapter} calls specially named
59 * JavaScript methods on an adaptee object when property access/update/call/new/delete is attempted on it. Example:
60 *<pre>
61 *    var y = {
62 *                __get__     : function (name) { ... }
63 *                __has__     : function (name) { ... }
64 *                __put__     : function (name, value) {...}
65 *                __call__    : function (name, arg1, arg2) {...}
66 *                __new__     : function (arg1, arg2) {...}
67 *                __delete__  : function (name) { ... }
68 *                __getKeys__ : function () { ... }
69 *            };
70 *
71 *    var x = new JSAdapter(y);
72 *
73 *    x.i;                        // calls y.__get__
74 *    x.foo();                    // calls y.__call__
75 *    new x();                    // calls y.__new__
76 *    i in x;                     // calls y.__has__
77 *    x.p = 10;                   // calls y.__put__
78 *    delete x.p;                 // calls y.__delete__
79 *    for (i in x) { print(i); }  // calls y.__getKeys__
80 * </pre>
81 * <p>
82 * The {@code __getKeys__} and {@code __getIds__} properties are mapped to the same operation. Concrete
83 * {@code JSAdapter} implementations are expected to use only one of these. As {@code __getIds__} exists for
84 * compatibility reasons only, use of {@code __getKeys__} is recommended.
85 * </p>
86 * <p>
87 * The JavaScript caller of an adapter object is oblivious of the property access/mutation/deletion's being adapted.
88 * </p>
89 * <p>
90 * The {@code JSAdapter} constructor can optionally receive an "overrides" object. The properties of overrides object
91 * are copied to the {@code JSAdapter} instance. In case user-accessed properties are among these, the adaptee's methods
92 * like {@code __get__}, {@code __put__} etc. are not called for them. This can be used to make certain "preferred"
93 * properties that can be accessed in the usual/faster way avoiding the proxy mechanism. Example:
94 * </p>
95 * <pre>
96 *     var x = new JSAdapter({ foo: 444, bar: 6546 }) {
97 *          __get__: function(name) { return name; }
98 *      };
99 *
100 *     x.foo;           // 444 directly retrieved without __get__ call
101 *     x.bar = 'hello'; // "bar" directly set without __put__ call
102 *     x.prop           // calls __get__("prop") as 'prop' is not overridden
103 * </pre>
104 * It is possible to pass a specific prototype for the {@code JSAdapter} instance by passing three arguments to the
105 * {@code JSAdapter} constructor. The exact signature of the {@code JSAdapter} constructor is as follows:
106 * <pre>
107 *     JSAdapter([proto], [overrides], adaptee);
108 * </pre>
109 * Both the {@code proto} and {@code overrides} arguments are optional - but {@code adaptee} is not. When {@code proto}
110 * is not passed, {@code JSAdapter.prototype} is used.
111 */
112@ScriptClass("JSAdapter")
113public final class NativeJSAdapter extends ScriptObject {
114    /** object get operation */
115    public static final String __get__       = "__get__";
116    /** object out operation */
117    public static final String __put__       = "__put__";
118    /** object call operation */
119    public static final String __call__      = "__call__";
120    /** object new operation */
121    public static final String __new__       = "__new__";
122    /** object getIds operation (provided for compatibility reasons; use of getKeys is preferred) */
123    public static final String __getIds__    = "__getIds__";
124    /** object getKeys operation */
125    public static final String __getKeys__   = "__getKeys__";
126    /** object getValues operation */
127    public static final String __getValues__ = "__getValues__";
128    /** object has operation */
129    public static final String __has__       = "__has__";
130    /** object delete operation */
131    public static final String __delete__    = "__delete__";
132
133    // the new extensibility, sealing and freezing operations
134
135    /** prevent extensions operation */
136    public static final String __preventExtensions__ = "__preventExtensions__";
137    /** isExtensible extensions operation */
138    public static final String __isExtensible__      = "__isExtensible__";
139    /** seal operation */
140    public static final String __seal__              = "__seal__";
141    /** isSealed extensions operation */
142    public static final String __isSealed__          = "__isSealed__";
143    /** freeze operation */
144    public static final String __freeze__            = "__freeze__";
145    /** isFrozen extensions operation */
146    public static final String __isFrozen__          = "__isFrozen__";
147
148    private final ScriptObject adaptee;
149    private final boolean overrides;
150
151    private static final MethodHandle IS_JSADAPTER = findOwnMH("isJSAdapter", boolean.class, Object.class, Object.class, MethodHandle.class, Object.class, ScriptFunction.class);
152
153    // initialized by nasgen
154    private static PropertyMap $nasgenmap$;
155
156    NativeJSAdapter(final Object overrides, final ScriptObject adaptee, final ScriptObject proto, final PropertyMap map) {
157        super(proto, map);
158        this.adaptee = wrapAdaptee(adaptee);
159        if (overrides instanceof ScriptObject) {
160            this.overrides = true;
161            final ScriptObject sobj = (ScriptObject)overrides;
162            this.addBoundProperties(sobj);
163        } else {
164            this.overrides = false;
165        }
166    }
167
168    private static ScriptObject wrapAdaptee(final ScriptObject adaptee) {
169        return new JO(adaptee);
170    }
171
172    @Override
173    public String getClassName() {
174        return "JSAdapter";
175    }
176
177    @Override
178    public int getInt(final Object key, final int programPoint) {
179        return (overrides && super.hasOwnProperty(key)) ? super.getInt(key, programPoint) : callAdapteeInt(programPoint, __get__, key);
180    }
181
182    @Override
183    public int getInt(final double key, final int programPoint) {
184        return (overrides && super.hasOwnProperty(key)) ? super.getInt(key, programPoint) : callAdapteeInt(programPoint, __get__, key);
185    }
186
187    @Override
188    public int getInt(final int key, final int programPoint) {
189        return (overrides && super.hasOwnProperty(key)) ? super.getInt(key, programPoint) : callAdapteeInt(programPoint, __get__, key);
190    }
191
192    @Override
193    public double getDouble(final Object key, final int programPoint) {
194        return (overrides && super.hasOwnProperty(key)) ? super.getDouble(key, programPoint) : callAdapteeDouble(programPoint, __get__, key);
195    }
196
197    @Override
198    public double getDouble(final double key, final int programPoint) {
199        return (overrides && super.hasOwnProperty(key)) ? super.getDouble(key, programPoint) : callAdapteeDouble(programPoint, __get__, key);
200    }
201
202    @Override
203    public double getDouble(final int key, final int programPoint) {
204        return (overrides && super.hasOwnProperty(key)) ? super.getDouble(key, programPoint) : callAdapteeDouble(programPoint, __get__, key);
205    }
206
207    @Override
208    public Object get(final Object key) {
209        return (overrides && super.hasOwnProperty(key)) ? super.get(key) : callAdaptee(__get__, key);
210    }
211
212    @Override
213    public Object get(final double key) {
214        return (overrides && super.hasOwnProperty(key)) ? super.get(key) : callAdaptee(__get__, key);
215    }
216
217    @Override
218    public Object get(final int key) {
219        return (overrides && super.hasOwnProperty(key)) ? super.get(key) : callAdaptee(__get__, key);
220    }
221
222    @Override
223    public void set(final Object key, final int value, final int flags) {
224        if (overrides && super.hasOwnProperty(key)) {
225            super.set(key, value, flags);
226        } else {
227            callAdaptee(__put__, key, value, flags);
228        }
229    }
230
231    @Override
232    public void set(final Object key, final double value, final int flags) {
233        if (overrides && super.hasOwnProperty(key)) {
234            super.set(key, value, flags);
235        } else {
236            callAdaptee(__put__, key, value, flags);
237        }
238    }
239
240    @Override
241    public void set(final Object key, final Object value, final int flags) {
242        if (overrides && super.hasOwnProperty(key)) {
243            super.set(key, value, flags);
244        } else {
245            callAdaptee(__put__, key, value, flags);
246        }
247    }
248
249    @Override
250    public void set(final double key, final int value, final int flags) {
251        if (overrides && super.hasOwnProperty(key)) {
252            super.set(key, value, flags);
253        } else {
254            callAdaptee(__put__, key, value, flags);
255        }
256    }
257
258    @Override
259    public void set(final double key, final double value, final int flags) {
260        if (overrides && super.hasOwnProperty(key)) {
261            super.set(key, value, flags);
262        } else {
263            callAdaptee(__put__, key, value, flags);
264        }
265    }
266
267    @Override
268    public void set(final double key, final Object value, final int flags) {
269        if (overrides && super.hasOwnProperty(key)) {
270            super.set(key, value, flags);
271        } else {
272            callAdaptee(__put__, key, value, flags);
273        }
274    }
275
276    @Override
277    public void set(final int key, final int value, final int flags) {
278        if (overrides && super.hasOwnProperty(key)) {
279            super.set(key, value, flags);
280        } else {
281            callAdaptee(__put__, key, value, flags);
282        }
283    }
284
285    @Override
286    public void set(final int key, final double value, final int flags) {
287        if (overrides && super.hasOwnProperty(key)) {
288            super.set(key, value, flags);
289        } else {
290            callAdaptee(__put__, key, value, flags);
291        }
292    }
293
294    @Override
295    public void set(final int key, final Object value, final int flags) {
296        if (overrides && super.hasOwnProperty(key)) {
297            super.set(key, value, flags);
298        } else {
299            callAdaptee(__put__, key, value, flags);
300        }
301    }
302
303    @Override
304    public boolean has(final Object key) {
305        if (overrides && super.hasOwnProperty(key)) {
306            return true;
307        }
308
309        return JSType.toBoolean(callAdaptee(Boolean.FALSE, __has__, key));
310    }
311
312    @Override
313    public boolean has(final int key) {
314        if (overrides && super.hasOwnProperty(key)) {
315            return true;
316        }
317
318        return JSType.toBoolean(callAdaptee(Boolean.FALSE, __has__, key));
319    }
320
321    @Override
322    public boolean has(final double key) {
323        if (overrides && super.hasOwnProperty(key)) {
324            return true;
325        }
326
327        return JSType.toBoolean(callAdaptee(Boolean.FALSE, __has__, key));
328    }
329
330    @Override
331    public boolean delete(final int key, final boolean strict) {
332        if (overrides && super.hasOwnProperty(key)) {
333            return super.delete(key, strict);
334        }
335
336        return JSType.toBoolean(callAdaptee(Boolean.TRUE, __delete__, key, strict));
337    }
338
339    @Override
340    public boolean delete(final double key, final boolean strict) {
341        if (overrides && super.hasOwnProperty(key)) {
342            return super.delete(key, strict);
343        }
344
345        return JSType.toBoolean(callAdaptee(Boolean.TRUE, __delete__, key, strict));
346    }
347
348    @Override
349    public boolean delete(final Object key, final boolean strict) {
350        if (overrides && super.hasOwnProperty(key)) {
351            return super.delete(key, strict);
352        }
353
354        return JSType.toBoolean(callAdaptee(Boolean.TRUE, __delete__, key, strict));
355    }
356
357    @Override
358    public Iterator<String> propertyIterator() {
359        // Try __getIds__ first, if not found then try __getKeys__
360        // In jdk6, we had added "__getIds__" so this is just for compatibility.
361        Object func = adaptee.get(__getIds__);
362        if (!(func instanceof ScriptFunction)) {
363            func = adaptee.get(__getKeys__);
364        }
365
366        Object obj;
367        if (func instanceof ScriptFunction) {
368            obj = ScriptRuntime.apply((ScriptFunction)func, adaptee);
369        } else {
370            obj = new NativeArray(0);
371        }
372
373        final List<String> array = new ArrayList<>();
374        for (final Iterator<Object> iter = ArrayLikeIterator.arrayLikeIterator(obj); iter.hasNext(); ) {
375            array.add((String)iter.next());
376        }
377
378        return array.iterator();
379    }
380
381
382    @Override
383    public Iterator<Object> valueIterator() {
384        final Object obj = callAdaptee(new NativeArray(0), __getValues__);
385        return ArrayLikeIterator.arrayLikeIterator(obj);
386    }
387
388    @Override
389    public ScriptObject preventExtensions() {
390        callAdaptee(__preventExtensions__);
391        return this;
392    }
393
394    @Override
395    public boolean isExtensible() {
396        return JSType.toBoolean(callAdaptee(Boolean.TRUE, __isExtensible__));
397    }
398
399    @Override
400    public ScriptObject seal() {
401        callAdaptee(__seal__);
402        return this;
403    }
404
405    @Override
406    public boolean isSealed() {
407        return JSType.toBoolean(callAdaptee(Boolean.FALSE, __isSealed__));
408    }
409
410    @Override
411    public ScriptObject freeze() {
412        callAdaptee(__freeze__);
413        return this;
414    }
415
416    @Override
417    public boolean isFrozen() {
418        return JSType.toBoolean(callAdaptee(Boolean.FALSE, __isFrozen__));
419    }
420
421    /**
422     * Constructor
423     *
424     * @param isNew is this NativeJSAdapter instantiated with the new operator
425     * @param self  self reference
426     * @param args  arguments ([adaptee], [overrides, adaptee] or [proto, overrides, adaptee]
427     * @return new NativeJSAdapter
428     */
429    @Constructor
430    public static NativeJSAdapter construct(final boolean isNew, final Object self, final Object... args) {
431        Object proto     = UNDEFINED;
432        Object overrides = UNDEFINED;
433        Object adaptee;
434
435        if (args == null || args.length == 0) {
436            throw typeError("not.an.object", "null");
437        }
438
439        switch (args.length) {
440        case 1:
441            adaptee = args[0];
442            break;
443
444        case 2:
445            overrides = args[0];
446            adaptee   = args[1];
447            break;
448
449        default:
450            //fallthru
451        case 3:
452            proto = args[0];
453            overrides = args[1];
454            adaptee = args[2];
455            break;
456        }
457
458        if (!(adaptee instanceof ScriptObject)) {
459            throw typeError("not.an.object", ScriptRuntime.safeToString(adaptee));
460        }
461
462        final Global global = Global.instance();
463        if (proto != null && !(proto instanceof ScriptObject)) {
464            proto = global.getJSAdapterPrototype();
465        }
466
467        return new NativeJSAdapter(overrides, (ScriptObject)adaptee, (ScriptObject)proto, $nasgenmap$);
468    }
469
470    @Override
471    protected GuardedInvocation findNewMethod(final CallSiteDescriptor desc, final LinkRequest request) {
472        return findHook(desc, __new__, false);
473    }
474
475    @Override
476    protected GuardedInvocation findGetMethod(final CallSiteDescriptor desc, final LinkRequest request, final StandardOperation operation) {
477        final String name = NashornCallSiteDescriptor.getOperand(desc);
478        if (overrides && super.hasOwnProperty(name)) {
479            try {
480                final GuardedInvocation inv = super.findGetMethod(desc, request, operation);
481                if (inv != null) {
482                    return inv;
483                }
484            } catch (final Exception e) {
485                //ignored
486            }
487        }
488
489        switch(operation) {
490        case GET_PROPERTY:
491        case GET_ELEMENT:
492            return findHook(desc, __get__);
493        case GET_METHOD:
494            final FindProperty find = adaptee.findProperty(__call__, true);
495            if (find != null) {
496                final Object value = find.getObjectValue();
497                if (value instanceof ScriptFunction) {
498                    final ScriptFunction func = (ScriptFunction)value;
499                    // TODO: It's a shame we need to produce a function bound to this and name, when we'd only need it bound
500                    // to name. Probably not a big deal, but if we can ever make it leaner, it'd be nice.
501                    return new GuardedInvocation(MH.dropArguments(MH.constant(Object.class,
502                            func.createBound(this, new Object[] { name })), 0, Object.class),
503                            testJSAdapter(adaptee, null, null, null),
504                            adaptee.getProtoSwitchPoints(__call__, find.getOwner()), null);
505                }
506            }
507            throw typeError("no.such.function", name, ScriptRuntime.safeToString(this));
508        default:
509            break;
510        }
511
512        throw new AssertionError("should not reach here");
513    }
514
515    @Override
516    protected GuardedInvocation findSetMethod(final CallSiteDescriptor desc, final LinkRequest request) {
517        if (overrides && super.hasOwnProperty(NashornCallSiteDescriptor.getOperand(desc))) {
518            try {
519                final GuardedInvocation inv = super.findSetMethod(desc, request);
520                if (inv != null) {
521                    return inv;
522                }
523            } catch (final Exception e) {
524                //ignored
525            }
526        }
527
528        return findHook(desc, __put__);
529    }
530
531    // -- Internals only below this point
532    private Object callAdaptee(final String name, final Object... args) {
533        return callAdaptee(UNDEFINED, name, args);
534    }
535
536    private double callAdapteeDouble(final int programPoint, final String name, final Object... args) {
537        return JSType.toNumberMaybeOptimistic(callAdaptee(name, args), programPoint);
538    }
539
540    private int callAdapteeInt(final int programPoint, final String name, final Object... args) {
541        return JSType.toInt32MaybeOptimistic(callAdaptee(name, args), programPoint);
542    }
543
544    private Object callAdaptee(final Object retValue, final String name, final Object... args) {
545        final Object func = adaptee.get(name);
546        if (func instanceof ScriptFunction) {
547            return ScriptRuntime.apply((ScriptFunction)func, adaptee, args);
548        }
549        return retValue;
550    }
551
552    private GuardedInvocation findHook(final CallSiteDescriptor desc, final String hook) {
553        return findHook(desc, hook, true);
554    }
555
556    private GuardedInvocation findHook(final CallSiteDescriptor desc, final String hook, final boolean useName) {
557        final FindProperty findData = adaptee.findProperty(hook, true);
558        final MethodType type = desc.getMethodType();
559        if (findData != null) {
560            final String name = NashornCallSiteDescriptor.getOperand(desc);
561            final Object value = findData.getObjectValue();
562            if (value instanceof ScriptFunction) {
563                final ScriptFunction func = (ScriptFunction)value;
564
565                final MethodHandle methodHandle = getCallMethodHandle(findData, type,
566                    useName ? name : null);
567                if (methodHandle != null) {
568                    return new GuardedInvocation(
569                            methodHandle,
570                            testJSAdapter(adaptee, findData.getGetter(Object.class, INVALID_PROGRAM_POINT, null), findData.getOwner(), func),
571                            adaptee.getProtoSwitchPoints(hook, findData.getOwner()), null);
572                }
573             }
574        }
575
576        switch (hook) {
577        case __call__:
578            throw typeError("no.such.function", NashornCallSiteDescriptor.getOperand(desc), ScriptRuntime.safeToString(this));
579        default:
580            final MethodHandle methodHandle = hook.equals(__put__) ?
581            MH.asType(Lookup.EMPTY_SETTER, type) :
582            Lookup.emptyGetter(type.returnType());
583            return new GuardedInvocation(methodHandle, testJSAdapter(adaptee, null, null, null), adaptee.getProtoSwitchPoints(hook, null), null);
584        }
585    }
586
587    private static MethodHandle testJSAdapter(final Object adaptee, final MethodHandle getter, final Object where, final ScriptFunction func) {
588        return MH.insertArguments(IS_JSADAPTER, 1, adaptee, getter, where, func);
589    }
590
591    @SuppressWarnings("unused")
592    private static boolean isJSAdapter(final Object self, final Object adaptee, final MethodHandle getter, final Object where, final ScriptFunction func) {
593        final boolean res = self instanceof NativeJSAdapter && ((NativeJSAdapter)self).getAdaptee() == adaptee;
594        if (res && getter != null) {
595            try {
596                return getter.invokeExact(where) == func;
597            } catch (final RuntimeException | Error e) {
598                throw e;
599            } catch (final Throwable t) {
600                throw new RuntimeException(t);
601            }
602        }
603
604        return res;
605    }
606
607    /**
608     * Get the adaptee
609     * @return adaptee ScriptObject
610     */
611    public ScriptObject getAdaptee() {
612        return adaptee;
613    }
614
615    private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
616        return MH.findStatic(MethodHandles.lookup(), NativeJSAdapter.class, name, MH.type(rtype, types));
617    }
618}
619