       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  * @since 1.8u40
      62  */

      63 public 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 }
    --- EOF ---