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