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