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