blob: 172c1035426175f13ce977b89aae37c0a64cc269 [file] [log] [blame]
/*
* 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_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 java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Arrays;
import java.util.Collections;
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.Context;
import jdk.nashorn.internal.runtime.DebugLogger;
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.ScriptRuntime;
import jdk.nashorn.internal.runtime.options.Options;
/**
* Generates the ScriptObject subclass structure with fields for a user objects.
*/
public final class ObjectClassGenerator {
/**
* Marker for scope parameters.
*/
static final String SCOPE_MARKER = "P";
/**
* Minimum number of extra fields in an object.
*/
static final int FIELD_PADDING = 4;
/**
* Rounding when calculating the number of fields.
*/
static final int FIELD_ROUNDING = 4;
/**
* Debug field logger
* Should we print debugging information for fields when they are generated and getters/setters are called?
*/
public static final DebugLogger LOG = new DebugLogger("fields", "nashorn.fields.debug");
/**
* is field debugging enabled. Several modules in codegen and properties use this, hence
* public access.
*/
public static final boolean DEBUG_FIELDS = LOG.isEnabled();
/**
* Should the runtime only use java.lang.Object slots for fields? If this is false, the representation
* will be a primitive 64-bit long value used for all primitives and a java.lang.Object for references.
* This introduces a larger number of method handles in the system, as we need to have different getters
* and setters for the different fields. Currently this introduces significant overhead in Hotspot.
*
* This is engineered to plug into the TaggedArray implementation, when it's done.
*/
public static final boolean OBJECT_FIELDS_ONLY = !Options.getBooleanProperty("nashorn.fields.dual");
/** The field types in the system */
private static final List<Type> FIELD_TYPES = new LinkedList<>();
/** What type is the primitive type in dual representation */
public static final Type PRIMITIVE_TYPE = Type.LONG;
/**
* The list of field types that we support - one type creates one field. This is currently either
* LONG + OBJECT or just OBJECT for classic mode.
*/
static {
if (!OBJECT_FIELDS_ONLY) {
System.err.println("WARNING!!! Running with primitive fields - there is untested functionality!");
FIELD_TYPES.add(PRIMITIVE_TYPE);
}
FIELD_TYPES.add(Type.OBJECT);
}
/** The context */
private final Context context;
/**
* The list of available accessor types in width order. This order is used for type guesses narrow{@literal ->} wide
* in the dual--fields world
*/
public static final List<Type> ACCESSOR_TYPES = Collections.unmodifiableList(
Arrays.asList(
Type.INT,
Type.LONG,
Type.NUMBER,
Type.OBJECT));
//these are hard coded for speed and so that we can switch on them
private static final int TYPE_INT_INDEX = 0; //getAccessorTypeIndex(int.class);
private static final int TYPE_LONG_INDEX = 1; //getAccessorTypeIndex(long.class);
private static final int TYPE_DOUBLE_INDEX = 2; //getAccessorTypeIndex(double.class);
private static final int TYPE_OBJECT_INDEX = 3; //getAccessorTypeIndex(Object.class);
/**
* Constructor
*
* @param context a context
*/
public ObjectClassGenerator(final Context context) {
this.context = context;
assert context != null;
}
/**
* Given a type of an accessor, return its index in [0..getNumberOfAccessorTypes())
*
* @param type the type
*
* @return the accessor index, or -1 if no accessor of this type exists
*/
public static int getAccessorTypeIndex(final Type type) {
return getAccessorTypeIndex(type.getTypeClass());
}
/**
* Given a class of an accessor, return its index in [0..getNumberOfAccessorTypes())
*
* Note that this is hardcoded with respect to the dynamic contents of the accessor
* types array for speed. Hotspot got stuck with this as 5% of the runtime in
* a benchmark when it looped over values and increased an index counter. :-(
*
* @param type the type
*
* @return the accessor index, or -1 if no accessor of this type exists
*/
public static int getAccessorTypeIndex(final Class<?> type) {
if (type == int.class) {
return 0;
} else if (type == long.class) {
return 1;
} else if (type == double.class) {
return 2;
} else if (!type.isPrimitive()) {
return 3;
}
return -1;
}
/**
* Return the number of accessor types available.
*
* @return number of accessor types in system
*/
public static int getNumberOfAccessorTypes() {
return ACCESSOR_TYPES.size();
}
/**
* Return the accessor type based on its index in [0..getNumberOfAccessorTypes())
* Indexes are ordered narrower{@literal ->}wider / optimistic{@literal ->}pessimistic. Invalidations always
* go to a type of higher index
*
* @param index accessor type index
*
* @return a type corresponding to the index.
*/
public static Type getAccessorType(final int index) {
return ACCESSOR_TYPES.get(index);
}
/**
* Returns the class name for JavaScript objects with fieldCount fields.
*
* @param fieldCount Number of fields to allocate.
*
* @return The class name.
*/
public static String getClassName(final int fieldCount) {
return fieldCount != 0 ? SCRIPTS_PACKAGE + '/' + JS_OBJECT_PREFIX.symbolName() + fieldCount :
SCRIPTS_PACKAGE + '/' + JS_OBJECT_PREFIX.symbolName();
}
/**
* 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
*
* @return The class name.
*/
public static String getClassName(final int fieldCount, final int paramCount) {
return SCRIPTS_PACKAGE + '/' + JS_OBJECT_PREFIX.symbolName() + 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)} or {@link #getClassName(int, int)}.
* @param clazz the JavaScript scope class.
* @return the number of fields in the scope class.
*/
public static int getFieldCount(Class<?> clazz) {
final String name = clazz.getSimpleName();
final String prefix = JS_OBJECT_PREFIX.symbolName();
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 static void initializeToUndefined(final MethodEmitter init, final String className, final List<String> fieldNames) {
if (fieldNames.isEmpty()) {
return;
}
// always initialize fields to undefined, even with --dual-fields. Then it's ok to
// remember things like "widest set type" in properties, and if it's object, don't
// add any special "return undefined" getters, saving an invalidation
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);
final String superName = className(ScriptObject.class);
final ClassEmitter classEmitter = newClassEmitter(className, superName);
final List<String> initFields = addFields(classEmitter, fieldCount);
final MethodEmitter init = newInitMethod(classEmitter);
initializeToUndefined(init, className, initFields);
init.returnVoid();
init.end();
newEmptyInit(classEmitter, className);
newAllocate(classEmitter, className);
return toByteArray(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);
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 initWithArguments = newInitScopeWithArgumentsMethod(classEmitter);
initializeToUndefined(initWithArguments, className, initFields);
initWithArguments.returnVoid();
initWithArguments.end();
return toByteArray(classEmitter);
}
/**
* Generates the needed fields.
*
* @param classEmitter Open class emitter.
* @param fieldCount Number of fields.
*
* @return List fields that need to be initialized.
*/
private static List<String> addFields(final ClassEmitter classEmitter, final int fieldCount) {
final List<String> initFields = new LinkedList<>();
for (int i = 0; i < fieldCount; i++) {
for (final Type type : FIELD_TYPES) {
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.getEnv(), 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;
}
/**
* 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, Object.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, Object.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 ClassEmitter classEmitter, final String className) {
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 ClassEmitter classEmitter, final String className) {
final MethodEmitter allocate = classEmitter.method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), ALLOCATE.symbolName(), ScriptObject.class, PropertyMap.class);
allocate.begin();
allocate._new(className);
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 ClassEmitter classEmitter) {
classEmitter.end();
final byte[] code = classEmitter.toByteArray();
final ScriptEnvironment env = context.getEnv();
if (env._print_code) {
env.getErr().println(ClassEmitter.disassemble(code));
}
if (env._verify_code) {
context.verify(code);
}
return code;
}
/** Double to long bits, used with --dual-fields for primitive double values */
private 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 */
private static MethodHandle UNPACK_DOUBLE =
MH.findStatic(MethodHandles.publicLookup(), Double.class, "longBitsToDouble", MH.type(double.class, long.class));
/** object conversion quickies with JS semantics - used for return value and parameter filter */
private static MethodHandle[] CONVERT_OBJECT = {
JSType.TO_INT32.methodHandle(),
JSType.TO_UINT32.methodHandle(),
JSType.TO_NUMBER.methodHandle(),
null
};
/**
* 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,
* primtives 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
*
* @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 fti = forType == null ? -1 : getAccessorTypeIndex(forType);
final int ti = getAccessorTypeIndex(type);
if (fti == TYPE_OBJECT_INDEX || OBJECT_FIELDS_ONLY) {
if (ti == TYPE_OBJECT_INDEX) {
return objectGetter;
}
return MH.filterReturnValue(objectGetter, CONVERT_OBJECT[ti]);
}
assert !OBJECT_FIELDS_ONLY;
if (forType == null) {
return GET_UNDEFINED[ti];
}
final MethodType pmt = primitiveGetter.type();
switch (fti) {
case TYPE_INT_INDEX:
case TYPE_LONG_INDEX:
switch (ti) {
case TYPE_INT_INDEX:
//get int while an int, truncating cast of long value
return MH.explicitCastArguments(primitiveGetter, pmt.changeReturnType(int.class));
case TYPE_LONG_INDEX:
return primitiveGetter;
default:
return MH.asType(primitiveGetter, pmt.changeReturnType(type));
}
case TYPE_DOUBLE_INDEX:
final MethodHandle getPrimitiveAsDouble = MH.filterReturnValue(primitiveGetter, UNPACK_DOUBLE);
switch (ti) {
case TYPE_INT_INDEX:
case TYPE_LONG_INDEX:
return MH.explicitCastArguments(getPrimitiveAsDouble, pmt.changeReturnType(type));
case TYPE_DOUBLE_INDEX:
return getPrimitiveAsDouble;
default:
return MH.asType(getPrimitiveAsDouble, pmt.changeReturnType(Object.class));
}
default:
assert false;
return null;
}
}
private static final MethodHandle IS_TYPE_GUARD = findOwnMH("isType", boolean.class, Class.class, Object.class);
@SuppressWarnings("unused")
private static boolean isType(final Class<?> boxedForType, final Object x) {
return 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 currenttype, 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);
}
/**
* 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 || OBJECT_FIELDS_ONLY) {
if (ti == TYPE_OBJECT_INDEX) {
return objectSetter;
}
return MH.asType(objectSetter, objectSetter.type().changeParameterType(1, type));
}
assert !OBJECT_FIELDS_ONLY;
final MethodType pmt = primitiveSetter.type();
switch (fti) {
case TYPE_INT_INDEX:
case TYPE_LONG_INDEX:
switch (ti) {
case TYPE_INT_INDEX:
return MH.asType(primitiveSetter, pmt.changeParameterType(1, int.class));
case TYPE_LONG_INDEX:
return primitiveSetter;
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:
assert false;
return null;
}
}
//
// Provide generic getters and setters for undefined types. If a type is undefined, all
// and marshals the set to the correct setter depending on the type of the value being set.
// Note that there are no actual undefined versions of int, long and double in JavaScript,
// but executing toInt32, toLong and toNumber always returns a working result, 0, 0L or NaN
//
/** The value of Undefined cast to an int32 */
public static final int UNDEFINED_INT = 0;
/** The value of Undefined cast to a long */
public static final long UNDEFINED_LONG = 0L;
/** The value of Undefined cast to a double */
public static final double UNDEFINED_DOUBLE = Double.NaN;
/**
* Compute type name for correct undefined getter
* @param type the type
* @return name of getter
*/
private static String typeName(final Type type) {
String name = type.getTypeClass().getName();
final int dot = name.lastIndexOf('.');
if (dot != -1) {
name = name.substring(dot + 1);
}
return Character.toUpperCase(name.charAt(0)) + name.substring(1);
}
/**
* Handles for undefined getters of the different types
*/
private static final MethodHandle[] GET_UNDEFINED = new MethodHandle[ObjectClassGenerator.getNumberOfAccessorTypes()];
/**
* Used to wrap getters for undefined values, where this matters. Currently only in dual fields.
* If an object starts out as undefined it needs special getters until it has been assigned
* something the first time
*
* @param returnType type to cast the undefined to
*
* @return undefined as returnType
*/
public static MethodHandle getUndefined(final Class<?> returnType) {
return GET_UNDEFINED[ObjectClassGenerator.getAccessorTypeIndex(returnType)];
}
static {
int pos = 0;
for (final Type type : ACCESSOR_TYPES) {
GET_UNDEFINED[pos++] = findOwnMH("getUndefined" + typeName(type), type.getTypeClass(), Object.class);
}
}
@SuppressWarnings("unused")
private static int getUndefinedInt(final Object obj) {
return UNDEFINED_INT;
}
@SuppressWarnings("unused")
private static long getUndefinedLong(final Object obj) {
return UNDEFINED_LONG;
}
@SuppressWarnings("unused")
private static double getUndefinedDouble(final Object obj) {
return UNDEFINED_DOUBLE;
}
@SuppressWarnings("unused")
private static Object getUndefinedObject(final Object obj) {
return ScriptRuntime.UNDEFINED;
}
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));
}
}