AccessorProperty.java revision 953:221a84ef44c0
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 private transient MethodHandle primitiveGetter; 140 141 /** Seed setter for the primitive version of this field (in -Dnashorn.fields.dual=true mode) */ 142 private transient MethodHandle primitiveSetter; 143 144 /** Seed getter for the Object version of this field */ 145 private transient MethodHandle objectGetter; 146 147 /** Seed setter for the Object version of this field */ 148 private 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 objectGetter 189 * @param objectSetter 190 * @param primitiveGetter 191 * @param primitiveSetter 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 glass. 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 } 271 272 private void initGetterSetter(final Class<?> structure) { 273 final int slot = getSlot(); 274 /* 275 * primitiveGetter and primitiveSetter are only used in dual fields mode. Setting them to null also 276 * works in dual field mode, it only means that the property never has a primitive 277 * representation. 278 */ 279 280 if (isParameter() && hasArguments()) { 281 //parameters are always stored in an object array, which may or may not be a good idea 282 final MethodHandle arguments = MH.getter(LOOKUP, structure, "arguments", ScriptObject.class); 283 objectGetter = MH.asType(MH.insertArguments(MH.filterArguments(ScriptObject.GET_ARGUMENT.methodHandle(), 0, arguments), 1, slot), Lookup.GET_OBJECT_TYPE); 284 objectSetter = MH.asType(MH.insertArguments(MH.filterArguments(ScriptObject.SET_ARGUMENT.methodHandle(), 0, arguments), 1, slot), Lookup.SET_OBJECT_TYPE); 285 primitiveGetter = null; 286 primitiveSetter = null; 287 } else { 288 final Accessors gs = GETTERS_SETTERS.get(structure); 289 objectGetter = gs.objectGetters[slot]; 290 primitiveGetter = gs.primitiveGetters[slot]; 291 objectSetter = gs.objectSetters[slot]; 292 primitiveSetter = gs.primitiveSetters[slot]; 293 } 294 295 initializeType(); 296 } 297 298 /** 299 * Constructor 300 * 301 * @param key key 302 * @param flags flags 303 * @param slot field slot index 304 * @param owner owner of property 305 * @param initialValue initial value to which the property can be set 306 */ 307 protected AccessorProperty(final String key, final int flags, final int slot, final ScriptObject owner, final Object initialValue) { 308 this(key, flags, owner.getClass(), slot); 309 setInitialValue(owner, initialValue); 310 } 311 312 /** 313 * Normal access property constructor that overrides the type 314 * Override the initial type. Used for Object Literals 315 * 316 * @param key key 317 * @param flags flags 318 * @param structure structure to JO subclass 319 * @param slot field slot index 320 * @param initialType initial type of the property 321 */ 322 public AccessorProperty(final String key, final int flags, final Class<?> structure, final int slot, final Class<?> initialType) { 323 this(key, flags, structure, slot); 324 setCurrentType(OBJECT_FIELDS_ONLY ? Object.class : initialType); 325 } 326 327 /** 328 * Copy constructor that may change type and in that case clear the cache. Important to do that before 329 * type change or getters will be created already stale. 330 * 331 * @param property property 332 * @param newType new type 333 */ 334 protected AccessorProperty(final AccessorProperty property, final Class<?> newType) { 335 super(property, property.getFlags()); 336 337 this.GETTER_CACHE = newType != property.getCurrentType() ? new MethodHandle[NOOF_TYPES] : property.GETTER_CACHE; 338 this.primitiveGetter = property.primitiveGetter; 339 this.primitiveSetter = property.primitiveSetter; 340 this.objectGetter = property.objectGetter; 341 this.objectSetter = property.objectSetter; 342 343 setCurrentType(newType); 344 } 345 346 /** 347 * COPY constructor 348 * 349 * @param property source property 350 */ 351 protected AccessorProperty(final AccessorProperty property) { 352 this(property, property.getCurrentType()); 353 } 354 355 /** 356 * Set initial value of a script object's property 357 * @param owner owner 358 * @param initialValue initial value 359 */ 360 protected final void setInitialValue(final ScriptObject owner, final Object initialValue) { 361 setCurrentType(JSType.unboxedFieldType(initialValue)); 362 if (initialValue instanceof Integer) { 363 invokeSetter(owner, ((Integer)initialValue).intValue()); 364 } else if (initialValue instanceof Long) { 365 invokeSetter(owner, ((Long)initialValue).longValue()); 366 } else if (initialValue instanceof Double) { 367 invokeSetter(owner, ((Double)initialValue).doubleValue()); 368 } else { 369 invokeSetter(owner, initialValue); 370 } 371 } 372 373 /** 374 * Initialize the type of a property 375 */ 376 protected final void initializeType() { 377 setCurrentType(OBJECT_FIELDS_ONLY ? Object.class : null); 378 } 379 380 private void readObject(final ObjectInputStream s) throws IOException, ClassNotFoundException { 381 s.defaultReadObject(); 382 // Restore getters array 383 GETTER_CACHE = new MethodHandle[NOOF_TYPES]; 384 } 385 386 private static MethodHandle bindTo(final MethodHandle mh, final Object receiver) { 387 if (mh == null) { 388 return null; 389 } 390 391 return MH.dropArguments(MH.bindTo(mh, receiver), 0, Object.class); 392 } 393 394 @Override 395 public Property copy() { 396 return new AccessorProperty(this); 397 } 398 399 @Override 400 public Property copy(final Class<?> newType) { 401 return new AccessorProperty(this, newType); 402 } 403 404 @Override 405 public int getIntValue(final ScriptObject self, final ScriptObject owner) { 406 try { 407 return (int)getGetter(int.class).invokeExact((Object)self); 408 } catch (final Error | RuntimeException e) { 409 throw e; 410 } catch (final Throwable e) { 411 throw new RuntimeException(e); 412 } 413 } 414 415 @Override 416 public long getLongValue(final ScriptObject self, final ScriptObject owner) { 417 try { 418 return (long)getGetter(long.class).invokeExact((Object)self); 419 } catch (final Error | RuntimeException e) { 420 throw e; 421 } catch (final Throwable e) { 422 throw new RuntimeException(e); 423 } 424 } 425 426 @Override 427 public double getDoubleValue(final ScriptObject self, final ScriptObject owner) { 428 try { 429 return (double)getGetter(double.class).invokeExact((Object)self); 430 } catch (final Error | RuntimeException e) { 431 throw e; 432 } catch (final Throwable e) { 433 throw new RuntimeException(e); 434 } 435 } 436 437 @Override 438 public Object getObjectValue(final ScriptObject self, final ScriptObject owner) { 439 try { 440 return getGetter(Object.class).invokeExact((Object)self); 441 } catch (final Error | RuntimeException e) { 442 throw e; 443 } catch (final Throwable e) { 444 throw new RuntimeException(e); 445 } 446 } 447 448 /** 449 * Invoke setter for this property with a value 450 * @param self owner 451 * @param value value 452 */ 453 protected final void invokeSetter(final ScriptObject self, final int value) { 454 try { 455 getSetter(int.class, self.getMap()).invokeExact((Object)self, value); 456 } catch (final Error | RuntimeException e) { 457 throw e; 458 } catch (final Throwable e) { 459 throw new RuntimeException(e); 460 } 461 } 462 463 /** 464 * Invoke setter for this property with a value 465 * @param self owner 466 * @param value value 467 */ 468 protected final void invokeSetter(final ScriptObject self, final long value) { 469 try { 470 getSetter(long.class, self.getMap()).invokeExact((Object)self, value); 471 } catch (final Error | RuntimeException e) { 472 throw e; 473 } catch (final Throwable e) { 474 throw new RuntimeException(e); 475 } 476 } 477 478 /** 479 * Invoke setter for this property with a value 480 * @param self owner 481 * @param value value 482 */ 483 protected final void invokeSetter(final ScriptObject self, final double value) { 484 try { 485 getSetter(double.class, self.getMap()).invokeExact((Object)self, value); 486 } catch (final Error | RuntimeException e) { 487 throw e; 488 } catch (final Throwable e) { 489 throw new RuntimeException(e); 490 } 491 } 492 493 /** 494 * Invoke setter for this property with a value 495 * @param self owner 496 * @param value value 497 */ 498 protected final void invokeSetter(final ScriptObject self, final Object value) { 499 try { 500 getSetter(Object.class, self.getMap()).invokeExact((Object)self, value); 501 } catch (final Error | RuntimeException e) { 502 throw e; 503 } catch (final Throwable e) { 504 throw new RuntimeException(e); 505 } 506 } 507 508 @Override 509 public void setValue(final ScriptObject self, final ScriptObject owner, final int value, final boolean strict) { 510 assert isConfigurable() || isWritable() : getKey() + " is not writable or configurable"; 511 invokeSetter(self, value); 512 } 513 514 @Override 515 public void setValue(final ScriptObject self, final ScriptObject owner, final long value, final boolean strict) { 516 assert isConfigurable() || isWritable() : getKey() + " is not writable or configurable"; 517 invokeSetter(self, value); 518 } 519 520 @Override 521 public void setValue(final ScriptObject self, final ScriptObject owner, final double value, final boolean strict) { 522 assert isConfigurable() || isWritable() : getKey() + " is not writable or configurable"; 523 invokeSetter(self, value); 524 } 525 526 @Override 527 public void setValue(final ScriptObject self, final ScriptObject owner, final Object value, final boolean strict) { 528 //this is sometimes used for bootstrapping, hence no assert. ugly. 529 invokeSetter(self, value); 530 } 531 532 @Override 533 void initMethodHandles(final Class<?> structure) { 534 if (!ScriptObject.class.isAssignableFrom(structure) || !StructureLoader.isStructureClass(structure.getName())) { 535 throw new IllegalArgumentException(); 536 } 537 if (!isSpill()) { 538 initGetterSetter(structure); 539 } 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 //all this does is add a return value filter for object fields only 553 final MethodHandle[] getterCache = GETTER_CACHE; 554 final MethodHandle cachedGetter = getterCache[i]; 555 final MethodHandle getter; 556 if (cachedGetter != null) { 557 getter = cachedGetter; 558 } else { 559 getter = debug( 560 createGetter( 561 getCurrentType(), 562 type, 563 primitiveGetter, 564 objectGetter, 565 INVALID_PROGRAM_POINT), 566 getCurrentType(), 567 type, 568 "get"); 569 getterCache[i] = getter; 570 } 571 assert getter.type().returnType() == type && getter.type().parameterType(0) == Object.class; 572 return getter; 573 } 574 575 @Override 576 public MethodHandle getOptimisticGetter(final Class<?> type, final int programPoint) { 577 // nasgen generated primitive fields like Math.PI have only one known unchangeable primitive type 578 if (objectGetter == null) { 579 return getOptimisticPrimitiveGetter(type, programPoint); 580 } 581 582 return debug( 583 createGetter( 584 getCurrentType(), 585 type, 586 primitiveGetter, 587 objectGetter, 588 programPoint), 589 getCurrentType(), 590 type, 591 "get"); 592 } 593 594 private MethodHandle getOptimisticPrimitiveGetter(final Class<?> type, final int programPoint) { 595 final MethodHandle g = getGetter(getCurrentType()); 596 return MH.asType(OptimisticReturnFilters.filterOptimisticReturnValue(g, type, programPoint), g.type().changeReturnType(type)); 597 } 598 599 private Property getWiderProperty(final Class<?> type) { 600 return copy(type); //invalidate cache of new property 601 602 } 603 604 private PropertyMap getWiderMap(final PropertyMap oldMap, final Property newProperty) { 605 final PropertyMap newMap = oldMap.replaceProperty(this, newProperty); 606 assert oldMap.size() > 0; 607 assert newMap.size() == oldMap.size(); 608 return newMap; 609 } 610 611 // the final three arguments are for debug printout purposes only 612 @SuppressWarnings("unused") 613 private static Object replaceMap(final Object sobj, final PropertyMap newMap) { 614 ((ScriptObject)sobj).setMap(newMap); 615 return sobj; 616 } 617 618 @SuppressWarnings("unused") 619 private static Object invalidateSwitchPoint(final Object obj, final SwitchPoint sp) { 620 SwitchPoint.invalidateAll(new SwitchPoint[] { sp }); 621 return obj; 622 } 623 624 private MethodHandle generateSetter(final Class<?> forType, final Class<?> type) { 625 return debug(createSetter(forType, type, primitiveSetter, objectSetter), getCurrentType(), type, "set"); 626 } 627 628 /** 629 * Is this property of the undefined type? 630 * @return true if undefined 631 */ 632 protected final boolean isUndefined() { 633 return getCurrentType() == null; 634 } 635 636 @Override 637 public MethodHandle getSetter(final Class<?> type, final PropertyMap currentMap) { 638 final int i = getAccessorTypeIndex(type); 639 final int ci = isUndefined() ? -1 : getAccessorTypeIndex(getCurrentType()); 640 final Class<?> forType = isUndefined() ? type : getCurrentType(); 641 642 //if we are asking for an object setter, but are still a primitive type, we might try to box it 643 MethodHandle mh; 644 if (needsInvalidator(i, ci)) { 645 final Property newProperty = getWiderProperty(type); 646 final PropertyMap newMap = getWiderMap(currentMap, newProperty); 647 648 final MethodHandle widerSetter = newProperty.getSetter(type, newMap); 649 final Class<?> ct = getCurrentType(); 650 mh = MH.filterArguments(widerSetter, 0, MH.insertArguments(debugReplace(ct, type, currentMap, newMap) , 1, newMap)); 651 if (ct != null && ct.isPrimitive() && !type.isPrimitive()) { 652 mh = ObjectClassGenerator.createGuardBoxedPrimitiveSetter(ct, generateSetter(ct, ct), mh); 653 } 654 } else { 655 mh = generateSetter(!forType.isPrimitive() ? Object.class : forType, type); 656 } 657 658 /** 659 * Check if this is a special global name that requires switchpoint invalidation 660 */ 661 final SwitchPoint ccb = getChangeCallback(); 662 if (ccb != null && ccb != NO_CHANGE_CALLBACK) { 663 mh = MH.filterArguments(mh, 0, MH.insertArguments(debugInvalidate(getKey(), ccb), 1, changeCallback)); 664 } 665 666 assert mh.type().returnType() == void.class : mh.type(); 667 668 return mh; 669 } 670 671 /** 672 * Get the change callback for this property 673 * @return switchpoint that is invalidated when property changes 674 */ 675 protected SwitchPoint getChangeCallback() { 676 if (changeCallback == null) { 677 try { 678 changeCallback = Global.instance().getChangeCallback(getKey()); 679 } catch (final NullPointerException e) { 680 assert !"apply".equals(getKey()) && !"call".equals(getKey()); 681 //empty 682 } 683 if (changeCallback == null) { 684 changeCallback = NO_CHANGE_CALLBACK; 685 } 686 } 687 return changeCallback; 688 } 689 690 @Override 691 public final boolean canChangeType() { 692 if (OBJECT_FIELDS_ONLY) { 693 return false; 694 } 695 return getCurrentType() != Object.class && (isConfigurable() || isWritable()); 696 } 697 698 private boolean needsInvalidator(final int ti, final int fti) { 699 return canChangeType() && ti > fti; 700 } 701 702 @Override 703 public final void setCurrentType(final Class<?> currentType) { 704 assert currentType != boolean.class : "no boolean storage support yet - fix this"; 705 this.currentType = currentType == null ? null : currentType.isPrimitive() ? currentType : Object.class; 706 } 707 708 @Override 709 public Class<?> getCurrentType() { 710 return currentType; 711 } 712 713 714 private MethodHandle debug(final MethodHandle mh, final Class<?> forType, final Class<?> type, final String tag) { 715 if (!Global.hasInstance()) { 716 return mh; 717 } 718 719 final Context context = Context.getContextTrusted(); 720 assert context != null; 721 722 return context.addLoggingToHandle( 723 ObjectClassGenerator.class, 724 Level.INFO, 725 mh, 726 0, 727 true, 728 new Supplier<String>() { 729 @Override 730 public String get() { 731 return tag + " '" + getKey() + "' (property="+ Debug.id(this) + ", slot=" + getSlot() + " " + getClass().getSimpleName() + " forType=" + stripName(forType) + ", type=" + stripName(type) + ')'; 732 } 733 }); 734 } 735 736 private MethodHandle debugReplace(final Class<?> oldType, final Class<?> newType, final PropertyMap oldMap, final PropertyMap newMap) { 737 if (!Global.hasInstance()) { 738 return REPLACE_MAP; 739 } 740 741 final Context context = Context.getContextTrusted(); 742 assert context != null; 743 744 MethodHandle mh = context.addLoggingToHandle( 745 ObjectClassGenerator.class, 746 REPLACE_MAP, 747 new Supplier<String>() { 748 @Override 749 public String get() { 750 return "Type change for '" + getKey() + "' " + oldType + "=>" + newType; 751 } 752 }); 753 754 mh = context.addLoggingToHandle( 755 ObjectClassGenerator.class, 756 Level.FINEST, 757 mh, 758 Integer.MAX_VALUE, 759 false, 760 new Supplier<String>() { 761 @Override 762 public String get() { 763 return "Setting map " + Debug.id(oldMap) + " => " + Debug.id(newMap) + " " + oldMap + " => " + newMap; 764 } 765 }); 766 return mh; 767 } 768 769 private static MethodHandle debugInvalidate(final String key, final SwitchPoint sp) { 770 if (!Global.hasInstance()) { 771 return INVALIDATE_SP; 772 } 773 774 final Context context = Context.getContextTrusted(); 775 assert context != null; 776 777 return context.addLoggingToHandle( 778 ObjectClassGenerator.class, 779 INVALIDATE_SP, 780 new Supplier<String>() { 781 @Override 782 public String get() { 783 return "Field change callback for " + key + " triggered: " + sp; 784 } 785 }); 786 } 787 788 private static MethodHandle findOwnMH_S(final String name, final Class<?> rtype, final Class<?>... types) { 789 return MH.findStatic(LOOKUP, AccessorProperty.class, name, MH.type(rtype, types)); 790 } 791} 792