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