| /* |
| * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package jdk.nashorn.internal.runtime; |
| |
| import static jdk.nashorn.internal.codegen.ObjectClassGenerator.PRIMITIVE_FIELD_TYPE; |
| import static jdk.nashorn.internal.codegen.ObjectClassGenerator.createGetter; |
| import static jdk.nashorn.internal.codegen.ObjectClassGenerator.createSetter; |
| import static jdk.nashorn.internal.codegen.ObjectClassGenerator.getFieldCount; |
| import static jdk.nashorn.internal.codegen.ObjectClassGenerator.getFieldName; |
| import static jdk.nashorn.internal.lookup.Lookup.MH; |
| import static jdk.nashorn.internal.lookup.MethodHandleFactory.stripName; |
| import static jdk.nashorn.internal.runtime.JSType.getAccessorTypeIndex; |
| import static jdk.nashorn.internal.runtime.JSType.getNumberOfAccessorTypes; |
| import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT; |
| import java.io.IOException; |
| import java.io.ObjectInputStream; |
| import java.lang.invoke.MethodHandle; |
| import java.lang.invoke.MethodHandles; |
| import java.lang.invoke.SwitchPoint; |
| import java.util.function.Supplier; |
| import java.util.logging.Level; |
| import jdk.nashorn.internal.codegen.ObjectClassGenerator; |
| import jdk.nashorn.internal.codegen.types.Type; |
| import jdk.nashorn.internal.lookup.Lookup; |
| import jdk.nashorn.internal.objects.Global; |
| |
| /** |
| * An AccessorProperty is the most generic property type. An AccessorProperty is |
| * represented as fields in a ScriptObject class. |
| */ |
| public class AccessorProperty extends Property { |
| private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); |
| |
| private static final MethodHandle REPLACE_MAP = findOwnMH_S("replaceMap", Object.class, Object.class, PropertyMap.class); |
| private static final MethodHandle INVALIDATE_SP = findOwnMH_S("invalidateSwitchPoint", Object.class, AccessorProperty.class, Object.class); |
| |
| private static final int NOOF_TYPES = getNumberOfAccessorTypes(); |
| private static final long serialVersionUID = 3371720170182154920L; |
| |
| /** |
| * Properties in different maps for the same structure class will share their field getters and setters. This could |
| * be further extended to other method handles that are looked up in the AccessorProperty constructor, but right now |
| * these are the most frequently retrieved ones, and lookup of method handle natives only registers in the profiler |
| * for them. |
| */ |
| private static ClassValue<Accessors> GETTERS_SETTERS = new ClassValue<Accessors>() { |
| @Override |
| protected Accessors computeValue(final Class<?> structure) { |
| return new Accessors(structure); |
| } |
| }; |
| |
| private static class Accessors { |
| final MethodHandle[] objectGetters; |
| final MethodHandle[] objectSetters; |
| final MethodHandle[] primitiveGetters; |
| final MethodHandle[] primitiveSetters; |
| |
| /** |
| * Normal |
| * @param structure |
| */ |
| Accessors(final Class<?> structure) { |
| final int fieldCount = getFieldCount(structure); |
| objectGetters = new MethodHandle[fieldCount]; |
| objectSetters = new MethodHandle[fieldCount]; |
| primitiveGetters = new MethodHandle[fieldCount]; |
| primitiveSetters = new MethodHandle[fieldCount]; |
| |
| for (int i = 0; i < fieldCount; i++) { |
| final String fieldName = getFieldName(i, Type.OBJECT); |
| final Class<?> typeClass = Type.OBJECT.getTypeClass(); |
| objectGetters[i] = MH.asType(MH.getter(LOOKUP, structure, fieldName, typeClass), Lookup.GET_OBJECT_TYPE); |
| objectSetters[i] = MH.asType(MH.setter(LOOKUP, structure, fieldName, typeClass), Lookup.SET_OBJECT_TYPE); |
| } |
| |
| if (!StructureLoader.isSingleFieldStructure(structure.getName())) { |
| for (int i = 0; i < fieldCount; i++) { |
| final String fieldNamePrimitive = getFieldName(i, PRIMITIVE_FIELD_TYPE); |
| final Class<?> typeClass = PRIMITIVE_FIELD_TYPE.getTypeClass(); |
| primitiveGetters[i] = MH.asType(MH.getter(LOOKUP, structure, fieldNamePrimitive, typeClass), Lookup.GET_PRIMITIVE_TYPE); |
| primitiveSetters[i] = MH.asType(MH.setter(LOOKUP, structure, fieldNamePrimitive, typeClass), Lookup.SET_PRIMITIVE_TYPE); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Property getter cache |
| * Note that we can't do the same simple caching for optimistic getters, |
| * due to the fact that they are bound to a program point, which will |
| * produce different boun method handles wrapping the same access mechanism |
| * depending on callsite |
| */ |
| private transient MethodHandle[] GETTER_CACHE = new MethodHandle[NOOF_TYPES]; |
| |
| /** |
| * Create a new accessor property. Factory method used by nasgen generated code. |
| * |
| * @param key {@link Property} key. |
| * @param propertyFlags {@link Property} flags. |
| * @param getter {@link Property} get accessor method. |
| * @param setter {@link Property} set accessor method. |
| * |
| * @return New {@link AccessorProperty} created. |
| */ |
| public static AccessorProperty create(final Object key, final int propertyFlags, final MethodHandle getter, final MethodHandle setter) { |
| return new AccessorProperty(key, propertyFlags, -1, getter, setter); |
| } |
| |
| /** Seed getter for the primitive version of this field (in -Dnashorn.fields.dual=true mode) */ |
| transient MethodHandle primitiveGetter; |
| |
| /** Seed setter for the primitive version of this field (in -Dnashorn.fields.dual=true mode) */ |
| transient MethodHandle primitiveSetter; |
| |
| /** Seed getter for the Object version of this field */ |
| transient MethodHandle objectGetter; |
| |
| /** Seed setter for the Object version of this field */ |
| transient MethodHandle objectSetter; |
| |
| /** |
| * Delegate constructor for bound properties. This is used for properties created by |
| * {@link ScriptRuntime#mergeScope} and the Nashorn {@code Object.bindProperties} method. |
| * The former is used to add a script's defined globals to the current global scope while |
| * still storing them in a JO-prefixed ScriptObject class. |
| * |
| * <p>All properties created by this constructor have the {@link #IS_BOUND} flag set.</p> |
| * |
| * @param property accessor property to rebind |
| * @param delegate delegate object to rebind receiver to |
| */ |
| AccessorProperty(final AccessorProperty property, final Object delegate) { |
| super(property, property.getFlags() | IS_BOUND); |
| |
| this.primitiveGetter = bindTo(property.primitiveGetter, delegate); |
| this.primitiveSetter = bindTo(property.primitiveSetter, delegate); |
| this.objectGetter = bindTo(property.objectGetter, delegate); |
| this.objectSetter = bindTo(property.objectSetter, delegate); |
| property.GETTER_CACHE = new MethodHandle[NOOF_TYPES]; |
| // Properties created this way are bound to a delegate |
| setType(property.getType()); |
| } |
| |
| /** |
| * SPILL PROPERTY or USER ACCESSOR PROPERTY abstract constructor |
| * |
| * Constructor for spill properties. Array getters and setters will be created on demand. |
| * |
| * @param key the property key |
| * @param flags the property flags |
| * @param slot spill slot |
| * @param primitiveGetter primitive getter |
| * @param primitiveSetter primitive setter |
| * @param objectGetter object getter |
| * @param objectSetter object setter |
| */ |
| protected AccessorProperty( |
| final Object key, |
| final int flags, |
| final int slot, |
| final MethodHandle primitiveGetter, |
| final MethodHandle primitiveSetter, |
| final MethodHandle objectGetter, |
| final MethodHandle objectSetter) { |
| super(key, flags, slot); |
| assert getClass() != AccessorProperty.class; |
| this.primitiveGetter = primitiveGetter; |
| this.primitiveSetter = primitiveSetter; |
| this.objectGetter = objectGetter; |
| this.objectSetter = objectSetter; |
| initializeType(); |
| } |
| |
| /** |
| * NASGEN constructor |
| * |
| * Constructor. Similar to the constructor with both primitive getters and setters, the difference |
| * here being that only one getter and setter (setter is optional for non writable fields) is given |
| * to the constructor, and the rest are created from those. Used e.g. by Nasgen classes |
| * |
| * @param key the property key |
| * @param flags the property flags |
| * @param slot the property field number or spill slot |
| * @param getter the property getter |
| * @param setter the property setter or null if non writable, non configurable |
| */ |
| private AccessorProperty(final Object key, final int flags, final int slot, final MethodHandle getter, final MethodHandle setter) { |
| super(key, flags | IS_BUILTIN | DUAL_FIELDS | (getter.type().returnType().isPrimitive() ? IS_NASGEN_PRIMITIVE : 0), slot); |
| assert !isSpill(); |
| |
| // we don't need to prep the setters these will never be invalidated as this is a nasgen |
| // or known type getter/setter. No invalidations will take place |
| |
| final Class<?> getterType = getter.type().returnType(); |
| final Class<?> setterType = setter == null ? null : setter.type().parameterType(1); |
| |
| assert setterType == null || setterType == getterType; |
| |
| if (getterType == int.class) { |
| primitiveGetter = MH.asType(getter, Lookup.GET_PRIMITIVE_TYPE); |
| primitiveSetter = setter == null ? null : MH.asType(setter, Lookup.SET_PRIMITIVE_TYPE); |
| } else if (getterType == double.class) { |
| primitiveGetter = MH.asType(MH.filterReturnValue(getter, ObjectClassGenerator.PACK_DOUBLE), Lookup.GET_PRIMITIVE_TYPE); |
| primitiveSetter = setter == null ? null : MH.asType(MH.filterArguments(setter, 1, ObjectClassGenerator.UNPACK_DOUBLE), Lookup.SET_PRIMITIVE_TYPE); |
| } else { |
| primitiveGetter = primitiveSetter = null; |
| } |
| |
| assert primitiveGetter == null || primitiveGetter.type() == Lookup.GET_PRIMITIVE_TYPE : primitiveGetter + "!=" + Lookup.GET_PRIMITIVE_TYPE; |
| assert primitiveSetter == null || primitiveSetter.type() == Lookup.SET_PRIMITIVE_TYPE : primitiveSetter; |
| |
| objectGetter = getter.type() != Lookup.GET_OBJECT_TYPE ? MH.asType(getter, Lookup.GET_OBJECT_TYPE) : getter; |
| objectSetter = setter != null && setter.type() != Lookup.SET_OBJECT_TYPE ? MH.asType(setter, Lookup.SET_OBJECT_TYPE) : setter; |
| |
| setType(getterType); |
| } |
| |
| /** |
| * Normal ACCESS PROPERTY constructor given a structure class. |
| * Constructor for dual field AccessorPropertys. |
| * |
| * @param key property key |
| * @param flags property flags |
| * @param structure structure for objects associated with this property |
| * @param slot property field number or spill slot |
| */ |
| public AccessorProperty(final Object key, final int flags, final Class<?> structure, final int slot) { |
| super(key, flags, slot); |
| |
| initGetterSetter(structure); |
| initializeType(); |
| } |
| |
| private void initGetterSetter(final Class<?> structure) { |
| final int slot = getSlot(); |
| /* |
| * primitiveGetter and primitiveSetter are only used in dual fields mode. Setting them to null also |
| * works in dual field mode, it only means that the property never has a primitive |
| * representation. |
| */ |
| |
| if (isParameter() && hasArguments()) { |
| //parameters are always stored in an object array, which may or may not be a good idea |
| final MethodHandle arguments = MH.getter(LOOKUP, structure, "arguments", ScriptObject.class); |
| objectGetter = MH.asType(MH.insertArguments(MH.filterArguments(ScriptObject.GET_ARGUMENT.methodHandle(), 0, arguments), 1, slot), Lookup.GET_OBJECT_TYPE); |
| objectSetter = MH.asType(MH.insertArguments(MH.filterArguments(ScriptObject.SET_ARGUMENT.methodHandle(), 0, arguments), 1, slot), Lookup.SET_OBJECT_TYPE); |
| primitiveGetter = null; |
| primitiveSetter = null; |
| } else { |
| final Accessors gs = GETTERS_SETTERS.get(structure); |
| objectGetter = gs.objectGetters[slot]; |
| primitiveGetter = gs.primitiveGetters[slot]; |
| objectSetter = gs.objectSetters[slot]; |
| primitiveSetter = gs.primitiveSetters[slot]; |
| } |
| |
| // Always use dual fields except for single field structures |
| assert hasDualFields() != StructureLoader.isSingleFieldStructure(structure.getName()); |
| } |
| |
| /** |
| * Constructor |
| * |
| * @param key key |
| * @param flags flags |
| * @param slot field slot index |
| * @param owner owner of property |
| * @param initialValue initial value to which the property can be set |
| */ |
| protected AccessorProperty(final Object key, final int flags, final int slot, final ScriptObject owner, final Object initialValue) { |
| this(key, flags, owner.getClass(), slot); |
| setInitialValue(owner, initialValue); |
| } |
| |
| /** |
| * Normal access property constructor that overrides the type |
| * Override the initial type. Used for Object Literals |
| * |
| * @param key key |
| * @param flags flags |
| * @param structure structure to JO subclass |
| * @param slot field slot index |
| * @param initialType initial type of the property |
| */ |
| public AccessorProperty(final Object key, final int flags, final Class<?> structure, final int slot, final Class<?> initialType) { |
| this(key, flags, structure, slot); |
| setType(hasDualFields() ? initialType : Object.class); |
| } |
| |
| /** |
| * Copy constructor that may change type and in that case clear the cache. Important to do that before |
| * type change or getters will be created already stale. |
| * |
| * @param property property |
| * @param newType new type |
| */ |
| protected AccessorProperty(final AccessorProperty property, final Class<?> newType) { |
| super(property, property.getFlags()); |
| |
| this.GETTER_CACHE = newType != property.getLocalType() ? new MethodHandle[NOOF_TYPES] : property.GETTER_CACHE; |
| this.primitiveGetter = property.primitiveGetter; |
| this.primitiveSetter = property.primitiveSetter; |
| this.objectGetter = property.objectGetter; |
| this.objectSetter = property.objectSetter; |
| |
| setType(newType); |
| } |
| |
| /** |
| * COPY constructor |
| * |
| * @param property source property |
| */ |
| protected AccessorProperty(final AccessorProperty property) { |
| this(property, property.getLocalType()); |
| } |
| |
| /** |
| * Set initial value of a script object's property |
| * @param owner owner |
| * @param initialValue initial value |
| */ |
| protected final void setInitialValue(final ScriptObject owner, final Object initialValue) { |
| setType(hasDualFields() ? JSType.unboxedFieldType(initialValue) : Object.class); |
| if (initialValue instanceof Integer) { |
| invokeSetter(owner, ((Integer)initialValue).intValue()); |
| } else if (initialValue instanceof Long) { |
| invokeSetter(owner, ((Long)initialValue).longValue()); |
| } else if (initialValue instanceof Double) { |
| invokeSetter(owner, ((Double)initialValue).doubleValue()); |
| } else { |
| invokeSetter(owner, initialValue); |
| } |
| } |
| |
| /** |
| * Initialize the type of a property |
| */ |
| protected final void initializeType() { |
| setType(!hasDualFields() ? Object.class : null); |
| } |
| |
| private void readObject(final ObjectInputStream s) throws IOException, ClassNotFoundException { |
| s.defaultReadObject(); |
| // Restore getters array |
| GETTER_CACHE = new MethodHandle[NOOF_TYPES]; |
| } |
| |
| private static MethodHandle bindTo(final MethodHandle mh, final Object receiver) { |
| if (mh == null) { |
| return null; |
| } |
| |
| return MH.dropArguments(MH.bindTo(mh, receiver), 0, Object.class); |
| } |
| |
| @Override |
| public Property copy() { |
| return new AccessorProperty(this); |
| } |
| |
| @Override |
| public Property copy(final Class<?> newType) { |
| return new AccessorProperty(this, newType); |
| } |
| |
| @Override |
| public int getIntValue(final ScriptObject self, final ScriptObject owner) { |
| try { |
| return (int)getGetter(int.class).invokeExact((Object)self); |
| } catch (final Error | RuntimeException e) { |
| throw e; |
| } catch (final Throwable e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| @Override |
| public double getDoubleValue(final ScriptObject self, final ScriptObject owner) { |
| try { |
| return (double)getGetter(double.class).invokeExact((Object)self); |
| } catch (final Error | RuntimeException e) { |
| throw e; |
| } catch (final Throwable e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| @Override |
| public Object getObjectValue(final ScriptObject self, final ScriptObject owner) { |
| try { |
| return getGetter(Object.class).invokeExact((Object)self); |
| } catch (final Error | RuntimeException e) { |
| throw e; |
| } catch (final Throwable e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| /** |
| * Invoke setter for this property with a value |
| * @param self owner |
| * @param value value |
| */ |
| protected final void invokeSetter(final ScriptObject self, final int value) { |
| try { |
| getSetter(int.class, self.getMap()).invokeExact((Object)self, value); |
| } catch (final Error | RuntimeException e) { |
| throw e; |
| } catch (final Throwable e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| /** |
| * Invoke setter for this property with a value |
| * @param self owner |
| * @param value value |
| */ |
| protected final void invokeSetter(final ScriptObject self, final double value) { |
| try { |
| getSetter(double.class, self.getMap()).invokeExact((Object)self, value); |
| } catch (final Error | RuntimeException e) { |
| throw e; |
| } catch (final Throwable e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| /** |
| * Invoke setter for this property with a value |
| * @param self owner |
| * @param value value |
| */ |
| protected final void invokeSetter(final ScriptObject self, final Object value) { |
| try { |
| getSetter(Object.class, self.getMap()).invokeExact((Object)self, value); |
| } catch (final Error | RuntimeException e) { |
| throw e; |
| } catch (final Throwable e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| @Override |
| public void setValue(final ScriptObject self, final ScriptObject owner, final int value, final boolean strict) { |
| assert isConfigurable() || isWritable() : getKey() + " is not writable or configurable"; |
| invokeSetter(self, value); |
| } |
| |
| @Override |
| public void setValue(final ScriptObject self, final ScriptObject owner, final double value, final boolean strict) { |
| assert isConfigurable() || isWritable() : getKey() + " is not writable or configurable"; |
| invokeSetter(self, value); |
| } |
| |
| @Override |
| public void setValue(final ScriptObject self, final ScriptObject owner, final Object value, final boolean strict) { |
| //this is sometimes used for bootstrapping, hence no assert. ugly. |
| invokeSetter(self, value); |
| } |
| |
| @Override |
| void initMethodHandles(final Class<?> structure) { |
| // sanity check for structure class |
| if (!ScriptObject.class.isAssignableFrom(structure) || !StructureLoader.isStructureClass(structure.getName())) { |
| throw new IllegalArgumentException(); |
| } |
| // this method is overridden in SpillProperty |
| assert !isSpill(); |
| initGetterSetter(structure); |
| } |
| |
| @Override |
| public MethodHandle getGetter(final Class<?> type) { |
| final int i = getAccessorTypeIndex(type); |
| |
| assert type == int.class || |
| type == double.class || |
| type == Object.class : |
| "invalid getter type " + type + " for " + getKey(); |
| |
| checkUndeclared(); |
| |
| //all this does is add a return value filter for object fields only |
| final MethodHandle[] getterCache = GETTER_CACHE; |
| final MethodHandle cachedGetter = getterCache[i]; |
| final MethodHandle getter; |
| if (cachedGetter != null) { |
| getter = cachedGetter; |
| } else { |
| getter = debug( |
| createGetter( |
| getLocalType(), |
| type, |
| primitiveGetter, |
| objectGetter, |
| INVALID_PROGRAM_POINT), |
| getLocalType(), |
| type, |
| "get"); |
| getterCache[i] = getter; |
| } |
| assert getter.type().returnType() == type && getter.type().parameterType(0) == Object.class; |
| return getter; |
| } |
| |
| @Override |
| public MethodHandle getOptimisticGetter(final Class<?> type, final int programPoint) { |
| // nasgen generated primitive fields like Math.PI have only one known unchangeable primitive type |
| if (objectGetter == null) { |
| return getOptimisticPrimitiveGetter(type, programPoint); |
| } |
| |
| checkUndeclared(); |
| |
| return debug( |
| createGetter( |
| getLocalType(), |
| type, |
| primitiveGetter, |
| objectGetter, |
| programPoint), |
| getLocalType(), |
| type, |
| "get"); |
| } |
| |
| private MethodHandle getOptimisticPrimitiveGetter(final Class<?> type, final int programPoint) { |
| final MethodHandle g = getGetter(getLocalType()); |
| return MH.asType(OptimisticReturnFilters.filterOptimisticReturnValue(g, type, programPoint), g.type().changeReturnType(type)); |
| } |
| |
| private Property getWiderProperty(final Class<?> type) { |
| return copy(type); //invalidate cache of new property |
| } |
| |
| private PropertyMap getWiderMap(final PropertyMap oldMap, final Property newProperty) { |
| final PropertyMap newMap = oldMap.replaceProperty(this, newProperty); |
| assert oldMap.size() > 0; |
| assert newMap.size() == oldMap.size(); |
| return newMap; |
| } |
| |
| private void checkUndeclared() { |
| if ((getFlags() & NEEDS_DECLARATION) != 0) { |
| // a lexically defined variable that hasn't seen its declaration - throw ReferenceError |
| throw ECMAErrors.referenceError("not.defined", getKey().toString()); |
| } |
| } |
| |
| // the final three arguments are for debug printout purposes only |
| @SuppressWarnings("unused") |
| private static Object replaceMap(final Object sobj, final PropertyMap newMap) { |
| ((ScriptObject)sobj).setMap(newMap); |
| return sobj; |
| } |
| |
| @SuppressWarnings("unused") |
| private static Object invalidateSwitchPoint(final AccessorProperty property, final Object obj) { |
| if (!property.builtinSwitchPoint.hasBeenInvalidated()) { |
| SwitchPoint.invalidateAll(new SwitchPoint[] { property.builtinSwitchPoint }); |
| } |
| return obj; |
| } |
| |
| private MethodHandle generateSetter(final Class<?> forType, final Class<?> type) { |
| return debug(createSetter(forType, type, primitiveSetter, objectSetter), getLocalType(), type, "set"); |
| } |
| |
| /** |
| * Is this property of the undefined type? |
| * @return true if undefined |
| */ |
| protected final boolean isUndefined() { |
| return getLocalType() == null; |
| } |
| |
| @Override |
| public boolean hasNativeSetter() { |
| return objectSetter != null; |
| } |
| |
| @Override |
| public MethodHandle getSetter(final Class<?> type, final PropertyMap currentMap) { |
| checkUndeclared(); |
| |
| final int typeIndex = getAccessorTypeIndex(type); |
| final int currentTypeIndex = getAccessorTypeIndex(getLocalType()); |
| |
| //if we are asking for an object setter, but are still a primitive type, we might try to box it |
| MethodHandle mh; |
| if (needsInvalidator(typeIndex, currentTypeIndex)) { |
| final Property newProperty = getWiderProperty(type); |
| final PropertyMap newMap = getWiderMap(currentMap, newProperty); |
| |
| final MethodHandle widerSetter = newProperty.getSetter(type, newMap); |
| final Class<?> ct = getLocalType(); |
| mh = MH.filterArguments(widerSetter, 0, MH.insertArguments(debugReplace(ct, type, currentMap, newMap) , 1, newMap)); |
| if (ct != null && ct.isPrimitive() && !type.isPrimitive()) { |
| mh = ObjectClassGenerator.createGuardBoxedPrimitiveSetter(ct, generateSetter(ct, ct), mh); |
| } |
| } else { |
| final Class<?> forType = isUndefined() ? type : getLocalType(); |
| mh = generateSetter(!forType.isPrimitive() ? Object.class : forType, type); |
| } |
| |
| if (isBuiltin()) { |
| mh = MH.filterArguments(mh, 0, debugInvalidate(MH.insertArguments(INVALIDATE_SP, 0, this), getKey().toString())); |
| } |
| |
| assert mh.type().returnType() == void.class : mh.type(); |
| |
| return mh; |
| } |
| |
| @Override |
| public final boolean canChangeType() { |
| if (!hasDualFields()) { |
| return false; |
| } |
| // Return true for currently undefined even if non-writable/configurable to allow initialization of ES6 CONST. |
| return getLocalType() == null || (getLocalType() != Object.class && (isConfigurable() || isWritable())); |
| } |
| |
| private boolean needsInvalidator(final int typeIndex, final int currentTypeIndex) { |
| return canChangeType() && typeIndex > currentTypeIndex; |
| } |
| |
| private MethodHandle debug(final MethodHandle mh, final Class<?> forType, final Class<?> type, final String tag) { |
| if (!Context.DEBUG || !Global.hasInstance()) { |
| return mh; |
| } |
| |
| final Context context = Context.getContextTrusted(); |
| assert context != null; |
| |
| return context.addLoggingToHandle( |
| ObjectClassGenerator.class, |
| Level.INFO, |
| mh, |
| 0, |
| true, |
| new Supplier<String>() { |
| @Override |
| public String get() { |
| return tag + " '" + getKey() + "' (property="+ Debug.id(this) + ", slot=" + getSlot() + " " + getClass().getSimpleName() + " forType=" + stripName(forType) + ", type=" + stripName(type) + ')'; |
| } |
| }); |
| } |
| |
| private MethodHandle debugReplace(final Class<?> oldType, final Class<?> newType, final PropertyMap oldMap, final PropertyMap newMap) { |
| if (!Context.DEBUG || !Global.hasInstance()) { |
| return REPLACE_MAP; |
| } |
| |
| final Context context = Context.getContextTrusted(); |
| assert context != null; |
| |
| MethodHandle mh = context.addLoggingToHandle( |
| ObjectClassGenerator.class, |
| REPLACE_MAP, |
| new Supplier<String>() { |
| @Override |
| public String get() { |
| return "Type change for '" + getKey() + "' " + oldType + "=>" + newType; |
| } |
| }); |
| |
| mh = context.addLoggingToHandle( |
| ObjectClassGenerator.class, |
| Level.FINEST, |
| mh, |
| Integer.MAX_VALUE, |
| false, |
| new Supplier<String>() { |
| @Override |
| public String get() { |
| return "Setting map " + Debug.id(oldMap) + " => " + Debug.id(newMap) + " " + oldMap + " => " + newMap; |
| } |
| }); |
| return mh; |
| } |
| |
| private static MethodHandle debugInvalidate(final MethodHandle invalidator, final String key) { |
| if (!Context.DEBUG || !Global.hasInstance()) { |
| return invalidator; |
| } |
| |
| final Context context = Context.getContextTrusted(); |
| assert context != null; |
| |
| return context.addLoggingToHandle( |
| ObjectClassGenerator.class, |
| invalidator, |
| new Supplier<String>() { |
| @Override |
| public String get() { |
| return "Field change callback for " + key + " triggered "; |
| } |
| }); |
| } |
| |
| private static MethodHandle findOwnMH_S(final String name, final Class<?> rtype, final Class<?>... types) { |
| return MH.findStatic(LOOKUP, AccessorProperty.class, name, MH.type(rtype, types)); |
| } |
| } |