NativeObject.java revision 1598:30c3bcdb762c
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.dynalink.CallSiteDescriptor;
43import jdk.dynalink.NamedOperation;
44import jdk.dynalink.Operation;
45import jdk.dynalink.StandardOperation;
46import jdk.dynalink.beans.BeansLinker;
47import jdk.dynalink.beans.StaticClass;
48import jdk.dynalink.linker.GuardedInvocation;
49import jdk.dynalink.linker.GuardingDynamicLinker;
50import jdk.dynalink.linker.LinkRequest;
51import jdk.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 2 19.1.2.8 Object.getOwnPropertySymbols ( O )
256     *
257     * @param self self reference
258     * @param obj  object to query for property names
259     * @return array of property names
260     */
261    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
262    public static ScriptObject getOwnPropertySymbols(final Object self, final Object obj) {
263        if (obj instanceof ScriptObject) {
264            return new NativeArray(((ScriptObject)obj).getOwnSymbols(true));
265        } else {
266            // TODO: we don't support this on ScriptObjectMirror objects yet
267            throw notAnObject(obj);
268        }
269    }
270
271    /**
272     * ECMA 15.2.3.5 Object.create ( O [, Properties] )
273     *
274     * @param self  self reference
275     * @param proto prototype object
276     * @param props properties to define
277     * @return object created
278     */
279    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
280    public static ScriptObject create(final Object self, final Object proto, final Object props) {
281        if (proto != null) {
282            Global.checkObject(proto);
283        }
284
285        // FIXME: should we create a proper object with correct number of
286        // properties?
287        final ScriptObject newObj = Global.newEmptyInstance();
288        newObj.setProto((ScriptObject)proto);
289        if (props != UNDEFINED) {
290            NativeObject.defineProperties(self, newObj, props);
291        }
292
293        return newObj;
294    }
295
296    /**
297     * ECMA 15.2.3.6 Object.defineProperty ( O, P, Attributes )
298     *
299     * @param self self reference
300     * @param obj  object in which to define a property
301     * @param prop property to define
302     * @param attr attributes for property descriptor
303     * @return object
304     */
305    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
306    public static ScriptObject defineProperty(final Object self, final Object obj, final Object prop, final Object attr) {
307        final ScriptObject sobj = Global.checkObject(obj);
308        sobj.defineOwnProperty(JSType.toPropertyKey(prop), attr, true);
309        return sobj;
310    }
311
312    /**
313     * ECMA 5.2.3.7 Object.defineProperties ( O, Properties )
314     *
315     * @param self  self reference
316     * @param obj   object in which to define properties
317     * @param props properties
318     * @return object
319     */
320    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
321    public static ScriptObject defineProperties(final Object self, final Object obj, final Object props) {
322        final ScriptObject sobj     = Global.checkObject(obj);
323        final Object       propsObj = Global.toObject(props);
324
325        if (propsObj instanceof ScriptObject) {
326            final Object[] keys = ((ScriptObject)propsObj).getOwnKeys(false);
327            for (final Object key : keys) {
328                final String prop = JSType.toString(key);
329                sobj.defineOwnProperty(prop, ((ScriptObject)propsObj).get(prop), true);
330            }
331        }
332        return sobj;
333    }
334
335    /**
336     * ECMA 15.2.3.8 Object.seal ( O )
337     *
338     * @param self self reference
339     * @param obj  object to seal
340     * @return sealed object
341     */
342    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
343    public static Object seal(final Object self, final Object obj) {
344        if (obj instanceof ScriptObject) {
345            return ((ScriptObject)obj).seal();
346        } else if (obj instanceof ScriptObjectMirror) {
347            return ((ScriptObjectMirror)obj).seal();
348        } else {
349            throw notAnObject(obj);
350        }
351    }
352
353
354    /**
355     * ECMA 15.2.3.9 Object.freeze ( O )
356     *
357     * @param self self reference
358     * @param obj object to freeze
359     * @return frozen object
360     */
361    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
362    public static Object freeze(final Object self, final Object obj) {
363        if (obj instanceof ScriptObject) {
364            return ((ScriptObject)obj).freeze();
365        } else if (obj instanceof ScriptObjectMirror) {
366            return ((ScriptObjectMirror)obj).freeze();
367        } else {
368            throw notAnObject(obj);
369        }
370    }
371
372    /**
373     * ECMA 15.2.3.10 Object.preventExtensions ( O )
374     *
375     * @param self self reference
376     * @param obj  object, for which to set the internal extensible property to false
377     * @return object
378     */
379    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
380    public static Object preventExtensions(final Object self, final Object obj) {
381        if (obj instanceof ScriptObject) {
382            return ((ScriptObject)obj).preventExtensions();
383        } else if (obj instanceof ScriptObjectMirror) {
384            return ((ScriptObjectMirror)obj).preventExtensions();
385        } else {
386            throw notAnObject(obj);
387        }
388    }
389
390    /**
391     * ECMA 15.2.3.11 Object.isSealed ( O )
392     *
393     * @param self self reference
394     * @param obj check whether an object is sealed
395     * @return true if sealed, false otherwise
396     */
397    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
398    public static boolean isSealed(final Object self, final Object obj) {
399        if (obj instanceof ScriptObject) {
400            return ((ScriptObject)obj).isSealed();
401        } else if (obj instanceof ScriptObjectMirror) {
402            return ((ScriptObjectMirror)obj).isSealed();
403        } else {
404            throw notAnObject(obj);
405        }
406    }
407
408    /**
409     * ECMA 15.2.3.12 Object.isFrozen ( O )
410     *
411     * @param self self reference
412     * @param obj check whether an object
413     * @return true if object is frozen, false otherwise
414     */
415    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
416    public static boolean isFrozen(final Object self, final Object obj) {
417        if (obj instanceof ScriptObject) {
418            return ((ScriptObject)obj).isFrozen();
419        } else if (obj instanceof ScriptObjectMirror) {
420            return ((ScriptObjectMirror)obj).isFrozen();
421        } else {
422            throw notAnObject(obj);
423        }
424    }
425
426    /**
427     * ECMA 15.2.3.13 Object.isExtensible ( O )
428     *
429     * @param self self reference
430     * @param obj check whether an object is extensible
431     * @return true if object is extensible, false otherwise
432     */
433    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
434    public static boolean isExtensible(final Object self, final Object obj) {
435        if (obj instanceof ScriptObject) {
436            return ((ScriptObject)obj).isExtensible();
437        } else if (obj instanceof ScriptObjectMirror) {
438            return ((ScriptObjectMirror)obj).isExtensible();
439        } else {
440            throw notAnObject(obj);
441        }
442    }
443
444    /**
445     * ECMA 15.2.3.14 Object.keys ( O )
446     *
447     * @param self self reference
448     * @param obj  object from which to extract keys
449     * @return array of keys in object
450     */
451    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
452    public static ScriptObject keys(final Object self, final Object obj) {
453        if (obj instanceof ScriptObject) {
454            final ScriptObject sobj = (ScriptObject)obj;
455            return new NativeArray(sobj.getOwnKeys(false));
456        } else if (obj instanceof ScriptObjectMirror) {
457            final ScriptObjectMirror sobjMirror = (ScriptObjectMirror)obj;
458            return new NativeArray(sobjMirror.getOwnKeys(false));
459        } else {
460            throw notAnObject(obj);
461        }
462    }
463
464    /**
465     * ECMA 15.2.2.1 , 15.2.1.1 new Object([value]) and Object([value])
466     *
467     * Constructor
468     *
469     * @param newObj is the new object instantiated with the new operator
470     * @param self   self reference
471     * @param value  value of object to be instantiated
472     * @return the new NativeObject
473     */
474    @Constructor
475    public static Object construct(final boolean newObj, final Object self, final Object value) {
476        final JSType type = JSType.ofNoFunction(value);
477
478        // Object(null), Object(undefined), Object() are same as "new Object()"
479
480        if (newObj || type == JSType.NULL || type == JSType.UNDEFINED) {
481            switch (type) {
482            case BOOLEAN:
483            case NUMBER:
484            case STRING:
485            case SYMBOL:
486                return Global.toObject(value);
487            case OBJECT:
488                return value;
489            case NULL:
490            case UNDEFINED:
491                // fall through..
492            default:
493                break;
494            }
495
496            return Global.newEmptyInstance();
497        }
498
499        return Global.toObject(value);
500    }
501
502    /**
503     * ECMA 15.2.4.2 Object.prototype.toString ( )
504     *
505     * @param self self reference
506     * @return ToString of object
507     */
508    @Function(attributes = Attribute.NOT_ENUMERABLE)
509    public static String toString(final Object self) {
510        return ScriptRuntime.builtinObjectToString(self);
511    }
512
513    /**
514     * ECMA 15.2.4.3 Object.prototype.toLocaleString ( )
515     *
516     * @param self self reference
517     * @return localized ToString
518     */
519    @Function(attributes = Attribute.NOT_ENUMERABLE)
520    public static Object toLocaleString(final Object self) {
521        final Object obj = JSType.toScriptObject(self);
522        if (obj instanceof ScriptObject) {
523            final InvokeByName toStringInvoker = getTO_STRING();
524            final ScriptObject sobj = (ScriptObject)obj;
525            try {
526                final Object toString = toStringInvoker.getGetter().invokeExact(sobj);
527
528                if (Bootstrap.isCallable(toString)) {
529                    return toStringInvoker.getInvoker().invokeExact(toString, sobj);
530                }
531            } catch (final RuntimeException | Error e) {
532                throw e;
533            } catch (final Throwable t) {
534                throw new RuntimeException(t);
535            }
536
537            throw typeError("not.a.function", "toString");
538        }
539
540        return ScriptRuntime.builtinObjectToString(self);
541    }
542
543    /**
544     * ECMA 15.2.4.4 Object.prototype.valueOf ( )
545     *
546     * @param self self reference
547     * @return value of object
548     */
549    @Function(attributes = Attribute.NOT_ENUMERABLE)
550    public static Object valueOf(final Object self) {
551        return Global.toObject(self);
552    }
553
554    /**
555     * ECMA 15.2.4.5 Object.prototype.hasOwnProperty (V)
556     *
557     * @param self self reference
558     * @param v property to check for
559     * @return true if property exists in object
560     */
561    @Function(attributes = Attribute.NOT_ENUMERABLE)
562    public static boolean hasOwnProperty(final Object self, final Object v) {
563        // Convert ScriptObjects to primitive with String.class hint
564        // but no need to convert other primitives to string.
565        final Object key = JSType.toPrimitive(v, String.class);
566        final Object obj = Global.toObject(self);
567
568        return obj instanceof ScriptObject && ((ScriptObject)obj).hasOwnProperty(key);
569    }
570
571    /**
572     * ECMA 15.2.4.6 Object.prototype.isPrototypeOf (V)
573     *
574     * @param self self reference
575     * @param v v prototype object to check against
576     * @return true if object is prototype of v
577     */
578    @Function(attributes = Attribute.NOT_ENUMERABLE)
579    public static boolean isPrototypeOf(final Object self, final Object v) {
580        if (!(v instanceof ScriptObject)) {
581            return false;
582        }
583
584        final Object obj   = Global.toObject(self);
585        ScriptObject proto = (ScriptObject)v;
586
587        do {
588            proto = proto.getProto();
589            if (proto == obj) {
590                return true;
591            }
592        } while (proto != null);
593
594        return false;
595    }
596
597    /**
598     * ECMA 15.2.4.7 Object.prototype.propertyIsEnumerable (V)
599     *
600     * @param self self reference
601     * @param v property to check if enumerable
602     * @return true if property is enumerable
603     */
604    @Function(attributes = Attribute.NOT_ENUMERABLE)
605    public static boolean propertyIsEnumerable(final Object self, final Object v) {
606        final String str = JSType.toString(v);
607        final Object obj = Global.toObject(self);
608
609        if (obj instanceof ScriptObject) {
610            final jdk.nashorn.internal.runtime.Property property = ((ScriptObject)obj).getMap().findProperty(str);
611            return property != null && property.isEnumerable();
612        }
613
614        return false;
615    }
616
617    /**
618     * Nashorn extension: Object.bindProperties
619     *
620     * Binds the source object's properties to the target object. Binding
621     * properties allows two-way read/write for the properties of the source object.
622     *
623     * Example:
624     * <pre>
625     * var obj = { x: 34, y: 100 };
626     * var foo = {}
627     *
628     * // bind properties of "obj" to "foo" object
629     * Object.bindProperties(foo, obj);
630     *
631     * // now, we can access/write on 'foo' properties
632     * print(foo.x); // prints obj.x which is 34
633     *
634     * // update obj.x via foo.x
635     * foo.x = "hello";
636     * print(obj.x); // prints "hello" now
637     *
638     * obj.x = 42;   // foo.x also becomes 42
639     * print(foo.x); // prints 42
640     * </pre>
641     * <p>
642     * The source object bound can be a ScriptObject or a ScriptOjectMirror.
643     * null or undefined source object results in TypeError being thrown.
644     * </p>
645     * Example:
646     * <pre>
647     * var obj = loadWithNewGlobal({
648     *    name: "test",
649     *    script: "obj = { x: 33, y: 'hello' }"
650     * });
651     *
652     * // bind 'obj's properties to global scope 'this'
653     * Object.bindProperties(this, obj);
654     * print(x);         // prints 33
655     * print(y);         // prints "hello"
656     * x = Math.PI;      // changes obj.x to Math.PI
657     * print(obj.x);     // prints Math.PI
658     * </pre>
659     *
660     * Limitations of property binding:
661     * <ul>
662     * <li> Only enumerable, immediate (not proto inherited) properties of the source object are bound.
663     * <li> If the target object already contains a property called "foo", the source's "foo" is skipped (not bound).
664     * <li> Properties added to the source object after binding to the target are not bound.
665     * <li> Property configuration changes on the source object (or on the target) is not propagated.
666     * <li> Delete of property on the target (or the source) is not propagated -
667     * only the property value is set to 'undefined' if the property happens to be a data property.
668     * </ul>
669     * <p>
670     * It is recommended that the bound properties be treated as non-configurable
671     * properties to avoid surprises.
672     * </p>
673     *
674     * @param self self reference
675     * @param target the target object to which the source object's properties are bound
676     * @param source the source object whose properties are bound to the target
677     * @return the target object after property binding
678     */
679    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
680    public static Object bindProperties(final Object self, final Object target, final Object source) {
681        // target object has to be a ScriptObject
682        final ScriptObject targetObj = Global.checkObject(target);
683        // check null or undefined source object
684        Global.checkObjectCoercible(source);
685
686        if (source instanceof ScriptObject) {
687            final ScriptObject sourceObj  = (ScriptObject)source;
688
689            final PropertyMap  sourceMap  = sourceObj.getMap();
690            final Property[]   properties = sourceMap.getProperties();
691            //replace the map and blow up everything to objects to work with dual fields :-(
692
693            // filter non-enumerable properties
694            final ArrayList<Property> propList = new ArrayList<>();
695            for (final Property prop : properties) {
696                if (prop.isEnumerable()) {
697                    final Object value = sourceObj.get(prop.getKey());
698                    prop.setType(Object.class);
699                    prop.setValue(sourceObj, sourceObj, value, false);
700                    propList.add(prop);
701                }
702            }
703
704            if (!propList.isEmpty()) {
705                targetObj.addBoundProperties(sourceObj, propList.toArray(new Property[propList.size()]));
706            }
707        } else if (source instanceof ScriptObjectMirror) {
708            // get enumerable, immediate properties of mirror
709            final ScriptObjectMirror mirror = (ScriptObjectMirror)source;
710            final String[] keys = mirror.getOwnKeys(false);
711            if (keys.length == 0) {
712                // nothing to bind
713                return target;
714            }
715
716            // make accessor properties using dynamic invoker getters and setters
717            final AccessorProperty[] props = new AccessorProperty[keys.length];
718            for (int idx = 0; idx < keys.length; idx++) {
719                props[idx] = createAccessorProperty(keys[idx]);
720            }
721
722            targetObj.addBoundProperties(source, props);
723        } else if (source instanceof StaticClass) {
724            final Class<?> clazz = ((StaticClass)source).getRepresentedClass();
725            Bootstrap.checkReflectionAccess(clazz, true);
726            bindBeanProperties(targetObj, source, BeansLinker.getReadableStaticPropertyNames(clazz),
727                    BeansLinker.getWritableStaticPropertyNames(clazz), BeansLinker.getStaticMethodNames(clazz));
728        } else {
729            final Class<?> clazz = source.getClass();
730            Bootstrap.checkReflectionAccess(clazz, false);
731            bindBeanProperties(targetObj, source, BeansLinker.getReadableInstancePropertyNames(clazz),
732                    BeansLinker.getWritableInstancePropertyNames(clazz), BeansLinker.getInstanceMethodNames(clazz));
733        }
734
735        return target;
736    }
737
738    private static AccessorProperty createAccessorProperty(final String name) {
739        final MethodHandle getter = Bootstrap.createDynamicInvoker(name, NashornCallSiteDescriptor.GET_METHOD_PROPERTY, MIRROR_GETTER_TYPE);
740        final MethodHandle setter = Bootstrap.createDynamicInvoker(name, NashornCallSiteDescriptor.SET_PROPERTY, MIRROR_SETTER_TYPE);
741        return AccessorProperty.create(name, 0, getter, setter);
742    }
743
744    /**
745     * Binds the source mirror object's properties to the target object. Binding
746     * properties allows two-way read/write for the properties of the source object.
747     * All inherited, enumerable properties are also bound. This method is used to
748     * to make 'with' statement work with ScriptObjectMirror as scope object.
749     *
750     * @param target the target object to which the source object's properties are bound
751     * @param source the source object whose properties are bound to the target
752     * @return the target object after property binding
753     */
754    public static Object bindAllProperties(final ScriptObject target, final ScriptObjectMirror source) {
755        final Set<String> keys = source.keySet();
756        // make accessor properties using dynamic invoker getters and setters
757        final AccessorProperty[] props = new AccessorProperty[keys.size()];
758        int idx = 0;
759        for (final String name : keys) {
760            props[idx] = createAccessorProperty(name);
761            idx++;
762        }
763
764        target.addBoundProperties(source, props);
765        return target;
766    }
767
768    private static void bindBeanProperties(final ScriptObject targetObj, final Object source,
769            final Collection<String> readablePropertyNames, final Collection<String> writablePropertyNames,
770            final Collection<String> methodNames) {
771        final Set<String> propertyNames = new HashSet<>(readablePropertyNames);
772        propertyNames.addAll(writablePropertyNames);
773
774        final Class<?> clazz = source.getClass();
775
776        final MethodType getterType = MethodType.methodType(Object.class, clazz);
777        final MethodType setterType = MethodType.methodType(Object.class, clazz, Object.class);
778
779        final GuardingDynamicLinker linker = Bootstrap.getBeanLinkerForClass(clazz);
780
781        final List<AccessorProperty> properties = new ArrayList<>(propertyNames.size() + methodNames.size());
782        for(final String methodName: methodNames) {
783            final MethodHandle method;
784            try {
785                method = getBeanOperation(linker, StandardOperation.GET_METHOD, methodName, getterType, source);
786            } catch(final IllegalAccessError e) {
787                // Presumably, this was a caller sensitive method. Ignore it and carry on.
788                continue;
789            }
790            properties.add(AccessorProperty.create(methodName, Property.NOT_WRITABLE, getBoundBeanMethodGetter(source,
791                    method), Lookup.EMPTY_SETTER));
792        }
793        for(final String propertyName: propertyNames) {
794            MethodHandle getter;
795            if(readablePropertyNames.contains(propertyName)) {
796                try {
797                    getter = getBeanOperation(linker, StandardOperation.GET_PROPERTY, propertyName, getterType, source);
798                } catch(final IllegalAccessError e) {
799                    // Presumably, this was a caller sensitive method. Ignore it and carry on.
800                    getter = Lookup.EMPTY_GETTER;
801                }
802            } else {
803                getter = Lookup.EMPTY_GETTER;
804            }
805            final boolean isWritable = writablePropertyNames.contains(propertyName);
806            MethodHandle setter;
807            if(isWritable) {
808                try {
809                    setter = getBeanOperation(linker, StandardOperation.SET_PROPERTY, propertyName, setterType, source);
810                } catch(final IllegalAccessError e) {
811                    // Presumably, this was a caller sensitive method. Ignore it and carry on.
812                    setter = Lookup.EMPTY_SETTER;
813                }
814            } else {
815                setter = Lookup.EMPTY_SETTER;
816            }
817            if(getter != Lookup.EMPTY_GETTER || setter != Lookup.EMPTY_SETTER) {
818                properties.add(AccessorProperty.create(propertyName, isWritable ? 0 : Property.NOT_WRITABLE, getter, setter));
819            }
820        }
821
822        targetObj.addBoundProperties(source, properties.toArray(new AccessorProperty[properties.size()]));
823    }
824
825    private static MethodHandle getBoundBeanMethodGetter(final Object source, final MethodHandle methodGetter) {
826        try {
827            // NOTE: we're relying on the fact that StandardOperation.GET_METHOD return value is constant for any given method
828            // name and object linked with BeansLinker. (Actually, an even stronger assumption is true: return value is
829            // constant for any given method name and object's class.)
830            return MethodHandles.dropArguments(MethodHandles.constant(Object.class,
831                    Bootstrap.bindCallable(methodGetter.invoke(source), source, null)), 0, Object.class);
832        } catch(RuntimeException|Error e) {
833            throw e;
834        } catch(final Throwable t) {
835            throw new RuntimeException(t);
836        }
837    }
838
839    private static MethodHandle getBeanOperation(final GuardingDynamicLinker linker, final StandardOperation operation,
840            final String name, final MethodType methodType, final Object source) {
841        final GuardedInvocation inv;
842        try {
843            inv = NashornBeansLinker.getGuardedInvocation(linker, createLinkRequest(new NamedOperation(operation, name), methodType, source), Bootstrap.getLinkerServices());
844            assert passesGuard(source, inv.getGuard());
845        } catch(RuntimeException|Error e) {
846            throw e;
847        } catch(final Throwable t) {
848            throw new RuntimeException(t);
849        }
850        assert inv.getSwitchPoints() == null; // Linkers in Dynalink's beans package don't use switchpoints.
851        // We discard the guard, as all method handles will be bound to a specific object.
852        return inv.getInvocation();
853    }
854
855    private static boolean passesGuard(final Object obj, final MethodHandle guard) throws Throwable {
856        return guard == null || (boolean)guard.invoke(obj);
857    }
858
859    private static LinkRequest createLinkRequest(final Operation operation, final MethodType methodType, final Object source) {
860        return new SimpleLinkRequest(new CallSiteDescriptor(MethodHandles.publicLookup(), operation,
861                methodType), false, source);
862    }
863
864    private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
865        return MH.findStatic(MethodHandles.lookup(), NativeObject.class, name, MH.type(rtype, types));
866    }
867}
868