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 }