/*
 * 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.codegen;

import static jdk.nashorn.internal.codegen.Compiler.SCRIPTS_PACKAGE;
import static jdk.nashorn.internal.codegen.CompilerConstants.ALLOCATE;
import static jdk.nashorn.internal.codegen.CompilerConstants.INIT_ARGUMENTS;
import static jdk.nashorn.internal.codegen.CompilerConstants.INIT_MAP;
import static jdk.nashorn.internal.codegen.CompilerConstants.INIT_SCOPE;
import static jdk.nashorn.internal.codegen.CompilerConstants.JAVA_THIS;
import static jdk.nashorn.internal.codegen.CompilerConstants.JS_OBJECT_DUAL_FIELD_PREFIX;
import static jdk.nashorn.internal.codegen.CompilerConstants.JS_OBJECT_SINGLE_FIELD_PREFIX;
import static jdk.nashorn.internal.codegen.CompilerConstants.className;
import static jdk.nashorn.internal.codegen.CompilerConstants.constructorNoLookup;
import static jdk.nashorn.internal.lookup.Lookup.MH;
import static jdk.nashorn.internal.runtime.JSType.CONVERT_OBJECT;
import static jdk.nashorn.internal.runtime.JSType.CONVERT_OBJECT_OPTIMISTIC;
import static jdk.nashorn.internal.runtime.JSType.GET_UNDEFINED;
import static jdk.nashorn.internal.runtime.JSType.TYPE_DOUBLE_INDEX;
import static jdk.nashorn.internal.runtime.JSType.TYPE_INT_INDEX;
import static jdk.nashorn.internal.runtime.JSType.TYPE_OBJECT_INDEX;
import static jdk.nashorn.internal.runtime.JSType.TYPE_UNDEFINED_INDEX;
import static jdk.nashorn.internal.runtime.JSType.getAccessorTypeIndex;
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.isValid;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import jdk.nashorn.internal.codegen.ClassEmitter.Flag;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.runtime.AccessorProperty;
import jdk.nashorn.internal.runtime.AllocationStrategy;
import jdk.nashorn.internal.runtime.Context;
import jdk.nashorn.internal.runtime.FunctionScope;
import jdk.nashorn.internal.runtime.JSType;
import jdk.nashorn.internal.runtime.PropertyMap;
import jdk.nashorn.internal.runtime.ScriptEnvironment;
import jdk.nashorn.internal.runtime.ScriptObject;
import jdk.nashorn.internal.runtime.Undefined;
import jdk.nashorn.internal.runtime.UnwarrantedOptimismException;
import jdk.nashorn.internal.runtime.logging.DebugLogger;
import jdk.nashorn.internal.runtime.logging.Loggable;
import jdk.nashorn.internal.runtime.logging.Logger;

/**
 * Generates the ScriptObject subclass structure with fields for a user objects.
 */
@Logger(name="fields")
public final class ObjectClassGenerator implements Loggable {

    /**
     * Type guard to make sure we don't unnecessarily explode field storages. Rather unbox e.g.
     * a java.lang.Number than blow up the field. Gradually, optimistic types should create almost
     * no boxed types
     */
    private static final MethodHandle IS_TYPE_GUARD = findOwnMH("isType", boolean.class, Class.class, Object.class);

    /**
     * Marker for scope parameters
     */
    private static final String SCOPE_MARKER = "P";

    /**
     * Minimum number of extra fields in an object.
     */
    static final int FIELD_PADDING  = 4;

    /**
     * Debug field logger
     * Should we print debugging information for fields when they are generated and getters/setters are called?
     */
    private final DebugLogger log;

    /** Field types for object-only fields */
    private static final Type[] FIELD_TYPES_OBJECT = new Type[] { Type.OBJECT };
    /** Field types for dual primitive/object fields */
    private static final Type[] FIELD_TYPES_DUAL   = new Type[] { Type.LONG, Type.OBJECT };

    /** What type is the primitive type in dual representation */
    public static final Type PRIMITIVE_FIELD_TYPE = Type.LONG;

    private static final MethodHandle GET_DIFFERENT           = findOwnMH("getDifferent", Object.class, Object.class, Class.class, MethodHandle.class, MethodHandle.class, int.class);
    private static final MethodHandle GET_DIFFERENT_UNDEFINED = findOwnMH("getDifferentUndefined", Object.class, int.class);

    private static boolean initialized = false;

    /** The context */
    private final Context context;

    private final boolean dualFields;

    /**
     * Constructor
     *
     * @param context a context
     * @param dualFields whether to use dual fields representation
     */
    public ObjectClassGenerator(final Context context, final boolean dualFields) {
        this.context = context;
        this.dualFields = dualFields;
        assert context != null;
        this.log = initLogger(context);
        if (!initialized) {
            initialized = true;
            if (!dualFields) {
                log.warning("Running with object fields only - this is a deprecated configuration.");
            }
        }
    }

    @Override
    public DebugLogger getLogger() {
        return log;
    }

    @Override
    public DebugLogger initLogger(final Context ctxt) {
        return ctxt.getLogger(this.getClass());
    }

    /**
     * Pack a number into a primitive long field
     * @param n number object
     * @return primitive long value with all the bits in the number
     */
    public static long pack(final Number n) {
        if (n instanceof Integer) {
            return n.intValue();
        } else if (n instanceof Long) {
            return n.longValue();
        } else if (n instanceof Double) {
            return Double.doubleToRawLongBits(n.doubleValue());
        }
        throw new AssertionError("cannot pack" + n);
    }

    private static String getPrefixName(final boolean dualFields) {
        return dualFields ? JS_OBJECT_DUAL_FIELD_PREFIX.symbolName() : JS_OBJECT_SINGLE_FIELD_PREFIX.symbolName();
    }

    private static String getPrefixName(final String className) {
        if (className.startsWith(JS_OBJECT_DUAL_FIELD_PREFIX.symbolName())) {
            return getPrefixName(true);
        } else if (className.startsWith(JS_OBJECT_SINGLE_FIELD_PREFIX.symbolName())) {
            return getPrefixName(false);
        }
        throw new AssertionError("Not a structure class: " + className);
    }

    /**
     * Returns the class name for JavaScript objects with fieldCount fields.
     *
     * @param fieldCount Number of fields to allocate.
     * @param dualFields whether to use dual fields representation
     * @return The class name.
     */
    public static String getClassName(final int fieldCount, final boolean dualFields) {
        final String prefix = getPrefixName(dualFields);
        return fieldCount != 0 ? SCRIPTS_PACKAGE + '/' + prefix + fieldCount :
                                 SCRIPTS_PACKAGE + '/' + prefix;
    }

    /**
     * Returns the class name for JavaScript scope with fieldCount fields and
     * paramCount parameters.
     *
     * @param fieldCount Number of fields to allocate.
     * @param paramCount Number of parameters to allocate
     * @param dualFields whether to use dual fields representation
     * @return The class name.
     */
    public static String getClassName(final int fieldCount, final int paramCount, final boolean dualFields) {
        return SCRIPTS_PACKAGE + '/' + getPrefixName(dualFields) + fieldCount + SCOPE_MARKER + paramCount;
    }

    /**
     * Returns the number of fields in the JavaScript scope class. Its name had to be generated using either
     * {@link #getClassName(int, boolean)} or {@link #getClassName(int, int, boolean)}.
     * @param clazz the JavaScript scope class.
     * @return the number of fields in the scope class.
     */
    public static int getFieldCount(final Class<?> clazz) {
        final String name = clazz.getSimpleName();
        final String prefix = getPrefixName(name);

        if (prefix.equals(name)) {
            return 0;
        }
        final int scopeMarker = name.indexOf(SCOPE_MARKER);
        return Integer.parseInt(scopeMarker == -1 ? name.substring(prefix.length()) : name.substring(prefix.length(), scopeMarker));
    }

    /**
     * Returns the name of a field based on number and type.
     *
     * @param fieldIndex Ordinal of field.
     * @param type       Type of field.
     *
     * @return The field name.
     */
    public static String getFieldName(final int fieldIndex, final Type type) {
        return type.getDescriptor().substring(0, 1) + fieldIndex;
    }

    /**
     * In the world of Object fields, we also have no undefined SwitchPoint, to reduce as much potential
     * MethodHandle overhead as possible. In that case, we explicitly need to assign undefined to fields
     * when we initialize them.
     *
     * @param init       constructor to generate code in
     * @param className  name of class
     * @param fieldNames fields to initialize to undefined, where applicable
     */
    private void initializeToUndefined(final MethodEmitter init, final String className, final List<String> fieldNames) {
        if (dualFields) {
            // no need to initialize anything to undefined in the dual field world
            // - then we have a constant getter for undefined for any unknown type
            return;
        }

        if (fieldNames.isEmpty()) {
            return;
        }

        init.load(Type.OBJECT, JAVA_THIS.slot());
        init.loadUndefined(Type.OBJECT);

        final Iterator<String> iter = fieldNames.iterator();
        while (iter.hasNext()) {
            final String fieldName = iter.next();
            if (iter.hasNext()) {
                init.dup2();
            }
            init.putField(className, fieldName, Type.OBJECT.getDescriptor());
        }
    }

    /**
     * Generate the byte codes for a JavaScript object class or scope.
     * Class name is a function of number of fields and number of param
     * fields
     *
     * @param descriptor Descriptor pulled from class name.
     *
     * @return Byte codes for generated class.
     */
    public byte[] generate(final String descriptor) {
        final String[] counts     = descriptor.split(SCOPE_MARKER);
        final int      fieldCount = Integer.valueOf(counts[0]);

        if (counts.length == 1) {
            return generate(fieldCount);
        }

        final int paramCount = Integer.valueOf(counts[1]);

        return generate(fieldCount, paramCount);
    }

    /**
     * Generate the byte codes for a JavaScript object class with fieldCount fields.
     *
     * @param fieldCount Number of fields in the JavaScript object.
     *
     * @return Byte codes for generated class.
     */
    public byte[] generate(final int fieldCount) {
        final String       className    = getClassName(fieldCount, dualFields);
        final String       superName    = className(ScriptObject.class);
        final ClassEmitter classEmitter = newClassEmitter(className, superName);

        addFields(classEmitter, fieldCount);

        final MethodEmitter init = newInitMethod(classEmitter);
        init.returnVoid();
        init.end();

        final MethodEmitter initWithSpillArrays = newInitWithSpillArraysMethod(classEmitter, ScriptObject.class);
        initWithSpillArrays.returnVoid();
        initWithSpillArrays.end();

        newEmptyInit(className, classEmitter);
        newAllocate(className, classEmitter);

        return toByteArray(className, classEmitter);
    }

    /**
     * Generate the byte codes for a JavaScript scope class with fieldCount fields
     * and paramCount parameters.
     *
     * @param fieldCount Number of fields in the JavaScript scope.
     * @param paramCount Number of parameters in the JavaScript scope
     * .
     * @return Byte codes for generated class.
     */
    public byte[] generate(final int fieldCount, final int paramCount) {
        final String       className    = getClassName(fieldCount, paramCount, dualFields);
        final String       superName    = className(FunctionScope.class);
        final ClassEmitter classEmitter = newClassEmitter(className, superName);
        final List<String> initFields   = addFields(classEmitter, fieldCount);

        final MethodEmitter init = newInitScopeMethod(classEmitter);
        initializeToUndefined(init, className, initFields);
        init.returnVoid();
        init.end();

        final MethodEmitter initWithSpillArrays = newInitWithSpillArraysMethod(classEmitter, FunctionScope.class);
        initializeToUndefined(initWithSpillArrays, className, initFields);
        initWithSpillArrays.returnVoid();
        initWithSpillArrays.end();

        final MethodEmitter initWithArguments = newInitScopeWithArgumentsMethod(classEmitter);
        initializeToUndefined(initWithArguments, className, initFields);
        initWithArguments.returnVoid();
        initWithArguments.end();

        return toByteArray(className, classEmitter);
    }

    /**
     * Generates the needed fields.
     *
     * @param classEmitter Open class emitter.
     * @param fieldCount   Number of fields.
     *
     * @return List fields that need to be initialized.
     */
    private List<String> addFields(final ClassEmitter classEmitter, final int fieldCount) {
        final List<String> initFields = new LinkedList<>();
        final Type[] fieldTypes = dualFields ? FIELD_TYPES_DUAL : FIELD_TYPES_OBJECT;
        for (int i = 0; i < fieldCount; i++) {
            for (final Type type : fieldTypes) {
                final String fieldName = getFieldName(i, type);
                classEmitter.field(fieldName, type.getTypeClass());

                if (type == Type.OBJECT) {
                    initFields.add(fieldName);
                }
            }
        }

        return initFields;
    }

    /**
     * Allocate and initialize a new class emitter.
     *
     * @param className Name of JavaScript class.
     *
     * @return Open class emitter.
     */
    private ClassEmitter newClassEmitter(final String className, final String superName) {
        final ClassEmitter classEmitter = new ClassEmitter(context, className, superName);
        classEmitter.begin();

        return classEmitter;
    }

    /**
     * Allocate and initialize a new <init> method.
     *
     * @param classEmitter  Open class emitter.
     *
     * @return Open method emitter.
     */
    private static MethodEmitter newInitMethod(final ClassEmitter classEmitter) {
        final MethodEmitter init = classEmitter.init(PropertyMap.class);
        init.begin();
        init.load(Type.OBJECT, JAVA_THIS.slot());
        init.load(Type.OBJECT, INIT_MAP.slot());
        init.invoke(constructorNoLookup(ScriptObject.class, PropertyMap.class));

        return init;
    }

     private static MethodEmitter newInitWithSpillArraysMethod(final ClassEmitter classEmitter, final Class<?> superClass) {
        final MethodEmitter init = classEmitter.init(PropertyMap.class, long[].class, Object[].class);
        init.begin();
        init.load(Type.OBJECT, JAVA_THIS.slot());
        init.load(Type.OBJECT, INIT_MAP.slot());
        init.load(Type.LONG_ARRAY, 2);
        init.load(Type.OBJECT_ARRAY, 3);
        init.invoke(constructorNoLookup(superClass, PropertyMap.class, long[].class, Object[].class));

        return init;
    }

    /**
     * Allocate and initialize a new <init> method for scopes.
     * @param classEmitter  Open class emitter.
     * @return Open method emitter.
     */
    private static MethodEmitter newInitScopeMethod(final ClassEmitter classEmitter) {
        final MethodEmitter init = classEmitter.init(PropertyMap.class, ScriptObject.class);
        init.begin();
        init.load(Type.OBJECT, JAVA_THIS.slot());
        init.load(Type.OBJECT, INIT_MAP.slot());
        init.load(Type.OBJECT, INIT_SCOPE.slot());
        init.invoke(constructorNoLookup(FunctionScope.class, PropertyMap.class, ScriptObject.class));

        return init;
    }

    /**
     * Allocate and initialize a new <init> method for scopes with arguments.
     * @param classEmitter  Open class emitter.
     * @return Open method emitter.
     */
    private static MethodEmitter newInitScopeWithArgumentsMethod(final ClassEmitter classEmitter) {
        final MethodEmitter init = classEmitter.init(PropertyMap.class, ScriptObject.class, ScriptObject.class);
        init.begin();
        init.load(Type.OBJECT, JAVA_THIS.slot());
        init.load(Type.OBJECT, INIT_MAP.slot());
        init.load(Type.OBJECT, INIT_SCOPE.slot());
        init.load(Type.OBJECT, INIT_ARGUMENTS.slot());
        init.invoke(constructorNoLookup(FunctionScope.class, PropertyMap.class, ScriptObject.class, ScriptObject.class));

        return init;
    }

    /**
     * Add an empty <init> method to the JavaScript class.
     *
     * @param classEmitter Open class emitter.
     * @param className    Name of JavaScript class.
     */
    private static void newEmptyInit(final String className, final ClassEmitter classEmitter) {
        final MethodEmitter emptyInit = classEmitter.init();
        emptyInit.begin();
        emptyInit.load(Type.OBJECT, JAVA_THIS.slot());
        emptyInit.loadNull();
        emptyInit.invoke(constructorNoLookup(className, PropertyMap.class));
        emptyInit.returnVoid();
        emptyInit.end();
    }

    /**
     * Add an empty <init> method to the JavaScript class.
     *
     * @param classEmitter Open class emitter.
     * @param className    Name of JavaScript class.
     */
    private static void newAllocate(final String className, final ClassEmitter classEmitter) {
        final MethodEmitter allocate = classEmitter.method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), ALLOCATE.symbolName(), ScriptObject.class, PropertyMap.class);
        allocate.begin();
        allocate._new(className, Type.typeFor(ScriptObject.class));
        allocate.dup();
        allocate.load(Type.typeFor(PropertyMap.class), 0);
        allocate.invoke(constructorNoLookup(className, PropertyMap.class));
        allocate._return();
        allocate.end();
    }

    /**
     * Collects the byte codes for a generated JavaScript class.
     *
     * @param classEmitter Open class emitter.
     * @return Byte codes for the class.
     */
    private byte[] toByteArray(final String className, final ClassEmitter classEmitter) {
        classEmitter.end();

        final byte[] code = classEmitter.toByteArray();
        final ScriptEnvironment env = context.getEnv();

        DumpBytecode.dumpBytecode(env, log, code, className);

        if (env._verify_code) {
            context.verify(code);
        }

        return code;
    }

    /** Double to long bits, used with --dual-fields for primitive double values */
    public static final MethodHandle PACK_DOUBLE =
        MH.explicitCastArguments(MH.findStatic(MethodHandles.publicLookup(), Double.class, "doubleToRawLongBits", MH.type(long.class, double.class)), MH.type(long.class, double.class));

    /** double bits to long, used with --dual-fields for primitive double values */
    public static final MethodHandle UNPACK_DOUBLE =
        MH.findStatic(MethodHandles.publicLookup(), Double.class, "longBitsToDouble", MH.type(double.class, long.class));

    //type != forType, so use the correct getter for forType, box it and throw
    @SuppressWarnings("unused")
    private static Object getDifferent(final Object receiver, final Class<?> forType, final MethodHandle primitiveGetter, final MethodHandle objectGetter, final int programPoint) {
        //create the sametype getter, and upcast to value. no matter what the store format is,
        //
        final MethodHandle sameTypeGetter = getterForType(forType, primitiveGetter, objectGetter);
        final MethodHandle mh = MH.asType(sameTypeGetter, sameTypeGetter.type().changeReturnType(Object.class));
        try {
            final Object value = mh.invokeExact(receiver);
            throw new UnwarrantedOptimismException(value, programPoint);
        } catch (final Error | RuntimeException e) {
            throw e;
        } catch (final Throwable e) {
            throw new RuntimeException(e);
        }
    }

    @SuppressWarnings("unused")
    private static Object getDifferentUndefined(final int programPoint) {
        throw new UnwarrantedOptimismException(Undefined.getUndefined(), programPoint);
    }

    private static MethodHandle getterForType(final Class<?> forType, final MethodHandle primitiveGetter, final MethodHandle objectGetter) {
        switch (getAccessorTypeIndex(forType)) {
        case TYPE_INT_INDEX:
            return MH.explicitCastArguments(primitiveGetter, primitiveGetter.type().changeReturnType(int.class));
        case TYPE_DOUBLE_INDEX:
            return MH.filterReturnValue(primitiveGetter, UNPACK_DOUBLE);
        case TYPE_OBJECT_INDEX:
            return objectGetter;
        default:
            throw new AssertionError(forType);
        }
    }

    //no optimism here. we do unconditional conversion to types
    private static MethodHandle createGetterInner(final Class<?> forType, final Class<?> type, final MethodHandle primitiveGetter, final MethodHandle objectGetter, final List<MethodHandle> converters, final int programPoint) {
        final int fti = forType == null ? TYPE_UNDEFINED_INDEX : getAccessorTypeIndex(forType);
        final int ti  = getAccessorTypeIndex(type);
        //this means fail if forType != type
        final boolean isOptimistic = converters == CONVERT_OBJECT_OPTIMISTIC;
        final boolean isPrimitiveStorage = forType != null && forType.isPrimitive();

        //which is the primordial getter
        final MethodHandle getter = primitiveGetter == null ? objectGetter : isPrimitiveStorage ? primitiveGetter : objectGetter;

        if (forType == null) {
            if (isOptimistic) {
                //return undefined if asking for object. otherwise throw UnwarrantedOptimismException
                if (ti == TYPE_OBJECT_INDEX) {
                    return MH.dropArguments(GET_UNDEFINED.get(TYPE_OBJECT_INDEX), 0, Object.class);
                }
                //throw exception
                return MH.asType(
                    MH.dropArguments(
                            MH.insertArguments(
                                    GET_DIFFERENT_UNDEFINED,
                                    0,
                                    programPoint),
                            0,
                            Object.class),
                    getter.type().changeReturnType(type));
            }
            //return an undefined and coerce it to the appropriate type
            return MH.dropArguments(GET_UNDEFINED.get(ti), 0, Object.class);
        }

        assert primitiveGetter != null || forType == Object.class : forType;

        if (isOptimistic) {
            if (fti < ti) {
                //asking for a wider type than currently stored. then it's OK to coerce.
                //e.g. stored as int,  ask for long or double
                //e.g. stored as long, ask for double
                assert fti != TYPE_UNDEFINED_INDEX;
                final MethodHandle tgetter = getterForType(forType, primitiveGetter, objectGetter);
                return MH.asType(tgetter, tgetter.type().changeReturnType(type));
            } else if (fti == ti) {
                //Fast path, never throw exception - exact getter, just unpack if needed
                return getterForType(forType, primitiveGetter, objectGetter);
            } else {
                assert fti > ti;
                //if asking for a narrower type than the storage - throw exception
                //unless FTI is object, in that case we have to go through the converters
                //there is no
                if (fti == TYPE_OBJECT_INDEX) {
                    return MH.filterReturnValue(
                            objectGetter,
                            MH.insertArguments(
                                    converters.get(ti),
                                    1,
                                    programPoint));
                }

                //asking for narrower primitive than we have stored, that is an
                //UnwarrantedOptimismException
                return MH.asType(
                        MH.filterArguments(
                            objectGetter,
                            0,
                            MH.insertArguments(
                                    GET_DIFFERENT,
                                    1,
                                    forType,
                                    primitiveGetter,
                                    objectGetter,
                                    programPoint)),
                        objectGetter.type().changeReturnType(type));
            }
        }

        assert !isOptimistic;
        // freely coerce the result to whatever you asked for, this is e.g. Object->int for a & b
        final MethodHandle tgetter = getterForType(forType, primitiveGetter, objectGetter);
        if (fti == TYPE_OBJECT_INDEX) {
            if (fti != ti) {
                return MH.filterReturnValue(tgetter, CONVERT_OBJECT.get(ti));
            }
            return tgetter;
        }

        assert primitiveGetter != null;
        final MethodType tgetterType = tgetter.type();
        switch (fti) {
        case TYPE_INT_INDEX: {
            return MH.asType(tgetter, tgetterType.changeReturnType(type));
        }
        case TYPE_DOUBLE_INDEX:
            switch (ti) {
            case TYPE_INT_INDEX:
                return MH.filterReturnValue(tgetter, JSType.TO_INT32_D.methodHandle);
            case TYPE_DOUBLE_INDEX:
                assert tgetterType.returnType() == double.class;
                return tgetter;
            default:
                return MH.asType(tgetter, tgetterType.changeReturnType(Object.class));
            }
        default:
            throw new UnsupportedOperationException(forType + "=>" + type);
        }
    }

    /**
     * Given a primitiveGetter (optional for non dual fields) and an objectSetter that retrieve
     * the primitive and object version of a field respectively, return one with the correct
     * method type and the correct filters. For example, if the value is stored as a double
     * and we want an Object getter, in the dual fields world we'd pick the primitiveGetter,
     * which reads a long, use longBitsToDouble on the result to unpack it, and then change the
     * return type to Object, boxing it. In the objects only world there are only object fields,
     * primitives are boxed when asked for them and we don't need to bother with primitive encoding
     * (or even undefined, which if forType==null) representation, so we just return whatever is
     * in the object field. The object field is always initiated to Undefined, so here, where we have
     * the representation for Undefined in all our bits, this is not a problem.
     * <p>
     * Representing undefined in a primitive is hard, for an int there aren't enough bits, for a long
     * we could limit the width of a representation, and for a double (as long as it is stored as long,
     * as all NaNs will turn into QNaN on ia32, which is one bit pattern, we should use a special NaN).
     * Naturally we could have special undefined values for all types which mean "go look in a wider field",
     * but the guards needed on every getter took too much time.
     * <p>
     * To see how this is used, look for example in {@link AccessorProperty#getGetter}
     * <p>
     * @param forType         representation of the underlying type in the field, null if undefined
     * @param type            type to retrieve it as
     * @param primitiveGetter getter to read the primitive version of this field (null if Objects Only)
     * @param objectGetter    getter to read the object version of this field
     * @param programPoint    program point for getter, if program point is INVALID_PROGRAM_POINT, then this is not an optimistic getter
     *
     * @return getter for the given representation that returns the given type
     */
    public static MethodHandle createGetter(final Class<?> forType, final Class<?> type, final MethodHandle primitiveGetter, final MethodHandle objectGetter, final int programPoint) {
        return createGetterInner(
                forType,
                type,
                primitiveGetter,
                objectGetter,
                isValid(programPoint) ? CONVERT_OBJECT_OPTIMISTIC : CONVERT_OBJECT,
                programPoint);
    }

    /**
     * This is similar to the {@link ObjectClassGenerator#createGetter} function. Performs
     * the necessary operations to massage a setter operand of type {@code type} to
     * fit into the primitive field (if primitive and dual fields is enabled) or into
     * the object field (box if primitive and dual fields is disabled)
     *
     * @param forType         representation of the underlying object
     * @param type            representation of field to write, and setter signature
     * @param primitiveSetter setter that writes to the primitive field (null if Objects Only)
     * @param objectSetter    setter that writes to the object field
     *
     * @return the setter for the given representation that takes a {@code type}
     */
    public static MethodHandle createSetter(final Class<?> forType, final Class<?> type, final MethodHandle primitiveSetter, final MethodHandle objectSetter) {
        assert forType != null;

        final int fti = getAccessorTypeIndex(forType);
        final int ti  = getAccessorTypeIndex(type);

        if (fti == TYPE_OBJECT_INDEX || primitiveSetter == null) {
            if (ti == TYPE_OBJECT_INDEX) {
                return objectSetter;
            }

            return MH.asType(objectSetter, objectSetter.type().changeParameterType(1, type));
        }

        final MethodType pmt = primitiveSetter.type();

        switch (fti) {
        case TYPE_INT_INDEX:
            switch (ti) {
            case TYPE_INT_INDEX:
                return MH.asType(primitiveSetter, pmt.changeParameterType(1, int.class));
            case TYPE_DOUBLE_INDEX:
                return MH.filterArguments(primitiveSetter, 1, PACK_DOUBLE);
            default:
                return objectSetter;
            }
        case TYPE_DOUBLE_INDEX:
            if (ti == TYPE_OBJECT_INDEX) {
                return objectSetter;
            }
            return MH.asType(MH.filterArguments(primitiveSetter, 1, PACK_DOUBLE), pmt.changeParameterType(1, type));
        default:
            throw new UnsupportedOperationException(forType + "=>" + type);
        }
    }

    @SuppressWarnings("unused")
    private static boolean isType(final Class<?> boxedForType, final Object x) {
        return x != null && x.getClass() == boxedForType;
    }

    private static Class<? extends Number> getBoxedType(final Class<?> forType) {
        if (forType == int.class) {
            return Integer.class;
        }

        if (forType == long.class) {
            return Long.class;
        }

        if (forType == double.class) {
            return Double.class;
        }

        assert false;
        return null;
    }

    /**
     * If we are setting boxed types (because the compiler couldn't determine which they were) to
     * a primitive field, we can reuse the primitive field getter, as long as we are setting an element
     * of the same boxed type as the primitive type representation
     *
     * @param forType           the current type
     * @param primitiveSetter   primitive setter for the current type with an element of the current type
     * @param objectSetter      the object setter
     *
     * @return method handle that checks if the element to be set is of the current type, even though it's boxed
     *  and instead of using the generic object setter, that would blow up the type and invalidate the map,
     *  unbox it and call the primitive setter instead
     */
    public static MethodHandle createGuardBoxedPrimitiveSetter(final Class<?> forType, final MethodHandle primitiveSetter, final MethodHandle objectSetter) {
        final Class<? extends Number> boxedForType = getBoxedType(forType);
        //object setter that checks for primitive if current type is primitive
        return MH.guardWithTest(
            MH.insertArguments(
                MH.dropArguments(
                    IS_TYPE_GUARD,
                    1,
                    Object.class),
                0,
                boxedForType),
                MH.asType(
                    primitiveSetter,
                    objectSetter.type()),
                objectSetter);
    }
    /**
     * Add padding to field count to avoid creating too many classes and have some spare fields
     * @param count the field count
     * @return the padded field count
     */
    static int getPaddedFieldCount(final int count) {
        return count / FIELD_PADDING * FIELD_PADDING + FIELD_PADDING;
    }

    private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
        return MH.findStatic(MethodHandles.lookup(), ObjectClassGenerator.class, name, MH.type(rtype, types));
    }

    /**
     * Creates the allocator class name and property map for a constructor function with the specified
     * number of "this" properties that it initializes.
     * @param thisProperties number of properties assigned to "this"
     * @return the allocation strategy
     */
    static AllocationStrategy createAllocationStrategy(final int thisProperties, final boolean dualFields) {
        final int paddedFieldCount = getPaddedFieldCount(thisProperties);
        return new AllocationStrategy(paddedFieldCount, dualFields);
    }
}