NativeJSAdapter.java revision 1805:7caf1f762f1d
1/* 2 * Copyright (c) 2010, 2015, 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 26package jdk.nashorn.internal.objects; 27 28import static jdk.nashorn.internal.lookup.Lookup.MH; 29import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; 30import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; 31import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT; 32 33import java.lang.invoke.MethodHandle; 34import java.lang.invoke.MethodHandles; 35import java.lang.invoke.MethodType; 36import java.util.ArrayList; 37import java.util.Iterator; 38import java.util.List; 39import jdk.dynalink.CallSiteDescriptor; 40import jdk.dynalink.linker.GuardedInvocation; 41import jdk.dynalink.linker.LinkRequest; 42import jdk.nashorn.internal.lookup.Lookup; 43import jdk.nashorn.internal.objects.annotations.Constructor; 44import jdk.nashorn.internal.objects.annotations.ScriptClass; 45import jdk.nashorn.internal.runtime.FindProperty; 46import jdk.nashorn.internal.runtime.JSType; 47import jdk.nashorn.internal.runtime.PropertyMap; 48import jdk.nashorn.internal.runtime.ScriptFunction; 49import jdk.nashorn.internal.runtime.ScriptObject; 50import jdk.nashorn.internal.runtime.ScriptRuntime; 51import jdk.nashorn.internal.runtime.arrays.ArrayLikeIterator; 52import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor; 53import jdk.nashorn.internal.scripts.JO; 54 55/** 56 * This class is the implementation of the Nashorn-specific global object named {@code JSAdapter}. It can be thought of 57 * as the {@link java.lang.reflect.Proxy} equivalent for JavaScript. A {@code NativeJSAdapter} calls specially named 58 * JavaScript methods on an adaptee object when property access/update/call/new/delete is attempted on it. Example: 59 *<pre> 60 * var y = { 61 * __get__ : function (name) { ... } 62 * __has__ : function (name) { ... } 63 * __put__ : function (name, value) {...} 64 * __call__ : function (name, arg1, arg2) {...} 65 * __new__ : function (arg1, arg2) {...} 66 * __delete__ : function (name) { ... } 67 * __getKeys__ : function () { ... } 68 * }; 69 * 70 * var x = new JSAdapter(y); 71 * 72 * x.i; // calls y.__get__ 73 * x.foo(); // calls y.__call__ 74 * new x(); // calls y.__new__ 75 * i in x; // calls y.__has__ 76 * x.p = 10; // calls y.__put__ 77 * delete x.p; // calls y.__delete__ 78 * for (i in x) { print(i); } // calls y.__getKeys__ 79 * </pre> 80 * <p> 81 * The {@code __getKeys__} and {@code __getIds__} properties are mapped to the same operation. Concrete 82 * {@code JSAdapter} implementations are expected to use only one of these. As {@code __getIds__} exists for 83 * compatibility reasons only, use of {@code __getKeys__} is recommended. 84 * </p> 85 * <p> 86 * The JavaScript caller of an adapter object is oblivious of the property access/mutation/deletion's being adapted. 87 * </p> 88 * <p> 89 * The {@code JSAdapter} constructor can optionally receive an "overrides" object. The properties of overrides object 90 * are copied to the {@code JSAdapter} instance. In case user-accessed properties are among these, the adaptee's methods 91 * like {@code __get__}, {@code __put__} etc. are not called for them. This can be used to make certain "preferred" 92 * properties that can be accessed in the usual/faster way avoiding the proxy mechanism. Example: 93 * </p> 94 * <pre> 95 * var x = new JSAdapter({ foo: 444, bar: 6546 }) { 96 * __get__: function(name) { return name; } 97 * }; 98 * 99 * x.foo; // 444 directly retrieved without __get__ call 100 * x.bar = 'hello'; // "bar" directly set without __put__ call 101 * x.prop // calls __get__("prop") as 'prop' is not overridden 102 * </pre> 103 * It is possible to pass a specific prototype for the {@code JSAdapter} instance by passing three arguments to the 104 * {@code JSAdapter} constructor. The exact signature of the {@code JSAdapter} constructor is as follows: 105 * <pre> 106 * JSAdapter([proto], [overrides], adaptee); 107 * </pre> 108 * Both the {@code proto} and {@code overrides} arguments are optional - but {@code adaptee} is not. When {@code proto} 109 * is not passed, {@code JSAdapter.prototype} is used. 110 */ 111@ScriptClass("JSAdapter") 112public final class NativeJSAdapter extends ScriptObject { 113 /** object get operation */ 114 public static final String __get__ = "__get__"; 115 /** object out operation */ 116 public static final String __put__ = "__put__"; 117 /** object call operation */ 118 public static final String __call__ = "__call__"; 119 /** object new operation */ 120 public static final String __new__ = "__new__"; 121 /** object getIds operation (provided for compatibility reasons; use of getKeys is preferred) */ 122 public static final String __getIds__ = "__getIds__"; 123 /** object getKeys operation */ 124 public static final String __getKeys__ = "__getKeys__"; 125 /** object getValues operation */ 126 public static final String __getValues__ = "__getValues__"; 127 /** object has operation */ 128 public static final String __has__ = "__has__"; 129 /** object delete operation */ 130 public static final String __delete__ = "__delete__"; 131 132 // the new extensibility, sealing and freezing operations 133 134 /** prevent extensions operation */ 135 public static final String __preventExtensions__ = "__preventExtensions__"; 136 /** isExtensible extensions operation */ 137 public static final String __isExtensible__ = "__isExtensible__"; 138 /** seal operation */ 139 public static final String __seal__ = "__seal__"; 140 /** isSealed extensions operation */ 141 public static final String __isSealed__ = "__isSealed__"; 142 /** freeze operation */ 143 public static final String __freeze__ = "__freeze__"; 144 /** isFrozen extensions operation */ 145 public static final String __isFrozen__ = "__isFrozen__"; 146 147 private final ScriptObject adaptee; 148 private final boolean overrides; 149 150 private static final MethodHandle IS_JSADAPTER = findOwnMH("isJSAdapter", boolean.class, Object.class, Object.class, MethodHandle.class, Object.class, ScriptFunction.class); 151 152 // initialized by nasgen 153 private static PropertyMap $nasgenmap$; 154 155 NativeJSAdapter(final Object overrides, final ScriptObject adaptee, final ScriptObject proto, final PropertyMap map) { 156 super(proto, map); 157 this.adaptee = wrapAdaptee(adaptee); 158 if (overrides instanceof ScriptObject) { 159 this.overrides = true; 160 final ScriptObject sobj = (ScriptObject)overrides; 161 this.addBoundProperties(sobj); 162 } else { 163 this.overrides = false; 164 } 165 } 166 167 private static ScriptObject wrapAdaptee(final ScriptObject adaptee) { 168 return new JO(adaptee); 169 } 170 171 @Override 172 public String getClassName() { 173 return "JSAdapter"; 174 } 175 176 @Override 177 public int getInt(final Object key, final int programPoint) { 178 return (overrides && super.hasOwnProperty(key)) ? super.getInt(key, programPoint) : callAdapteeInt(programPoint, __get__, key); 179 } 180 181 @Override 182 public int getInt(final double key, final int programPoint) { 183 return (overrides && super.hasOwnProperty(key)) ? super.getInt(key, programPoint) : callAdapteeInt(programPoint, __get__, key); 184 } 185 186 @Override 187 public int getInt(final int key, final int programPoint) { 188 return (overrides && super.hasOwnProperty(key)) ? super.getInt(key, programPoint) : callAdapteeInt(programPoint, __get__, key); 189 } 190 191 @Override 192 public double getDouble(final Object key, final int programPoint) { 193 return (overrides && super.hasOwnProperty(key)) ? super.getDouble(key, programPoint) : callAdapteeDouble(programPoint, __get__, key); 194 } 195 196 @Override 197 public double getDouble(final double key, final int programPoint) { 198 return (overrides && super.hasOwnProperty(key)) ? super.getDouble(key, programPoint) : callAdapteeDouble(programPoint, __get__, key); 199 } 200 201 @Override 202 public double getDouble(final int key, final int programPoint) { 203 return (overrides && super.hasOwnProperty(key)) ? super.getDouble(key, programPoint) : callAdapteeDouble(programPoint, __get__, key); 204 } 205 206 @Override 207 public Object get(final Object key) { 208 return (overrides && super.hasOwnProperty(key)) ? super.get(key) : callAdaptee(__get__, key); 209 } 210 211 @Override 212 public Object get(final double key) { 213 return (overrides && super.hasOwnProperty(key)) ? super.get(key) : callAdaptee(__get__, key); 214 } 215 216 @Override 217 public Object get(final int key) { 218 return (overrides && super.hasOwnProperty(key)) ? super.get(key) : callAdaptee(__get__, key); 219 } 220 221 @Override 222 public void set(final Object key, final int value, final int flags) { 223 if (overrides && super.hasOwnProperty(key)) { 224 super.set(key, value, flags); 225 } else { 226 callAdaptee(__put__, key, value, flags); 227 } 228 } 229 230 @Override 231 public void set(final Object key, final double value, final int flags) { 232 if (overrides && super.hasOwnProperty(key)) { 233 super.set(key, value, flags); 234 } else { 235 callAdaptee(__put__, key, value, flags); 236 } 237 } 238 239 @Override 240 public void set(final Object key, final Object value, final int flags) { 241 if (overrides && super.hasOwnProperty(key)) { 242 super.set(key, value, flags); 243 } else { 244 callAdaptee(__put__, key, value, flags); 245 } 246 } 247 248 @Override 249 public void set(final double key, final int value, final int flags) { 250 if (overrides && super.hasOwnProperty(key)) { 251 super.set(key, value, flags); 252 } else { 253 callAdaptee(__put__, key, value, flags); 254 } 255 } 256 257 @Override 258 public void set(final double key, final double value, final int flags) { 259 if (overrides && super.hasOwnProperty(key)) { 260 super.set(key, value, flags); 261 } else { 262 callAdaptee(__put__, key, value, flags); 263 } 264 } 265 266 @Override 267 public void set(final double key, final Object value, final int flags) { 268 if (overrides && super.hasOwnProperty(key)) { 269 super.set(key, value, flags); 270 } else { 271 callAdaptee(__put__, key, value, flags); 272 } 273 } 274 275 @Override 276 public void set(final int key, final int value, final int flags) { 277 if (overrides && super.hasOwnProperty(key)) { 278 super.set(key, value, flags); 279 } else { 280 callAdaptee(__put__, key, value, flags); 281 } 282 } 283 284 @Override 285 public void set(final int key, final double value, final int flags) { 286 if (overrides && super.hasOwnProperty(key)) { 287 super.set(key, value, flags); 288 } else { 289 callAdaptee(__put__, key, value, flags); 290 } 291 } 292 293 @Override 294 public void set(final int key, final Object value, final int flags) { 295 if (overrides && super.hasOwnProperty(key)) { 296 super.set(key, value, flags); 297 } else { 298 callAdaptee(__put__, key, value, flags); 299 } 300 } 301 302 @Override 303 public boolean has(final Object key) { 304 if (overrides && super.hasOwnProperty(key)) { 305 return true; 306 } 307 308 return JSType.toBoolean(callAdaptee(Boolean.FALSE, __has__, key)); 309 } 310 311 @Override 312 public boolean has(final int key) { 313 if (overrides && super.hasOwnProperty(key)) { 314 return true; 315 } 316 317 return JSType.toBoolean(callAdaptee(Boolean.FALSE, __has__, key)); 318 } 319 320 @Override 321 public boolean has(final double key) { 322 if (overrides && super.hasOwnProperty(key)) { 323 return true; 324 } 325 326 return JSType.toBoolean(callAdaptee(Boolean.FALSE, __has__, key)); 327 } 328 329 @Override 330 public boolean delete(final int key, final boolean strict) { 331 if (overrides && super.hasOwnProperty(key)) { 332 return super.delete(key, strict); 333 } 334 335 return JSType.toBoolean(callAdaptee(Boolean.TRUE, __delete__, key, strict)); 336 } 337 338 @Override 339 public boolean delete(final double key, final boolean strict) { 340 if (overrides && super.hasOwnProperty(key)) { 341 return super.delete(key, strict); 342 } 343 344 return JSType.toBoolean(callAdaptee(Boolean.TRUE, __delete__, key, strict)); 345 } 346 347 @Override 348 public boolean delete(final Object key, final boolean strict) { 349 if (overrides && super.hasOwnProperty(key)) { 350 return super.delete(key, strict); 351 } 352 353 return JSType.toBoolean(callAdaptee(Boolean.TRUE, __delete__, key, strict)); 354 } 355 356 @Override 357 public Iterator<String> propertyIterator() { 358 // Try __getIds__ first, if not found then try __getKeys__ 359 // In jdk6, we had added "__getIds__" so this is just for compatibility. 360 Object func = adaptee.get(__getIds__); 361 if (!(func instanceof ScriptFunction)) { 362 func = adaptee.get(__getKeys__); 363 } 364 365 Object obj; 366 if (func instanceof ScriptFunction) { 367 obj = ScriptRuntime.apply((ScriptFunction)func, this); 368 } else { 369 obj = new NativeArray(0); 370 } 371 372 final List<String> array = new ArrayList<>(); 373 for (final Iterator<Object> iter = ArrayLikeIterator.arrayLikeIterator(obj); iter.hasNext(); ) { 374 array.add((String)iter.next()); 375 } 376 377 return array.iterator(); 378 } 379 380 381 @Override 382 public Iterator<Object> valueIterator() { 383 final Object obj = callAdaptee(new NativeArray(0), __getValues__); 384 return ArrayLikeIterator.arrayLikeIterator(obj); 385 } 386 387 @Override 388 public ScriptObject preventExtensions() { 389 callAdaptee(__preventExtensions__); 390 return this; 391 } 392 393 @Override 394 public boolean isExtensible() { 395 return JSType.toBoolean(callAdaptee(Boolean.TRUE, __isExtensible__)); 396 } 397 398 @Override 399 public ScriptObject seal() { 400 callAdaptee(__seal__); 401 return this; 402 } 403 404 @Override 405 public boolean isSealed() { 406 return JSType.toBoolean(callAdaptee(Boolean.FALSE, __isSealed__)); 407 } 408 409 @Override 410 public ScriptObject freeze() { 411 callAdaptee(__freeze__); 412 return this; 413 } 414 415 @Override 416 public boolean isFrozen() { 417 return JSType.toBoolean(callAdaptee(Boolean.FALSE, __isFrozen__)); 418 } 419 420 /** 421 * Constructor 422 * 423 * @param isNew is this NativeJSAdapter instantiated with the new operator 424 * @param self self reference 425 * @param args arguments ([adaptee], [overrides, adaptee] or [proto, overrides, adaptee] 426 * @return new NativeJSAdapter 427 */ 428 @Constructor 429 public static NativeJSAdapter construct(final boolean isNew, final Object self, final Object... args) { 430 Object proto = UNDEFINED; 431 Object overrides = UNDEFINED; 432 Object adaptee; 433 434 if (args == null || args.length == 0) { 435 throw typeError("not.an.object", "null"); 436 } 437 438 switch (args.length) { 439 case 1: 440 adaptee = args[0]; 441 break; 442 443 case 2: 444 overrides = args[0]; 445 adaptee = args[1]; 446 break; 447 448 default: 449 //fallthru 450 case 3: 451 proto = args[0]; 452 overrides = args[1]; 453 adaptee = args[2]; 454 break; 455 } 456 457 if (!(adaptee instanceof ScriptObject)) { 458 throw typeError("not.an.object", ScriptRuntime.safeToString(adaptee)); 459 } 460 461 final Global global = Global.instance(); 462 if (proto != null && !(proto instanceof ScriptObject)) { 463 proto = global.getJSAdapterPrototype(); 464 } 465 466 return new NativeJSAdapter(overrides, (ScriptObject)adaptee, (ScriptObject)proto, $nasgenmap$); 467 } 468 469 @Override 470 protected GuardedInvocation findNewMethod(final CallSiteDescriptor desc, final LinkRequest request) { 471 return findHook(desc, __new__, false); 472 } 473 474 @Override 475 protected GuardedInvocation findGetMethod(final CallSiteDescriptor desc, final LinkRequest request) { 476 final String name = NashornCallSiteDescriptor.getOperand(desc); 477 if (overrides && super.hasOwnProperty(name)) { 478 try { 479 final GuardedInvocation inv = super.findGetMethod(desc, request); 480 if (inv != null) { 481 return inv; 482 } 483 } catch (final Exception e) { 484 //ignored 485 } 486 } 487 488 if (!NashornCallSiteDescriptor.isMethodFirstOperation(desc)) { 489 return findHook(desc, __get__); 490 } else { 491 final FindProperty find = adaptee.findProperty(__call__, true); 492 if (find != null) { 493 final Object value = find.getObjectValue(); 494 if (value instanceof ScriptFunction) { 495 final ScriptFunction func = (ScriptFunction)value; 496 // TODO: It's a shame we need to produce a function bound to this and name, when we'd only need it bound 497 // to name. Probably not a big deal, but if we can ever make it leaner, it'd be nice. 498 return new GuardedInvocation(MH.dropArguments(MH.constant(Object.class, 499 func.createBound(this, new Object[] { name })), 0, Object.class), 500 testJSAdapter(adaptee, null, null, null), 501 adaptee.getProtoSwitchPoints(__call__, find.getOwner()), null); 502 } 503 } 504 throw typeError("no.such.function", name, ScriptRuntime.safeToString(this)); 505 } 506 } 507 508 @Override 509 protected GuardedInvocation findSetMethod(final CallSiteDescriptor desc, final LinkRequest request) { 510 if (overrides && super.hasOwnProperty(NashornCallSiteDescriptor.getOperand(desc))) { 511 try { 512 final GuardedInvocation inv = super.findSetMethod(desc, request); 513 if (inv != null) { 514 return inv; 515 } 516 } catch (final Exception e) { 517 //ignored 518 } 519 } 520 521 return findHook(desc, __put__); 522 } 523 524 // -- Internals only below this point 525 private Object callAdaptee(final String name, final Object... args) { 526 return callAdaptee(UNDEFINED, name, args); 527 } 528 529 private double callAdapteeDouble(final int programPoint, final String name, final Object... args) { 530 return JSType.toNumberMaybeOptimistic(callAdaptee(name, args), programPoint); 531 } 532 533 private int callAdapteeInt(final int programPoint, final String name, final Object... args) { 534 return JSType.toInt32MaybeOptimistic(callAdaptee(name, args), programPoint); 535 } 536 537 private Object callAdaptee(final Object retValue, final String name, final Object... args) { 538 final Object func = adaptee.get(name); 539 if (func instanceof ScriptFunction) { 540 return ScriptRuntime.apply((ScriptFunction)func, this, args); 541 } 542 return retValue; 543 } 544 545 private GuardedInvocation findHook(final CallSiteDescriptor desc, final String hook) { 546 return findHook(desc, hook, true); 547 } 548 549 private GuardedInvocation findHook(final CallSiteDescriptor desc, final String hook, final boolean useName) { 550 final FindProperty findData = adaptee.findProperty(hook, true); 551 final MethodType type = desc.getMethodType(); 552 if (findData != null) { 553 final String name = NashornCallSiteDescriptor.getOperand(desc); 554 final Object value = findData.getObjectValue(); 555 if (value instanceof ScriptFunction) { 556 final ScriptFunction func = (ScriptFunction)value; 557 558 final MethodHandle methodHandle = getCallMethodHandle(findData, type, 559 useName ? name : null); 560 if (methodHandle != null) { 561 return new GuardedInvocation( 562 methodHandle, 563 testJSAdapter(adaptee, findData.getGetter(Object.class, INVALID_PROGRAM_POINT, null), findData.getOwner(), func), 564 adaptee.getProtoSwitchPoints(hook, findData.getOwner()), null); 565 } 566 } 567 } 568 569 switch (hook) { 570 case __call__: 571 throw typeError("no.such.function", NashornCallSiteDescriptor.getOperand(desc), ScriptRuntime.safeToString(this)); 572 default: 573 final MethodHandle methodHandle = hook.equals(__put__) ? 574 MH.asType(Lookup.EMPTY_SETTER, type) : 575 Lookup.emptyGetter(type.returnType()); 576 return new GuardedInvocation(methodHandle, testJSAdapter(adaptee, null, null, null), adaptee.getProtoSwitchPoints(hook, null), null); 577 } 578 } 579 580 private static MethodHandle testJSAdapter(final Object adaptee, final MethodHandle getter, final Object where, final ScriptFunction func) { 581 return MH.insertArguments(IS_JSADAPTER, 1, adaptee, getter, where, func); 582 } 583 584 @SuppressWarnings("unused") 585 private static boolean isJSAdapter(final Object self, final Object adaptee, final MethodHandle getter, final Object where, final ScriptFunction func) { 586 final boolean res = self instanceof NativeJSAdapter && ((NativeJSAdapter)self).getAdaptee() == adaptee; 587 if (res && getter != null) { 588 try { 589 return getter.invokeExact(where) == func; 590 } catch (final RuntimeException | Error e) { 591 throw e; 592 } catch (final Throwable t) { 593 throw new RuntimeException(t); 594 } 595 } 596 597 return res; 598 } 599 600 /** 601 * Get the adaptee 602 * @return adaptee ScriptObject 603 */ 604 public ScriptObject getAdaptee() { 605 return adaptee; 606 } 607 608 private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) { 609 return MH.findStatic(MethodHandles.lookup(), NativeJSAdapter.class, name, MH.type(rtype, types)); 610 } 611} 612