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