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.security.AccessController; 29 import java.security.PrivilegedAction; 30 import java.util.AbstractMap; 31 import java.util.ArrayList; 32 import java.util.Collection; 33 import java.util.Collections; 34 import java.util.LinkedHashSet; 35 import java.util.Iterator; 36 import java.util.List; 37 import java.util.Map; 38 import java.util.Set; 39 import java.util.concurrent.Callable; 40 import javax.script.Bindings; 41 import jdk.nashorn.internal.runtime.Context; 42 import jdk.nashorn.internal.runtime.ScriptFunction; 43 import jdk.nashorn.internal.runtime.ScriptObject; 44 import jdk.nashorn.internal.runtime.ScriptRuntime; 45 46 /** 47 * Mirror object that wraps a given ScriptObject instance. User can 48 * access ScriptObject via the javax.script.Bindings interface or 49 * netscape.javascript.JSObject interface. 50 */ 51 public final class ScriptObjectMirror extends JSObject implements Bindings { 52 private final ScriptObject sobj; 53 private final ScriptObject global; 54 55 ScriptObjectMirror(final ScriptObject sobj, final ScriptObject global) { 56 this.sobj = sobj; 57 this.global = global; 58 } 59 60 @Override 61 public boolean equals(final Object other) { 62 if (other instanceof ScriptObjectMirror) { 63 return sobj.equals(((ScriptObjectMirror)other).sobj); 64 } 65 66 return false; 67 } 68 69 @Override 70 public int hashCode() { 71 return sobj.hashCode(); 72 } 73 74 @Override 75 public String toString() { 76 return inGlobal(new Callable<String>() { 77 @Override 78 public String call() { 79 return ScriptRuntime.safeToString(sobj); 80 } 81 }); 82 } 83 84 private <V> V inGlobal(final Callable<V> callable) { 85 final ScriptObject oldGlobal = NashornScriptEngine.getNashornGlobal(); 86 final boolean globalChanged = (oldGlobal != global); 87 if (globalChanged) { 88 NashornScriptEngine.setNashornGlobal(global); 89 } 90 try { 91 return callable.call(); 92 } catch (final RuntimeException e) { 93 throw e; 94 } catch (final Exception e) { 95 throw new AssertionError("Cannot happen", e); 96 } finally { 97 if (globalChanged) { 98 NashornScriptEngine.setNashornGlobal(oldGlobal); 99 } 100 } 101 } 102 103 // JSObject methods 104 @Override 105 public Object call(final String methodName, final Object args[]) { 106 final ScriptObject oldGlobal = NashornScriptEngine.getNashornGlobal(); 107 final boolean globalChanged = (oldGlobal != global); 108 109 try { 110 if (globalChanged) { 111 NashornScriptEngine.setNashornGlobal(global); 112 } 113 114 final Object val = sobj.get(methodName); 115 if (! (val instanceof ScriptFunction)) { 116 throw new RuntimeException("No such method: " + methodName); 117 } 118 119 final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args; 120 return wrap(ScriptRuntime.checkAndApply((ScriptFunction)val, sobj, unwrapArray(modArgs, global)), global); 121 } catch (final RuntimeException | Error e) { 122 throw e; 123 } catch (final Throwable t) { 124 throw new RuntimeException(t); 125 } finally { 126 if (globalChanged) { 127 NashornScriptEngine.setNashornGlobal(oldGlobal); 128 } 129 } 130 } 131 132 @Override 133 public Object eval(final String s) { 134 return inGlobal(new Callable<Object>() { 135 @Override 136 public Object call() { 137 final Context context = AccessController.doPrivileged( 138 new PrivilegedAction<Context>() { 139 @Override 140 public Context run() { 141 return Context.getContext(); 142 } 143 }); 144 return wrap(context.eval(global, s, null, null, false), global); 145 } 146 }); 147 } 148 149 @Override 150 public Object getMember(final String name) { 151 return inGlobal(new Callable<Object>() { 152 @Override public Object call() { 153 return wrap(sobj.get(name), global); 154 } 155 }); 156 } 157 158 @Override 159 public Object getSlot(final int index) { 160 return inGlobal(new Callable<Object>() { 161 @Override public Object call() { 162 return wrap(sobj.get(index), global); 163 } 164 }); 165 } 166 167 @Override 168 public void removeMember(final String name) { 169 remove(name); 170 } 171 172 @Override 173 public void setMember(final String name, final Object value) { 174 put(name, value); 175 } 176 177 @Override 178 public void setSlot(final int index, final Object value) { 179 inGlobal(new Callable<Void>() { 180 @Override public Void call() { 181 sobj.set(index, unwrap(value, global), global.isStrictContext()); 182 return null; 183 } 184 }); 185 } 186 187 @Override 188 public void clear() { 189 inGlobal(new Callable<Object>() { 190 @Override public Object call() { 191 sobj.clear(); 192 return null; 193 } 194 }); 195 } 196 197 @Override 198 public boolean containsKey(final Object key) { 199 return inGlobal(new Callable<Boolean>() { 200 @Override public Boolean call() { 201 return sobj.containsKey(unwrap(key, global)); 202 } 203 }); 204 } 205 206 @Override 207 public boolean containsValue(final Object value) { 208 return inGlobal(new Callable<Boolean>() { 209 @Override public Boolean call() { 210 return sobj.containsValue(unwrap(value, global)); 211 } 212 }); 213 } 214 215 @Override 216 public Set<Map.Entry<String, Object>> entrySet() { 217 return inGlobal(new Callable<Set<Map.Entry<String, Object>>>() { 218 @Override public Set<Map.Entry<String, Object>> call() { 219 final Iterator<String> iter = sobj.propertyIterator(); 220 final Set<Map.Entry<String, Object>> entries = new LinkedHashSet<>(); 221 222 while (iter.hasNext()) { 223 final String key = iter.next(); 224 final Object value = translateUndefined(wrap(sobj.get(key), global)); 225 entries.add(new AbstractMap.SimpleImmutableEntry<>(key, value)); 226 } 227 228 return Collections.unmodifiableSet(entries); 229 } 230 }); 231 } 232 233 @Override 234 public Object get(final Object key) { 235 return inGlobal(new Callable<Object>() { 236 @Override public Object call() { 237 return translateUndefined(wrap(sobj.get(key), global)); 238 } 239 }); 240 } 241 242 @Override 243 public boolean isEmpty() { 244 return inGlobal(new Callable<Boolean>() { 245 @Override public Boolean call() { 246 return sobj.isEmpty(); 247 } 248 }); 249 } 250 251 @Override 252 public Set<String> keySet() { 253 return inGlobal(new Callable<Set<String>>() { 254 @Override public Set<String> call() { 255 final Iterator<String> iter = sobj.propertyIterator(); 256 final Set<String> keySet = new LinkedHashSet<>(); 257 258 while (iter.hasNext()) { 259 keySet.add(iter.next()); 260 } 261 262 return Collections.unmodifiableSet(keySet); 263 } 264 }); 265 } 266 267 @Override 268 public Object put(final String key, final Object value) { 269 final ScriptObject oldGlobal = NashornScriptEngine.getNashornGlobal(); 270 final boolean globalChanged = (oldGlobal != global); 271 return inGlobal(new Callable<Object>() { 272 @Override public Object call() { 273 final Object modValue = globalChanged? wrap(value, oldGlobal) : value; 274 return translateUndefined(wrap(sobj.put(key, unwrap(modValue, global)), global)); 275 } 276 }); 277 } 278 279 @Override 280 public void putAll(final Map<? extends String, ? extends Object> map) { 281 final ScriptObject oldGlobal = NashornScriptEngine.getNashornGlobal(); 282 final boolean globalChanged = (oldGlobal != global); 283 final boolean strict = sobj.isStrictContext(); 284 inGlobal(new Callable<Object>() { 285 @Override public Object call() { 286 for (final Map.Entry<? extends String, ? extends Object> entry : map.entrySet()) { 287 final Object value = entry.getValue(); 288 final Object modValue = globalChanged? wrap(value, oldGlobal) : value; 289 sobj.set(entry.getKey(), unwrap(modValue, global), strict); 290 } 291 return null; 292 } 293 }); 294 } 295 296 @Override 297 public Object remove(final Object key) { 298 return inGlobal(new Callable<Object>() { 299 @Override public Object call() { 300 return wrap(sobj.remove(unwrap(key, global)), global); 301 } 302 }); 303 } 304 305 /** 306 * Delete a property from this object. 307 * 308 * @param key the property to be deleted 309 * 310 * @return if the delete was successful or not 311 */ 312 public boolean delete(final Object key) { 313 return inGlobal(new Callable<Boolean>() { 314 @Override public Boolean call() { 315 return sobj.delete(unwrap(key, global)); 316 } 317 }); 318 } 319 320 @Override 321 public int size() { 322 return inGlobal(new Callable<Integer>() { 323 @Override public Integer call() { 324 return sobj.size(); 325 } 326 }); 327 } 328 329 @Override 330 public Collection<Object> values() { 331 return inGlobal(new Callable<Collection<Object>>() { 332 @Override public Collection<Object> call() { 333 final List<Object> values = new ArrayList<>(size()); 334 final Iterator<Object> iter = sobj.valueIterator(); 335 336 while (iter.hasNext()) { 337 values.add(translateUndefined(wrap(iter.next(), global))); 338 } 339 340 return Collections.unmodifiableList(values); 341 } 342 }); 343 } 344 345 // Support for ECMAScript Object API on mirrors 346 347 /** 348 * Return the __proto__ of this object. 349 * @return __proto__ object. 350 */ 351 public Object getProto() { 352 return inGlobal(new Callable<Object>() { 353 @Override public Object call() { 354 return wrap(getScriptObject().getProto(), global); 355 } 356 }); 357 } 358 359 /** 360 * ECMA 8.12.1 [[GetOwnProperty]] (P) 361 * 362 * @param key property key 363 * 364 * @return Returns the Property Descriptor of the named own property of this 365 * object, or undefined if absent. 366 */ 367 public Object getOwnPropertyDescriptor(final String key) { 368 return inGlobal(new Callable<Object>() { 369 @Override public Object call() { 370 return wrap(getScriptObject().getOwnPropertyDescriptor(key), global); 371 } 372 }); 373 } 374 375 /** 376 * return an array of own property keys associated with the object. 377 * 378 * @param all True if to include non-enumerable keys. 379 * @return Array of keys. 380 */ 381 public String[] getOwnKeys(final boolean all) { 382 return inGlobal(new Callable<String[]>() { 383 @Override public String[] call() { 384 return getScriptObject().getOwnKeys(all); 385 } 386 }); 387 } 388 389 /** 390 * Flag this script object as non extensible 391 * 392 * @return the object after being made non extensible 393 */ 394 public ScriptObjectMirror preventExtensions() { 395 return inGlobal(new Callable<ScriptObjectMirror>() { 396 @Override public ScriptObjectMirror call() { 397 getScriptObject().preventExtensions(); 398 return ScriptObjectMirror.this; 399 } 400 }); 401 } 402 403 /** 404 * Check if this script object is extensible 405 * @return true if extensible 406 */ 407 public boolean isExtensible() { 408 return inGlobal(new Callable<Boolean>() { 409 @Override public Boolean call() { 410 return getScriptObject().isExtensible(); 411 } 412 }); 413 } 414 415 /** 416 * ECMAScript 15.2.3.8 - seal implementation 417 * @return the sealed script object 418 */ 419 public ScriptObjectMirror seal() { 420 return inGlobal(new Callable<ScriptObjectMirror>() { 421 @Override public ScriptObjectMirror call() { 422 getScriptObject().seal(); 423 return ScriptObjectMirror.this; 424 } 425 }); 426 } 427 428 /** 429 * Check whether this script object is sealed 430 * @return true if sealed 431 */ 432 public boolean isSealed() { 433 return inGlobal(new Callable<Boolean>() { 434 @Override public Boolean call() { 435 return getScriptObject().isSealed(); 436 } 437 }); 438 } 439 440 /** 441 * ECMA 15.2.39 - freeze implementation. Freeze this script object 442 * @return the frozen script object 443 */ 444 public ScriptObjectMirror freeze() { 445 return inGlobal(new Callable<ScriptObjectMirror>() { 446 @Override public ScriptObjectMirror call() { 447 getScriptObject().freeze(); 448 return ScriptObjectMirror.this; 449 } 450 }); 451 } 452 453 /** 454 * Check whether this script object is frozen 455 * @return true if frozen 456 */ 457 public boolean isFrozen() { 458 return inGlobal(new Callable<Boolean>() { 459 @Override public Boolean call() { 460 return getScriptObject().isFrozen(); 461 } 462 }); 463 } 464 465 // ECMAScript instanceof check 466 467 /** 468 * Checking whether a script object is an instance of another by 469 * walking the proto chain 470 * 471 * @param instance instace to check 472 * @return true if 'instance' is an instance of this object 473 */ 474 public boolean isInstance(final ScriptObjectMirror instance) { 475 // if not belongs to my global scope, return false 476 if (instance == null || global != instance.global) { 477 return false; 478 } 479 480 return inGlobal(new Callable<Boolean>() { 481 @Override public Boolean call() { 482 return getScriptObject().isInstance(instance.getScriptObject()); 483 } 484 }); 485 } 486 487 /** 488 * Utility to check if given object is ECMAScript undefined value 489 * 490 * @param obj object to check 491 * @return true if 'obj' is ECMAScript undefined value 492 */ 493 public static boolean isUndefined(final Object obj) { 494 return obj == ScriptRuntime.UNDEFINED; 495 } 496 497 /** 498 * is this a function object? 499 * 500 * @return if this mirror wraps a ECMAScript function instance 501 */ 502 public boolean isFunction() { 503 return getScriptObject() instanceof ScriptFunction; 504 } 505 506 /** 507 * is this a 'use strict' function object? 508 * 509 * @return true if this mirror represents a ECMAScript 'use strict' function 510 */ 511 public boolean isStrictFunction() { 512 return isFunction() && ((ScriptFunction)getScriptObject()).isStrict(); 513 } 514 515 /** 516 * is this an array object? 517 * 518 * @return if this mirror wraps a ECMAScript array object 519 */ 520 public boolean isArray() { 521 return getScriptObject().isArray(); 522 } 523 524 // These are public only so that Context can access these. 525 526 /** 527 * Make a script object mirror on given object if needed. 528 * 529 * @param obj object to be wrapped 530 * @param homeGlobal global to which this object belongs 531 * @return wrapped object 532 */ 533 public static Object wrap(final Object obj, final ScriptObject homeGlobal) { 534 return (obj instanceof ScriptObject) ? new ScriptObjectMirror((ScriptObject)obj, homeGlobal) : obj; 535 } 536 537 /** 538 * Unwrap a script object mirror if needed. 539 * 540 * @param obj object to be unwrapped 541 * @param homeGlobal global to which this object belongs 542 * @return unwrapped object 543 */ 544 public static Object unwrap(final Object obj, final ScriptObject homeGlobal) { 545 if (obj instanceof ScriptObjectMirror) { 546 final ScriptObjectMirror mirror = (ScriptObjectMirror)obj; 547 return (mirror.global == homeGlobal)? mirror.sobj : obj; 548 } 549 550 return obj; 551 } 552 553 /** 554 * Wrap an array of object to script object mirrors if needed. 555 * 556 * @param args array to be unwrapped 557 * @param homeGlobal global to which this object belongs 558 * @return wrapped array 559 */ 560 public static Object[] wrapArray(final Object[] args, final ScriptObject homeGlobal) { 561 if (args == null || args.length == 0) { 562 return args; 563 } 564 565 final Object[] newArgs = new Object[args.length]; 566 int index = 0; 567 for (final Object obj : args) { 568 newArgs[index] = wrap(obj, homeGlobal); 569 index++; 570 } 571 return newArgs; 572 } 573 574 /** 575 * Unwrap an array of script object mirrors if needed. 576 * 577 * @param args array to be unwrapped 578 * @param homeGlobal global to which this object belongs 579 * @return unwrapped array 580 */ 581 public static Object[] unwrapArray(final Object[] args, final ScriptObject homeGlobal) { 582 if (args == null || args.length == 0) { 583 return args; 584 } 585 586 final Object[] newArgs = new Object[args.length]; 587 int index = 0; 588 for (final Object obj : args) { 589 newArgs[index] = unwrap(obj, homeGlobal); 590 index++; 591 } 592 return newArgs; 593 } 594 595 // package-privates below this. 596 ScriptObject getScriptObject() { 597 return sobj; 598 } 599 600 static Object translateUndefined(Object obj) { 601 return (obj == ScriptRuntime.UNDEFINED)? null : obj; 602 } 603 }