/* * Copyright (c) 2013-2014, Parallel Universe Software Co. All rights reserved. * * This program and the accompanying materials are dual-licensed under * either the terms of the Eclipse Public License v1.0 as published by * the Eclipse Foundation * * or (per the licensee's choosing) * * under the terms of the GNU Lesser General Public License version 3.0 * as published by the Free Software Foundation. */ package co.paralleluniverse.data.record; import co.paralleluniverse.actors.MutabilityTester; import co.paralleluniverse.concurrent.util.MapUtil; import com.google.common.collect.ImmutableSet; import com.google.common.reflect.TypeToken; import java.lang.invoke.MethodHandle; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Represents a record type, and includes a name, and a list of fields along with their names and types. * <p/> * A new record type must be declared as a static member of a class. The class must only include the definition of a single record type, * and this class is called the type's <i>identifier class</i>, because it is used only to uniquely identify the record type (only its name is used internally). * <p/> * Here's an example record type definition: * * ```java * class A { * public static final RecordType<A> aType = RecordType.newType(A.class); * public static final IntField<A> $id = stateType.intField("id"); * public static final DoubleField<A> $foo = stateType.doubleField("id", Field.TRANSIENT); * public static final ObjectField<A, String> $name = stateType.objectField("name", String.class); * public static final ObjectField<A, List<String>> $emails = stateType.objectField("emails", new TypeToken<List<String>() {}); * } * ``` * * {@code A} is the type's <i>identifier class</i>. The fields are instances of {@link Field} and are, by convention, * given identifiers that begin with a {@code \$} to make it clear that they identify fields rather than values. * <br/> * A new record is instantiated by calling one of the {@code newInstance} methods. * * @author pron */ public class RecordType<R> implements SealedRecordType<R> { private static final Logger LOG = LoggerFactory.getLogger(RecordType.class); public enum Mode { /** * About 2.5 times slower than REFLECTION in Java 7, but doesn't use boxing and doesn't generate garbage. The default. */ METHOD_HANDLE, /** * About 8x slower than UNSAFE and GENERATION */ REFLECTION, /** * Just a little slower than GENERATION. Can only work on fields; doesn't work if there are getters/setters. */ UNSAFE, /** * The fastest method (as fast as direct settings of fields), but can only be used if both the target object's class * as well as the fields or getters/setters are public. */ GENERATION }; private final String name; private final RecordType<? super R> parent; private final List<Field<? super R, ?>> fields; private int fieldIndex; private boolean sealed; private Set<Field<? super R, ?>> fieldSet; private int primitiveIndex; private int primitiveOffset; private int objectIndex; private int objectOffset; private int[] offsets; private final ThreadLocal<Mode> currentMode = new ThreadLocal<Mode>(); private final ClassValue<ClassInfo> vtables; // private static final ConcurrentMap<String, RecordType<?>> loadedTypes = MapUtil.newConcurrentHashMap(); /** * Creates a new record type, possibly extending a super type. * If a supertype is provided, this type inherits its fields. * * @param type the {@link RecordType <i>identifier class</i>} of this record type. * @param parent the super-type of this type (may be {@code null}). */ public static <R> RecordType<R> newType(Class<R> type, RecordType<? super R> parent) { return new RecordType<R>(type, parent); } /** * Creates a new record type with no super type. * * @param type the {@link RecordType <i>identifier class</i>} of this record type. */ public static <R> RecordType<R> newType(Class<R> type) { return newType(type, null); } /** * Returns the {@link RecordType} whose <i>identifier class</i>'s name is the given name. * * @param name the {@link RecordType}'s <i>identifier class</i>'s name * @return the {@link RecordType} whose <i>identifier class</i>'s name is the given name, or {@code null} if no such record type exists. */ public static RecordType<?> forName(String name) throws ClassNotFoundException { return forClass(Class.forName(name)); } /** * Returns the {@link RecordType} whose <i>identifier class</i> is the given class. * * @param type the {@link RecordType}'s <i>identifier class</i> * @return the {@link RecordType} whose <i>identifier class</i> is the given class, or {@code null} if no such record type exists. */ public static <T> RecordType<T> forClass(Class<T> type) { sealClassType(type); return (RecordType<T>) loadedTypes.get(type.getName()); } private static void sealClassType(Class<?> clazz) { java.lang.reflect.Field[] fs = clazz.getDeclaredFields(); try { for (java.lang.reflect.Field f : fs) { if (Modifier.isStatic(f.getModifiers()) && RecordType.class.isAssignableFrom(f.getType())) { f.setAccessible(true); ((RecordType<?>) f.get(null)).seal(); } } } catch (IllegalAccessException e) { throw new RuntimeException(e); } } private static void addType(String name, RecordType<?> type) { for (;;) { RecordType<?> oldType = loadedTypes.get(name); if (oldType == null) { oldType = loadedTypes.putIfAbsent(name, type); if (oldType == null) break; } else if (isCompatible(oldType, type)) { if (loadedTypes.replace(name, oldType, type)) break; } else { throw new RuntimeException("RecordType " + type + " incompatible with an already loaded type of the same name: " + oldType); } } } private static boolean isCompatible(RecordType<?> oldType, RecordType<?> newType) { List<Field> oldFields = new ArrayList<Field>(oldType.fields()); List<Field> newFileds = new ArrayList<Field>(newType.fields()); if (newFileds.size() < oldFields.size()) return false; for (int i = 0; i < oldFields.size(); i++) { if (!oldFields.get(i).equals(newFileds.get(i))) return false; } return true; } /** * Creates a new record type, possibly extending a super type. * If a supertype is provided, this type inherits its fields. * * @param type the {@link RecordType <i>identifier class</i>} of this record type. * @param parent the super-type of this type (may be {@code null}). */ public RecordType(Class<R> type, RecordType<? super R> parent) { this.name = type.getName().intern(); this.parent = parent; if (parent != null) { parent.seal(); this.fields = new ArrayList<Field<? super R, ?>>(parent.fields); this.fieldIndex = parent.fieldIndex; this.primitiveIndex = parent.primitiveIndex; this.primitiveOffset = parent.primitiveOffset; this.objectIndex = parent.objectIndex; this.objectOffset = parent.objectOffset; } else { this.fields = new ArrayList<Field<? super R, ?>>(); this.fieldIndex = 0; } this.vtables = new ClassValue<ClassInfo>() { @Override protected ClassInfo computeValue(Class<?> type) { seal(); return new ClassInfo(currentMode.get(), type, RecordType.this); } }; } /** * Creates a new record type with no super type. * * @param type the {@link RecordType <i>identifier class</i>} of this record type. */ public RecordType(Class<R> type) { this(type, null); } @Override public int hashCode() { int hash = 7; hash = 53 * hash + Objects.hashCode(this.name); return hash; } @Override public boolean equals(Object obj) { if (obj == this) return true; if (obj == null) return false; if (!(obj instanceof RecordType)) return false; final RecordType<R> other = (RecordType<R>) obj; if (!Objects.equals(this.name, other.name)) return false; return true; } @Override public String toString() { return name + fields.toString(); } /** * This type's name */ @Override public String getName() { return name; } /** * Adds a {@code boolean} scalar field to the type * * @param name the field name * @return the field */ public Field.BooleanField<R> booleanField(String name) { return booleanField(name, 0); } /** * Adds a {@code boolean} scalar field to the type * * @param name the field name * @param flags the field's flags * @return the field */ public Field.BooleanField<R> booleanField(String name, int flags) { return addField(new Field.BooleanField<R>(name, -1, flags)); } /** * Adds a {@code byte} scalar field to the type * * @param name the field name * @return the field */ public Field.ByteField<R> byteField(String name) { return byteField(name, 0); } /** * Adds a {@code byte} scalar field to the type * * @param name the field name * @param flags the field's flags * @return the field */ public Field.ByteField<R> byteField(String name, int flags) { return addField(new Field.ByteField<R>(name, -1, flags)); } /** * Adds a {@code short} scalar field to the type * * @param name the field name * @return the field */ public Field.ShortField<R> shortField(String name) { return shortField(name, 0); } /** * Adds a {@code short} scalar field to the type * * @param name the field name * @param flags the field's flags * @return the field */ public Field.ShortField<R> shortField(String name, int flags) { return addField(new Field.ShortField<R>(name, -1, flags)); } /** * Adds an {@code int} scalar field to the type * * @param name the field name * @return the field */ public Field.IntField<R> intField(String name) { return intField(name, 0); } /** * Adds an {@code short} scalar field to the type * * @param name the field name * @param flags the field's flags * @return the field */ public Field.IntField<R> intField(String name, int flags) { return addField(new Field.IntField<R>(name, -1, flags)); } /** * Adds a {@code long} scalar field to the type * * @param name the field name * @return the field */ public Field.LongField<R> longField(String name) { return longField(name, 0); } /** * Adds a {@code long} scalar field to the type * * @param name the field name * @param flags the field's flags * @return the field */ public Field.LongField<R> longField(String name, int flags) { return addField(new Field.LongField<R>(name, -1, flags)); } /** * Adds a {@code float} scalar field to the type * * @param name the field name * @return the field */ public Field.FloatField<R> floatField(String name) { return floatField(name, 0); } /** * Adds a {@code float} scalar field to the type * * @param name the field name * @param flags the field's flags * @return the field */ public Field.FloatField<R> floatField(String name, int flags) { return addField(new Field.FloatField<R>(name, -1, flags)); } /** * Adds a {@code double} scalar field to the type * * @param name the field name * @return the field */ public Field.DoubleField<R> doubleField(String name) { return doubleField(name, 0); } /** * Adds a {@code double} scalar field to the type * * @param name the field name * @param flags the field's flags * @return the field */ public Field.DoubleField<R> doubleField(String name, int flags) { return addField(new Field.DoubleField<R>(name, -1, flags)); } /** * Adds a {@code char} scalar field to the type * * @param name the field name * @return the field */ public Field.CharField<R> charField(String name) { return charField(name, 0); } /** * Adds a {@code char} scalar field to the type * * @param name the field name * @param flags the field's flags * @return the field */ public Field.CharField<R> charField(String name, int flags) { return addField(new Field.CharField<R>(name, -1, flags)); } /** * Adds an {@code Object} scalar field to the type * * @param <V> the fields type * @param name the field name * @param type the type of the field * @return the field */ public <V> Field.ObjectField<R, V> objectField(String name, Class<V> type) { return objectField(name, type, 0); } /** * Adds a {@code Object} scalar field to the type * * @param <V> the fields type * @param name the field name * @param type the type of the field * @param flags the field's flags * @return the field */ public <V> Field.ObjectField<R, V> objectField(String name, Class<V> type, int flags) { return addField(new Field.ObjectField<R, V>(name, checkMutability(type, name), -1, flags)); } /** * Adds an {@code Object} scalar field to the type * * @param <V> the fields type * @param name the field name * @param type the type of the field (as a {@link TypeToken}) * @return the field */ public <V> Field.ObjectField<R, V> objectField(String name, TypeToken<V> type) { return objectField(name, type, 0); } /** * Adds a {@code Object} scalar field to the type * * @param <V> the fields type * @param name the field name * @param type the type of the field (as a {@link TypeToken}) * @param flags the field's flags * @return the field */ public <V> Field.ObjectField<R, V> objectField(String name, TypeToken<V> type, int flags) { return addField(new Field.ObjectField<R, V>(name, checkMutability(type.getRawType(), name), -1, flags)); } /** * Adds a {@code boolean} array field to the type * * @param name the field name * @param length the length of the array * @return the field */ public Field.BooleanArrayField<R> booleanArrayField(String name, int length) { return booleanArrayField(name, length, 0); } /** * Adds a {@code boolean} array field to the type * * @param name the field name * @param length the length of the array * @param flags the field's flags * @return the field */ public Field.BooleanArrayField<R> booleanArrayField(String name, int length, int flags) { return addField(new Field.BooleanArrayField<R>(name, length, -1, flags)); } /** * Adds a {@code byte} array field to the type * * @param name the field name * @param length the length of the array * @return the field */ public Field.ByteArrayField<R> byteArrayField(String name, int length) { return byteArrayField(name, length, 0); } /** * Adds a {@code byte} array field to the type * * @param name the field name * @param length the length of the array * @param flags the field's flags * @return the field */ public Field.ByteArrayField<R> byteArrayField(String name, int length, int flags) { return addField(new Field.ByteArrayField<R>(name, length, -1, flags)); } /** * Adds a {@code short} array field to the type * * @param name the field name * @param length the length of the array * @return the field */ public Field.ShortArrayField<R> shortArrayField(String name, int length) { return shortArrayField(name, length, 0); } /** * Adds a {@code short} array field to the type * * @param name the field name * @param length the length of the array * @param flags the field's flags * @return the field */ public Field.ShortArrayField<R> shortArrayField(String name, int length, int flags) { return addField(new Field.ShortArrayField<R>(name, length, -1, flags)); } /** * Adds an {@code int} array field to the type * * @param name the field name * @param length the length of the array * @return the field */ public Field.IntArrayField<R> intArrayField(String name, int length) { return intArrayField(name, length, 0); } /** * Adds an {@code int} array field to the type * * @param name the field name * @param length the length of the array * @param flags the field's flags * @return the field */ public Field.IntArrayField<R> intArrayField(String name, int length, int flags) { return addField(new Field.IntArrayField<R>(name, length, -1, flags)); } /** * Adds a {@code long} array field to the type * * @param name the field name * @param length the length of the array * @return the field */ public Field.LongArrayField<R> longArrayField(String name, int length) { return longArrayField(name, length, 0); } /** * Adds a {@code long} array field to the type * * @param name the field name * @param length the length of the array * @param flags the field's flags * @return the field */ public Field.LongArrayField<R> longArrayField(String name, int length, int flags) { return addField(new Field.LongArrayField<R>(name, length, -1, flags)); } /** * Adds a {@code float} array field to the type * * @param name the field name * @param length the length of the array * @return the field */ public Field.FloatArrayField<R> floatArrayField(String name, int length) { return floatArrayField(name, length, 0); } /** * Adds a {@code float} array field to the type * * @param name the field name * @param length the length of the array * @param flags the field's flags * @return the field */ public Field.FloatArrayField<R> floatArrayField(String name, int length, int flags) { return addField(new Field.FloatArrayField<R>(name, length, -1, flags)); } /** * Adds a {@code double} array field to the type * * @param name the field name * @param length the length of the array * @return the field */ public Field.DoubleArrayField<R> doubleArrayField(String name, int length) { return doubleArrayField(name, length, 0); } /** * Adds a {@code double} array field to the type * * @param name the field name * @param length the length of the array * @param flags the field's flags * @return the field */ public Field.DoubleArrayField<R> doubleArrayField(String name, int length, int flags) { return addField(new Field.DoubleArrayField<R>(name, length, -1, flags)); } /** * Adds a {@code char} array field to the type * * @param name the field name * @param length the length of the array * @return the field */ public Field.CharArrayField<R> charArrayField(String name, int length) { return charArrayField(name, length, 0); } /** * Adds a {@code char} array field to the type * * @param name the field name * @param length the length of the array * @param flags the field's flags * @return the field */ public Field.CharArrayField<R> charArrayField(String name, int length, int flags) { return addField(new Field.CharArrayField<R>(name, length, -1, flags)); } /** * Adds an {@code Object} array field to the type * * @param <V> the field's element type * @param name the field name * @param type the type of the field * @param length the length of the array * @return the field */ public <V> Field.ObjectArrayField<R, V> objectArrayField(String name, Class<V> type, int length) { return objectArrayField(name, type, length, 0); } /** * Adds an {@code Object} array field to the type * * @param <V> the field's element type * @param name the field name * @param type the type of the field * @param length the length of the array * @param flags the field's flags * @return the field */ public <V> Field.ObjectArrayField<R, V> objectArrayField(String name, Class<V> type, int length, int flags) { return addField(new Field.ObjectArrayField<R, V>(name, checkMutability(type, name), length, -1, flags)); } private <T> Class<T> checkMutability(Class<T> clazz, String fieldName) { if (Record.class.isAssignableFrom(clazz)) return clazz; List<java.lang.reflect.Field> mutableFields = new ArrayList<>(); for (java.lang.reflect.Field f : clazz.getFields()) { if (!Modifier.isStatic(f.getModifiers()) && !Modifier.isFinal(f.getModifiers())) mutableFields.add(f); } Pattern setterPattern = Pattern.compile("set([^a-z].*)?"); List<Method> setters = new ArrayList<>(); for (Method m : clazz.getMethods()) { if (!Modifier.isStatic(m.getModifiers()) && setterPattern.matcher(m.getName()).matches()) setters.add(m); } if (!mutableFields.isEmpty() || !setters.isEmpty()) { StringBuilder sb = new StringBuilder("WARNING: Field " + fieldName + " of class " + clazz.getName() + " of record type " + name + " appears to be mutable."); if (!mutableFields.isEmpty()) sb.append(' ').append("Public non-final fields: ").append(mutableFields).append('.'); if (!setters.isEmpty()) sb.append(' ').append("Public setters: ").append(setters).append('.'); LOG.warn(sb.toString()); } MutabilityTester.testMutability(clazz); return clazz; } private <F extends Field<R, ?>> F addField(F field) { if (sealed) throw new IllegalStateException("Cannot add fields once a record has been instantiated"); assert field.id < 0; final int id = fieldIndex; this.fieldIndex++; final Field<R, ?> f; switch (field.type()) { case Field.BOOLEAN: f = Field.booleanField(field.name(), id, field.flags()); break; case Field.BYTE: f = Field.byteField(field.name(), id, field.flags()); break; case Field.SHORT: f = Field.shortField(field.name(), id, field.flags()); break; case Field.INT: f = Field.intField(field.name(), id, field.flags()); break; case Field.LONG: f = Field.longField(field.name(), id, field.flags()); break; case Field.FLOAT: f = Field.floatField(field.name(), id, field.flags()); break; case Field.DOUBLE: f = Field.doubleField(field.name(), id, field.flags()); break; case Field.CHAR: f = Field.charField(field.name(), id, field.flags()); break; case Field.BOOLEAN_ARRAY: f = Field.booleanArrayField(field.name(), ((Field.ArrayField<R, ?>) field).length, id, field.flags()); break; case Field.BYTE_ARRAY: f = Field.byteArrayField(field.name(), ((Field.ArrayField<R, ?>) field).length, id, field.flags()); break; case Field.SHORT_ARRAY: f = Field.shortArrayField(field.name(), ((Field.ArrayField<R, ?>) field).length, id, field.flags()); break; case Field.INT_ARRAY: f = Field.intArrayField(field.name(), ((Field.ArrayField<R, ?>) field).length, id, field.flags()); break; case Field.LONG_ARRAY: f = Field.longArrayField(field.name(), ((Field.ArrayField<R, ?>) field).length, id, field.flags()); break; case Field.FLOAT_ARRAY: f = Field.floatArrayField(field.name(), ((Field.ArrayField<R, ?>) field).length, id, field.flags()); break; case Field.DOUBLE_ARRAY: f = Field.doubleArrayField(field.name(), ((Field.ArrayField<R, ?>) field).length, id, field.flags()); break; case Field.CHAR_ARRAY: f = Field.charArrayField(field.name(), ((Field.ArrayField<R, ?>) field).length, id, field.flags()); break; case Field.OBJECT: f = Field.objectField(field.name(), (Class) field.typeClass(), id, field.flags()); break; case Field.OBJECT_ARRAY: f = Field.objectArrayField(field.name(), field.typeClass().getComponentType(), ((Field.ArrayField<R, ?>) field).length, id, field.flags()); break; default: throw new AssertionError(); } fields.add(f); return (F) f; } private void seal() { if (!sealed) { this.sealed = true; this.fieldSet = (Set) ImmutableSet.copyOf(fields); this.offsets = new int[fields.size()]; for (Field<?, ?> field : fields) { final int offset; if (field.type() == Field.OBJECT || field.type() == Field.OBJECT_ARRAY) { offset = objectOffset; objectOffset += (field instanceof Field.ArrayField ? ((Field.ArrayField) field).length : 1); objectIndex++; } else { offset = primitiveOffset; primitiveOffset += field.size(); primitiveIndex++; } offsets[field.id()] = offset; } addType(name, this); } } static class ClassInfo { final Entry[] table; final Mode mode; final Set<Field<?, ?>> fieldSet; private ClassInfo(Mode mode, Class<?> type, RecordType<?> recordType) { try { final Collection<? extends Field<?, ?>> fields = recordType.fields(); if (mode == null) { boolean unsafePossible = true; boolean generationPossible = true; for (Field<?, ?> field : fields) { final Method getter = field instanceof Field.ArrayField ? getIndexedGetter(type, field) : getGetter(type, field); final Method setter = field instanceof Field.ArrayField ? getIndexedSetter(type, field) : getSetter(type, field); final java.lang.reflect.Field f = getter == null ? getField(type, field) : null; if (f == null && getter == null) throw new RuntimeException("Field " + field.name() + " defined in record type " + recordType + " is neither an instance field or a getter of class " + type.getName()); if (f == null && getter != null) unsafePossible = false; if (getter == null && ((f.getModifiers() & Modifier.PUBLIC) == 0)) generationPossible = false; } if (unsafePossible) mode = Mode.UNSAFE; else if (generationPossible) mode = Mode.GENERATION; else mode = Mode.METHOD_HANDLE; } this.mode = mode; this.table = new Entry[fields.size()]; final List<Field<?, ?>> implementedFields = new ArrayList<>(); for (Field<?, ?> field : fields) { final Method getter = field instanceof Field.ArrayField ? getIndexedGetter(type, field) : getGetter(type, field); final Method setter = field instanceof Field.ArrayField ? getIndexedSetter(type, field) : getSetter(type, field); final java.lang.reflect.Field f = (getter == null ? getField(type, field) : null); final boolean indexed = f == null && field instanceof Field.ArrayField; final MethodHandle getterHandle; final MethodHandle setterHandle; if (mode == Mode.METHOD_HANDLE) { getterHandle = DynamicMethodHandleRecord.getGetterMethodHandle(field, f, getter); setterHandle = DynamicMethodHandleRecord.getSetterMethodHandle(field, f, setter); } else { getterHandle = null; setterHandle = null; } final long offset; if (mode == Mode.UNSAFE) { if (f == null && getter != null) throw new RuntimeException("Cannot use UNSAFE mode for class " + type.getName() + " because field " + field.name + " has a getter and/or a setter"); offset = DynamicUnsafeRecord.getFieldOffset(type, f); } else offset = -1L; final DynamicGeneratedRecord.Accessor accessor; if (mode == Mode.GENERATION) { if ((type.getModifiers() & Modifier.PUBLIC) == 0) throw new RuntimeException("Cannot use GENERATION mode because class " + type.getName() + " is not public."); if (f != null && (f.getModifiers() & Modifier.PUBLIC) == 0) throw new RuntimeException("Cannot use GENERATION mode because field " + f.getName() + " in class " + type.getName() + " is not public."); accessor = DynamicGeneratedRecord.generateAccessor(type, field, f, getter, setter); } else accessor = null; if (f != null || getter != null) implementedFields.add(field); table[field.id()] = new Entry(f, getter, setter, getterHandle, setterHandle, offset, accessor, indexed); } this.fieldSet = (Set) ImmutableSet.copyOf(implementedFields); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } } } private static java.lang.reflect.Field getField(Class<?> type, Field field) { java.lang.reflect.Field f = null; try { f = type.getField(field.name()); } catch (NoSuchFieldException e) { } try { f = type.getDeclaredField(field.name()); } catch (NoSuchFieldException e) { } if (f == null || Modifier.isStatic(f.getModifiers())) return null; f.setAccessible(true); return f; } private static Method getGetter(Class<?> type, Field field) { Method m = null; try { m = type.getMethod("get" + capitalize(field.name())); } catch (NoSuchMethodException e) { } if (m == null && field.type() == Field.BOOLEAN) { try { m = type.getMethod("is" + capitalize(field.name())); } catch (NoSuchMethodException e) { } } if (m == null || Modifier.isStatic(m.getModifiers())) return null; return m; } private static Method getSetter(Class<?> type, Field field) { Method m = null; try { m = type.getMethod("set" + capitalize(field.name()), field.typeClass()); } catch (NoSuchMethodException e) { } if (m == null || Modifier.isStatic(m.getModifiers())) return null; return m; } private static Method getIndexedGetter(Class<?> type, Field field) { assert field instanceof Field.ArrayField; Method m = null; try { m = type.getMethod("get" + capitalize(field.name()), int.class); } catch (NoSuchMethodException e) { } if (m == null && field.type() == Field.BOOLEAN_ARRAY) { try { m = type.getMethod("is" + capitalize(field.name()), int.class); } catch (NoSuchMethodException e) { } } if (m == null || Modifier.isStatic(m.getModifiers())) return null; return m; } private static Method getIndexedSetter(Class<?> type, Field field) { assert field instanceof Field.ArrayField; Method m = null; try { m = type.getMethod("set" + capitalize(field.name()), int.class, field.typeClass().getComponentType()); } catch (NoSuchMethodException e) { } if (m == null || Modifier.isStatic(m.getModifiers())) return null; return m; } private static String capitalize(String str) { return Character.toUpperCase(str.charAt(0)) + str.substring(1); } static class Entry { final java.lang.reflect.Field field; final Method getter; final Method setter; final MethodHandle getterHandle; final MethodHandle setterHandle; final boolean readOnly; final long offset; final DynamicGeneratedRecord.Accessor accessor; final boolean indexed; public Entry(java.lang.reflect.Field field, Method getter, Method setter, MethodHandle getterHandle, MethodHandle setterHandle, long offset, DynamicGeneratedRecord.Accessor accessor, boolean indexed) { this.field = field; this.getter = getter; this.setter = setter; this.getterHandle = getterHandle; this.setterHandle = setterHandle; this.offset = offset; this.accessor = accessor; this.indexed = indexed; this.readOnly = setter == null && (field == null || (!indexed && Modifier.isFinal(field.getModifiers()))); } } /** * The type's fields * * @return the type's fields */ public Set<Field<? super R, ?>> fields() { seal(); return fieldSet; } ClassInfo getClassInfo(Class<?> clazz) { return vtables.get(clazz); } int getPrimitiveIndex() { return primitiveIndex; } int getObjectIndex() { return objectIndex; } int getPrimitiveOffset() { return primitiveOffset; } int getObjectOffset() { return objectOffset; } int[] getOffsets() { return offsets; } /** * Test's whether a record is an instance of this type (or one of its subtypes). * * @param record the record to test * @return {@code true} if {@code record} is an instance of this type (or one of its subtypes); {@code false} otherwise. */ @Override public boolean isInstance(Record<?> record) { for (RecordType<?> t = (RecordType<?>)record.type(); t != null; t = t.parent) { if (this.equals(t)) return true; } return false; } /** * Creates an new record instance of this type. * The returned implementation stores the record in an efficient memory representation. * * @return a newly constructed record of this type. */ @Override public Record<R> newInstance() { seal(); return new SimpleRecord<R>(this); } /** * Wraps a given object with a record instance of this type. * The record's fields are mapped to the target's fields or getters/setters of the same name. * Changes to the record will be reflected in the target object and vice versa. * <p/> * The record's implementation {@link Mode} mode will be the best (fastest) one available for the target's class. * * @param target the POJO to wrap as a record * @return a newly constructed record of this type, which reflects {@code target}. */ @Override public Record<R> wrap(Object target) { return wrap(target, null); } /** * Wraps a given object with a record instance of this type. * The record's fields are mapped to the target's fields or getters/setters of the same name. * Changes to the record will be reflected in the target object and vice versa. * * @param target the POJO to wrap as a record * @param mode the record's implementation {@link Mode} mode. * @return a newly constructed record of this type, which reflects {@code target}. */ @Override public Record<R> wrap(Object target, Mode mode) { seal(); currentMode.set(mode); ClassInfo ci = vtables.get(target.getClass()); if (mode == null) mode = ci.mode; if (mode != Mode.REFLECTION && ci.mode != mode) throw new IllegalStateException("Target's class, " + target.getClass().getName() + ", has been mirrored with a different, incompatible mode, " + ci.mode); switch (mode) { case METHOD_HANDLE: return new DynamicMethodHandleRecord<R>(this, target); case REFLECTION: return new DynamicReflectionRecord<R>(this, target); case UNSAFE: return new DynamicUnsafeRecord<R>(this, target); case GENERATION: return new DynamicGeneratedRecord<R>(this, target); } throw new AssertionError("unreachable"); } /** * Creates an new {@link RecordArray} instance of this type. * The returned implementation stores the record array in an efficient memory representation. * * @return a newly constructed record array of this type. */ @Override public RecordArray<R> newArray(int size) { seal(); return new SimpleRecordArray<R>(this, size); } }