ScriptObjectMirror.java revision 1590:1916a2c680d8
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.api.scripting;
27
28import java.nio.ByteBuffer;
29import java.security.AccessControlContext;
30import java.security.AccessController;
31import java.security.Permissions;
32import java.security.PrivilegedAction;
33import java.security.ProtectionDomain;
34import java.util.AbstractMap;
35import java.util.ArrayList;
36import java.util.Collection;
37import java.util.Collections;
38import java.util.Iterator;
39import java.util.LinkedHashSet;
40import java.util.List;
41import java.util.Map;
42import java.util.Objects;
43import java.util.Set;
44import java.util.concurrent.Callable;
45import javax.script.Bindings;
46import jdk.nashorn.internal.objects.Global;
47import jdk.nashorn.internal.runtime.ConsString;
48import jdk.nashorn.internal.runtime.Context;
49import jdk.nashorn.internal.runtime.ECMAException;
50import jdk.nashorn.internal.runtime.JSONListAdapter;
51import jdk.nashorn.internal.runtime.JSType;
52import jdk.nashorn.internal.runtime.ScriptFunction;
53import jdk.nashorn.internal.runtime.ScriptObject;
54import jdk.nashorn.internal.runtime.ScriptRuntime;
55import jdk.nashorn.internal.runtime.arrays.ArrayData;
56import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
57
58/**
59 * Mirror object that wraps a given Nashorn Script object.
60 *
61 * @since 1.8u40
62 */
63public final class ScriptObjectMirror extends AbstractJSObject implements Bindings {
64    private static AccessControlContext getContextAccCtxt() {
65        final Permissions perms = new Permissions();
66        perms.add(new RuntimePermission(Context.NASHORN_GET_CONTEXT));
67        return new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, perms) });
68    }
69
70    private static final AccessControlContext GET_CONTEXT_ACC_CTXT = getContextAccCtxt();
71
72    private final ScriptObject sobj;
73    private final Global  global;
74    private final boolean strict;
75    private final boolean jsonCompatible;
76
77    @Override
78    public boolean equals(final Object other) {
79        if (other instanceof ScriptObjectMirror) {
80            return sobj.equals(((ScriptObjectMirror)other).sobj);
81        }
82
83        return false;
84    }
85
86    @Override
87    public int hashCode() {
88        return sobj.hashCode();
89    }
90
91    @Override
92    public String toString() {
93        return inGlobal(new Callable<String>() {
94            @Override
95            public String call() {
96                return ScriptRuntime.safeToString(sobj);
97            }
98        });
99    }
100
101    // JSObject methods
102
103    @Override
104    public Object call(final Object thiz, final Object... args) {
105        final Global oldGlobal = Context.getGlobal();
106        final boolean globalChanged = (oldGlobal != global);
107
108        try {
109            if (globalChanged) {
110                Context.setGlobal(global);
111            }
112
113            if (sobj instanceof ScriptFunction) {
114                final Object[] modArgs = globalChanged? wrapArrayLikeMe(args, oldGlobal) : args;
115                final Object self = globalChanged? wrapLikeMe(thiz, oldGlobal) : thiz;
116                return wrapLikeMe(ScriptRuntime.apply((ScriptFunction)sobj, unwrap(self, global), unwrapArray(modArgs, global)));
117            }
118
119            throw new RuntimeException("not a function: " + toString());
120        } catch (final NashornException ne) {
121            throw ne.initEcmaError(global);
122        } catch (final RuntimeException | Error e) {
123            throw e;
124        } catch (final Throwable t) {
125            throw new RuntimeException(t);
126        } finally {
127            if (globalChanged) {
128                Context.setGlobal(oldGlobal);
129            }
130        }
131    }
132
133    @Override
134    public Object newObject(final Object... args) {
135        final Global oldGlobal = Context.getGlobal();
136        final boolean globalChanged = (oldGlobal != global);
137
138        try {
139            if (globalChanged) {
140                Context.setGlobal(global);
141            }
142
143            if (sobj instanceof ScriptFunction) {
144                final Object[] modArgs = globalChanged? wrapArrayLikeMe(args, oldGlobal) : args;
145                return wrapLikeMe(ScriptRuntime.construct((ScriptFunction)sobj, unwrapArray(modArgs, global)));
146            }
147
148            throw new RuntimeException("not a constructor: " + toString());
149        } catch (final NashornException ne) {
150            throw ne.initEcmaError(global);
151        } catch (final RuntimeException | Error e) {
152            throw e;
153        } catch (final Throwable t) {
154            throw new RuntimeException(t);
155        } finally {
156            if (globalChanged) {
157                Context.setGlobal(oldGlobal);
158            }
159        }
160    }
161
162    @Override
163    public Object eval(final String s) {
164        return inGlobal(new Callable<Object>() {
165            @Override
166            public Object call() {
167                final Context context = AccessController.doPrivileged(
168                        new PrivilegedAction<Context>() {
169                            @Override
170                            public Context run() {
171                                return Context.getContext();
172                            }
173                        }, GET_CONTEXT_ACC_CTXT);
174                return wrapLikeMe(context.eval(global, s, sobj, null));
175            }
176        });
177    }
178
179    /**
180     * Call member function
181     * @param functionName function name
182     * @param args         arguments
183     * @return return value of function
184     */
185    public Object callMember(final String functionName, final Object... args) {
186        Objects.requireNonNull(functionName);
187        final Global oldGlobal = Context.getGlobal();
188        final boolean globalChanged = (oldGlobal != global);
189
190        try {
191            if (globalChanged) {
192                Context.setGlobal(global);
193            }
194
195            final Object val = sobj.get(functionName);
196            if (val instanceof ScriptFunction) {
197                final Object[] modArgs = globalChanged? wrapArrayLikeMe(args, oldGlobal) : args;
198                return wrapLikeMe(ScriptRuntime.apply((ScriptFunction)val, sobj, unwrapArray(modArgs, global)));
199            } else if (val instanceof JSObject && ((JSObject)val).isFunction()) {
200                return ((JSObject)val).call(sobj, args);
201            }
202
203            throw new NoSuchMethodException("No such function " + functionName);
204        } catch (final NashornException ne) {
205            throw ne.initEcmaError(global);
206        } catch (final RuntimeException | Error e) {
207            throw e;
208        } catch (final Throwable t) {
209            throw new RuntimeException(t);
210        } finally {
211            if (globalChanged) {
212                Context.setGlobal(oldGlobal);
213            }
214        }
215    }
216
217    @Override
218    public Object getMember(final String name) {
219        Objects.requireNonNull(name);
220        return inGlobal(new Callable<Object>() {
221            @Override public Object call() {
222                return wrapLikeMe(sobj.get(name));
223            }
224        });
225    }
226
227    @Override
228    public Object getSlot(final int index) {
229        return inGlobal(new Callable<Object>() {
230            @Override public Object call() {
231                return wrapLikeMe(sobj.get(index));
232            }
233        });
234    }
235
236    @Override
237    public boolean hasMember(final String name) {
238        Objects.requireNonNull(name);
239        return inGlobal(new Callable<Boolean>() {
240            @Override public Boolean call() {
241                return sobj.has(name);
242            }
243        });
244    }
245
246    @Override
247    public boolean hasSlot(final int slot) {
248        return inGlobal(new Callable<Boolean>() {
249            @Override public Boolean call() {
250                return sobj.has(slot);
251            }
252        });
253    }
254
255    @Override
256    public void removeMember(final String name) {
257        remove(Objects.requireNonNull(name));
258    }
259
260    @Override
261    public void setMember(final String name, final Object value) {
262        put(Objects.requireNonNull(name), value);
263    }
264
265    @Override
266    public void setSlot(final int index, final Object value) {
267        inGlobal(new Callable<Void>() {
268            @Override public Void call() {
269                sobj.set(index, unwrap(value, global), getCallSiteFlags());
270                return null;
271            }
272        });
273    }
274
275    /**
276     * Nashorn extension: setIndexedPropertiesToExternalArrayData.
277     * set indexed properties be exposed from a given nio ByteBuffer.
278     *
279     * @param buf external buffer - should be a nio ByteBuffer
280     */
281    public void setIndexedPropertiesToExternalArrayData(final ByteBuffer buf) {
282        inGlobal(new Callable<Void>() {
283            @Override public Void call() {
284                sobj.setArray(ArrayData.allocate(buf));
285                return null;
286            }
287        });
288    }
289
290
291    @Override
292    public boolean isInstance(final Object obj) {
293        if (! (obj instanceof ScriptObjectMirror)) {
294            return false;
295        }
296
297        final ScriptObjectMirror instance = (ScriptObjectMirror)obj;
298        // if not belongs to my global scope, return false
299        if (global != instance.global) {
300            return false;
301        }
302
303        return inGlobal(new Callable<Boolean>() {
304            @Override public Boolean call() {
305                return sobj.isInstance(instance.sobj);
306            }
307        });
308    }
309
310    @Override
311    public String getClassName() {
312        return sobj.getClassName();
313    }
314
315    @Override
316    public boolean isFunction() {
317        return sobj instanceof ScriptFunction;
318    }
319
320    @Override
321    public boolean isStrictFunction() {
322        return isFunction() && ((ScriptFunction)sobj).isStrict();
323    }
324
325    @Override
326    public boolean isArray() {
327        return sobj.isArray();
328    }
329
330    // javax.script.Bindings methods
331
332    @Override
333    public void clear() {
334        inGlobal(new Callable<Object>() {
335            @Override public Object call() {
336                sobj.clear(strict);
337                return null;
338            }
339        });
340    }
341
342    @Override
343    public boolean containsKey(final Object key) {
344        checkKey(key);
345        return inGlobal(new Callable<Boolean>() {
346            @Override public Boolean call() {
347                return sobj.containsKey(key);
348            }
349        });
350    }
351
352    @Override
353    public boolean containsValue(final Object value) {
354        return inGlobal(new Callable<Boolean>() {
355            @Override public Boolean call() {
356                return sobj.containsValue(unwrap(value, global));
357            }
358        });
359    }
360
361    @Override
362    public Set<Map.Entry<String, Object>> entrySet() {
363        return inGlobal(new Callable<Set<Map.Entry<String, Object>>>() {
364            @Override public Set<Map.Entry<String, Object>> call() {
365                final Iterator<String>               iter    = sobj.propertyIterator();
366                final Set<Map.Entry<String, Object>> entries = new LinkedHashSet<>();
367
368                while (iter.hasNext()) {
369                    final String key   = iter.next();
370                    final Object value = translateUndefined(wrapLikeMe(sobj.get(key)));
371                    entries.add(new AbstractMap.SimpleImmutableEntry<>(key, value));
372                }
373
374                return Collections.unmodifiableSet(entries);
375            }
376        });
377    }
378
379    @Override
380    public Object get(final Object key) {
381        checkKey(key);
382        return inGlobal(new Callable<Object>() {
383            @Override public Object call() {
384                return translateUndefined(wrapLikeMe(sobj.get(key)));
385            }
386        });
387    }
388
389    @Override
390    public boolean isEmpty() {
391        return inGlobal(new Callable<Boolean>() {
392            @Override public Boolean call() {
393                return sobj.isEmpty();
394            }
395        });
396    }
397
398    @Override
399    public Set<String> keySet() {
400        return inGlobal(new Callable<Set<String>>() {
401            @Override public Set<String> call() {
402                final Iterator<String> iter   = sobj.propertyIterator();
403                final Set<String>      keySet = new LinkedHashSet<>();
404
405                while (iter.hasNext()) {
406                    keySet.add(iter.next());
407                }
408
409                return Collections.unmodifiableSet(keySet);
410            }
411        });
412    }
413
414    @Override
415    public Object put(final String key, final Object value) {
416        checkKey(key);
417        final ScriptObject oldGlobal = Context.getGlobal();
418        final boolean globalChanged = (oldGlobal != global);
419        return inGlobal(new Callable<Object>() {
420            @Override public Object call() {
421                final Object modValue = globalChanged? wrapLikeMe(value, oldGlobal) : value;
422                return translateUndefined(wrapLikeMe(sobj.put(key, unwrap(modValue, global), strict)));
423            }
424        });
425    }
426
427    @Override
428    public void putAll(final Map<? extends String, ? extends Object> map) {
429        Objects.requireNonNull(map);
430        final ScriptObject oldGlobal = Context.getGlobal();
431        final boolean globalChanged = (oldGlobal != global);
432        inGlobal(new Callable<Object>() {
433            @Override public Object call() {
434                for (final Map.Entry<? extends String, ? extends Object> entry : map.entrySet()) {
435                    final Object value = entry.getValue();
436                    final Object modValue = globalChanged? wrapLikeMe(value, oldGlobal) : value;
437                    final String key = entry.getKey();
438                    checkKey(key);
439                    sobj.set(key, unwrap(modValue, global), getCallSiteFlags());
440                }
441                return null;
442            }
443        });
444    }
445
446    @Override
447    public Object remove(final Object key) {
448        checkKey(key);
449        return inGlobal(new Callable<Object>() {
450            @Override public Object call() {
451                return translateUndefined(wrapLikeMe(sobj.remove(key, strict)));
452            }
453        });
454    }
455
456    /**
457     * Delete a property from this object.
458     *
459     * @param key the property to be deleted
460     *
461     * @return if the delete was successful or not
462     */
463    public boolean delete(final Object key) {
464        return inGlobal(new Callable<Boolean>() {
465            @Override public Boolean call() {
466                return sobj.delete(unwrap(key, global), strict);
467            }
468        });
469    }
470
471    @Override
472    public int size() {
473        return inGlobal(new Callable<Integer>() {
474            @Override public Integer call() {
475                return sobj.size();
476            }
477        });
478    }
479
480    @Override
481    public Collection<Object> values() {
482        return inGlobal(new Callable<Collection<Object>>() {
483            @Override public Collection<Object> call() {
484                final List<Object>     values = new ArrayList<>(size());
485                final Iterator<Object> iter   = sobj.valueIterator();
486
487                while (iter.hasNext()) {
488                    values.add(translateUndefined(wrapLikeMe(iter.next())));
489                }
490
491                return Collections.unmodifiableList(values);
492            }
493        });
494    }
495
496    // Support for ECMAScript Object API on mirrors
497
498    /**
499     * Return the __proto__ of this object.
500     * @return __proto__ object.
501     */
502    public Object getProto() {
503        return inGlobal(new Callable<Object>() {
504            @Override public Object call() {
505                return wrapLikeMe(sobj.getProto());
506            }
507        });
508    }
509
510    /**
511     * Set the __proto__ of this object.
512     * @param proto new proto for this object
513     */
514    public void setProto(final Object proto) {
515        inGlobal(new Callable<Void>() {
516            @Override public Void call() {
517                sobj.setPrototypeOf(unwrap(proto, global));
518                return null;
519            }
520        });
521    }
522
523    /**
524     * ECMA 8.12.1 [[GetOwnProperty]] (P)
525     *
526     * @param key property key
527     *
528     * @return Returns the Property Descriptor of the named own property of this
529     * object, or undefined if absent.
530     */
531    public Object getOwnPropertyDescriptor(final String key) {
532        return inGlobal(new Callable<Object>() {
533            @Override public Object call() {
534                return wrapLikeMe(sobj.getOwnPropertyDescriptor(key));
535            }
536        });
537    }
538
539    /**
540     * return an array of own property keys associated with the object.
541     *
542     * @param all True if to include non-enumerable keys.
543     * @return Array of keys.
544     */
545    public String[] getOwnKeys(final boolean all) {
546        return inGlobal(new Callable<String[]>() {
547            @Override public String[] call() {
548                return sobj.getOwnKeys(all);
549            }
550        });
551    }
552
553    /**
554     * Flag this script object as non extensible
555     *
556     * @return the object after being made non extensible
557     */
558    public ScriptObjectMirror preventExtensions() {
559        return inGlobal(new Callable<ScriptObjectMirror>() {
560            @Override public ScriptObjectMirror call() {
561                sobj.preventExtensions();
562                return ScriptObjectMirror.this;
563            }
564        });
565    }
566
567    /**
568     * Check if this script object is extensible
569     * @return true if extensible
570     */
571    public boolean isExtensible() {
572        return inGlobal(new Callable<Boolean>() {
573            @Override public Boolean call() {
574                return sobj.isExtensible();
575            }
576        });
577    }
578
579    /**
580     * ECMAScript 15.2.3.8 - seal implementation
581     * @return the sealed script object
582     */
583    public ScriptObjectMirror seal() {
584        return inGlobal(new Callable<ScriptObjectMirror>() {
585            @Override public ScriptObjectMirror call() {
586                sobj.seal();
587                return ScriptObjectMirror.this;
588            }
589        });
590    }
591
592    /**
593     * Check whether this script object is sealed
594     * @return true if sealed
595     */
596    public boolean isSealed() {
597        return inGlobal(new Callable<Boolean>() {
598            @Override public Boolean call() {
599                return sobj.isSealed();
600            }
601        });
602    }
603
604    /**
605     * ECMA 15.2.39 - freeze implementation. Freeze this script object
606     * @return the frozen script object
607     */
608    public ScriptObjectMirror freeze() {
609        return inGlobal(new Callable<ScriptObjectMirror>() {
610            @Override public ScriptObjectMirror call() {
611                sobj.freeze();
612                return ScriptObjectMirror.this;
613            }
614        });
615    }
616
617    /**
618     * Check whether this script object is frozen
619     * @return true if frozen
620     */
621    public boolean isFrozen() {
622        return inGlobal(new Callable<Boolean>() {
623            @Override public Boolean call() {
624                return sobj.isFrozen();
625            }
626        });
627    }
628
629    /**
630     * Utility to check if given object is ECMAScript undefined value
631     *
632     * @param obj object to check
633     * @return true if 'obj' is ECMAScript undefined value
634     */
635    public static boolean isUndefined(final Object obj) {
636        return obj == ScriptRuntime.UNDEFINED;
637    }
638
639    /**
640     * Utility to convert this script object to the given type.
641     *
642     * @param <T> destination type to convert to
643     * @param type destination type to convert to
644     * @return converted object
645     */
646    public <T> T to(final Class<T> type) {
647        return inGlobal(new Callable<T>() {
648            @Override
649            public T call() {
650                return type.cast(ScriptUtils.convert(sobj, type));
651            }
652        });
653    }
654
655    /**
656     * Make a script object mirror on given object if needed. Also converts ConsString instances to Strings.
657     *
658     * @param obj object to be wrapped/converted
659     * @param homeGlobal global to which this object belongs. Not used for ConsStrings.
660     * @return wrapped/converted object
661     */
662    public static Object wrap(final Object obj, final Object homeGlobal) {
663        return wrap(obj, homeGlobal, false);
664    }
665
666    /**
667     * Make a script object mirror on given object if needed. Also converts ConsString instances to Strings. The
668     * created wrapper will implement the Java {@code List} interface if {@code obj} is a JavaScript
669     * {@code Array} object; this is compatible with Java JSON libraries expectations. Arrays retrieved through its
670     * properties (transitively) will also implement the list interface.
671     *
672     * @param obj object to be wrapped/converted
673     * @param homeGlobal global to which this object belongs. Not used for ConsStrings.
674     * @return wrapped/converted object
675     */
676    public static Object wrapAsJSONCompatible(final Object obj, final Object homeGlobal) {
677        return wrap(obj, homeGlobal, true);
678    }
679
680    /**
681     * Make a script object mirror on given object if needed. Also converts ConsString instances to Strings.
682     *
683     * @param obj object to be wrapped/converted
684     * @param homeGlobal global to which this object belongs. Not used for ConsStrings.
685     * @param jsonCompatible if true, the created wrapper will implement the Java {@code List} interface if
686     * {@code obj} is a JavaScript {@code Array} object. Arrays retrieved through its properties (transitively)
687     * will also implement the list interface.
688     * @return wrapped/converted object
689     */
690    private static Object wrap(final Object obj, final Object homeGlobal, final boolean jsonCompatible) {
691        if(obj instanceof ScriptObject) {
692            if (!(homeGlobal instanceof Global)) {
693                return obj;
694            }
695            final ScriptObject sobj = (ScriptObject)obj;
696            final Global global = (Global)homeGlobal;
697            final ScriptObjectMirror mirror = new ScriptObjectMirror(sobj, global, jsonCompatible);
698            if (jsonCompatible && sobj.isArray()) {
699                return new JSONListAdapter(mirror, global);
700            }
701            return mirror;
702        } else if(obj instanceof ConsString) {
703            return obj.toString();
704        } else if (jsonCompatible && obj instanceof ScriptObjectMirror) {
705            // Since choosing JSON compatible representation is an explicit decision on user's part, if we're asked to
706            // wrap a mirror that was not JSON compatible, explicitly create its compatible counterpart following the
707            // principle of least surprise.
708            return ((ScriptObjectMirror)obj).asJSONCompatible();
709        }
710        return obj;
711    }
712
713    /**
714     * Wraps the passed object with the same jsonCompatible flag as this mirror.
715     * @param obj the object
716     * @param homeGlobal the object's home global.
717     * @return a wrapper for the object.
718     */
719    private Object wrapLikeMe(final Object obj, final Object homeGlobal) {
720        return wrap(obj, homeGlobal, jsonCompatible);
721    }
722
723    /**
724     * Wraps the passed object with the same home global and jsonCompatible flag as this mirror.
725     * @param obj the object
726     * @return a wrapper for the object.
727     */
728    private Object wrapLikeMe(final Object obj) {
729        return wrapLikeMe(obj, global);
730    }
731
732    /**
733     * Unwrap a script object mirror if needed.
734     *
735     * @param obj object to be unwrapped
736     * @param homeGlobal global to which this object belongs
737     * @return unwrapped object
738     */
739    public static Object unwrap(final Object obj, final Object homeGlobal) {
740        if (obj instanceof ScriptObjectMirror) {
741            final ScriptObjectMirror mirror = (ScriptObjectMirror)obj;
742            return (mirror.global == homeGlobal)? mirror.sobj : obj;
743        } else if (obj instanceof JSONListAdapter) {
744            return ((JSONListAdapter)obj).unwrap(homeGlobal);
745        }
746
747        return obj;
748    }
749
750    /**
751     * Wrap an array of object to script object mirrors if needed.
752     *
753     * @param args array to be unwrapped
754     * @param homeGlobal global to which this object belongs
755     * @return wrapped array
756     */
757    public static Object[] wrapArray(final Object[] args, final Object homeGlobal) {
758        return wrapArray(args, homeGlobal, false);
759    }
760
761    private static Object[] wrapArray(final Object[] args, final Object homeGlobal, final boolean jsonCompatible) {
762        if (args == null || args.length == 0) {
763            return args;
764        }
765
766        final Object[] newArgs = new Object[args.length];
767        int index = 0;
768        for (final Object obj : args) {
769            newArgs[index] = wrap(obj, homeGlobal, jsonCompatible);
770            index++;
771        }
772        return newArgs;
773    }
774
775    private Object[] wrapArrayLikeMe(final Object[] args, final Object homeGlobal) {
776        return wrapArray(args, homeGlobal, jsonCompatible);
777    }
778
779    /**
780     * Unwrap an array of script object mirrors if needed.
781     *
782     * @param args array to be unwrapped
783     * @param homeGlobal global to which this object belongs
784     * @return unwrapped array
785     */
786    public static Object[] unwrapArray(final Object[] args, final Object homeGlobal) {
787        if (args == null || args.length == 0) {
788            return args;
789        }
790
791        final Object[] newArgs = new Object[args.length];
792        int index = 0;
793        for (final Object obj : args) {
794            newArgs[index] = unwrap(obj, homeGlobal);
795            index++;
796        }
797        return newArgs;
798    }
799
800    /**
801     * Are the given objects mirrors to same underlying object?
802     *
803     * @param obj1 first object
804     * @param obj2 second object
805     * @return true if obj1 and obj2 are identical script objects or mirrors of it.
806     */
807    public static boolean identical(final Object obj1, final Object obj2) {
808        final Object o1 = (obj1 instanceof ScriptObjectMirror)?
809            ((ScriptObjectMirror)obj1).sobj : obj1;
810
811        final Object o2 = (obj2 instanceof ScriptObjectMirror)?
812            ((ScriptObjectMirror)obj2).sobj : obj2;
813
814        return o1 == o2;
815    }
816
817    // package-privates below this.
818
819    ScriptObjectMirror(final ScriptObject sobj, final Global global) {
820        this(sobj, global, false);
821    }
822
823    private ScriptObjectMirror(final ScriptObject sobj, final Global global, final boolean jsonCompatible) {
824        assert sobj != null : "ScriptObjectMirror on null!";
825        assert global != null : "home Global is null";
826
827        this.sobj = sobj;
828        this.global = global;
829        this.strict = global.isStrictContext();
830        this.jsonCompatible = jsonCompatible;
831    }
832
833    // accessors for script engine
834    ScriptObject getScriptObject() {
835        return sobj;
836    }
837
838    Global getHomeGlobal() {
839        return global;
840    }
841
842    static Object translateUndefined(final Object obj) {
843        return (obj == ScriptRuntime.UNDEFINED)? null : obj;
844    }
845
846    private int getCallSiteFlags() {
847        return strict ? NashornCallSiteDescriptor.CALLSITE_STRICT : 0;
848    }
849
850    // internals only below this.
851    private <V> V inGlobal(final Callable<V> callable) {
852        final Global oldGlobal = Context.getGlobal();
853        final boolean globalChanged = (oldGlobal != global);
854        if (globalChanged) {
855            Context.setGlobal(global);
856        }
857        try {
858            return callable.call();
859        } catch (final NashornException ne) {
860            throw ne.initEcmaError(global);
861        } catch (final RuntimeException e) {
862            throw e;
863        } catch (final Exception e) {
864            throw new AssertionError("Cannot happen", e);
865        } finally {
866            if (globalChanged) {
867                Context.setGlobal(oldGlobal);
868            }
869        }
870    }
871
872    /**
873     * Ensures the key is not null, empty string, or a non-String object. The contract of the {@link Bindings}
874     * interface requires that these are not accepted as keys.
875     * @param key the key to check
876     * @throws NullPointerException if key is null
877     * @throws ClassCastException if key is not a String
878     * @throws IllegalArgumentException if key is empty string
879     */
880    private static void checkKey(final Object key) {
881        Objects.requireNonNull(key, "key can not be null");
882
883        if (!(key instanceof String)) {
884            throw new ClassCastException("key should be a String. It is " + key.getClass().getName() + " instead.");
885        } else if (((String)key).length() == 0) {
886            throw new IllegalArgumentException("key can not be empty");
887        }
888    }
889
890    @Override @Deprecated
891    public double toNumber() {
892        return inGlobal(new Callable<Double>() {
893            @Override public Double call() {
894                return JSType.toNumber(sobj);
895            }
896        });
897    }
898
899    @Override
900    public Object getDefaultValue(final Class<?> hint) {
901        return inGlobal(new Callable<Object>() {
902            @Override public Object call() {
903                try {
904                    return sobj.getDefaultValue(hint);
905                } catch (final ECMAException e) {
906                    // We're catching ECMAException (likely TypeError), and translating it to
907                    // UnsupportedOperationException. This in turn will be translated into TypeError of the
908                    // caller's Global by JSType#toPrimitive(JSObject,Class) therefore ensuring that it's
909                    // recognized as "instanceof TypeError" in the caller.
910                    throw new UnsupportedOperationException(e.getMessage(), e);
911                }
912            }
913        });
914    }
915
916    private ScriptObjectMirror asJSONCompatible() {
917        if (this.jsonCompatible) {
918            return this;
919        }
920        return new ScriptObjectMirror(sobj, global, true);
921    }
922}
923