com.esotericsoftware.kryo.serializers.FieldSerializer Maven / Gradle / Ivy
/* Copyright (c) 2008, Nathan Sweet
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided with the distribution.
* - Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
package com.esotericsoftware.kryo.serializers;
import static com.esotericsoftware.minlog.Log.*;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.NotNull;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.util.IntArray;
import com.esotericsoftware.kryo.util.ObjectMap;
import com.esotericsoftware.kryo.util.Util;
import com.esotericsoftware.reflectasm.FieldAccess;
// BOZO - Make primitive serialization with ReflectASM configurable?
/** Serializes objects using direct field assignment. FieldSerializer is generic and can serialize most classes without any
* configuration. It is efficient and writes only the field data, without any extra information. It does not support adding,
* removing, or changing the type of fields without invalidating previously serialized bytes. This can be acceptable in many
* situations, such as when sending data over a network, but may not be a good choice for long term data storage because the Java
* classes cannot evolve. Because FieldSerializer attempts to read and write non-public fields by default, it is important to
* evaluate each class that will be serialized. If fields are public, bytecode generation will be used instead of reflection.
* @see Serializer
* @see Kryo#register(Class, Serializer)
* @see VersionFieldSerializer
* @see TaggedFieldSerializer
* @see CompatibleFieldSerializer
* @author Nathan Sweet
* @author Roman Levenstein */
public class FieldSerializer extends Serializer implements Comparator {
final Kryo kryo;
final Class type;
/** type variables declared for this type */
final TypeVariable[] typeParameters;
final Class componentType;
protected final FieldSerializerConfig config;
private CachedField[] fields = new CachedField[0];
private CachedField[] transientFields = new CachedField[0];
protected HashSet removedFields = new HashSet();
Object access;
private FieldSerializerUnsafeUtil unsafeUtil;
private FieldSerializerGenericsUtil genericsUtil;
private FieldSerializerAnnotationsUtil annotationsUtil;
/** Concrete classes passed as values for type variables */
private Class[] generics;
private Generics genericsScope;
/** If set, this serializer tries to use a variable length encoding for int and long fields */
private boolean varIntsEnabled;
/** If set, adjacent primitive fields are written in bulk This flag may only work with Oracle JVMs, because they layout
* primitive fields in memory in such a way that primitive fields are grouped together. This option has effect only when used
* with Unsafe-based FieldSerializer.
*
* FIXME: Not all versions of Sun/Oracle JDK properly work with this option. Disable it for now. Later add dynamic checks to
* see if this feature is supported by a current JDK version.
*
*/
private boolean useMemRegions = false;
private boolean hasObjectFields = false;
static CachedFieldFactory asmFieldFactory;
static CachedFieldFactory objectFieldFactory;
static CachedFieldFactory unsafeFieldFactory;
static boolean unsafeAvailable;
static Class> unsafeUtilClass;
static Method sortFieldsByOffsetMethod;
static {
try {
unsafeUtilClass = FieldSerializer.class.getClassLoader().loadClass("com.esotericsoftware.kryo.util.UnsafeUtil");
Method unsafeMethod = unsafeUtilClass.getMethod("unsafe");
sortFieldsByOffsetMethod = unsafeUtilClass.getMethod("sortFieldsByOffset", List.class);
Object unsafe = unsafeMethod.invoke(null);
if (unsafe != null) unsafeAvailable = true;
} catch (Throwable e) {
if (TRACE) trace("kryo", "sun.misc.Unsafe is unavailable.");
}
}
{
varIntsEnabled = true;
if (TRACE) trace("kryo", "Optimize ints: " + varIntsEnabled);
}
public FieldSerializer (Kryo kryo, Class type) {
this(kryo, type, null);
}
public FieldSerializer (Kryo kryo, Class type, Class[] generics) {
this(kryo, type, generics, kryo.getFieldSerializerConfig().clone());
}
protected FieldSerializer (Kryo kryo, Class type, Class[] generics, FieldSerializerConfig config) {
this.config = config;
this.kryo = kryo;
this.type = type;
this.generics = generics;
this.typeParameters = type.getTypeParameters();
if (this.typeParameters == null || this.typeParameters.length == 0)
this.componentType = type.getComponentType();
else
this.componentType = null;
this.genericsUtil = new FieldSerializerGenericsUtil(this);
this.unsafeUtil = FieldSerializerUnsafeUtil.Factory.getInstance(this);
this.annotationsUtil = new FieldSerializerAnnotationsUtil(this);
rebuildCachedFields();
}
/** Called when the list of cached fields must be rebuilt. This is done any time settings are changed that affect which fields
* will be used. It is called from the constructor for FieldSerializer, but not for subclasses. Subclasses must call this from
* their constructor. */
protected void rebuildCachedFields () {
rebuildCachedFields(false);
}
/** Rebuilds the list of cached fields.
* @param minorRebuild if set, processing due to changes in generic type parameters will be optimized */
protected void rebuildCachedFields (boolean minorRebuild) {
/** TODO: Optimize rebuildCachedFields invocations performed due to changes in generic type parameters */
if (TRACE && generics != null) trace("kryo", "Generic type parameters: " + Arrays.toString(generics));
if (type.isInterface()) {
fields = new CachedField[0]; // No fields to serialize.
return;
}
hasObjectFields = false;
if (config.isOptimizedGenerics()) {
// For generic classes, generate a mapping from type variable names to the concrete types
// This mapping is the same for the whole class.
Generics genScope = genericsUtil.buildGenericsScope(type, generics);
genericsScope = genScope;
// Push proper scopes at serializer construction time
if (genericsScope != null) kryo.getGenericsResolver().pushScope(type, genericsScope);
}
List validFields;
List validTransientFields;
IntArray useAsm = new IntArray();
if (!minorRebuild) {
// Collect all fields.
List allFields = new ArrayList();
Class nextClass = type;
while (nextClass != Object.class) {
Field[] declaredFields = nextClass.getDeclaredFields();
if (declaredFields != null) {
for (Field f : declaredFields) {
if (Modifier.isStatic(f.getModifiers())) continue;
allFields.add(f);
}
}
nextClass = nextClass.getSuperclass();
}
ObjectMap context = kryo.getContext();
// Sort fields by their offsets
if (useMemRegions && !config.isUseAsm() && unsafeAvailable) {
try {
Field[] allFieldsArray = (Field[])sortFieldsByOffsetMethod.invoke(null, allFields);
allFields = Arrays.asList(allFieldsArray);
} catch (Exception e) {
throw new RuntimeException("Cannot invoke UnsafeUtil.sortFieldsByOffset()", e);
}
}
// TODO: useAsm is modified as a side effect, this should be pulled out of buildValidFields
// Build a list of valid non-transient fields
validFields = buildValidFields(false, allFields, context, useAsm);
// Build a list of valid transient fields
validTransientFields = buildValidFields(true, allFields, context, useAsm);
// Use ReflectASM for any public fields.
if (config.isUseAsm() && !Util.IS_ANDROID && Modifier.isPublic(type.getModifiers()) && useAsm.indexOf(1) != -1) {
try {
access = FieldAccess.get(type);
} catch (RuntimeException ignored) {
}
}
} else {
// It is a minor rebuild
validFields = buildValidFieldsFromCachedFields(fields, useAsm);
// Build a list of valid transient fields
validTransientFields = buildValidFieldsFromCachedFields(transientFields, useAsm);
}
List cachedFields = new ArrayList(validFields.size());
List cachedTransientFields = new ArrayList(validTransientFields.size());
// Process non-transient fields
createCachedFields(useAsm, validFields, cachedFields, 0);
// Process transient fields
createCachedFields(useAsm, validTransientFields, cachedTransientFields, validFields.size());
Collections.sort(cachedFields, this);
fields = cachedFields.toArray(new CachedField[cachedFields.size()]);
Collections.sort(cachedTransientFields, this);
transientFields = cachedTransientFields.toArray(new CachedField[cachedTransientFields.size()]);
initializeCachedFields();
if (genericsScope != null) kryo.getGenericsResolver().popScope();
if (!minorRebuild) {
for (CachedField field : removedFields)
removeField(field);
}
annotationsUtil.processAnnotatedFields(this);
}
private List buildValidFieldsFromCachedFields (CachedField[] cachedFields, IntArray useAsm) {
ArrayList fields = new ArrayList(cachedFields.length);
for (CachedField f : cachedFields) {
fields.add(f.field);
useAsm.add((f.accessIndex > -1) ? 1 : 0);
}
return fields;
}
private List buildValidFields (boolean transientFields, List allFields, ObjectMap context, IntArray useAsm) {
List result = new ArrayList(allFields.size());
for (int i = 0, n = allFields.size(); i < n; i++) {
Field field = allFields.get(i);
int modifiers = field.getModifiers();
if (Modifier.isTransient(modifiers) != transientFields) continue;
if (Modifier.isStatic(modifiers)) continue;
if (field.isSynthetic() && config.isIgnoreSyntheticFields()) continue;
if (!field.isAccessible()) {
if (!config.isSetFieldsAsAccessible()) continue;
try {
field.setAccessible(true);
} catch (AccessControlException ex) {
continue;
}
}
Optional optional = field.getAnnotation(Optional.class);
if (optional != null && !context.containsKey(optional.value())) continue;
result.add(field);
// BOZO - Must be public?
useAsm
.add(!Modifier.isFinal(modifiers) && Modifier.isPublic(modifiers) && Modifier.isPublic(field.getType().getModifiers())
? 1 : 0);
}
return result;
}
private void createCachedFields (IntArray useAsm, List validFields, List cachedFields, int baseIndex) {
if (config.isUseAsm() || !useMemRegions) {
for (int i = 0, n = validFields.size(); i < n; i++) {
Field field = validFields.get(i);
int accessIndex = -1;
if (access != null && useAsm.get(baseIndex + i) == 1) accessIndex = ((FieldAccess)access).getIndex(field.getName());
cachedFields.add(newCachedField(field, cachedFields.size(), accessIndex));
}
} else {
unsafeUtil.createUnsafeCacheFieldsAndRegions(validFields, cachedFields, baseIndex, useAsm);
}
}
public void setGenerics (Kryo kryo, Class[] generics) {
if (!config.isOptimizedGenerics()) return;
this.generics = generics;
if (typeParameters != null && typeParameters.length > 0) {
// There is no need to rebuild all cached fields from scratch.
// Generic parameter types do not affect the set of fields, offsets of fields,
// transient and non-transient properties. They only affect the type of
// fields and serializers selected for each field.
rebuildCachedFields(true);
}
}
/** Get generic type parameters of the class controlled by this serializer.
* @return generic type parameters or null, if there are none. */
public Class[] getGenerics () {
return generics;
}
protected void initializeCachedFields () {
}
CachedField newCachedField (Field field, int fieldIndex, int accessIndex) {
Class[] fieldClass = new Class[] {field.getType()};
Type fieldGenericType = (config.isOptimizedGenerics()) ? field.getGenericType() : null;
CachedField cachedField;
if (!config.isOptimizedGenerics() || fieldGenericType == fieldClass[0]) {
// For optimized generics this is a field without generic type parameters
if (TRACE) trace("kryo", "Field " + field.getName() + ": " + fieldClass[0]);
cachedField = newMatchingCachedField(field, accessIndex, fieldClass[0], fieldGenericType, null);
} else {
cachedField = genericsUtil.newCachedFieldOfGenericType(field, accessIndex, fieldClass, fieldGenericType);
}
if (cachedField instanceof ObjectField) {
hasObjectFields = true;
}
cachedField.field = field;
cachedField.varIntsEnabled = varIntsEnabled;
if (!config.isUseAsm()) {
cachedField.offset = unsafeUtil.getObjectFieldOffset(field);
}
cachedField.access = (FieldAccess)access;
cachedField.accessIndex = accessIndex;
cachedField.canBeNull = config.isFieldsCanBeNull() && !fieldClass[0].isPrimitive()
&& !field.isAnnotationPresent(NotNull.class);
// Always use the same serializer for this field if the field's class is final.
if (kryo.isFinal(fieldClass[0]) || config.isFixedFieldTypes()) cachedField.valueClass = fieldClass[0];
return cachedField;
}
CachedField newMatchingCachedField (Field field, int accessIndex, Class fieldClass, Type fieldGenericType,
Class[] fieldGenerics) {
CachedField cachedField;
if (accessIndex != -1) {
cachedField = getAsmFieldFactory().createCachedField(fieldClass, field, this);
} else if (!config.isUseAsm()) {
cachedField = getUnsafeFieldFactory().createCachedField(fieldClass, field, this);
} else {
cachedField = getObjectFieldFactory().createCachedField(fieldClass, field, this);
if (config.isOptimizedGenerics()) {
if (fieldGenerics != null)
((ObjectField)cachedField).generics = fieldGenerics;
else if (fieldGenericType != null) {
Class[] cachedFieldGenerics = FieldSerializerGenericsUtil.getGenerics(fieldGenericType, kryo);
((ObjectField)cachedField).generics = cachedFieldGenerics;
if (TRACE) trace("kryo", "Field generics: " + Arrays.toString(cachedFieldGenerics));
}
}
}
return cachedField;
}
private CachedFieldFactory getAsmFieldFactory () {
if (asmFieldFactory == null) asmFieldFactory = new AsmCachedFieldFactory();
return asmFieldFactory;
}
private CachedFieldFactory getObjectFieldFactory () {
if (objectFieldFactory == null) objectFieldFactory = new ObjectCachedFieldFactory();
return objectFieldFactory;
}
private CachedFieldFactory getUnsafeFieldFactory () {
// Use reflection to load UnsafeFieldFactory, so that there is no explicit dependency
// on anything using Unsafe. This is required to make FieldSerializer work on those
// platforms that do not support sun.misc.Unsafe properly.
if (unsafeFieldFactory == null) {
try {
unsafeFieldFactory = (CachedFieldFactory)this.getClass().getClassLoader()
.loadClass("com.esotericsoftware.kryo.serializers.UnsafeCachedFieldFactory").newInstance();
} catch (Exception e) {
throw new RuntimeException("Cannot create UnsafeFieldFactory", e);
}
}
return unsafeFieldFactory;
}
public int compare (CachedField o1, CachedField o2) {
// Fields are sorted by alpha so the order of the data is known.
return getCachedFieldName(o1).compareTo(getCachedFieldName(o2));
}
/** Sets the default value for {@link CachedField#setCanBeNull(boolean)}. Calling this method resets the {@link #getFields()
* cached fields}.
* @param fieldsCanBeNull False if none of the fields are null. Saves 0-1 byte per field. True if it is not known (default). */
public void setFieldsCanBeNull (boolean fieldsCanBeNull) {
config.setFieldsCanBeNull(fieldsCanBeNull);
rebuildCachedFields();
}
/** Controls which fields are serialized. Calling this method resets the {@link #getFields() cached fields}.
* @param setFieldsAsAccessible If true, all non-transient fields (inlcuding private fields) will be serialized and
* {@link Field#setAccessible(boolean) set as accessible} if necessary (default). If false, only fields in the public
* API will be serialized. */
public void setFieldsAsAccessible (boolean setFieldsAsAccessible) {
config.setFieldsAsAccessible(setFieldsAsAccessible);
rebuildCachedFields();
}
/** Controls if synthetic fields are serialized. Default is true. Calling this method resets the {@link #getFields() cached
* fields}.
* @param ignoreSyntheticFields If true, only non-synthetic fields will be serialized. */
public void setIgnoreSyntheticFields (boolean ignoreSyntheticFields) {
config.setIgnoreSyntheticFields(ignoreSyntheticFields);
rebuildCachedFields();
}
/** Sets the default value for {@link CachedField#setClass(Class)} to the field's declared type. This allows FieldSerializer to
* be more efficient, since it knows field values will not be a subclass of their declared type. Default is false. Calling this
* method resets the {@link #getFields() cached fields}. */
public void setFixedFieldTypes (boolean fixedFieldTypes) {
config.setFixedFieldTypes(fixedFieldTypes);
rebuildCachedFields();
}
/** Controls whether ASM should be used. Calling this method resets the {@link #getFields() cached fields}.
* @param setUseAsm If true, ASM will be used for fast serialization. If false, Unsafe will be used (default) */
public void setUseAsm (boolean setUseAsm) {
config.setUseAsm(setUseAsm);
rebuildCachedFields();
}
// Enable/disable copying of transient fields
public void setCopyTransient (boolean setCopyTransient) {
config.setCopyTransient(setCopyTransient);
}
// Enable/disable serialization of transient fields
public void setSerializeTransient (boolean setSerializeTransient) {
config.setSerializeTransient(setSerializeTransient);
}
/** Controls if the serialization of generics should be optimized for smaller size.
*
* Important: This setting changes the serialized representation, so that data can be deserialized only with
* if this setting is the same as it was for serialization.
*
* @param setOptimizedGenerics If true, the serialization of generics will be optimize for smaller size (default: false) */
public void setOptimizedGenerics (boolean setOptimizedGenerics) {
config.setOptimizedGenerics(setOptimizedGenerics);
rebuildCachedFields();
}
/** This method can be called for different fields having the same type. Even though the raw type is the same, if the type is
* generic, it could happen that different concrete classes are used to instantiate it. Therefore, in case of different
* instantiation parameters, the fields analysis should be repeated.
*
* TODO: Cache serializer instances generated for a given set of generic parameters. Reuse it later instead of recomputing
* every time. */
public void write (Kryo kryo, Output output, T object) {
if (TRACE) trace("kryo", "FieldSerializer.write fields of class: " + object.getClass().getName());
if (config.isOptimizedGenerics()) {
if (typeParameters != null && generics != null) {
// Rebuild fields info. It may result in rebuilding the genericScope
rebuildCachedFields();
}
if (genericsScope != null) {
// Push proper scopes at serializer usage time
kryo.getGenericsResolver().pushScope(type, genericsScope);
}
}
CachedField[] fields = this.fields;
for (int i = 0, n = fields.length; i < n; i++)
fields[i].write(output, object);
// Serialize transient fields
if (config.isSerializeTransient()) {
for (int i = 0, n = transientFields.length; i < n; i++)
transientFields[i].write(output, object);
}
if (config.isOptimizedGenerics() && genericsScope != null) {
// Pop the scope for generics
kryo.getGenericsResolver().popScope();
}
}
public T read (Kryo kryo, Input input, Class type) {
try {
if (config.isOptimizedGenerics()) {
if (typeParameters != null && generics != null) {
// Rebuild fields info. It may result in rebuilding the
// genericScope
rebuildCachedFields();
}
if (genericsScope != null) {
// Push a new scope for generics
kryo.getGenericsResolver().pushScope(type, genericsScope);
}
}
T object = create(kryo, input, type);
kryo.reference(object);
CachedField[] fields = this.fields;
for (int i = 0, n = fields.length; i < n; i++)
fields[i].read(input, object);
// De-serialize transient fields
if (config.isSerializeTransient()) {
for (int i = 0, n = transientFields.length; i < n; i++)
transientFields[i].read(input, object);
}
return object;
} finally {
if (config.isOptimizedGenerics() && genericsScope != null && kryo.getGenericsResolver() != null) {
// Pop the scope for generics
kryo.getGenericsResolver().popScope();
}
}
}
/** Used by {@link #read(Kryo, Input, Class)} to create the new object. This can be overridden to customize object creation, eg
* to call a constructor with arguments. The default implementation uses {@link Kryo#newInstance(Class)}. */
protected T create (Kryo kryo, Input input, Class type) {
return kryo.newInstance(type);
}
/** Allows specific fields to be optimized. */
public CachedField getField (String fieldName) {
for (CachedField cachedField : fields)
if (getCachedFieldName(cachedField).equals(fieldName)) return cachedField;
throw new IllegalArgumentException("Field \"" + fieldName + "\" not found on class: " + type.getName());
}
protected String getCachedFieldName (CachedField cachedField) {
return config.getCachedFieldNameStrategy().getName(cachedField);
}
/** Removes a field so that it won't be serialized. */
public void removeField (String fieldName) {
for (int i = 0; i < fields.length; i++) {
CachedField cachedField = fields[i];
if (getCachedFieldName(cachedField).equals(fieldName)) {
CachedField[] newFields = new CachedField[fields.length - 1];
System.arraycopy(fields, 0, newFields, 0, i);
System.arraycopy(fields, i + 1, newFields, i, newFields.length - i);
fields = newFields;
removedFields.add(cachedField);
return;
}
}
for (int i = 0; i < transientFields.length; i++) {
CachedField cachedField = transientFields[i];
if (getCachedFieldName(cachedField).equals(fieldName)) {
CachedField[] newFields = new CachedField[transientFields.length - 1];
System.arraycopy(transientFields, 0, newFields, 0, i);
System.arraycopy(transientFields, i + 1, newFields, i, newFields.length - i);
transientFields = newFields;
removedFields.add(cachedField);
return;
}
}
throw new IllegalArgumentException("Field \"" + fieldName + "\" not found on class: " + type.getName());
}
/** Removes a field so that it won't be serialized. */
public void removeField (CachedField removeField) {
for (int i = 0; i < fields.length; i++) {
CachedField cachedField = fields[i];
if (cachedField == removeField) {
CachedField[] newFields = new CachedField[fields.length - 1];
System.arraycopy(fields, 0, newFields, 0, i);
System.arraycopy(fields, i + 1, newFields, i, newFields.length - i);
fields = newFields;
removedFields.add(cachedField);
return;
}
}
for (int i = 0; i < transientFields.length; i++) {
CachedField cachedField = transientFields[i];
if (cachedField == removeField) {
CachedField[] newFields = new CachedField[transientFields.length - 1];
System.arraycopy(transientFields, 0, newFields, 0, i);
System.arraycopy(transientFields, i + 1, newFields, i, newFields.length - i);
transientFields = newFields;
removedFields.add(cachedField);
return;
}
}
throw new IllegalArgumentException("Field \"" + removeField + "\" not found on class: " + type.getName());
}
/** Get all fields controlled by this FieldSerializer
* @return all fields controlled by this FieldSerializer */
public CachedField[] getFields () {
return fields;
}
/** Get all transient fields controlled by this FieldSerializer
* @return all transient fields controlled by this FieldSerializer */
public CachedField[] getTransientFields () {
return transientFields;
}
public Class getType () {
return type;
}
public Kryo getKryo () {
return kryo;
}
public boolean getUseAsmEnabled () {
return config.isUseAsm();
}
public boolean getUseMemRegions () {
return useMemRegions;
}
public boolean getCopyTransient () {
return config.isCopyTransient();
}
public boolean getSerializeTransient () {
return config.isSerializeTransient();
}
/** Used by {@link #copy(Kryo, Object)} to create the new object. This can be overridden to customize object creation, eg to
* call a constructor with arguments. The default implementation uses {@link Kryo#newInstance(Class)}. */
protected T createCopy (Kryo kryo, T original) {
return (T)kryo.newInstance(original.getClass());
}
public T copy (Kryo kryo, T original) {
T copy = createCopy(kryo, original);
kryo.reference(copy);
// Copy transient fields
if (config.isCopyTransient()) {
for (int i = 0, n = transientFields.length; i < n; i++)
transientFields[i].copy(original, copy);
}
for (int i = 0, n = fields.length; i < n; i++)
fields[i].copy(original, copy);
return copy;
}
final Generics getGenericsScope () {
return genericsScope;
}
/** Controls how a field will be serialized. */
public static abstract class CachedField {
Field field;
FieldAccess access;
Class valueClass;
Serializer serializer;
boolean canBeNull;
int accessIndex = -1;
long offset = -1;
boolean varIntsEnabled = true;
/** @param valueClass The concrete class of the values for this field. This saves 1-2 bytes. The serializer registered for
* the specified class will be used. Only set to a non-null value if the field type in the class definition is
* final or the values for this field will not vary. */
public void setClass (Class valueClass) {
this.valueClass = valueClass;
this.serializer = null;
}
/** @param valueClass The concrete class of the values for this field. This saves 1-2 bytes. Only set to a non-null value if
* the field type in the class definition is final or the values for this field will not vary. */
public void setClass (Class valueClass, Serializer serializer) {
this.valueClass = valueClass;
this.serializer = serializer;
}
public void setSerializer (Serializer serializer) {
this.serializer = serializer;
}
public Serializer getSerializer () {
return this.serializer;
}
public void setCanBeNull (boolean canBeNull) {
this.canBeNull = canBeNull;
}
public Field getField () {
return field;
}
public String toString () {
return field.getName();
}
abstract public void write (Output output, Object object);
abstract public void read (Input input, Object object);
abstract public void copy (Object original, Object copy);
}
public static interface CachedFieldFactory {
public CachedField createCachedField (Class fieldClass, Field field, FieldSerializer ser);
}
public interface CachedFieldNameStrategy {
CachedFieldNameStrategy DEFAULT = new CachedFieldNameStrategy() {
@Override
public String getName (CachedField cachedField) {
return cachedField.field.getName();
}
};
CachedFieldNameStrategy EXTENDED = new CachedFieldNameStrategy() {
@Override
public String getName (CachedField cachedField) {
return cachedField.field.getDeclaringClass().getSimpleName() + "." + cachedField.field.getName();
}
};
String getName (CachedField cachedField);
}
/** Indicates a field should be ignored when its declaring class is registered unless the {@link Kryo#getContext() context} has
* a value set for the specified key. This can be useful when a field must be serialized for one purpose, but not for another.
* Eg, a class for a networked application could have a field that should not be serialized and sent to clients, but should be
* serialized when stored on the server.
* @author Nathan Sweet */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
static public @interface Optional {
public String value();
}
/** Used to annotate fields with a specific Kryo serializer. */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Bind {
/** Value.
*
* @return the class extends serializer> used for this field */
@SuppressWarnings("rawtypes")
Class extends Serializer> value();
}
}