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.Set;
  43 import java.util.concurrent.Callable;
  44 import javax.script.Bindings;
  45 import jdk.nashorn.internal.objects.Global;
  46 import jdk.nashorn.internal.runtime.ConsString;
  47 import jdk.nashorn.internal.runtime.Context;
  48 import jdk.nashorn.internal.runtime.JSType;
  49 import jdk.nashorn.internal.runtime.ScriptFunction;
  50 import jdk.nashorn.internal.runtime.ScriptObject;
  51 import jdk.nashorn.internal.runtime.ScriptRuntime;
  52 import jdk.nashorn.internal.runtime.arrays.ArrayData;
  53 import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
  54 
  55 /**
  56  * Mirror object that wraps a given Nashorn Script object.
  57  *
  58  * @since 1.8u40
  59  */
  60 @jdk.Exported
  61 public final class ScriptObjectMirror extends AbstractJSObject implements Bindings {
  62     private static AccessControlContext getContextAccCtxt() {
  63         final Permissions perms = new Permissions();
  64         perms.add(new RuntimePermission(Context.NASHORN_GET_CONTEXT));
  65         return new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, perms) });
  66     }
  67 
  68     private static final AccessControlContext GET_CONTEXT_ACC_CTXT = getContextAccCtxt();
  69 
  70     private final ScriptObject sobj;
  71     private final Global  global;
  72     private final boolean strict;
  73 
  74     @Override
  75     public boolean equals(final Object other) {
  76         if (other instanceof ScriptObjectMirror) {
  77             return sobj.equals(((ScriptObjectMirror)other).sobj);
  78         }
  79 
  80         return false;
  81     }
  82 
  83     @Override
  84     public int hashCode() {
  85         return sobj.hashCode();
  86     }
  87 
  88     @Override
  89     public String toString() {
  90         return inGlobal(new Callable<String>() {
  91             @Override
  92             public String call() {
  93                 return ScriptRuntime.safeToString(sobj);
  94             }
  95         });
  96     }
  97 
  98     // JSObject methods
  99 
 100     @Override
 101     public Object call(final Object thiz, final Object... args) {
 102         final Global oldGlobal = Context.getGlobal();
 103         final boolean globalChanged = (oldGlobal != global);
 104 
 105         try {
 106             if (globalChanged) {
 107                 Context.setGlobal(global);
 108             }
 109 
 110             if (sobj instanceof ScriptFunction) {
 111                 final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args;
 112                 final Object self = globalChanged? wrap(thiz, oldGlobal) : thiz;
 113                 return wrap(ScriptRuntime.apply((ScriptFunction)sobj, unwrap(self, global), unwrapArray(modArgs, global)), global);
 114             }
 115 
 116             throw new RuntimeException("not a function: " + toString());
 117         } catch (final NashornException ne) {
 118             throw ne.initEcmaError(global);
 119         } catch (final RuntimeException | Error e) {
 120             throw e;
 121         } catch (final Throwable t) {
 122             throw new RuntimeException(t);
 123         } finally {
 124             if (globalChanged) {
 125                 Context.setGlobal(oldGlobal);
 126             }
 127         }
 128     }
 129 
 130     @Override
 131     public Object newObject(final Object... args) {
 132         final Global oldGlobal = Context.getGlobal();
 133         final boolean globalChanged = (oldGlobal != global);
 134 
 135         try {
 136             if (globalChanged) {
 137                 Context.setGlobal(global);
 138             }
 139 
 140             if (sobj instanceof ScriptFunction) {
 141                 final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args;
 142                 return wrap(ScriptRuntime.construct((ScriptFunction)sobj, unwrapArray(modArgs, global)), global);
 143             }
 144 
 145             throw new RuntimeException("not a constructor: " + toString());
 146         } catch (final NashornException ne) {
 147             throw ne.initEcmaError(global);
 148         } catch (final RuntimeException | Error e) {
 149             throw e;
 150         } catch (final Throwable t) {
 151             throw new RuntimeException(t);
 152         } finally {
 153             if (globalChanged) {
 154                 Context.setGlobal(oldGlobal);
 155             }
 156         }
 157     }
 158 
 159     @Override
 160     public Object eval(final String s) {
 161         return inGlobal(new Callable<Object>() {
 162             @Override
 163             public Object call() {
 164                 final Context context = AccessController.doPrivileged(
 165                         new PrivilegedAction<Context>() {
 166                             @Override
 167                             public Context run() {
 168                                 return Context.getContext();
 169                             }
 170                         }, GET_CONTEXT_ACC_CTXT);
 171                 return wrap(context.eval(global, s, sobj, null, false), global);
 172             }
 173         });
 174     }
 175 
 176     /**
 177      * Call member function
 178      * @param functionName function name
 179      * @param args         arguments
 180      * @return return value of function
 181      */
 182     public Object callMember(final String functionName, final Object... args) {
 183         functionName.getClass(); // null check
 184         final Global oldGlobal = Context.getGlobal();
 185         final boolean globalChanged = (oldGlobal != global);
 186 
 187         try {
 188             if (globalChanged) {
 189                 Context.setGlobal(global);
 190             }
 191 
 192             final Object val = sobj.get(functionName);
 193             if (val instanceof ScriptFunction) {
 194                 final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args;
 195                 return wrap(ScriptRuntime.apply((ScriptFunction)val, sobj, unwrapArray(modArgs, global)), global);
 196             } else if (val instanceof JSObject && ((JSObject)val).isFunction()) {
 197                 return ((JSObject)val).call(sobj, args);
 198             }
 199 
 200             throw new NoSuchMethodException("No such function " + functionName);
 201         } catch (final NashornException ne) {
 202             throw ne.initEcmaError(global);
 203         } catch (final RuntimeException | Error e) {
 204             throw e;
 205         } catch (final Throwable t) {
 206             throw new RuntimeException(t);
 207         } finally {
 208             if (globalChanged) {
 209                 Context.setGlobal(oldGlobal);
 210             }
 211         }
 212     }
 213 
 214     @Override
 215     public Object getMember(final String name) {
 216         name.getClass();
 217         return inGlobal(new Callable<Object>() {
 218             @Override public Object call() {
 219                 return wrap(sobj.get(name), global);
 220             }
 221         });
 222     }
 223 
 224     @Override
 225     public Object getSlot(final int index) {
 226         return inGlobal(new Callable<Object>() {
 227             @Override public Object call() {
 228                 return wrap(sobj.get(index), global);
 229             }
 230         });
 231     }
 232 
 233     @Override
 234     public boolean hasMember(final String name) {
 235         name.getClass();
 236         return inGlobal(new Callable<Boolean>() {
 237             @Override public Boolean call() {
 238                 return sobj.has(name);
 239             }
 240         });
 241     }
 242 
 243     @Override
 244     public boolean hasSlot(final int slot) {
 245         return inGlobal(new Callable<Boolean>() {
 246             @Override public Boolean call() {
 247                 return sobj.has(slot);
 248             }
 249         });
 250     }
 251 
 252     @Override
 253     public void removeMember(final String name) {
 254         name.getClass();
 255         remove(name);
 256     }
 257 
 258     @Override
 259     public void setMember(final String name, final Object value) {
 260         name.getClass();
 261         put(name, value);
 262     }
 263 
 264     @Override
 265     public void setSlot(final int index, final Object value) {
 266         inGlobal(new Callable<Void>() {
 267             @Override public Void call() {
 268                 sobj.set(index, unwrap(value, global), getCallSiteFlags());
 269                 return null;
 270             }
 271         });
 272     }
 273 
 274     /**
 275      * Nashorn extension: setIndexedPropertiesToExternalArrayData.
 276      * set indexed properties be exposed from a given nio ByteBuffer.
 277      *
 278      * @param buf external buffer - should be a nio ByteBuffer
 279      */
 280     public void setIndexedPropertiesToExternalArrayData(final ByteBuffer buf) {
 281         inGlobal(new Callable<Void>() {
 282             @Override public Void call() {
 283                 sobj.setArray(ArrayData.allocate(buf));
 284                 return null;
 285             }
 286         });
 287     }
 288 
 289 
 290     @Override
 291     public boolean isInstance(final Object obj) {
 292         if (! (obj instanceof ScriptObjectMirror)) {
 293             return false;
 294         }
 295 
 296         final ScriptObjectMirror instance = (ScriptObjectMirror)obj;
 297         // if not belongs to my global scope, return false
 298         if (global != instance.global) {
 299             return false;
 300         }
 301 
 302         return inGlobal(new Callable<Boolean>() {
 303             @Override public Boolean call() {
 304                 return sobj.isInstance(instance.sobj);
 305             }
 306         });
 307     }
 308 
 309     @Override
 310     public String getClassName() {
 311         return sobj.getClassName();
 312     }
 313 
 314     @Override
 315     public boolean isFunction() {
 316         return sobj instanceof ScriptFunction;
 317     }
 318 
 319     @Override
 320     public boolean isStrictFunction() {
 321         return isFunction() && ((ScriptFunction)sobj).isStrict();
 322     }
 323 
 324     @Override
 325     public boolean isArray() {
 326         return sobj.isArray();
 327     }
 328 
 329     // javax.script.Bindings methods
 330 
 331     @Override
 332     public void clear() {
 333         inGlobal(new Callable<Object>() {
 334             @Override public Object call() {
 335                 sobj.clear(strict);
 336                 return null;
 337             }
 338         });
 339     }
 340 
 341     @Override
 342     public boolean containsKey(final Object key) {
 343         checkKey(key);
 344         return inGlobal(new Callable<Boolean>() {
 345             @Override public Boolean call() {
 346                 return sobj.containsKey(key);
 347             }
 348         });
 349     }
 350 
 351     @Override
 352     public boolean containsValue(final Object value) {
 353         return inGlobal(new Callable<Boolean>() {
 354             @Override public Boolean call() {
 355                 return sobj.containsValue(unwrap(value, global));
 356             }
 357         });
 358     }
 359 
 360     @Override
 361     public Set<Map.Entry<String, Object>> entrySet() {
 362         return inGlobal(new Callable<Set<Map.Entry<String, Object>>>() {
 363             @Override public Set<Map.Entry<String, Object>> call() {
 364                 final Iterator<String>               iter    = sobj.propertyIterator();
 365                 final Set<Map.Entry<String, Object>> entries = new LinkedHashSet<>();
 366 
 367                 while (iter.hasNext()) {
 368                     final String key   = iter.next();
 369                     final Object value = translateUndefined(wrap(sobj.get(key), global));
 370                     entries.add(new AbstractMap.SimpleImmutableEntry<>(key, value));
 371                 }
 372 
 373                 return Collections.unmodifiableSet(entries);
 374             }
 375         });
 376     }
 377 
 378     @Override
 379     public Object get(final Object key) {
 380         checkKey(key);
 381         return inGlobal(new Callable<Object>() {
 382             @Override public Object call() {
 383                 return translateUndefined(wrap(sobj.get(key), global));
 384             }
 385         });
 386     }
 387 
 388     @Override
 389     public boolean isEmpty() {
 390         return inGlobal(new Callable<Boolean>() {
 391             @Override public Boolean call() {
 392                 return sobj.isEmpty();
 393             }
 394         });
 395     }
 396 
 397     @Override
 398     public Set<String> keySet() {
 399         return inGlobal(new Callable<Set<String>>() {
 400             @Override public Set<String> call() {
 401                 final Iterator<String> iter   = sobj.propertyIterator();
 402                 final Set<String>      keySet = new LinkedHashSet<>();
 403 
 404                 while (iter.hasNext()) {
 405                     keySet.add(iter.next());
 406                 }
 407 
 408                 return Collections.unmodifiableSet(keySet);
 409             }
 410         });
 411     }
 412 
 413     @Override
 414     public Object put(final String key, final Object value) {
 415         checkKey(key);
 416         final ScriptObject oldGlobal = Context.getGlobal();
 417         final boolean globalChanged = (oldGlobal != global);
 418         return inGlobal(new Callable<Object>() {
 419             @Override public Object call() {
 420                 final Object modValue = globalChanged? wrap(value, oldGlobal) : value;
 421                 return translateUndefined(wrap(sobj.put(key, unwrap(modValue, global), strict), global));
 422             }
 423         });
 424     }
 425 
 426     @Override
 427     public void putAll(final Map<? extends String, ? extends Object> map) {
 428         if (map == null) {
 429             throw new NullPointerException("map is null");
 430         }
 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? wrap(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 wrap(sobj.remove(key, strict), global);
 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(wrap(iter.next(), global)));
 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 wrap(sobj.getProto(), global);
 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 wrap(sobj.getOwnPropertyDescriptor(key), global);
 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         if(obj instanceof ScriptObject) {
 665             return homeGlobal instanceof Global ? new ScriptObjectMirror((ScriptObject)obj, (Global)homeGlobal) : obj;
 666         }
 667         if(obj instanceof ConsString) {
 668             return obj.toString();
 669         }
 670         return obj;
 671     }
 672 
 673     /**
 674      * Unwrap a script object mirror if needed.
 675      *
 676      * @param obj object to be unwrapped
 677      * @param homeGlobal global to which this object belongs
 678      * @return unwrapped object
 679      */
 680     public static Object unwrap(final Object obj, final Object homeGlobal) {
 681         if (obj instanceof ScriptObjectMirror) {
 682             final ScriptObjectMirror mirror = (ScriptObjectMirror)obj;
 683             return (mirror.global == homeGlobal)? mirror.sobj : obj;
 684         }
 685 
 686         return obj;
 687     }
 688 
 689     /**
 690      * Wrap an array of object to script object mirrors if needed.
 691      *
 692      * @param args array to be unwrapped
 693      * @param homeGlobal global to which this object belongs
 694      * @return wrapped array
 695      */
 696     public static Object[] wrapArray(final Object[] args, final Object homeGlobal) {
 697         if (args == null || args.length == 0) {
 698             return args;
 699         }
 700 
 701         final Object[] newArgs = new Object[args.length];
 702         int index = 0;
 703         for (final Object obj : args) {
 704             newArgs[index] = wrap(obj, homeGlobal);
 705             index++;
 706         }
 707         return newArgs;
 708     }
 709 
 710     /**
 711      * Unwrap an array of script object mirrors if needed.
 712      *
 713      * @param args array to be unwrapped
 714      * @param homeGlobal global to which this object belongs
 715      * @return unwrapped array
 716      */
 717     public static Object[] unwrapArray(final Object[] args, final Object homeGlobal) {
 718         if (args == null || args.length == 0) {
 719             return args;
 720         }
 721 
 722         final Object[] newArgs = new Object[args.length];
 723         int index = 0;
 724         for (final Object obj : args) {
 725             newArgs[index] = unwrap(obj, homeGlobal);
 726             index++;
 727         }
 728         return newArgs;
 729     }
 730 
 731     /**
 732      * Are the given objects mirrors to same underlying object?
 733      *
 734      * @param obj1 first object
 735      * @param obj2 second object
 736      * @return true if obj1 and obj2 are identical script objects or mirrors of it.
 737      */
 738     public static boolean identical(final Object obj1, final Object obj2) {
 739         final Object o1 = (obj1 instanceof ScriptObjectMirror)?
 740             ((ScriptObjectMirror)obj1).sobj : obj1;
 741 
 742         final Object o2 = (obj2 instanceof ScriptObjectMirror)?
 743             ((ScriptObjectMirror)obj2).sobj : obj2;
 744 
 745         return o1 == o2;
 746     }
 747 
 748     // package-privates below this.
 749 
 750     ScriptObjectMirror(final ScriptObject sobj, final Global global) {
 751         assert sobj != null : "ScriptObjectMirror on null!";
 752         assert global != null : "home Global is null";
 753 
 754         this.sobj = sobj;
 755         this.global = global;
 756         this.strict = global.isStrictContext();
 757     }
 758 
 759     // accessors for script engine
 760     ScriptObject getScriptObject() {
 761         return sobj;
 762     }
 763 
 764     Global getHomeGlobal() {
 765         return global;
 766     }
 767 
 768     static Object translateUndefined(final Object obj) {
 769         return (obj == ScriptRuntime.UNDEFINED)? null : obj;
 770     }
 771 
 772     private int getCallSiteFlags() {
 773         return strict ? NashornCallSiteDescriptor.CALLSITE_STRICT : 0;
 774     }
 775 
 776     // internals only below this.
 777     private <V> V inGlobal(final Callable<V> callable) {
 778         final Global oldGlobal = Context.getGlobal();
 779         final boolean globalChanged = (oldGlobal != global);
 780         if (globalChanged) {
 781             Context.setGlobal(global);
 782         }
 783         try {
 784             return callable.call();
 785         } catch (final NashornException ne) {
 786             throw ne.initEcmaError(global);
 787         } catch (final RuntimeException e) {
 788             throw e;
 789         } catch (final Exception e) {
 790             throw new AssertionError("Cannot happen", e);
 791         } finally {
 792             if (globalChanged) {
 793                 Context.setGlobal(oldGlobal);
 794             }
 795         }
 796     }
 797 
 798     /**
 799      * Ensures the key is not null, empty string, or a non-String object. The contract of the {@link Bindings}
 800      * interface requires that these are not accepted as keys.
 801      * @param key the key to check
 802      * @throws NullPointerException if key is null
 803      * @throws ClassCastException if key is not a String
 804      * @throws IllegalArgumentException if key is empty string
 805      */
 806     private static void checkKey(final Object key) {
 807         if (key == null) {
 808             throw new NullPointerException("key can not be null");
 809         } else if (!(key instanceof String)) {
 810             throw new ClassCastException("key should be a String. It is " + key.getClass().getName() + " instead.");
 811         } else if (((String)key).length() == 0) {
 812             throw new IllegalArgumentException("key can not be empty");
 813         }
 814     }
 815 
 816     @Override
 817     public double toNumber() {
 818         return inGlobal(new Callable<Double>() {
 819             @Override public Double call() {
 820                 return JSType.toNumber(sobj);
 821             }
 822         });
 823     }
 824 }