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 @jdk.Exported
  64 public final class ScriptObjectMirror extends AbstractJSObject implements Bindings {
  65     private static AccessControlContext getContextAccCtxt() {
  66         final Permissions perms = new Permissions();
  67         perms.add(new RuntimePermission(Context.NASHORN_GET_CONTEXT));
  68         return new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, perms) });
  69     }
  70 
  71     private static final AccessControlContext GET_CONTEXT_ACC_CTXT = getContextAccCtxt();
  72 
  73     private final ScriptObject sobj;
  74     private final Global  global;
  75     private final boolean strict;
  76     private final boolean jsonCompatible;
  77 
  78     @Override
  79     public boolean equals(final Object other) {
  80         if (other instanceof ScriptObjectMirror) {
  81             return sobj.equals(((ScriptObjectMirror)other).sobj);
  82         }
  83 
  84         return false;
  85     }
  86 
  87     @Override
  88     public int hashCode() {
  89         return sobj.hashCode();
  90     }
  91 
  92     @Override
  93     public String toString() {
  94         return inGlobal(new Callable<String>() {
  95             @Override
  96             public String call() {
  97                 return ScriptRuntime.safeToString(sobj);
  98             }
  99         });
 100     }
 101 
 102     // JSObject methods
 103 
 104     @Override
 105     public Object call(final Object thiz, final Object... args) {
 106         final Global oldGlobal = Context.getGlobal();
 107         final boolean globalChanged = (oldGlobal != global);
 108 
 109         try {
 110             if (globalChanged) {
 111                 Context.setGlobal(global);
 112             }
 113 
 114             if (sobj instanceof ScriptFunction) {
 115                 final Object[] modArgs = globalChanged? wrapArrayLikeMe(args, oldGlobal) : args;
 116                 final Object self = globalChanged? wrapLikeMe(thiz, oldGlobal) : thiz;
 117                 return wrapLikeMe(ScriptRuntime.apply((ScriptFunction)sobj, unwrap(self, global), unwrapArray(modArgs, global)));
 118             }
 119 
 120             throw new RuntimeException("not a function: " + toString());
 121         } catch (final NashornException ne) {
 122             throw ne.initEcmaError(global);
 123         } catch (final RuntimeException | Error e) {
 124             throw e;
 125         } catch (final Throwable t) {
 126             throw new RuntimeException(t);
 127         } finally {
 128             if (globalChanged) {
 129                 Context.setGlobal(oldGlobal);
 130             }
 131         }
 132     }
 133 
 134     @Override
 135     public Object newObject(final Object... args) {
 136         final Global oldGlobal = Context.getGlobal();
 137         final boolean globalChanged = (oldGlobal != global);
 138 
 139         try {
 140             if (globalChanged) {
 141                 Context.setGlobal(global);
 142             }
 143 
 144             if (sobj instanceof ScriptFunction) {
 145                 final Object[] modArgs = globalChanged? wrapArrayLikeMe(args, oldGlobal) : args;
 146                 return wrapLikeMe(ScriptRuntime.construct((ScriptFunction)sobj, unwrapArray(modArgs, global)));
 147             }
 148 
 149             throw new RuntimeException("not a constructor: " + toString());
 150         } catch (final NashornException ne) {
 151             throw ne.initEcmaError(global);
 152         } catch (final RuntimeException | Error e) {
 153             throw e;
 154         } catch (final Throwable t) {
 155             throw new RuntimeException(t);
 156         } finally {
 157             if (globalChanged) {
 158                 Context.setGlobal(oldGlobal);
 159             }
 160         }
 161     }
 162 
 163     @Override
 164     public Object eval(final String s) {
 165         return inGlobal(new Callable<Object>() {
 166             @Override
 167             public Object call() {
 168                 final Context context = AccessController.doPrivileged(
 169                         new PrivilegedAction<Context>() {
 170                             @Override
 171                             public Context run() {
 172                                 return Context.getContext();
 173                             }
 174                         }, GET_CONTEXT_ACC_CTXT);
 175                 return wrapLikeMe(context.eval(global, s, sobj, null, false));
 176             }
 177         });
 178     }
 179 
 180     /**
 181      * Call member function
 182      * @param functionName function name
 183      * @param args         arguments
 184      * @return return value of function
 185      */
 186     public Object callMember(final String functionName, final Object... args) {
 187         Objects.requireNonNull(functionName);
 188         final Global oldGlobal = Context.getGlobal();
 189         final boolean globalChanged = (oldGlobal != global);
 190 
 191         try {
 192             if (globalChanged) {
 193                 Context.setGlobal(global);
 194             }
 195 
 196             final Object val = sobj.get(functionName);
 197             if (val instanceof ScriptFunction) {
 198                 final Object[] modArgs = globalChanged? wrapArrayLikeMe(args, oldGlobal) : args;
 199                 return wrapLikeMe(ScriptRuntime.apply((ScriptFunction)val, sobj, unwrapArray(modArgs, global)));
 200             } else if (val instanceof JSObject && ((JSObject)val).isFunction()) {
 201                 return ((JSObject)val).call(sobj, args);
 202             }
 203 
 204             throw new NoSuchMethodException("No such function " + functionName);
 205         } catch (final NashornException ne) {
 206             throw ne.initEcmaError(global);
 207         } catch (final RuntimeException | Error e) {
 208             throw e;
 209         } catch (final Throwable t) {
 210             throw new RuntimeException(t);
 211         } finally {
 212             if (globalChanged) {
 213                 Context.setGlobal(oldGlobal);
 214             }
 215         }
 216     }
 217 
 218     @Override
 219     public Object getMember(final String name) {
 220         Objects.requireNonNull(name);
 221         return inGlobal(new Callable<Object>() {
 222             @Override public Object call() {
 223                 return wrapLikeMe(sobj.get(name));
 224             }
 225         });
 226     }
 227 
 228     @Override
 229     public Object getSlot(final int index) {
 230         return inGlobal(new Callable<Object>() {
 231             @Override public Object call() {
 232                 return wrapLikeMe(sobj.get(index));
 233             }
 234         });
 235     }
 236 
 237     @Override
 238     public boolean hasMember(final String name) {
 239         Objects.requireNonNull(name);
 240         return inGlobal(new Callable<Boolean>() {
 241             @Override public Boolean call() {
 242                 return sobj.has(name);
 243             }
 244         });
 245     }
 246 
 247     @Override
 248     public boolean hasSlot(final int slot) {
 249         return inGlobal(new Callable<Boolean>() {
 250             @Override public Boolean call() {
 251                 return sobj.has(slot);
 252             }
 253         });
 254     }
 255 
 256     @Override
 257     public void removeMember(final String name) {
 258         remove(Objects.requireNonNull(name));

 259     }
 260 
 261     @Override
 262     public void setMember(final String name, final Object value) {
 263         put(Objects.requireNonNull(name), value);

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