NativeObject.java revision 1598:30c3bcdb762c
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 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; 31 32import java.lang.invoke.MethodHandle; 33import java.lang.invoke.MethodHandles; 34import java.lang.invoke.MethodType; 35import java.nio.ByteBuffer; 36import java.util.ArrayList; 37import java.util.Collection; 38import java.util.HashSet; 39import java.util.List; 40import java.util.Set; 41import java.util.concurrent.Callable; 42import jdk.dynalink.CallSiteDescriptor; 43import jdk.dynalink.NamedOperation; 44import jdk.dynalink.Operation; 45import jdk.dynalink.StandardOperation; 46import jdk.dynalink.beans.BeansLinker; 47import jdk.dynalink.beans.StaticClass; 48import jdk.dynalink.linker.GuardedInvocation; 49import jdk.dynalink.linker.GuardingDynamicLinker; 50import jdk.dynalink.linker.LinkRequest; 51import jdk.dynalink.linker.support.SimpleLinkRequest; 52import jdk.nashorn.api.scripting.ScriptObjectMirror; 53import jdk.nashorn.internal.lookup.Lookup; 54import jdk.nashorn.internal.objects.annotations.Attribute; 55import jdk.nashorn.internal.objects.annotations.Constructor; 56import jdk.nashorn.internal.objects.annotations.Function; 57import jdk.nashorn.internal.objects.annotations.ScriptClass; 58import jdk.nashorn.internal.objects.annotations.Where; 59import jdk.nashorn.internal.runtime.AccessorProperty; 60import jdk.nashorn.internal.runtime.ECMAException; 61import jdk.nashorn.internal.runtime.JSType; 62import jdk.nashorn.internal.runtime.Property; 63import jdk.nashorn.internal.runtime.PropertyMap; 64import jdk.nashorn.internal.runtime.ScriptObject; 65import jdk.nashorn.internal.runtime.ScriptRuntime; 66import jdk.nashorn.internal.runtime.arrays.ArrayData; 67import jdk.nashorn.internal.runtime.linker.Bootstrap; 68import jdk.nashorn.internal.runtime.linker.InvokeByName; 69import jdk.nashorn.internal.runtime.linker.NashornBeansLinker; 70import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor; 71 72/** 73 * ECMA 15.2 Object objects 74 * 75 * JavaScript Object constructor/prototype. Note: instances of this class are 76 * never created. This class is not even a subclass of ScriptObject. But, we use 77 * this class to generate prototype and constructor for "Object". 78 * 79 */ 80@ScriptClass("Object") 81public final class NativeObject { 82 /** Methodhandle to proto getter */ 83 public static final MethodHandle GET__PROTO__ = findOwnMH("get__proto__", ScriptObject.class, Object.class); 84 85 /** Methodhandle to proto setter */ 86 public static final MethodHandle SET__PROTO__ = findOwnMH("set__proto__", Object.class, Object.class, Object.class); 87 88 private static final Object TO_STRING = new Object(); 89 90 private static InvokeByName getTO_STRING() { 91 return Global.instance().getInvokeByName(TO_STRING, 92 new Callable<InvokeByName>() { 93 @Override 94 public InvokeByName call() { 95 return new InvokeByName("toString", ScriptObject.class); 96 } 97 }); 98 } 99 100 @SuppressWarnings("unused") 101 private static ScriptObject get__proto__(final Object self) { 102 // See ES6 draft spec: B.2.2.1.1 get Object.prototype.__proto__ 103 // Step 1 Let O be the result of calling ToObject passing the this. 104 final ScriptObject sobj = Global.checkObject(Global.toObject(self)); 105 return sobj.getProto(); 106 } 107 108 @SuppressWarnings("unused") 109 private static Object set__proto__(final Object self, final Object proto) { 110 // See ES6 draft spec: B.2.2.1.2 set Object.prototype.__proto__ 111 // Step 1 112 Global.checkObjectCoercible(self); 113 // Step 4 114 if (! (self instanceof ScriptObject)) { 115 return UNDEFINED; 116 } 117 118 final ScriptObject sobj = (ScriptObject)self; 119 // __proto__ assignment ignores non-nulls and non-objects 120 // step 3: If Type(proto) is neither Object nor Null, then return undefined. 121 if (proto == null || proto instanceof ScriptObject) { 122 sobj.setPrototypeOf(proto); 123 } 124 return UNDEFINED; 125 } 126 127 private static final MethodType MIRROR_GETTER_TYPE = MethodType.methodType(Object.class, ScriptObjectMirror.class); 128 private static final MethodType MIRROR_SETTER_TYPE = MethodType.methodType(Object.class, ScriptObjectMirror.class, Object.class); 129 130 // initialized by nasgen 131 @SuppressWarnings("unused") 132 private static PropertyMap $nasgenmap$; 133 134 private NativeObject() { 135 // don't create me! 136 throw new UnsupportedOperationException(); 137 } 138 139 private static ECMAException notAnObject(final Object obj) { 140 return typeError("not.an.object", ScriptRuntime.safeToString(obj)); 141 } 142 143 /** 144 * Nashorn extension: setIndexedPropertiesToExternalArrayData 145 * 146 * @param self self reference 147 * @param obj object whose index properties are backed by buffer 148 * @param buf external buffer - should be a nio ByteBuffer 149 * @return the 'obj' object 150 */ 151 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 152 public static ScriptObject setIndexedPropertiesToExternalArrayData(final Object self, final Object obj, final Object buf) { 153 Global.checkObject(obj); 154 final ScriptObject sobj = (ScriptObject)obj; 155 if (buf instanceof ByteBuffer) { 156 sobj.setArray(ArrayData.allocate((ByteBuffer)buf)); 157 } else { 158 throw typeError("not.a.bytebuffer", "setIndexedPropertiesToExternalArrayData's buf argument"); 159 } 160 return sobj; 161 } 162 163 164 /** 165 * ECMA 15.2.3.2 Object.getPrototypeOf ( O ) 166 * 167 * @param self self reference 168 * @param obj object to get prototype from 169 * @return the prototype of an object 170 */ 171 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 172 public static Object getPrototypeOf(final Object self, final Object obj) { 173 if (obj instanceof ScriptObject) { 174 return ((ScriptObject)obj).getProto(); 175 } else if (obj instanceof ScriptObjectMirror) { 176 return ((ScriptObjectMirror)obj).getProto(); 177 } else { 178 final JSType type = JSType.of(obj); 179 if (type == JSType.OBJECT) { 180 // host (Java) objects have null __proto__ 181 return null; 182 } 183 184 // must be some JS primitive 185 throw notAnObject(obj); 186 } 187 } 188 189 /** 190 * Nashorn extension: Object.setPrototypeOf ( O, proto ) 191 * Also found in ES6 draft specification. 192 * 193 * @param self self reference 194 * @param obj object to set prototype for 195 * @param proto prototype object to be used 196 * @return object whose prototype is set 197 */ 198 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 199 public static Object setPrototypeOf(final Object self, final Object obj, final Object proto) { 200 if (obj instanceof ScriptObject) { 201 ((ScriptObject)obj).setPrototypeOf(proto); 202 return obj; 203 } else if (obj instanceof ScriptObjectMirror) { 204 ((ScriptObjectMirror)obj).setProto(proto); 205 return obj; 206 } 207 208 throw notAnObject(obj); 209 } 210 211 /** 212 * ECMA 15.2.3.3 Object.getOwnPropertyDescriptor ( O, P ) 213 * 214 * @param self self reference 215 * @param obj object from which to get property descriptor for {@code ToString(prop)} 216 * @param prop property descriptor 217 * @return property descriptor 218 */ 219 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 220 public static Object getOwnPropertyDescriptor(final Object self, final Object obj, final Object prop) { 221 if (obj instanceof ScriptObject) { 222 final String key = JSType.toString(prop); 223 final ScriptObject sobj = (ScriptObject)obj; 224 225 return sobj.getOwnPropertyDescriptor(key); 226 } else if (obj instanceof ScriptObjectMirror) { 227 final String key = JSType.toString(prop); 228 final ScriptObjectMirror sobjMirror = (ScriptObjectMirror)obj; 229 230 return sobjMirror.getOwnPropertyDescriptor(key); 231 } else { 232 throw notAnObject(obj); 233 } 234 } 235 236 /** 237 * ECMA 15.2.3.4 Object.getOwnPropertyNames ( O ) 238 * 239 * @param self self reference 240 * @param obj object to query for property names 241 * @return array of property names 242 */ 243 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 244 public static ScriptObject getOwnPropertyNames(final Object self, final Object obj) { 245 if (obj instanceof ScriptObject) { 246 return new NativeArray(((ScriptObject)obj).getOwnKeys(true)); 247 } else if (obj instanceof ScriptObjectMirror) { 248 return new NativeArray(((ScriptObjectMirror)obj).getOwnKeys(true)); 249 } else { 250 throw notAnObject(obj); 251 } 252 } 253 254 /** 255 * ECMA 2 19.1.2.8 Object.getOwnPropertySymbols ( O ) 256 * 257 * @param self self reference 258 * @param obj object to query for property names 259 * @return array of property names 260 */ 261 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 262 public static ScriptObject getOwnPropertySymbols(final Object self, final Object obj) { 263 if (obj instanceof ScriptObject) { 264 return new NativeArray(((ScriptObject)obj).getOwnSymbols(true)); 265 } else { 266 // TODO: we don't support this on ScriptObjectMirror objects yet 267 throw notAnObject(obj); 268 } 269 } 270 271 /** 272 * ECMA 15.2.3.5 Object.create ( O [, Properties] ) 273 * 274 * @param self self reference 275 * @param proto prototype object 276 * @param props properties to define 277 * @return object created 278 */ 279 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 280 public static ScriptObject create(final Object self, final Object proto, final Object props) { 281 if (proto != null) { 282 Global.checkObject(proto); 283 } 284 285 // FIXME: should we create a proper object with correct number of 286 // properties? 287 final ScriptObject newObj = Global.newEmptyInstance(); 288 newObj.setProto((ScriptObject)proto); 289 if (props != UNDEFINED) { 290 NativeObject.defineProperties(self, newObj, props); 291 } 292 293 return newObj; 294 } 295 296 /** 297 * ECMA 15.2.3.6 Object.defineProperty ( O, P, Attributes ) 298 * 299 * @param self self reference 300 * @param obj object in which to define a property 301 * @param prop property to define 302 * @param attr attributes for property descriptor 303 * @return object 304 */ 305 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 306 public static ScriptObject defineProperty(final Object self, final Object obj, final Object prop, final Object attr) { 307 final ScriptObject sobj = Global.checkObject(obj); 308 sobj.defineOwnProperty(JSType.toPropertyKey(prop), attr, true); 309 return sobj; 310 } 311 312 /** 313 * ECMA 5.2.3.7 Object.defineProperties ( O, Properties ) 314 * 315 * @param self self reference 316 * @param obj object in which to define properties 317 * @param props properties 318 * @return object 319 */ 320 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 321 public static ScriptObject defineProperties(final Object self, final Object obj, final Object props) { 322 final ScriptObject sobj = Global.checkObject(obj); 323 final Object propsObj = Global.toObject(props); 324 325 if (propsObj instanceof ScriptObject) { 326 final Object[] keys = ((ScriptObject)propsObj).getOwnKeys(false); 327 for (final Object key : keys) { 328 final String prop = JSType.toString(key); 329 sobj.defineOwnProperty(prop, ((ScriptObject)propsObj).get(prop), true); 330 } 331 } 332 return sobj; 333 } 334 335 /** 336 * ECMA 15.2.3.8 Object.seal ( O ) 337 * 338 * @param self self reference 339 * @param obj object to seal 340 * @return sealed object 341 */ 342 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 343 public static Object seal(final Object self, final Object obj) { 344 if (obj instanceof ScriptObject) { 345 return ((ScriptObject)obj).seal(); 346 } else if (obj instanceof ScriptObjectMirror) { 347 return ((ScriptObjectMirror)obj).seal(); 348 } else { 349 throw notAnObject(obj); 350 } 351 } 352 353 354 /** 355 * ECMA 15.2.3.9 Object.freeze ( O ) 356 * 357 * @param self self reference 358 * @param obj object to freeze 359 * @return frozen object 360 */ 361 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 362 public static Object freeze(final Object self, final Object obj) { 363 if (obj instanceof ScriptObject) { 364 return ((ScriptObject)obj).freeze(); 365 } else if (obj instanceof ScriptObjectMirror) { 366 return ((ScriptObjectMirror)obj).freeze(); 367 } else { 368 throw notAnObject(obj); 369 } 370 } 371 372 /** 373 * ECMA 15.2.3.10 Object.preventExtensions ( O ) 374 * 375 * @param self self reference 376 * @param obj object, for which to set the internal extensible property to false 377 * @return object 378 */ 379 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 380 public static Object preventExtensions(final Object self, final Object obj) { 381 if (obj instanceof ScriptObject) { 382 return ((ScriptObject)obj).preventExtensions(); 383 } else if (obj instanceof ScriptObjectMirror) { 384 return ((ScriptObjectMirror)obj).preventExtensions(); 385 } else { 386 throw notAnObject(obj); 387 } 388 } 389 390 /** 391 * ECMA 15.2.3.11 Object.isSealed ( O ) 392 * 393 * @param self self reference 394 * @param obj check whether an object is sealed 395 * @return true if sealed, false otherwise 396 */ 397 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 398 public static boolean isSealed(final Object self, final Object obj) { 399 if (obj instanceof ScriptObject) { 400 return ((ScriptObject)obj).isSealed(); 401 } else if (obj instanceof ScriptObjectMirror) { 402 return ((ScriptObjectMirror)obj).isSealed(); 403 } else { 404 throw notAnObject(obj); 405 } 406 } 407 408 /** 409 * ECMA 15.2.3.12 Object.isFrozen ( O ) 410 * 411 * @param self self reference 412 * @param obj check whether an object 413 * @return true if object is frozen, false otherwise 414 */ 415 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 416 public static boolean isFrozen(final Object self, final Object obj) { 417 if (obj instanceof ScriptObject) { 418 return ((ScriptObject)obj).isFrozen(); 419 } else if (obj instanceof ScriptObjectMirror) { 420 return ((ScriptObjectMirror)obj).isFrozen(); 421 } else { 422 throw notAnObject(obj); 423 } 424 } 425 426 /** 427 * ECMA 15.2.3.13 Object.isExtensible ( O ) 428 * 429 * @param self self reference 430 * @param obj check whether an object is extensible 431 * @return true if object is extensible, false otherwise 432 */ 433 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 434 public static boolean isExtensible(final Object self, final Object obj) { 435 if (obj instanceof ScriptObject) { 436 return ((ScriptObject)obj).isExtensible(); 437 } else if (obj instanceof ScriptObjectMirror) { 438 return ((ScriptObjectMirror)obj).isExtensible(); 439 } else { 440 throw notAnObject(obj); 441 } 442 } 443 444 /** 445 * ECMA 15.2.3.14 Object.keys ( O ) 446 * 447 * @param self self reference 448 * @param obj object from which to extract keys 449 * @return array of keys in object 450 */ 451 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 452 public static ScriptObject keys(final Object self, final Object obj) { 453 if (obj instanceof ScriptObject) { 454 final ScriptObject sobj = (ScriptObject)obj; 455 return new NativeArray(sobj.getOwnKeys(false)); 456 } else if (obj instanceof ScriptObjectMirror) { 457 final ScriptObjectMirror sobjMirror = (ScriptObjectMirror)obj; 458 return new NativeArray(sobjMirror.getOwnKeys(false)); 459 } else { 460 throw notAnObject(obj); 461 } 462 } 463 464 /** 465 * ECMA 15.2.2.1 , 15.2.1.1 new Object([value]) and Object([value]) 466 * 467 * Constructor 468 * 469 * @param newObj is the new object instantiated with the new operator 470 * @param self self reference 471 * @param value value of object to be instantiated 472 * @return the new NativeObject 473 */ 474 @Constructor 475 public static Object construct(final boolean newObj, final Object self, final Object value) { 476 final JSType type = JSType.ofNoFunction(value); 477 478 // Object(null), Object(undefined), Object() are same as "new Object()" 479 480 if (newObj || type == JSType.NULL || type == JSType.UNDEFINED) { 481 switch (type) { 482 case BOOLEAN: 483 case NUMBER: 484 case STRING: 485 case SYMBOL: 486 return Global.toObject(value); 487 case OBJECT: 488 return value; 489 case NULL: 490 case UNDEFINED: 491 // fall through.. 492 default: 493 break; 494 } 495 496 return Global.newEmptyInstance(); 497 } 498 499 return Global.toObject(value); 500 } 501 502 /** 503 * ECMA 15.2.4.2 Object.prototype.toString ( ) 504 * 505 * @param self self reference 506 * @return ToString of object 507 */ 508 @Function(attributes = Attribute.NOT_ENUMERABLE) 509 public static String toString(final Object self) { 510 return ScriptRuntime.builtinObjectToString(self); 511 } 512 513 /** 514 * ECMA 15.2.4.3 Object.prototype.toLocaleString ( ) 515 * 516 * @param self self reference 517 * @return localized ToString 518 */ 519 @Function(attributes = Attribute.NOT_ENUMERABLE) 520 public static Object toLocaleString(final Object self) { 521 final Object obj = JSType.toScriptObject(self); 522 if (obj instanceof ScriptObject) { 523 final InvokeByName toStringInvoker = getTO_STRING(); 524 final ScriptObject sobj = (ScriptObject)obj; 525 try { 526 final Object toString = toStringInvoker.getGetter().invokeExact(sobj); 527 528 if (Bootstrap.isCallable(toString)) { 529 return toStringInvoker.getInvoker().invokeExact(toString, sobj); 530 } 531 } catch (final RuntimeException | Error e) { 532 throw e; 533 } catch (final Throwable t) { 534 throw new RuntimeException(t); 535 } 536 537 throw typeError("not.a.function", "toString"); 538 } 539 540 return ScriptRuntime.builtinObjectToString(self); 541 } 542 543 /** 544 * ECMA 15.2.4.4 Object.prototype.valueOf ( ) 545 * 546 * @param self self reference 547 * @return value of object 548 */ 549 @Function(attributes = Attribute.NOT_ENUMERABLE) 550 public static Object valueOf(final Object self) { 551 return Global.toObject(self); 552 } 553 554 /** 555 * ECMA 15.2.4.5 Object.prototype.hasOwnProperty (V) 556 * 557 * @param self self reference 558 * @param v property to check for 559 * @return true if property exists in object 560 */ 561 @Function(attributes = Attribute.NOT_ENUMERABLE) 562 public static boolean hasOwnProperty(final Object self, final Object v) { 563 // Convert ScriptObjects to primitive with String.class hint 564 // but no need to convert other primitives to string. 565 final Object key = JSType.toPrimitive(v, String.class); 566 final Object obj = Global.toObject(self); 567 568 return obj instanceof ScriptObject && ((ScriptObject)obj).hasOwnProperty(key); 569 } 570 571 /** 572 * ECMA 15.2.4.6 Object.prototype.isPrototypeOf (V) 573 * 574 * @param self self reference 575 * @param v v prototype object to check against 576 * @return true if object is prototype of v 577 */ 578 @Function(attributes = Attribute.NOT_ENUMERABLE) 579 public static boolean isPrototypeOf(final Object self, final Object v) { 580 if (!(v instanceof ScriptObject)) { 581 return false; 582 } 583 584 final Object obj = Global.toObject(self); 585 ScriptObject proto = (ScriptObject)v; 586 587 do { 588 proto = proto.getProto(); 589 if (proto == obj) { 590 return true; 591 } 592 } while (proto != null); 593 594 return false; 595 } 596 597 /** 598 * ECMA 15.2.4.7 Object.prototype.propertyIsEnumerable (V) 599 * 600 * @param self self reference 601 * @param v property to check if enumerable 602 * @return true if property is enumerable 603 */ 604 @Function(attributes = Attribute.NOT_ENUMERABLE) 605 public static boolean propertyIsEnumerable(final Object self, final Object v) { 606 final String str = JSType.toString(v); 607 final Object obj = Global.toObject(self); 608 609 if (obj instanceof ScriptObject) { 610 final jdk.nashorn.internal.runtime.Property property = ((ScriptObject)obj).getMap().findProperty(str); 611 return property != null && property.isEnumerable(); 612 } 613 614 return false; 615 } 616 617 /** 618 * Nashorn extension: Object.bindProperties 619 * 620 * Binds the source object's properties to the target object. Binding 621 * properties allows two-way read/write for the properties of the source object. 622 * 623 * Example: 624 * <pre> 625 * var obj = { x: 34, y: 100 }; 626 * var foo = {} 627 * 628 * // bind properties of "obj" to "foo" object 629 * Object.bindProperties(foo, obj); 630 * 631 * // now, we can access/write on 'foo' properties 632 * print(foo.x); // prints obj.x which is 34 633 * 634 * // update obj.x via foo.x 635 * foo.x = "hello"; 636 * print(obj.x); // prints "hello" now 637 * 638 * obj.x = 42; // foo.x also becomes 42 639 * print(foo.x); // prints 42 640 * </pre> 641 * <p> 642 * The source object bound can be a ScriptObject or a ScriptOjectMirror. 643 * null or undefined source object results in TypeError being thrown. 644 * </p> 645 * Example: 646 * <pre> 647 * var obj = loadWithNewGlobal({ 648 * name: "test", 649 * script: "obj = { x: 33, y: 'hello' }" 650 * }); 651 * 652 * // bind 'obj's properties to global scope 'this' 653 * Object.bindProperties(this, obj); 654 * print(x); // prints 33 655 * print(y); // prints "hello" 656 * x = Math.PI; // changes obj.x to Math.PI 657 * print(obj.x); // prints Math.PI 658 * </pre> 659 * 660 * Limitations of property binding: 661 * <ul> 662 * <li> Only enumerable, immediate (not proto inherited) properties of the source object are bound. 663 * <li> If the target object already contains a property called "foo", the source's "foo" is skipped (not bound). 664 * <li> Properties added to the source object after binding to the target are not bound. 665 * <li> Property configuration changes on the source object (or on the target) is not propagated. 666 * <li> Delete of property on the target (or the source) is not propagated - 667 * only the property value is set to 'undefined' if the property happens to be a data property. 668 * </ul> 669 * <p> 670 * It is recommended that the bound properties be treated as non-configurable 671 * properties to avoid surprises. 672 * </p> 673 * 674 * @param self self reference 675 * @param target the target object to which the source object's properties are bound 676 * @param source the source object whose properties are bound to the target 677 * @return the target object after property binding 678 */ 679 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 680 public static Object bindProperties(final Object self, final Object target, final Object source) { 681 // target object has to be a ScriptObject 682 final ScriptObject targetObj = Global.checkObject(target); 683 // check null or undefined source object 684 Global.checkObjectCoercible(source); 685 686 if (source instanceof ScriptObject) { 687 final ScriptObject sourceObj = (ScriptObject)source; 688 689 final PropertyMap sourceMap = sourceObj.getMap(); 690 final Property[] properties = sourceMap.getProperties(); 691 //replace the map and blow up everything to objects to work with dual fields :-( 692 693 // filter non-enumerable properties 694 final ArrayList<Property> propList = new ArrayList<>(); 695 for (final Property prop : properties) { 696 if (prop.isEnumerable()) { 697 final Object value = sourceObj.get(prop.getKey()); 698 prop.setType(Object.class); 699 prop.setValue(sourceObj, sourceObj, value, false); 700 propList.add(prop); 701 } 702 } 703 704 if (!propList.isEmpty()) { 705 targetObj.addBoundProperties(sourceObj, propList.toArray(new Property[propList.size()])); 706 } 707 } else if (source instanceof ScriptObjectMirror) { 708 // get enumerable, immediate properties of mirror 709 final ScriptObjectMirror mirror = (ScriptObjectMirror)source; 710 final String[] keys = mirror.getOwnKeys(false); 711 if (keys.length == 0) { 712 // nothing to bind 713 return target; 714 } 715 716 // make accessor properties using dynamic invoker getters and setters 717 final AccessorProperty[] props = new AccessorProperty[keys.length]; 718 for (int idx = 0; idx < keys.length; idx++) { 719 props[idx] = createAccessorProperty(keys[idx]); 720 } 721 722 targetObj.addBoundProperties(source, props); 723 } else if (source instanceof StaticClass) { 724 final Class<?> clazz = ((StaticClass)source).getRepresentedClass(); 725 Bootstrap.checkReflectionAccess(clazz, true); 726 bindBeanProperties(targetObj, source, BeansLinker.getReadableStaticPropertyNames(clazz), 727 BeansLinker.getWritableStaticPropertyNames(clazz), BeansLinker.getStaticMethodNames(clazz)); 728 } else { 729 final Class<?> clazz = source.getClass(); 730 Bootstrap.checkReflectionAccess(clazz, false); 731 bindBeanProperties(targetObj, source, BeansLinker.getReadableInstancePropertyNames(clazz), 732 BeansLinker.getWritableInstancePropertyNames(clazz), BeansLinker.getInstanceMethodNames(clazz)); 733 } 734 735 return target; 736 } 737 738 private static AccessorProperty createAccessorProperty(final String name) { 739 final MethodHandle getter = Bootstrap.createDynamicInvoker(name, NashornCallSiteDescriptor.GET_METHOD_PROPERTY, MIRROR_GETTER_TYPE); 740 final MethodHandle setter = Bootstrap.createDynamicInvoker(name, NashornCallSiteDescriptor.SET_PROPERTY, MIRROR_SETTER_TYPE); 741 return AccessorProperty.create(name, 0, getter, setter); 742 } 743 744 /** 745 * Binds the source mirror object's properties to the target object. Binding 746 * properties allows two-way read/write for the properties of the source object. 747 * All inherited, enumerable properties are also bound. This method is used to 748 * to make 'with' statement work with ScriptObjectMirror as scope object. 749 * 750 * @param target the target object to which the source object's properties are bound 751 * @param source the source object whose properties are bound to the target 752 * @return the target object after property binding 753 */ 754 public static Object bindAllProperties(final ScriptObject target, final ScriptObjectMirror source) { 755 final Set<String> keys = source.keySet(); 756 // make accessor properties using dynamic invoker getters and setters 757 final AccessorProperty[] props = new AccessorProperty[keys.size()]; 758 int idx = 0; 759 for (final String name : keys) { 760 props[idx] = createAccessorProperty(name); 761 idx++; 762 } 763 764 target.addBoundProperties(source, props); 765 return target; 766 } 767 768 private static void bindBeanProperties(final ScriptObject targetObj, final Object source, 769 final Collection<String> readablePropertyNames, final Collection<String> writablePropertyNames, 770 final Collection<String> methodNames) { 771 final Set<String> propertyNames = new HashSet<>(readablePropertyNames); 772 propertyNames.addAll(writablePropertyNames); 773 774 final Class<?> clazz = source.getClass(); 775 776 final MethodType getterType = MethodType.methodType(Object.class, clazz); 777 final MethodType setterType = MethodType.methodType(Object.class, clazz, Object.class); 778 779 final GuardingDynamicLinker linker = Bootstrap.getBeanLinkerForClass(clazz); 780 781 final List<AccessorProperty> properties = new ArrayList<>(propertyNames.size() + methodNames.size()); 782 for(final String methodName: methodNames) { 783 final MethodHandle method; 784 try { 785 method = getBeanOperation(linker, StandardOperation.GET_METHOD, methodName, getterType, source); 786 } catch(final IllegalAccessError e) { 787 // Presumably, this was a caller sensitive method. Ignore it and carry on. 788 continue; 789 } 790 properties.add(AccessorProperty.create(methodName, Property.NOT_WRITABLE, getBoundBeanMethodGetter(source, 791 method), Lookup.EMPTY_SETTER)); 792 } 793 for(final String propertyName: propertyNames) { 794 MethodHandle getter; 795 if(readablePropertyNames.contains(propertyName)) { 796 try { 797 getter = getBeanOperation(linker, StandardOperation.GET_PROPERTY, propertyName, getterType, source); 798 } catch(final IllegalAccessError e) { 799 // Presumably, this was a caller sensitive method. Ignore it and carry on. 800 getter = Lookup.EMPTY_GETTER; 801 } 802 } else { 803 getter = Lookup.EMPTY_GETTER; 804 } 805 final boolean isWritable = writablePropertyNames.contains(propertyName); 806 MethodHandle setter; 807 if(isWritable) { 808 try { 809 setter = getBeanOperation(linker, StandardOperation.SET_PROPERTY, propertyName, setterType, source); 810 } catch(final IllegalAccessError e) { 811 // Presumably, this was a caller sensitive method. Ignore it and carry on. 812 setter = Lookup.EMPTY_SETTER; 813 } 814 } else { 815 setter = Lookup.EMPTY_SETTER; 816 } 817 if(getter != Lookup.EMPTY_GETTER || setter != Lookup.EMPTY_SETTER) { 818 properties.add(AccessorProperty.create(propertyName, isWritable ? 0 : Property.NOT_WRITABLE, getter, setter)); 819 } 820 } 821 822 targetObj.addBoundProperties(source, properties.toArray(new AccessorProperty[properties.size()])); 823 } 824 825 private static MethodHandle getBoundBeanMethodGetter(final Object source, final MethodHandle methodGetter) { 826 try { 827 // NOTE: we're relying on the fact that StandardOperation.GET_METHOD return value is constant for any given method 828 // name and object linked with BeansLinker. (Actually, an even stronger assumption is true: return value is 829 // constant for any given method name and object's class.) 830 return MethodHandles.dropArguments(MethodHandles.constant(Object.class, 831 Bootstrap.bindCallable(methodGetter.invoke(source), source, null)), 0, Object.class); 832 } catch(RuntimeException|Error e) { 833 throw e; 834 } catch(final Throwable t) { 835 throw new RuntimeException(t); 836 } 837 } 838 839 private static MethodHandle getBeanOperation(final GuardingDynamicLinker linker, final StandardOperation operation, 840 final String name, final MethodType methodType, final Object source) { 841 final GuardedInvocation inv; 842 try { 843 inv = NashornBeansLinker.getGuardedInvocation(linker, createLinkRequest(new NamedOperation(operation, name), methodType, source), Bootstrap.getLinkerServices()); 844 assert passesGuard(source, inv.getGuard()); 845 } catch(RuntimeException|Error e) { 846 throw e; 847 } catch(final Throwable t) { 848 throw new RuntimeException(t); 849 } 850 assert inv.getSwitchPoints() == null; // Linkers in Dynalink's beans package don't use switchpoints. 851 // We discard the guard, as all method handles will be bound to a specific object. 852 return inv.getInvocation(); 853 } 854 855 private static boolean passesGuard(final Object obj, final MethodHandle guard) throws Throwable { 856 return guard == null || (boolean)guard.invoke(obj); 857 } 858 859 private static LinkRequest createLinkRequest(final Operation operation, final MethodType methodType, final Object source) { 860 return new SimpleLinkRequest(new CallSiteDescriptor(MethodHandles.publicLookup(), operation, 861 methodType), false, source); 862 } 863 864 private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) { 865 return MH.findStatic(MethodHandles.lookup(), NativeObject.class, name, MH.type(rtype, types)); 866 } 867} 868