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