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