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 
26 package jdk.nashorn.api.scripting;
27 
28 import java.nio.ByteBuffer;
29 import java.security.AccessControlContext;
30 import java.security.AccessController;
31 import java.security.Permissions;
32 import java.security.PrivilegedAction;
33 import java.security.ProtectionDomain;
34 import java.util.AbstractMap;
35 import java.util.ArrayList;
36 import java.util.Collection;
37 import java.util.Collections;
38 import java.util.Iterator;
39 import java.util.LinkedHashSet;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Objects;
43 import java.util.Set;
44 import java.util.concurrent.Callable;
45 import javax.script.Bindings;
46 import jdk.nashorn.internal.objects.Global;
47 import jdk.nashorn.internal.runtime.ConsString;
48 import jdk.nashorn.internal.runtime.Context;
49 import jdk.nashorn.internal.runtime.ECMAException;
50 import jdk.nashorn.internal.runtime.JSONListAdapter;
51 import jdk.nashorn.internal.runtime.JSType;
52 import jdk.nashorn.internal.runtime.ScriptFunction;
53 import jdk.nashorn.internal.runtime.ScriptObject;
54 import jdk.nashorn.internal.runtime.ScriptRuntime;
55 import jdk.nashorn.internal.runtime.arrays.ArrayData;
56 import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
57 
58 /**
59  * Mirror object that wraps a given Nashorn Script object.
60  *
61  * @deprecated Nashorn JavaScript script engine and APIs, and the jjs tool
62  * are deprecated with the intent to remove them in a future release.
63  *
64  * @since 1.8u40
65  */
66 @Deprecated(since="11", forRemoval=true)
67 public final class ScriptObjectMirror extends AbstractJSObject implements Bindings {
getContextAccCtxt()68     private static AccessControlContext getContextAccCtxt() {
69         final Permissions perms = new Permissions();
70         perms.add(new RuntimePermission(Context.NASHORN_GET_CONTEXT));
71         return new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, perms) });
72     }
73 
74     private static final AccessControlContext GET_CONTEXT_ACC_CTXT = getContextAccCtxt();
75 
76     private final ScriptObject sobj;
77     private final Global  global;
78     private final boolean strict;
79     private final boolean jsonCompatible;
80 
81     @Override
equals(final Object other)82     public boolean equals(final Object other) {
83         if (other instanceof ScriptObjectMirror) {
84             return sobj.equals(((ScriptObjectMirror)other).sobj);
85         }
86 
87         return false;
88     }
89 
90     @Override
hashCode()91     public int hashCode() {
92         return sobj.hashCode();
93     }
94 
95     @Override
toString()96     public String toString() {
97         return inGlobal(new Callable<String>() {
98             @Override
99             public String call() {
100                 return ScriptRuntime.safeToString(sobj);
101             }
102         });
103     }
104 
105     // JSObject methods
106 
107     @Override
108     public Object call(final Object thiz, final Object... args) {
109         final Global oldGlobal = Context.getGlobal();
110         final boolean globalChanged = (oldGlobal != global);
111 
112         try {
113             if (globalChanged) {
114                 Context.setGlobal(global);
115             }
116 
117             if (sobj instanceof ScriptFunction) {
118                 final Object[] modArgs = globalChanged? wrapArrayLikeMe(args, oldGlobal) : args;
119                 final Object self = globalChanged? wrapLikeMe(thiz, oldGlobal) : thiz;
120                 return wrapLikeMe(ScriptRuntime.apply((ScriptFunction)sobj, unwrap(self, global), unwrapArray(modArgs, global)));
121             }
122 
123             throw new RuntimeException("not a function: " + toString());
124         } catch (final NashornException ne) {
125             throw ne.initEcmaError(global);
126         } catch (final RuntimeException | Error e) {
127             throw e;
128         } catch (final Throwable t) {
129             throw new RuntimeException(t);
130         } finally {
131             if (globalChanged) {
132                 Context.setGlobal(oldGlobal);
133             }
134         }
135     }
136 
137     @Override
138     public Object newObject(final Object... args) {
139         final Global oldGlobal = Context.getGlobal();
140         final boolean globalChanged = (oldGlobal != global);
141 
142         try {
143             if (globalChanged) {
144                 Context.setGlobal(global);
145             }
146 
147             if (sobj instanceof ScriptFunction) {
148                 final Object[] modArgs = globalChanged? wrapArrayLikeMe(args, oldGlobal) : args;
149                 return wrapLikeMe(ScriptRuntime.construct((ScriptFunction)sobj, unwrapArray(modArgs, global)));
150             }
151 
152             throw new RuntimeException("not a constructor: " + toString());
153         } catch (final NashornException ne) {
154             throw ne.initEcmaError(global);
155         } catch (final RuntimeException | Error e) {
156             throw e;
157         } catch (final Throwable t) {
158             throw new RuntimeException(t);
159         } finally {
160             if (globalChanged) {
161                 Context.setGlobal(oldGlobal);
162             }
163         }
164     }
165 
166     @Override
167     public Object eval(final String s) {
168         return inGlobal(new Callable<Object>() {
169             @Override
170             public Object call() {
171                 final Context context = AccessController.doPrivileged(
172                         new PrivilegedAction<Context>() {
173                             @Override
174                             public Context run() {
175                                 return Context.getContext();
176                             }
177                         }, GET_CONTEXT_ACC_CTXT);
178                 return wrapLikeMe(context.eval(global, s, sobj, null));
179             }
180         });
181     }
182 
183     /**
184      * Call member function
185      * @param functionName function name
186      * @param args         arguments
187      * @return return value of function
188      */
189     public Object callMember(final String functionName, final Object... args) {
190         Objects.requireNonNull(functionName);
191         final Global oldGlobal = Context.getGlobal();
192         final boolean globalChanged = (oldGlobal != global);
193 
194         try {
195             if (globalChanged) {
196                 Context.setGlobal(global);
197             }
198 
199             final Object val = sobj.get(functionName);
200             if (val instanceof ScriptFunction) {
201                 final Object[] modArgs = globalChanged? wrapArrayLikeMe(args, oldGlobal) : args;
202                 return wrapLikeMe(ScriptRuntime.apply((ScriptFunction)val, sobj, unwrapArray(modArgs, global)));
203             } else if (val instanceof JSObject && ((JSObject)val).isFunction()) {
204                 return ((JSObject)val).call(sobj, args);
205             }
206 
207             throw new NoSuchMethodException("No such function " + functionName);
208         } catch (final NashornException ne) {
209             throw ne.initEcmaError(global);
210         } catch (final RuntimeException | Error e) {
211             throw e;
212         } catch (final Throwable t) {
213             throw new RuntimeException(t);
214         } finally {
215             if (globalChanged) {
216                 Context.setGlobal(oldGlobal);
217             }
218         }
219     }
220 
221     @Override
222     public Object getMember(final String name) {
223         Objects.requireNonNull(name);
224         return inGlobal(new Callable<Object>() {
225             @Override public Object call() {
226                 return wrapLikeMe(sobj.get(name));
227             }
228         });
229     }
230 
231     @Override
232     public Object getSlot(final int index) {
233         return inGlobal(new Callable<Object>() {
234             @Override public Object call() {
235                 return wrapLikeMe(sobj.get(index));
236             }
237         });
238     }
239 
240     @Override
241     public boolean hasMember(final String name) {
242         Objects.requireNonNull(name);
243         return inGlobal(new Callable<Boolean>() {
244             @Override public Boolean call() {
245                 return sobj.has(name);
246             }
247         });
248     }
249 
250     @Override
251     public boolean hasSlot(final int slot) {
252         return inGlobal(new Callable<Boolean>() {
253             @Override public Boolean call() {
254                 return sobj.has(slot);
255             }
256         });
257     }
258 
259     @Override
260     public void removeMember(final String name) {
261         remove(Objects.requireNonNull(name));
262     }
263 
264     @Override
265     public void setMember(final String name, final Object value) {
266         put(Objects.requireNonNull(name), value);
267     }
268 
269     @Override
270     public void setSlot(final int index, final Object value) {
271         inGlobal(new Callable<Void>() {
272             @Override public Void call() {
273                 sobj.set(index, unwrap(value, global), getCallSiteFlags());
274                 return null;
275             }
276         });
277     }
278 
279     /**
280      * Nashorn extension: setIndexedPropertiesToExternalArrayData.
281      * set indexed properties be exposed from a given nio ByteBuffer.
282      *
283      * @param buf external buffer - should be a nio ByteBuffer
284      */
285     public void setIndexedPropertiesToExternalArrayData(final ByteBuffer buf) {
286         inGlobal(new Callable<Void>() {
287             @Override public Void call() {
288                 sobj.setArray(ArrayData.allocate(buf));
289                 return null;
290             }
291         });
292     }
293 
294     @Override
295     public boolean isInstance(final Object instance) {
296         if (! (instance instanceof ScriptObjectMirror)) {
297             return false;
298         }
299 
300         final ScriptObjectMirror mirror = (ScriptObjectMirror)instance;
301         // if not belongs to my global scope, return false
302         if (global != mirror.global) {
303             return false;
304         }
305 
306         return inGlobal(new Callable<Boolean>() {
307             @Override public Boolean call() {
308                 return sobj.isInstance(mirror.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);
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.
660      *
661      * @param obj object to be wrapped/converted
662      * @param homeGlobal global to which this object belongs.
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. The created wrapper will implement
671      * the Java {@code List} interface if {@code obj} is a JavaScript {@code Array} object;
672      * 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.
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.
685      *
686      * @param obj object to be wrapped/converted
687      * @param homeGlobal global to which this object belongs.
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