AccessorProperty.java revision 1002:2f0161551858
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.runtime; 27 28import static jdk.nashorn.internal.codegen.ObjectClassGenerator.OBJECT_FIELDS_ONLY; 29import static jdk.nashorn.internal.codegen.ObjectClassGenerator.PRIMITIVE_FIELD_TYPE; 30import static jdk.nashorn.internal.codegen.ObjectClassGenerator.createGetter; 31import static jdk.nashorn.internal.codegen.ObjectClassGenerator.createSetter; 32import static jdk.nashorn.internal.codegen.ObjectClassGenerator.getFieldCount; 33import static jdk.nashorn.internal.codegen.ObjectClassGenerator.getFieldName; 34import static jdk.nashorn.internal.lookup.Lookup.MH; 35import static jdk.nashorn.internal.lookup.MethodHandleFactory.stripName; 36import static jdk.nashorn.internal.runtime.JSType.getAccessorTypeIndex; 37import static jdk.nashorn.internal.runtime.JSType.getNumberOfAccessorTypes; 38import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT; 39 40import java.io.IOException; 41import java.io.ObjectInputStream; 42import java.lang.invoke.MethodHandle; 43import java.lang.invoke.MethodHandles; 44import java.lang.invoke.SwitchPoint; 45import java.util.function.Supplier; 46import java.util.logging.Level; 47import jdk.nashorn.internal.codegen.ObjectClassGenerator; 48import jdk.nashorn.internal.codegen.types.Type; 49import jdk.nashorn.internal.lookup.Lookup; 50import jdk.nashorn.internal.objects.Global; 51 52/** 53 * An AccessorProperty is the most generic property type. An AccessorProperty is 54 * represented as fields in a ScriptObject class. 55 */ 56public class AccessorProperty extends Property { 57 private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); 58 59 private static final MethodHandle REPLACE_MAP = findOwnMH_S("replaceMap", Object.class, Object.class, PropertyMap.class); 60 private static final MethodHandle INVALIDATE_SP = findOwnMH_S("invalidateSwitchPoint", Object.class, Object.class, SwitchPoint.class); 61 62 private static final SwitchPoint NO_CHANGE_CALLBACK = new SwitchPoint(); 63 64 private static final int NOOF_TYPES = getNumberOfAccessorTypes(); 65 private static final long serialVersionUID = 3371720170182154920L; 66 67 /** 68 * Properties in different maps for the same structure class will share their field getters and setters. This could 69 * be further extended to other method handles that are looked up in the AccessorProperty constructor, but right now 70 * these are the most frequently retrieved ones, and lookup of method handle natives only registers in the profiler 71 * for them. 72 */ 73 private static ClassValue<Accessors> GETTERS_SETTERS = new ClassValue<Accessors>() { 74 @Override 75 protected Accessors computeValue(final Class<?> structure) { 76 return new Accessors(structure); 77 } 78 }; 79 80 private static class Accessors { 81 final MethodHandle[] objectGetters; 82 final MethodHandle[] objectSetters; 83 final MethodHandle[] primitiveGetters; 84 final MethodHandle[] primitiveSetters; 85 86 /** 87 * Normal 88 * @param structure 89 */ 90 Accessors(final Class<?> structure) { 91 final int fieldCount = getFieldCount(structure); 92 objectGetters = new MethodHandle[fieldCount]; 93 objectSetters = new MethodHandle[fieldCount]; 94 primitiveGetters = new MethodHandle[fieldCount]; 95 primitiveSetters = new MethodHandle[fieldCount]; 96 97 for (int i = 0; i < fieldCount; i++) { 98 final String fieldName = getFieldName(i, Type.OBJECT); 99 final Class<?> typeClass = Type.OBJECT.getTypeClass(); 100 objectGetters[i] = MH.asType(MH.getter(LOOKUP, structure, fieldName, typeClass), Lookup.GET_OBJECT_TYPE); 101 objectSetters[i] = MH.asType(MH.setter(LOOKUP, structure, fieldName, typeClass), Lookup.SET_OBJECT_TYPE); 102 } 103 104 if (!OBJECT_FIELDS_ONLY) { 105 for (int i = 0; i < fieldCount; i++) { 106 final String fieldNamePrimitive = getFieldName(i, PRIMITIVE_FIELD_TYPE); 107 final Class<?> typeClass = PRIMITIVE_FIELD_TYPE.getTypeClass(); 108 primitiveGetters[i] = MH.asType(MH.getter(LOOKUP, structure, fieldNamePrimitive, typeClass), Lookup.GET_PRIMITIVE_TYPE); 109 primitiveSetters[i] = MH.asType(MH.setter(LOOKUP, structure, fieldNamePrimitive, typeClass), Lookup.SET_PRIMITIVE_TYPE); 110 } 111 } 112 } 113 } 114 115 /** 116 * Property getter cache 117 * Note that we can't do the same simple caching for optimistic getters, 118 * due to the fact that they are bound to a program point, which will 119 * produce different boun method handles wrapping the same access mechanism 120 * depending on callsite 121 */ 122 private MethodHandle[] GETTER_CACHE = new MethodHandle[NOOF_TYPES]; 123 124 /** 125 * Create a new accessor property. Factory method used by nasgen generated code. 126 * 127 * @param key {@link Property} key. 128 * @param propertyFlags {@link Property} flags. 129 * @param getter {@link Property} get accessor method. 130 * @param setter {@link Property} set accessor method. 131 * 132 * @return New {@link AccessorProperty} created. 133 */ 134 public static AccessorProperty create(final String key, final int propertyFlags, final MethodHandle getter, final MethodHandle setter) { 135 return new AccessorProperty(key, propertyFlags, -1, getter, setter); 136 } 137 138 /** Seed getter for the primitive version of this field (in -Dnashorn.fields.dual=true mode) */ 139 transient MethodHandle primitiveGetter; 140 141 /** Seed setter for the primitive version of this field (in -Dnashorn.fields.dual=true mode) */ 142 transient MethodHandle primitiveSetter; 143 144 /** Seed getter for the Object version of this field */ 145 transient MethodHandle objectGetter; 146 147 /** Seed setter for the Object version of this field */ 148 transient MethodHandle objectSetter; 149 150 /** 151 * Current type of this object, in object only mode, this is an Object.class. In dual-fields mode 152 * null means undefined, and primitive types are allowed. The reason a special type is used for 153 * undefined, is that are no bits left to represent it in primitive types 154 */ 155 private Class<?> currentType; 156 157 /** 158 * Delegate constructor for bound properties. This is used for properties created by 159 * {@link ScriptRuntime#mergeScope} and the Nashorn {@code Object.bindProperties} method. 160 * The former is used to add a script's defined globals to the current global scope while 161 * still storing them in a JO-prefixed ScriptObject class. 162 * 163 * <p>All properties created by this constructor have the {@link #IS_BOUND} flag set.</p> 164 * 165 * @param property accessor property to rebind 166 * @param delegate delegate object to rebind receiver to 167 */ 168 AccessorProperty(final AccessorProperty property, final Object delegate) { 169 super(property, property.getFlags() | IS_BOUND); 170 171 this.primitiveGetter = bindTo(property.primitiveGetter, delegate); 172 this.primitiveSetter = bindTo(property.primitiveSetter, delegate); 173 this.objectGetter = bindTo(property.objectGetter, delegate); 174 this.objectSetter = bindTo(property.objectSetter, delegate); 175 property.GETTER_CACHE = new MethodHandle[NOOF_TYPES]; 176 // Properties created this way are bound to a delegate 177 setCurrentType(property.getCurrentType()); 178 } 179 180 /** 181 * SPILL PROPERTY or USER ACCESSOR PROPERTY abstract constructor 182 * 183 * Constructor for spill properties. Array getters and setters will be created on demand. 184 * 185 * @param key the property key 186 * @param flags the property flags 187 * @param slot spill slot 188 * @param primitiveGetter primitive getter 189 * @param primitiveSetter primitive setter 190 * @param objectGetter object getter 191 * @param objectSetter object setter 192 */ 193 protected AccessorProperty( 194 final String key, 195 final int flags, 196 final int slot, 197 final MethodHandle primitiveGetter, 198 final MethodHandle primitiveSetter, 199 final MethodHandle objectGetter, 200 final MethodHandle objectSetter) { 201 super(key, flags, slot); 202 assert getClass() != AccessorProperty.class; 203 this.primitiveGetter = primitiveGetter; 204 this.primitiveSetter = primitiveSetter; 205 this.objectGetter = objectGetter; 206 this.objectSetter = objectSetter; 207 initializeType(); 208 } 209 210 /** 211 * NASGEN constructor 212 * 213 * Constructor. Similar to the constructor with both primitive getters and setters, the difference 214 * here being that only one getter and setter (setter is optional for non writable fields) is given 215 * to the constructor, and the rest are created from those. Used e.g. by Nasgen classes 216 * 217 * @param key the property key 218 * @param flags the property flags 219 * @param slot the property field number or spill slot 220 * @param getter the property getter 221 * @param setter the property setter or null if non writable, non configurable 222 */ 223 private AccessorProperty(final String key, final int flags, final int slot, final MethodHandle getter, final MethodHandle setter) { 224 super(key, flags | (getter.type().returnType().isPrimitive() ? IS_NASGEN_PRIMITIVE : 0), slot); 225 assert !isSpill(); 226 227 // we don't need to prep the setters these will never be invalidated as this is a nasgen 228 // or known type getter/setter. No invalidations will take place 229 230 final Class<?> getterType = getter.type().returnType(); 231 final Class<?> setterType = setter == null ? null : setter.type().parameterType(1); 232 233 assert setterType == null || setterType == getterType; 234 if (OBJECT_FIELDS_ONLY) { 235 primitiveGetter = primitiveSetter = null; 236 } else { 237 if (getterType == int.class || getterType == long.class) { 238 primitiveGetter = MH.asType(getter, Lookup.GET_PRIMITIVE_TYPE); 239 primitiveSetter = setter == null ? null : MH.asType(setter, Lookup.SET_PRIMITIVE_TYPE); 240 } else if (getterType == double.class) { 241 primitiveGetter = MH.asType(MH.filterReturnValue(getter, ObjectClassGenerator.PACK_DOUBLE), Lookup.GET_PRIMITIVE_TYPE); 242 primitiveSetter = setter == null ? null : MH.asType(MH.filterArguments(setter, 1, ObjectClassGenerator.UNPACK_DOUBLE), Lookup.SET_PRIMITIVE_TYPE); 243 } else { 244 primitiveGetter = primitiveSetter = null; 245 } 246 } 247 248 assert primitiveGetter == null || primitiveGetter.type() == Lookup.GET_PRIMITIVE_TYPE : primitiveGetter + "!=" + Lookup.GET_PRIMITIVE_TYPE; 249 assert primitiveSetter == null || primitiveSetter.type() == Lookup.SET_PRIMITIVE_TYPE : primitiveSetter; 250 251 objectGetter = getter.type() != Lookup.GET_OBJECT_TYPE ? MH.asType(getter, Lookup.GET_OBJECT_TYPE) : getter; 252 objectSetter = setter != null && setter.type() != Lookup.SET_OBJECT_TYPE ? MH.asType(setter, Lookup.SET_OBJECT_TYPE) : setter; 253 254 setCurrentType(OBJECT_FIELDS_ONLY ? Object.class : getterType); 255 } 256 257 /** 258 * Normal ACCESS PROPERTY constructor given a structure class. 259 * Constructor for dual field AccessorPropertys. 260 * 261 * @param key property key 262 * @param flags property flags 263 * @param structure structure for objects associated with this property 264 * @param slot property field number or spill slot 265 */ 266 public AccessorProperty(final String key, final int flags, final Class<?> structure, final int slot) { 267 super(key, flags, slot); 268 269 initGetterSetter(structure); 270 initializeType(); 271 } 272 273 private void initGetterSetter(final Class<?> structure) { 274 final int slot = getSlot(); 275 /* 276 * primitiveGetter and primitiveSetter are only used in dual fields mode. Setting them to null also 277 * works in dual field mode, it only means that the property never has a primitive 278 * representation. 279 */ 280 281 if (isParameter() && hasArguments()) { 282 //parameters are always stored in an object array, which may or may not be a good idea 283 final MethodHandle arguments = MH.getter(LOOKUP, structure, "arguments", ScriptObject.class); 284 objectGetter = MH.asType(MH.insertArguments(MH.filterArguments(ScriptObject.GET_ARGUMENT.methodHandle(), 0, arguments), 1, slot), Lookup.GET_OBJECT_TYPE); 285 objectSetter = MH.asType(MH.insertArguments(MH.filterArguments(ScriptObject.SET_ARGUMENT.methodHandle(), 0, arguments), 1, slot), Lookup.SET_OBJECT_TYPE); 286 primitiveGetter = null; 287 primitiveSetter = null; 288 } else { 289 final Accessors gs = GETTERS_SETTERS.get(structure); 290 objectGetter = gs.objectGetters[slot]; 291 primitiveGetter = gs.primitiveGetters[slot]; 292 objectSetter = gs.objectSetters[slot]; 293 primitiveSetter = gs.primitiveSetters[slot]; 294 } 295 } 296 297 /** 298 * Constructor 299 * 300 * @param key key 301 * @param flags flags 302 * @param slot field slot index 303 * @param owner owner of property 304 * @param initialValue initial value to which the property can be set 305 */ 306 protected AccessorProperty(final String key, final int flags, final int slot, final ScriptObject owner, final Object initialValue) { 307 this(key, flags, owner.getClass(), slot); 308 setInitialValue(owner, initialValue); 309 } 310 311 /** 312 * Normal access property constructor that overrides the type 313 * Override the initial type. Used for Object Literals 314 * 315 * @param key key 316 * @param flags flags 317 * @param structure structure to JO subclass 318 * @param slot field slot index 319 * @param initialType initial type of the property 320 */ 321 public AccessorProperty(final String key, final int flags, final Class<?> structure, final int slot, final Class<?> initialType) { 322 this(key, flags, structure, slot); 323 setCurrentType(OBJECT_FIELDS_ONLY ? Object.class : initialType); 324 } 325 326 /** 327 * Copy constructor that may change type and in that case clear the cache. Important to do that before 328 * type change or getters will be created already stale. 329 * 330 * @param property property 331 * @param newType new type 332 */ 333 protected AccessorProperty(final AccessorProperty property, final Class<?> newType) { 334 super(property, property.getFlags()); 335 336 this.GETTER_CACHE = newType != property.getCurrentType() ? new MethodHandle[NOOF_TYPES] : property.GETTER_CACHE; 337 this.primitiveGetter = property.primitiveGetter; 338 this.primitiveSetter = property.primitiveSetter; 339 this.objectGetter = property.objectGetter; 340 this.objectSetter = property.objectSetter; 341 342 setCurrentType(newType); 343 } 344 345 /** 346 * COPY constructor 347 * 348 * @param property source property 349 */ 350 protected AccessorProperty(final AccessorProperty property) { 351 this(property, property.getCurrentType()); 352 } 353 354 /** 355 * Set initial value of a script object's property 356 * @param owner owner 357 * @param initialValue initial value 358 */ 359 protected final void setInitialValue(final ScriptObject owner, final Object initialValue) { 360 setCurrentType(JSType.unboxedFieldType(initialValue)); 361 if (initialValue instanceof Integer) { 362 invokeSetter(owner, ((Integer)initialValue).intValue()); 363 } else if (initialValue instanceof Long) { 364 invokeSetter(owner, ((Long)initialValue).longValue()); 365 } else if (initialValue instanceof Double) { 366 invokeSetter(owner, ((Double)initialValue).doubleValue()); 367 } else { 368 invokeSetter(owner, initialValue); 369 } 370 } 371 372 /** 373 * Initialize the type of a property 374 */ 375 protected final void initializeType() { 376 setCurrentType(OBJECT_FIELDS_ONLY ? Object.class : null); 377 } 378 379 private void readObject(final ObjectInputStream s) throws IOException, ClassNotFoundException { 380 s.defaultReadObject(); 381 // Restore getters array 382 GETTER_CACHE = new MethodHandle[NOOF_TYPES]; 383 } 384 385 private static MethodHandle bindTo(final MethodHandle mh, final Object receiver) { 386 if (mh == null) { 387 return null; 388 } 389 390 return MH.dropArguments(MH.bindTo(mh, receiver), 0, Object.class); 391 } 392 393 @Override 394 public Property copy() { 395 return new AccessorProperty(this); 396 } 397 398 @Override 399 public Property copy(final Class<?> newType) { 400 return new AccessorProperty(this, newType); 401 } 402 403 @Override 404 public int getIntValue(final ScriptObject self, final ScriptObject owner) { 405 try { 406 return (int)getGetter(int.class).invokeExact((Object)self); 407 } catch (final Error | RuntimeException e) { 408 throw e; 409 } catch (final Throwable e) { 410 throw new RuntimeException(e); 411 } 412 } 413 414 @Override 415 public long getLongValue(final ScriptObject self, final ScriptObject owner) { 416 try { 417 return (long)getGetter(long.class).invokeExact((Object)self); 418 } catch (final Error | RuntimeException e) { 419 throw e; 420 } catch (final Throwable e) { 421 throw new RuntimeException(e); 422 } 423 } 424 425 @Override 426 public double getDoubleValue(final ScriptObject self, final ScriptObject owner) { 427 try { 428 return (double)getGetter(double.class).invokeExact((Object)self); 429 } catch (final Error | RuntimeException e) { 430 throw e; 431 } catch (final Throwable e) { 432 throw new RuntimeException(e); 433 } 434 } 435 436 @Override 437 public Object getObjectValue(final ScriptObject self, final ScriptObject owner) { 438 try { 439 return getGetter(Object.class).invokeExact((Object)self); 440 } catch (final Error | RuntimeException e) { 441 throw e; 442 } catch (final Throwable e) { 443 throw new RuntimeException(e); 444 } 445 } 446 447 /** 448 * Invoke setter for this property with a value 449 * @param self owner 450 * @param value value 451 */ 452 protected final void invokeSetter(final ScriptObject self, final int value) { 453 try { 454 getSetter(int.class, self.getMap()).invokeExact((Object)self, value); 455 } catch (final Error | RuntimeException e) { 456 throw e; 457 } catch (final Throwable e) { 458 throw new RuntimeException(e); 459 } 460 } 461 462 /** 463 * Invoke setter for this property with a value 464 * @param self owner 465 * @param value value 466 */ 467 protected final void invokeSetter(final ScriptObject self, final long value) { 468 try { 469 getSetter(long.class, self.getMap()).invokeExact((Object)self, value); 470 } catch (final Error | RuntimeException e) { 471 throw e; 472 } catch (final Throwable e) { 473 throw new RuntimeException(e); 474 } 475 } 476 477 /** 478 * Invoke setter for this property with a value 479 * @param self owner 480 * @param value value 481 */ 482 protected final void invokeSetter(final ScriptObject self, final double value) { 483 try { 484 getSetter(double.class, self.getMap()).invokeExact((Object)self, value); 485 } catch (final Error | RuntimeException e) { 486 throw e; 487 } catch (final Throwable e) { 488 throw new RuntimeException(e); 489 } 490 } 491 492 /** 493 * Invoke setter for this property with a value 494 * @param self owner 495 * @param value value 496 */ 497 protected final void invokeSetter(final ScriptObject self, final Object value) { 498 try { 499 getSetter(Object.class, self.getMap()).invokeExact((Object)self, value); 500 } catch (final Error | RuntimeException e) { 501 throw e; 502 } catch (final Throwable e) { 503 throw new RuntimeException(e); 504 } 505 } 506 507 @Override 508 public void setValue(final ScriptObject self, final ScriptObject owner, final int value, final boolean strict) { 509 assert isConfigurable() || isWritable() : getKey() + " is not writable or configurable"; 510 invokeSetter(self, value); 511 } 512 513 @Override 514 public void setValue(final ScriptObject self, final ScriptObject owner, final long value, final boolean strict) { 515 assert isConfigurable() || isWritable() : getKey() + " is not writable or configurable"; 516 invokeSetter(self, value); 517 } 518 519 @Override 520 public void setValue(final ScriptObject self, final ScriptObject owner, final double value, final boolean strict) { 521 assert isConfigurable() || isWritable() : getKey() + " is not writable or configurable"; 522 invokeSetter(self, value); 523 } 524 525 @Override 526 public void setValue(final ScriptObject self, final ScriptObject owner, final Object value, final boolean strict) { 527 //this is sometimes used for bootstrapping, hence no assert. ugly. 528 invokeSetter(self, value); 529 } 530 531 @Override 532 void initMethodHandles(final Class<?> structure) { 533 // sanity check for structure class 534 if (!ScriptObject.class.isAssignableFrom(structure) || !StructureLoader.isStructureClass(structure.getName())) { 535 throw new IllegalArgumentException(); 536 } 537 // this method is overridden in SpillProperty 538 assert !isSpill(); 539 initGetterSetter(structure); 540 } 541 542 @Override 543 public MethodHandle getGetter(final Class<?> type) { 544 final int i = getAccessorTypeIndex(type); 545 546 assert type == int.class || 547 type == long.class || 548 type == double.class || 549 type == Object.class : 550 "invalid getter type " + type + " for " + getKey(); 551 552 checkUndeclared(); 553 554 //all this does is add a return value filter for object fields only 555 final MethodHandle[] getterCache = GETTER_CACHE; 556 final MethodHandle cachedGetter = getterCache[i]; 557 final MethodHandle getter; 558 if (cachedGetter != null) { 559 getter = cachedGetter; 560 } else { 561 getter = debug( 562 createGetter( 563 getCurrentType(), 564 type, 565 primitiveGetter, 566 objectGetter, 567 INVALID_PROGRAM_POINT), 568 getCurrentType(), 569 type, 570 "get"); 571 getterCache[i] = getter; 572 } 573 assert getter.type().returnType() == type && getter.type().parameterType(0) == Object.class; 574 return getter; 575 } 576 577 @Override 578 public MethodHandle getOptimisticGetter(final Class<?> type, final int programPoint) { 579 // nasgen generated primitive fields like Math.PI have only one known unchangeable primitive type 580 if (objectGetter == null) { 581 return getOptimisticPrimitiveGetter(type, programPoint); 582 } 583 584 checkUndeclared(); 585 586 return debug( 587 createGetter( 588 getCurrentType(), 589 type, 590 primitiveGetter, 591 objectGetter, 592 programPoint), 593 getCurrentType(), 594 type, 595 "get"); 596 } 597 598 private MethodHandle getOptimisticPrimitiveGetter(final Class<?> type, final int programPoint) { 599 final MethodHandle g = getGetter(getCurrentType()); 600 return MH.asType(OptimisticReturnFilters.filterOptimisticReturnValue(g, type, programPoint), g.type().changeReturnType(type)); 601 } 602 603 private Property getWiderProperty(final Class<?> type) { 604 return copy(type); //invalidate cache of new property 605 606 } 607 608 private PropertyMap getWiderMap(final PropertyMap oldMap, final Property newProperty) { 609 final PropertyMap newMap = oldMap.replaceProperty(this, newProperty); 610 assert oldMap.size() > 0; 611 assert newMap.size() == oldMap.size(); 612 return newMap; 613 } 614 615 private void checkUndeclared() { 616 if ((getFlags() & NEEDS_DECLARATION) != 0) { 617 // a lexically defined variable that hasn't seen its declaration - throw ReferenceError 618 throw ECMAErrors.referenceError("not.defined", getKey()); 619 } 620 } 621 622 // the final three arguments are for debug printout purposes only 623 @SuppressWarnings("unused") 624 private static Object replaceMap(final Object sobj, final PropertyMap newMap) { 625 ((ScriptObject)sobj).setMap(newMap); 626 return sobj; 627 } 628 629 @SuppressWarnings("unused") 630 private static Object invalidateSwitchPoint(final Object obj, final SwitchPoint sp) { 631 SwitchPoint.invalidateAll(new SwitchPoint[] { sp }); 632 return obj; 633 } 634 635 private MethodHandle generateSetter(final Class<?> forType, final Class<?> type) { 636 return debug(createSetter(forType, type, primitiveSetter, objectSetter), getCurrentType(), type, "set"); 637 } 638 639 /** 640 * Is this property of the undefined type? 641 * @return true if undefined 642 */ 643 protected final boolean isUndefined() { 644 return getCurrentType() == null; 645 } 646 647 @Override 648 public MethodHandle getSetter(final Class<?> type, final PropertyMap currentMap) { 649 checkUndeclared(); 650 651 final int typeIndex = getAccessorTypeIndex(type); 652 final int currentTypeIndex = getAccessorTypeIndex(getCurrentType()); 653 654 //if we are asking for an object setter, but are still a primitive type, we might try to box it 655 MethodHandle mh; 656 if (needsInvalidator(typeIndex, currentTypeIndex)) { 657 final Property newProperty = getWiderProperty(type); 658 final PropertyMap newMap = getWiderMap(currentMap, newProperty); 659 660 final MethodHandle widerSetter = newProperty.getSetter(type, newMap); 661 final Class<?> ct = getCurrentType(); 662 mh = MH.filterArguments(widerSetter, 0, MH.insertArguments(debugReplace(ct, type, currentMap, newMap) , 1, newMap)); 663 if (ct != null && ct.isPrimitive() && !type.isPrimitive()) { 664 mh = ObjectClassGenerator.createGuardBoxedPrimitiveSetter(ct, generateSetter(ct, ct), mh); 665 } 666 } else { 667 final Class<?> forType = isUndefined() ? type : getCurrentType(); 668 mh = generateSetter(!forType.isPrimitive() ? Object.class : forType, type); 669 } 670 671 /** 672 * Check if this is a special global name that requires switchpoint invalidation 673 */ 674 final SwitchPoint ccb = getChangeCallback(); 675 if (ccb != null && ccb != NO_CHANGE_CALLBACK) { 676 mh = MH.filterArguments(mh, 0, MH.insertArguments(debugInvalidate(getKey(), ccb), 1, changeCallback)); 677 } 678 679 assert mh.type().returnType() == void.class : mh.type(); 680 681 return mh; 682 } 683 684 /** 685 * Get the change callback for this property 686 * @return switchpoint that is invalidated when property changes 687 */ 688 protected SwitchPoint getChangeCallback() { 689 if (changeCallback == null) { 690 try { 691 changeCallback = Global.instance().getChangeCallback(getKey()); 692 } catch (final NullPointerException e) { 693 assert !"apply".equals(getKey()) && !"call".equals(getKey()); 694 //empty 695 } 696 if (changeCallback == null) { 697 changeCallback = NO_CHANGE_CALLBACK; 698 } 699 } 700 return changeCallback; 701 } 702 703 @Override 704 public final boolean canChangeType() { 705 if (OBJECT_FIELDS_ONLY) { 706 return false; 707 } 708 // Return true for currently undefined even if non-writable/configurable to allow initialization of ES6 CONST. 709 return getCurrentType() == null || (getCurrentType() != Object.class && (isConfigurable() || isWritable())); 710 } 711 712 private boolean needsInvalidator(final int typeIndex, final int currentTypeIndex) { 713 return canChangeType() && typeIndex > currentTypeIndex; 714 } 715 716 @Override 717 public final void setCurrentType(final Class<?> currentType) { 718 assert currentType != boolean.class : "no boolean storage support yet - fix this"; 719 this.currentType = currentType == null ? null : currentType.isPrimitive() ? currentType : Object.class; 720 } 721 722 @Override 723 public Class<?> getCurrentType() { 724 return currentType; 725 } 726 727 728 private MethodHandle debug(final MethodHandle mh, final Class<?> forType, final Class<?> type, final String tag) { 729 if (!Context.DEBUG || !Global.hasInstance()) { 730 return mh; 731 } 732 733 final Context context = Context.getContextTrusted(); 734 assert context != null; 735 736 return context.addLoggingToHandle( 737 ObjectClassGenerator.class, 738 Level.INFO, 739 mh, 740 0, 741 true, 742 new Supplier<String>() { 743 @Override 744 public String get() { 745 return tag + " '" + getKey() + "' (property="+ Debug.id(this) + ", slot=" + getSlot() + " " + getClass().getSimpleName() + " forType=" + stripName(forType) + ", type=" + stripName(type) + ')'; 746 } 747 }); 748 } 749 750 private MethodHandle debugReplace(final Class<?> oldType, final Class<?> newType, final PropertyMap oldMap, final PropertyMap newMap) { 751 if (!Context.DEBUG || !Global.hasInstance()) { 752 return REPLACE_MAP; 753 } 754 755 final Context context = Context.getContextTrusted(); 756 assert context != null; 757 758 MethodHandle mh = context.addLoggingToHandle( 759 ObjectClassGenerator.class, 760 REPLACE_MAP, 761 new Supplier<String>() { 762 @Override 763 public String get() { 764 return "Type change for '" + getKey() + "' " + oldType + "=>" + newType; 765 } 766 }); 767 768 mh = context.addLoggingToHandle( 769 ObjectClassGenerator.class, 770 Level.FINEST, 771 mh, 772 Integer.MAX_VALUE, 773 false, 774 new Supplier<String>() { 775 @Override 776 public String get() { 777 return "Setting map " + Debug.id(oldMap) + " => " + Debug.id(newMap) + " " + oldMap + " => " + newMap; 778 } 779 }); 780 return mh; 781 } 782 783 private static MethodHandle debugInvalidate(final String key, final SwitchPoint sp) { 784 if (!Context.DEBUG || !Global.hasInstance()) { 785 return INVALIDATE_SP; 786 } 787 788 final Context context = Context.getContextTrusted(); 789 assert context != null; 790 791 return context.addLoggingToHandle( 792 ObjectClassGenerator.class, 793 INVALIDATE_SP, 794 new Supplier<String>() { 795 @Override 796 public String get() { 797 return "Field change callback for " + key + " triggered: " + sp; 798 } 799 }); 800 } 801 802 private static MethodHandle findOwnMH_S(final String name, final Class<?> rtype, final Class<?>... types) { 803 return MH.findStatic(LOOKUP, AccessorProperty.class, name, MH.type(rtype, types)); 804 } 805} 806