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