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