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