NativeObject.java revision 1483:7cb19fa78763
1/*
2 * Copyright (c) 2010, 2013, 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;
31
32import java.lang.invoke.MethodHandle;
33import java.lang.invoke.MethodHandles;
34import java.lang.invoke.MethodType;
35import java.nio.ByteBuffer;
36import java.util.ArrayList;
37import java.util.Collection;
38import java.util.HashSet;
39import java.util.List;
40import java.util.Set;
41import java.util.concurrent.Callable;
42import jdk.internal.dynalink.CallSiteDescriptor;
43import jdk.internal.dynalink.NamedOperation;
44import jdk.internal.dynalink.Operation;
45import jdk.internal.dynalink.StandardOperation;
46import jdk.internal.dynalink.beans.BeansLinker;
47import jdk.internal.dynalink.beans.StaticClass;
48import jdk.internal.dynalink.linker.GuardedInvocation;
49import jdk.internal.dynalink.linker.GuardingDynamicLinker;
50import jdk.internal.dynalink.linker.LinkRequest;
51import jdk.internal.dynalink.linker.support.SimpleLinkRequest;
52import jdk.nashorn.api.scripting.ScriptObjectMirror;
53import jdk.nashorn.internal.lookup.Lookup;
54import jdk.nashorn.internal.objects.annotations.Attribute;
55import jdk.nashorn.internal.objects.annotations.Constructor;
56import jdk.nashorn.internal.objects.annotations.Function;
57import jdk.nashorn.internal.objects.annotations.ScriptClass;
58import jdk.nashorn.internal.objects.annotations.Where;
59import jdk.nashorn.internal.runtime.AccessorProperty;
60import jdk.nashorn.internal.runtime.ECMAException;
61import jdk.nashorn.internal.runtime.JSType;
62import jdk.nashorn.internal.runtime.Property;
63import jdk.nashorn.internal.runtime.PropertyMap;
64import jdk.nashorn.internal.runtime.ScriptObject;
65import jdk.nashorn.internal.runtime.ScriptRuntime;
66import jdk.nashorn.internal.runtime.arrays.ArrayData;
67import jdk.nashorn.internal.runtime.linker.Bootstrap;
68import jdk.nashorn.internal.runtime.linker.InvokeByName;
69import jdk.nashorn.internal.runtime.linker.NashornBeansLinker;
70import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
71
72/**
73 * ECMA 15.2 Object objects
74 *
75 * JavaScript Object constructor/prototype. Note: instances of this class are
76 * never created. This class is not even a subclass of ScriptObject. But, we use
77 * this class to generate prototype and constructor for "Object".
78 *
79 */
80@ScriptClass("Object")
81public final class NativeObject {
82    /** Methodhandle to proto getter */
83    public static final MethodHandle GET__PROTO__ = findOwnMH("get__proto__", ScriptObject.class, Object.class);
84
85    /** Methodhandle to proto setter */
86    public static final MethodHandle SET__PROTO__ = findOwnMH("set__proto__", Object.class, Object.class, Object.class);
87
88    private static final Object TO_STRING = new Object();
89
90    private static InvokeByName getTO_STRING() {
91        return Global.instance().getInvokeByName(TO_STRING,
92                new Callable<InvokeByName>() {
93                    @Override
94                    public InvokeByName call() {
95                        return new InvokeByName("toString", ScriptObject.class);
96                    }
97                });
98    }
99
100    @SuppressWarnings("unused")
101    private static ScriptObject get__proto__(final Object self) {
102        // See ES6 draft spec: B.2.2.1.1 get Object.prototype.__proto__
103        // Step 1 Let O be the result of calling ToObject passing the this.
104        final ScriptObject sobj = Global.checkObject(Global.toObject(self));
105        return sobj.getProto();
106    }
107
108    @SuppressWarnings("unused")
109    private static Object set__proto__(final Object self, final Object proto) {
110        // See ES6 draft spec: B.2.2.1.2 set Object.prototype.__proto__
111        // Step 1
112        Global.checkObjectCoercible(self);
113        // Step 4
114        if (! (self instanceof ScriptObject)) {
115            return UNDEFINED;
116        }
117
118        final ScriptObject sobj = (ScriptObject)self;
119        // __proto__ assignment ignores non-nulls and non-objects
120        // step 3: If Type(proto) is neither Object nor Null, then return undefined.
121        if (proto == null || proto instanceof ScriptObject) {
122            sobj.setPrototypeOf(proto);
123        }
124        return UNDEFINED;
125    }
126
127    private static final MethodType MIRROR_GETTER_TYPE = MethodType.methodType(Object.class, ScriptObjectMirror.class);
128    private static final MethodType MIRROR_SETTER_TYPE = MethodType.methodType(Object.class, ScriptObjectMirror.class, Object.class);
129
130    // initialized by nasgen
131    @SuppressWarnings("unused")
132    private static PropertyMap $nasgenmap$;
133
134    private NativeObject() {
135        // don't create me!
136        throw new UnsupportedOperationException();
137    }
138
139    private static ECMAException notAnObject(final Object obj) {
140        return typeError("not.an.object", ScriptRuntime.safeToString(obj));
141    }
142
143    /**
144     * Nashorn extension: setIndexedPropertiesToExternalArrayData
145     *
146     * @param self self reference
147     * @param obj object whose index properties are backed by buffer
148     * @param buf external buffer - should be a nio ByteBuffer
149     * @return the 'obj' object
150     */
151    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
152    public static ScriptObject setIndexedPropertiesToExternalArrayData(final Object self, final Object obj, final Object buf) {
153        Global.checkObject(obj);
154        final ScriptObject sobj = (ScriptObject)obj;
155        if (buf instanceof ByteBuffer) {
156            sobj.setArray(ArrayData.allocate((ByteBuffer)buf));
157        } else {
158            throw typeError("not.a.bytebuffer", "setIndexedPropertiesToExternalArrayData's buf argument");
159        }
160        return sobj;
161    }
162
163
164    /**
165     * ECMA 15.2.3.2 Object.getPrototypeOf ( O )
166     *
167     * @param  self self reference
168     * @param  obj object to get prototype from
169     * @return the prototype of an object
170     */
171    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
172    public static Object getPrototypeOf(final Object self, final Object obj) {
173        if (obj instanceof ScriptObject) {
174            return ((ScriptObject)obj).getProto();
175        } else if (obj instanceof ScriptObjectMirror) {
176            return ((ScriptObjectMirror)obj).getProto();
177        } else {
178            final JSType type = JSType.of(obj);
179            if (type == JSType.OBJECT) {
180                // host (Java) objects have null __proto__
181                return null;
182            }
183
184            // must be some JS primitive
185            throw notAnObject(obj);
186        }
187    }
188
189    /**
190     * Nashorn extension: Object.setPrototypeOf ( O, proto )
191     * Also found in ES6 draft specification.
192     *
193     * @param  self self reference
194     * @param  obj object to set prototype for
195     * @param  proto prototype object to be used
196     * @return object whose prototype is set
197     */
198    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
199    public static Object setPrototypeOf(final Object self, final Object obj, final Object proto) {
200        if (obj instanceof ScriptObject) {
201            ((ScriptObject)obj).setPrototypeOf(proto);
202            return obj;
203        } else if (obj instanceof ScriptObjectMirror) {
204            ((ScriptObjectMirror)obj).setProto(proto);
205            return obj;
206        }
207
208        throw notAnObject(obj);
209    }
210
211    /**
212     * ECMA 15.2.3.3 Object.getOwnPropertyDescriptor ( O, P )
213     *
214     * @param self  self reference
215     * @param obj   object from which to get property descriptor for {@code ToString(prop)}
216     * @param prop  property descriptor
217     * @return property descriptor
218     */
219    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
220    public static Object getOwnPropertyDescriptor(final Object self, final Object obj, final Object prop) {
221        if (obj instanceof ScriptObject) {
222            final String       key  = JSType.toString(prop);
223            final ScriptObject sobj = (ScriptObject)obj;
224
225            return sobj.getOwnPropertyDescriptor(key);
226        } else if (obj instanceof ScriptObjectMirror) {
227            final String       key  = JSType.toString(prop);
228            final ScriptObjectMirror sobjMirror = (ScriptObjectMirror)obj;
229
230            return sobjMirror.getOwnPropertyDescriptor(key);
231        } else {
232            throw notAnObject(obj);
233        }
234    }
235
236    /**
237     * ECMA 15.2.3.4 Object.getOwnPropertyNames ( O )
238     *
239     * @param self self reference
240     * @param obj  object to query for property names
241     * @return array of property names
242     */
243    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
244    public static ScriptObject getOwnPropertyNames(final Object self, final Object obj) {
245        if (obj instanceof ScriptObject) {
246            return new NativeArray(((ScriptObject)obj).getOwnKeys(true));
247        } else if (obj instanceof ScriptObjectMirror) {
248            return new NativeArray(((ScriptObjectMirror)obj).getOwnKeys(true));
249        } else {
250            throw notAnObject(obj);
251        }
252    }
253
254    /**
255     * ECMA 15.2.3.5 Object.create ( O [, Properties] )
256     *
257     * @param self  self reference
258     * @param proto prototype object
259     * @param props properties to define
260     * @return object created
261     */
262    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
263    public static ScriptObject create(final Object self, final Object proto, final Object props) {
264        if (proto != null) {
265            Global.checkObject(proto);
266        }
267
268        // FIXME: should we create a proper object with correct number of
269        // properties?
270        final ScriptObject newObj = Global.newEmptyInstance();
271        newObj.setProto((ScriptObject)proto);
272        if (props != UNDEFINED) {
273            NativeObject.defineProperties(self, newObj, props);
274        }
275
276        return newObj;
277    }
278
279    /**
280     * ECMA 15.2.3.6 Object.defineProperty ( O, P, Attributes )
281     *
282     * @param self self reference
283     * @param obj  object in which to define a property
284     * @param prop property to define
285     * @param attr attributes for property descriptor
286     * @return object
287     */
288    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
289    public static ScriptObject defineProperty(final Object self, final Object obj, final Object prop, final Object attr) {
290        final ScriptObject sobj = Global.checkObject(obj);
291        sobj.defineOwnProperty(JSType.toString(prop), attr, true);
292        return sobj;
293    }
294
295    /**
296     * ECMA 5.2.3.7 Object.defineProperties ( O, Properties )
297     *
298     * @param self  self reference
299     * @param obj   object in which to define properties
300     * @param props properties
301     * @return object
302     */
303    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
304    public static ScriptObject defineProperties(final Object self, final Object obj, final Object props) {
305        final ScriptObject sobj     = Global.checkObject(obj);
306        final Object       propsObj = Global.toObject(props);
307
308        if (propsObj instanceof ScriptObject) {
309            final Object[] keys = ((ScriptObject)propsObj).getOwnKeys(false);
310            for (final Object key : keys) {
311                final String prop = JSType.toString(key);
312                sobj.defineOwnProperty(prop, ((ScriptObject)propsObj).get(prop), true);
313            }
314        }
315        return sobj;
316    }
317
318    /**
319     * ECMA 15.2.3.8 Object.seal ( O )
320     *
321     * @param self self reference
322     * @param obj  object to seal
323     * @return sealed object
324     */
325    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
326    public static Object seal(final Object self, final Object obj) {
327        if (obj instanceof ScriptObject) {
328            return ((ScriptObject)obj).seal();
329        } else if (obj instanceof ScriptObjectMirror) {
330            return ((ScriptObjectMirror)obj).seal();
331        } else {
332            throw notAnObject(obj);
333        }
334    }
335
336
337    /**
338     * ECMA 15.2.3.9 Object.freeze ( O )
339     *
340     * @param self self reference
341     * @param obj object to freeze
342     * @return frozen object
343     */
344    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
345    public static Object freeze(final Object self, final Object obj) {
346        if (obj instanceof ScriptObject) {
347            return ((ScriptObject)obj).freeze();
348        } else if (obj instanceof ScriptObjectMirror) {
349            return ((ScriptObjectMirror)obj).freeze();
350        } else {
351            throw notAnObject(obj);
352        }
353    }
354
355    /**
356     * ECMA 15.2.3.10 Object.preventExtensions ( O )
357     *
358     * @param self self reference
359     * @param obj  object, for which to set the internal extensible property to false
360     * @return object
361     */
362    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
363    public static Object preventExtensions(final Object self, final Object obj) {
364        if (obj instanceof ScriptObject) {
365            return ((ScriptObject)obj).preventExtensions();
366        } else if (obj instanceof ScriptObjectMirror) {
367            return ((ScriptObjectMirror)obj).preventExtensions();
368        } else {
369            throw notAnObject(obj);
370        }
371    }
372
373    /**
374     * ECMA 15.2.3.11 Object.isSealed ( O )
375     *
376     * @param self self reference
377     * @param obj check whether an object is sealed
378     * @return true if sealed, false otherwise
379     */
380    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
381    public static boolean isSealed(final Object self, final Object obj) {
382        if (obj instanceof ScriptObject) {
383            return ((ScriptObject)obj).isSealed();
384        } else if (obj instanceof ScriptObjectMirror) {
385            return ((ScriptObjectMirror)obj).isSealed();
386        } else {
387            throw notAnObject(obj);
388        }
389    }
390
391    /**
392     * ECMA 15.2.3.12 Object.isFrozen ( O )
393     *
394     * @param self self reference
395     * @param obj check whether an object
396     * @return true if object is frozen, false otherwise
397     */
398    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
399    public static boolean isFrozen(final Object self, final Object obj) {
400        if (obj instanceof ScriptObject) {
401            return ((ScriptObject)obj).isFrozen();
402        } else if (obj instanceof ScriptObjectMirror) {
403            return ((ScriptObjectMirror)obj).isFrozen();
404        } else {
405            throw notAnObject(obj);
406        }
407    }
408
409    /**
410     * ECMA 15.2.3.13 Object.isExtensible ( O )
411     *
412     * @param self self reference
413     * @param obj check whether an object is extensible
414     * @return true if object is extensible, false otherwise
415     */
416    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
417    public static boolean isExtensible(final Object self, final Object obj) {
418        if (obj instanceof ScriptObject) {
419            return ((ScriptObject)obj).isExtensible();
420        } else if (obj instanceof ScriptObjectMirror) {
421            return ((ScriptObjectMirror)obj).isExtensible();
422        } else {
423            throw notAnObject(obj);
424        }
425    }
426
427    /**
428     * ECMA 15.2.3.14 Object.keys ( O )
429     *
430     * @param self self reference
431     * @param obj  object from which to extract keys
432     * @return array of keys in object
433     */
434    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
435    public static ScriptObject keys(final Object self, final Object obj) {
436        if (obj instanceof ScriptObject) {
437            final ScriptObject sobj = (ScriptObject)obj;
438            return new NativeArray(sobj.getOwnKeys(false));
439        } else if (obj instanceof ScriptObjectMirror) {
440            final ScriptObjectMirror sobjMirror = (ScriptObjectMirror)obj;
441            return new NativeArray(sobjMirror.getOwnKeys(false));
442        } else {
443            throw notAnObject(obj);
444        }
445    }
446
447    /**
448     * ECMA 15.2.2.1 , 15.2.1.1 new Object([value]) and Object([value])
449     *
450     * Constructor
451     *
452     * @param newObj is the new object instantiated with the new operator
453     * @param self   self reference
454     * @param value  value of object to be instantiated
455     * @return the new NativeObject
456     */
457    @Constructor
458    public static Object construct(final boolean newObj, final Object self, final Object value) {
459        final JSType type = JSType.ofNoFunction(value);
460
461        // Object(null), Object(undefined), Object() are same as "new Object()"
462
463        if (newObj || type == JSType.NULL || type == JSType.UNDEFINED) {
464            switch (type) {
465            case BOOLEAN:
466            case NUMBER:
467            case STRING:
468                return Global.toObject(value);
469            case OBJECT:
470                return value;
471            case NULL:
472            case UNDEFINED:
473                // fall through..
474            default:
475                break;
476            }
477
478            return Global.newEmptyInstance();
479        }
480
481        return Global.toObject(value);
482    }
483
484    /**
485     * ECMA 15.2.4.2 Object.prototype.toString ( )
486     *
487     * @param self self reference
488     * @return ToString of object
489     */
490    @Function(attributes = Attribute.NOT_ENUMERABLE)
491    public static String toString(final Object self) {
492        return ScriptRuntime.builtinObjectToString(self);
493    }
494
495    /**
496     * ECMA 15.2.4.3 Object.prototype.toLocaleString ( )
497     *
498     * @param self self reference
499     * @return localized ToString
500     */
501    @Function(attributes = Attribute.NOT_ENUMERABLE)
502    public static Object toLocaleString(final Object self) {
503        final Object obj = JSType.toScriptObject(self);
504        if (obj instanceof ScriptObject) {
505            final InvokeByName toStringInvoker = getTO_STRING();
506            final ScriptObject sobj = (ScriptObject)obj;
507            try {
508                final Object toString = toStringInvoker.getGetter().invokeExact(sobj);
509
510                if (Bootstrap.isCallable(toString)) {
511                    return toStringInvoker.getInvoker().invokeExact(toString, sobj);
512                }
513            } catch (final RuntimeException | Error e) {
514                throw e;
515            } catch (final Throwable t) {
516                throw new RuntimeException(t);
517            }
518
519            throw typeError("not.a.function", "toString");
520        }
521
522        return ScriptRuntime.builtinObjectToString(self);
523    }
524
525    /**
526     * ECMA 15.2.4.4 Object.prototype.valueOf ( )
527     *
528     * @param self self reference
529     * @return value of object
530     */
531    @Function(attributes = Attribute.NOT_ENUMERABLE)
532    public static Object valueOf(final Object self) {
533        return Global.toObject(self);
534    }
535
536    /**
537     * ECMA 15.2.4.5 Object.prototype.hasOwnProperty (V)
538     *
539     * @param self self reference
540     * @param v property to check for
541     * @return true if property exists in object
542     */
543    @Function(attributes = Attribute.NOT_ENUMERABLE)
544    public static boolean hasOwnProperty(final Object self, final Object v) {
545        // Convert ScriptObjects to primitive with String.class hint
546        // but no need to convert other primitives to string.
547        final Object key = JSType.toPrimitive(v, String.class);
548        final Object obj = Global.toObject(self);
549
550        return obj instanceof ScriptObject && ((ScriptObject)obj).hasOwnProperty(key);
551    }
552
553    /**
554     * ECMA 15.2.4.6 Object.prototype.isPrototypeOf (V)
555     *
556     * @param self self reference
557     * @param v v prototype object to check against
558     * @return true if object is prototype of v
559     */
560    @Function(attributes = Attribute.NOT_ENUMERABLE)
561    public static boolean isPrototypeOf(final Object self, final Object v) {
562        if (!(v instanceof ScriptObject)) {
563            return false;
564        }
565
566        final Object obj   = Global.toObject(self);
567        ScriptObject proto = (ScriptObject)v;
568
569        do {
570            proto = proto.getProto();
571            if (proto == obj) {
572                return true;
573            }
574        } while (proto != null);
575
576        return false;
577    }
578
579    /**
580     * ECMA 15.2.4.7 Object.prototype.propertyIsEnumerable (V)
581     *
582     * @param self self reference
583     * @param v property to check if enumerable
584     * @return true if property is enumerable
585     */
586    @Function(attributes = Attribute.NOT_ENUMERABLE)
587    public static boolean propertyIsEnumerable(final Object self, final Object v) {
588        final String str = JSType.toString(v);
589        final Object obj = Global.toObject(self);
590
591        if (obj instanceof ScriptObject) {
592            final jdk.nashorn.internal.runtime.Property property = ((ScriptObject)obj).getMap().findProperty(str);
593            return property != null && property.isEnumerable();
594        }
595
596        return false;
597    }
598
599    /**
600     * Nashorn extension: Object.bindProperties
601     *
602     * Binds the source object's properties to the target object. Binding
603     * properties allows two-way read/write for the properties of the source object.
604     *
605     * Example:
606     * <pre>
607     * var obj = { x: 34, y: 100 };
608     * var foo = {}
609     *
610     * // bind properties of "obj" to "foo" object
611     * Object.bindProperties(foo, obj);
612     *
613     * // now, we can access/write on 'foo' properties
614     * print(foo.x); // prints obj.x which is 34
615     *
616     * // update obj.x via foo.x
617     * foo.x = "hello";
618     * print(obj.x); // prints "hello" now
619     *
620     * obj.x = 42;   // foo.x also becomes 42
621     * print(foo.x); // prints 42
622     * </pre>
623     * <p>
624     * The source object bound can be a ScriptObject or a ScriptOjectMirror.
625     * null or undefined source object results in TypeError being thrown.
626     * </p>
627     * Example:
628     * <pre>
629     * var obj = loadWithNewGlobal({
630     *    name: "test",
631     *    script: "obj = { x: 33, y: 'hello' }"
632     * });
633     *
634     * // bind 'obj's properties to global scope 'this'
635     * Object.bindProperties(this, obj);
636     * print(x);         // prints 33
637     * print(y);         // prints "hello"
638     * x = Math.PI;      // changes obj.x to Math.PI
639     * print(obj.x);     // prints Math.PI
640     * </pre>
641     *
642     * Limitations of property binding:
643     * <ul>
644     * <li> Only enumerable, immediate (not proto inherited) properties of the source object are bound.
645     * <li> If the target object already contains a property called "foo", the source's "foo" is skipped (not bound).
646     * <li> Properties added to the source object after binding to the target are not bound.
647     * <li> Property configuration changes on the source object (or on the target) is not propagated.
648     * <li> Delete of property on the target (or the source) is not propagated -
649     * only the property value is set to 'undefined' if the property happens to be a data property.
650     * </ul>
651     * <p>
652     * It is recommended that the bound properties be treated as non-configurable
653     * properties to avoid surprises.
654     * </p>
655     *
656     * @param self self reference
657     * @param target the target object to which the source object's properties are bound
658     * @param source the source object whose properties are bound to the target
659     * @return the target object after property binding
660     */
661    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
662    public static Object bindProperties(final Object self, final Object target, final Object source) {
663        // target object has to be a ScriptObject
664        final ScriptObject targetObj = Global.checkObject(target);
665        // check null or undefined source object
666        Global.checkObjectCoercible(source);
667
668        if (source instanceof ScriptObject) {
669            final ScriptObject sourceObj  = (ScriptObject)source;
670
671            final PropertyMap  sourceMap  = sourceObj.getMap();
672            final Property[]   properties = sourceMap.getProperties();
673            //replace the map and blow up everything to objects to work with dual fields :-(
674
675            // filter non-enumerable properties
676            final ArrayList<Property> propList = new ArrayList<>();
677            for (final Property prop : properties) {
678                if (prop.isEnumerable()) {
679                    final Object value = sourceObj.get(prop.getKey());
680                    prop.setType(Object.class);
681                    prop.setValue(sourceObj, sourceObj, value, false);
682                    propList.add(prop);
683                }
684            }
685
686            if (!propList.isEmpty()) {
687                targetObj.addBoundProperties(sourceObj, propList.toArray(new Property[propList.size()]));
688            }
689        } else if (source instanceof ScriptObjectMirror) {
690            // get enumerable, immediate properties of mirror
691            final ScriptObjectMirror mirror = (ScriptObjectMirror)source;
692            final String[] keys = mirror.getOwnKeys(false);
693            if (keys.length == 0) {
694                // nothing to bind
695                return target;
696            }
697
698            // make accessor properties using dynamic invoker getters and setters
699            final AccessorProperty[] props = new AccessorProperty[keys.length];
700            for (int idx = 0; idx < keys.length; idx++) {
701                props[idx] = createAccessorProperty(keys[idx]);
702            }
703
704            targetObj.addBoundProperties(source, props);
705        } else if (source instanceof StaticClass) {
706            final Class<?> clazz = ((StaticClass)source).getRepresentedClass();
707            Bootstrap.checkReflectionAccess(clazz, true);
708            bindBeanProperties(targetObj, source, BeansLinker.getReadableStaticPropertyNames(clazz),
709                    BeansLinker.getWritableStaticPropertyNames(clazz), BeansLinker.getStaticMethodNames(clazz));
710        } else {
711            final Class<?> clazz = source.getClass();
712            Bootstrap.checkReflectionAccess(clazz, false);
713            bindBeanProperties(targetObj, source, BeansLinker.getReadableInstancePropertyNames(clazz),
714                    BeansLinker.getWritableInstancePropertyNames(clazz), BeansLinker.getInstanceMethodNames(clazz));
715        }
716
717        return target;
718    }
719
720    private static AccessorProperty createAccessorProperty(final String name) {
721        final MethodHandle getter = Bootstrap.createDynamicInvoker(name, NashornCallSiteDescriptor.GET_METHOD_PROPERTY, MIRROR_GETTER_TYPE);
722        final MethodHandle setter = Bootstrap.createDynamicInvoker(name, NashornCallSiteDescriptor.SET_PROPERTY, MIRROR_SETTER_TYPE);
723        return AccessorProperty.create(name, 0, getter, setter);
724    }
725
726    /**
727     * Binds the source mirror object's properties to the target object. Binding
728     * properties allows two-way read/write for the properties of the source object.
729     * All inherited, enumerable properties are also bound. This method is used to
730     * to make 'with' statement work with ScriptObjectMirror as scope object.
731     *
732     * @param target the target object to which the source object's properties are bound
733     * @param source the source object whose properties are bound to the target
734     * @return the target object after property binding
735     */
736    public static Object bindAllProperties(final ScriptObject target, final ScriptObjectMirror source) {
737        final Set<String> keys = source.keySet();
738        // make accessor properties using dynamic invoker getters and setters
739        final AccessorProperty[] props = new AccessorProperty[keys.size()];
740        int idx = 0;
741        for (final String name : keys) {
742            props[idx] = createAccessorProperty(name);
743            idx++;
744        }
745
746        target.addBoundProperties(source, props);
747        return target;
748    }
749
750    private static void bindBeanProperties(final ScriptObject targetObj, final Object source,
751            final Collection<String> readablePropertyNames, final Collection<String> writablePropertyNames,
752            final Collection<String> methodNames) {
753        final Set<String> propertyNames = new HashSet<>(readablePropertyNames);
754        propertyNames.addAll(writablePropertyNames);
755
756        final Class<?> clazz = source.getClass();
757
758        final MethodType getterType = MethodType.methodType(Object.class, clazz);
759        final MethodType setterType = MethodType.methodType(Object.class, clazz, Object.class);
760
761        final GuardingDynamicLinker linker = BeansLinker.getLinkerForClass(clazz);
762
763        final List<AccessorProperty> properties = new ArrayList<>(propertyNames.size() + methodNames.size());
764        for(final String methodName: methodNames) {
765            final MethodHandle method;
766            try {
767                method = getBeanOperation(linker, StandardOperation.GET_METHOD, methodName, getterType, source);
768            } catch(final IllegalAccessError e) {
769                // Presumably, this was a caller sensitive method. Ignore it and carry on.
770                continue;
771            }
772            properties.add(AccessorProperty.create(methodName, Property.NOT_WRITABLE, getBoundBeanMethodGetter(source,
773                    method), Lookup.EMPTY_SETTER));
774        }
775        for(final String propertyName: propertyNames) {
776            MethodHandle getter;
777            if(readablePropertyNames.contains(propertyName)) {
778                try {
779                    getter = getBeanOperation(linker, StandardOperation.GET_PROPERTY, propertyName, getterType, source);
780                } catch(final IllegalAccessError e) {
781                    // Presumably, this was a caller sensitive method. Ignore it and carry on.
782                    getter = Lookup.EMPTY_GETTER;
783                }
784            } else {
785                getter = Lookup.EMPTY_GETTER;
786            }
787            final boolean isWritable = writablePropertyNames.contains(propertyName);
788            MethodHandle setter;
789            if(isWritable) {
790                try {
791                    setter = getBeanOperation(linker, StandardOperation.SET_PROPERTY, propertyName, setterType, source);
792                } catch(final IllegalAccessError e) {
793                    // Presumably, this was a caller sensitive method. Ignore it and carry on.
794                    setter = Lookup.EMPTY_SETTER;
795                }
796            } else {
797                setter = Lookup.EMPTY_SETTER;
798            }
799            if(getter != Lookup.EMPTY_GETTER || setter != Lookup.EMPTY_SETTER) {
800                properties.add(AccessorProperty.create(propertyName, isWritable ? 0 : Property.NOT_WRITABLE, getter, setter));
801            }
802        }
803
804        targetObj.addBoundProperties(source, properties.toArray(new AccessorProperty[properties.size()]));
805    }
806
807    private static MethodHandle getBoundBeanMethodGetter(final Object source, final MethodHandle methodGetter) {
808        try {
809            // NOTE: we're relying on the fact that StandardOperation.GET_METHOD return value is constant for any given method
810            // name and object linked with BeansLinker. (Actually, an even stronger assumption is true: return value is
811            // constant for any given method name and object's class.)
812            return MethodHandles.dropArguments(MethodHandles.constant(Object.class,
813                    Bootstrap.bindCallable(methodGetter.invoke(source), source, null)), 0, Object.class);
814        } catch(RuntimeException|Error e) {
815            throw e;
816        } catch(final Throwable t) {
817            throw new RuntimeException(t);
818        }
819    }
820
821    private static MethodHandle getBeanOperation(final GuardingDynamicLinker linker, final StandardOperation operation,
822            final String name, final MethodType methodType, final Object source) {
823        final GuardedInvocation inv;
824        try {
825            inv = NashornBeansLinker.getGuardedInvocation(linker, createLinkRequest(new NamedOperation(operation, name), methodType, source), Bootstrap.getLinkerServices());
826            assert passesGuard(source, inv.getGuard());
827        } catch(RuntimeException|Error e) {
828            throw e;
829        } catch(final Throwable t) {
830            throw new RuntimeException(t);
831        }
832        assert inv.getSwitchPoints() == null; // Linkers in Dynalink's beans package don't use switchpoints.
833        // We discard the guard, as all method handles will be bound to a specific object.
834        return inv.getInvocation();
835    }
836
837    private static boolean passesGuard(final Object obj, final MethodHandle guard) throws Throwable {
838        return guard == null || (boolean)guard.invoke(obj);
839    }
840
841    private static LinkRequest createLinkRequest(final Operation operation, final MethodType methodType, final Object source) {
842        return new SimpleLinkRequest(new CallSiteDescriptor(MethodHandles.publicLookup(), operation,
843                methodType), false, source);
844    }
845
846    private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
847        return MH.findStatic(MethodHandles.lookup(), NativeObject.class, name, MH.type(rtype, types));
848    }
849}
850