       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 {
      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
      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
      91     public int hashCode() {
      92         return sobj.hashCode();
      93     }
      94
      95     @Override
      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 }