All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.esotericsoftware.kryo.serializers.FieldSerializer Maven / Gradle / Ivy

Go to download

Easy Redis Java client and Real-Time Data Platform. Valkey compatible. Sync/Async/RxJava3/Reactive API. Client side caching. Over 50 Redis based Java objects and services: JCache API, Apache Tomcat, Hibernate, Spring, Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Scheduler, RPC

There is a newer version: 3.40.2
Show newest version
/* Copyright (c) 2008-2023, 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.kryo.util.Util.*;
import static com.esotericsoftware.minlog.Log.*;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.KryoException;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.SerializerFactory;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.util.Generics;
import com.esotericsoftware.kryo.util.Generics.GenericType;
import com.esotericsoftware.kryo.util.Generics.GenericsHierarchy;
import com.esotericsoftware.reflectasm.FieldAccess;

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;

/** Serializes objects using direct field assignment. FieldSerializer is generic and can serialize most classes without any
 * configuration. All non-public fields are written and read by default, so it is important to evaluate each class that will be
 * serialized. If fields are public, serialization may be faster.
 * 

* FieldSerializer is efficient by writing only the field data, without any schema information, using the Java class files as the * schema. It does not support adding, removing, or changing the type of fields without invalidating previously serialized bytes. * Renaming fields is allowed only if it doesn't change the alphabetical order of the fields. *

* FieldSerializer's compatibility drawbacks 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. Subclasses provided more flexible * compatibility. * @see Serializer * @see Kryo#register(Class, Serializer) * @see VersionFieldSerializer * @see TaggedFieldSerializer * @see CompatibleFieldSerializer * @author Nathan Sweet * @author Roman Levenstein */ public class FieldSerializer extends Serializer { final Kryo kryo; final Class type; final FieldSerializerConfig config; final CachedFields cachedFields; private final GenericsHierarchy genericsHierarchy; public FieldSerializer (Kryo kryo, Class type) { this(kryo, type, new FieldSerializerConfig()); } public FieldSerializer (Kryo kryo, Class type, FieldSerializerConfig config) { if (type == null) throw new IllegalArgumentException("type cannot be null."); if (type.isPrimitive()) throw new IllegalArgumentException("type cannot be a primitive class: " + type); if (config == null) throw new IllegalArgumentException("config cannot be null."); this.kryo = kryo; this.type = type; this.config = config; final Generics generics = kryo.getGenerics(); genericsHierarchy = generics.buildHierarchy(type); cachedFields = new CachedFields(this); cachedFields.rebuild(); } /** Called when {@link #getFields()} and {@link #getCopyFields()} have been repopulated. Subclasses can override this method to * configure or remove cached fields. */ protected void initializeCachedFields () { } /** If the returned config settings are modified, {@link #updateFields()} must be called. */ public FieldSerializerConfig getFieldSerializerConfig () { return config; } /** Must be called after {@link #getFieldSerializerConfig()} settings are changed to repopulate the cached fields. */ public void updateFields () { if (TRACE) trace("kryo", "Update fields: " + className(type)); cachedFields.rebuild(); } public void write (Kryo kryo, Output output, T object) { int pop = pushTypeVariables(); CachedField[] fields = cachedFields.fields; for (int i = 0, n = fields.length; i < n; i++) { if (TRACE) log("Write", fields[i], output.position()); try { fields[i].write(output, object); } catch (KryoException e) { throw e; } catch (OutOfMemoryError | Exception e) { throw new KryoException("Error writing " + fields[i] + " at position " + output.position(), e); } } popTypeVariables(pop); } public T read (Kryo kryo, Input input, Class type) { int pop = pushTypeVariables(); T object = create(kryo, input, type); kryo.reference(object); CachedField[] fields = cachedFields.fields; for (int i = 0, n = fields.length; i < n; i++) { if (TRACE) log("Read", fields[i], input.position()); try { fields[i].read(input, object); } catch (KryoException e) { throw e; } catch (OutOfMemoryError | Exception e) { throw new KryoException("Error reading " + fields[i] + " at position " + input.position(), e); } } popTypeVariables(pop); return object; } /** Prepares the type variables for the serialized type. Must be balanced with {@link #popTypeVariables(int)} if >0 is * returned. */ protected int pushTypeVariables () { GenericType[] genericTypes = kryo.getGenerics().nextGenericTypes(); if (genericTypes == null) return 0; int pop = kryo.getGenerics().pushTypeVariables(genericsHierarchy, genericTypes); if (TRACE && pop > 0) trace("kryo", "Generics: " + kryo.getGenerics()); return pop; } protected void popTypeVariables (int pop) { Generics generics = kryo.getGenerics(); if (pop > 0) { generics.popTypeVariables(pop); } generics.popGenericType(); } /** 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); } protected void log (String prefix, CachedField cachedField, int position) { String fieldClassName; if (cachedField instanceof ReflectField) { ReflectField reflectField = (ReflectField)cachedField; Class fieldClass = reflectField.resolveFieldClass(); if (fieldClass == null) fieldClass = cachedField.field.getType(); fieldClassName = simpleName(fieldClass, reflectField.genericType); } else { if (cachedField.valueClass != null) fieldClassName = cachedField.valueClass.getSimpleName(); else fieldClassName = cachedField.field.getType().getSimpleName(); } trace("kryo", prefix + " field " + fieldClassName + ": " + cachedField.name + " (" + className(cachedField.field.getDeclaringClass()) + ')' + pos(position)); } /** Returns the field with the specified name, allowing field specific settings to be configured. */ public CachedField getField (String fieldName) { for (CachedField cachedField : cachedFields.fields) if (cachedField.name.equals(fieldName)) return cachedField; throw new IllegalArgumentException("Field \"" + fieldName + "\" not found on class: " + type.getName()); } /** Removes a field so that it won't be serialized. */ public void removeField (String fieldName) { cachedFields.removeField(fieldName); } /** Removes a field so that it won't be serialized. */ public void removeField (CachedField field) { cachedFields.removeField(field); } /** Returns the fields used for serialization. */ public CachedField[] getFields () { return cachedFields.fields; } /** Returns the fields used for copying. */ public CachedField[] getCopyFields () { return cachedFields.copyFields; } public Class getType () { return type; } public Kryo getKryo () { return kryo; } /** Used by {@link #copy(Kryo, Object)} to create a 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); for (int i = 0, n = cachedFields.copyFields.length; i < n; i++) cachedFields.copyFields[i].copy(original, copy); return copy; } /** Settings for serializing a field. */ public abstract static class CachedField { final Field field; String name; Class valueClass; Serializer serializer; boolean canBeNull, varEncoding = true, optimizePositive, reuseSerializer = true; // For AsmField. FieldAccess access; int accessIndex = -1; // For UnsafeField. long offset; // For TaggedFieldSerializer. int tag; public CachedField (Field field) { this.field = field; } /** The concrete class of the values for this field, or null if it is not known. This saves 1-2 bytes. Only set to a * non-null value if the values for this field are known to be of the specified type (or null). Default is the field type if * it is a primitive, primitive wrapper, or final or if {@link FieldSerializerConfig#setFixedFieldTypes(boolean)} is * true. */ public void setValueClass (Class valueClass) { this.valueClass = valueClass; } /** @return May be null. */ public Class getValueClass () { return valueClass; } /** Sets both {@link #setValueClass(Class)} and {@link #setSerializer(Serializer)}. */ public void setValueClass (Class valueClass, Serializer serializer) { this.valueClass = valueClass; this.serializer = serializer; } /** The serializer to be used for this field, or null to use the serializer registered with {@link Kryo} for the type. Some * serializers require the {@link #setValueClass(Class) value class} to also be set. Default is null. */ public void setSerializer (Serializer serializer) { this.serializer = serializer; } /** @return May be null. */ public Serializer getSerializer () { return this.serializer; } /** When false, it is assumed the field value can never be null. This saves 0-1 bytes. Default is false for primitives, * otherwise {@link FieldSerializerConfig#setFieldsCanBeNull(boolean)} is used unless the field has the {@link NotNull} * annotation. *

* If the field type is a type variable, the default value is used. */ public void setCanBeNull (boolean canBeNull) { this.canBeNull = canBeNull; } public boolean getCanBeNull () { return canBeNull; } /** When true, variable length encoding is used for int or long fields. Default is true. * @see FieldSerializerConfig#setVariableLengthEncoding(boolean) * @see Output#setVariableLengthEncoding(boolean) * @see Input#setVariableLengthEncoding(boolean) */ public void setVariableLengthEncoding (boolean varEncoding) { this.varEncoding = varEncoding; } public boolean getVariableLengthEncoding () { return varEncoding; } /** When true, variable length int and long values are written with fewer bytes for positive values and more bytes for * negative values. Default is false. */ public void setOptimizePositive (boolean optimizePositive) { this.optimizePositive = optimizePositive; } public boolean getOptimizePositive () { return optimizePositive; } /** When true, serializers are re-used for all instances of the field if the {@link #valueClass} is known. Re-using * serializers is significantly faster than looking them up for every read/write. However, this only works reliably when the * {@link #valueClass} of the field never changes. Serializers that do not guarantee this must set the flag to false. */ void setReuseSerializer (boolean reuseSerializer) { this.reuseSerializer = reuseSerializer; } boolean getReuseSerializer () { return reuseSerializer; } public String getName () { return name; } public Field getField () { return field; } public String toString () { return name; } public abstract void write (Output output, Object object); public abstract void read (Input input, Object object); public abstract void copy (Object original, Object copy); } /** 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) public @interface Optional { public String value(); } /** Used to annotate fields with a specific Kryo serializer. * @see CachedField#setSerializer(Serializer) */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Bind { /** @see CachedField#setValueClass(Class) */ Class valueClass() default Object.class; /** The serializer class to serialize the annotated field, which will be created by the {@link #serializerFactory()}. Can be * omitted if the serializer factory knows what type of serializer to create. * @see CachedField#setSerializer(Serializer) */ Class serializer() default Serializer.class; /** The factory used to create the serializer. */ Class serializerFactory() default SerializerFactory.class; /** @see CachedField#setCanBeNull(boolean) */ boolean canBeNull() default true; /** @see CachedField#setVariableLengthEncoding(boolean) */ boolean variableLengthEncoding() default true; /** @see CachedField#setOptimizePositive(boolean) */ boolean optimizePositive() default false; } /** Indicates a field can never be null when it is being serialized and deserialized. Some serializers use this to save space. * Eg, {@link FieldSerializer} may save 1 byte per field. * @author Nathan Sweet */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface NotNull { } /** Configuration for FieldSerializer instances. */ public static class FieldSerializerConfig implements Cloneable { boolean fieldsCanBeNull = true; boolean setFieldsAsAccessible = true; boolean ignoreSyntheticFields = true; boolean fixedFieldTypes; boolean copyTransient = true; boolean serializeTransient; boolean varEncoding = true; boolean extendedFieldNames; public FieldSerializerConfig clone () { try { return (FieldSerializerConfig)super.clone(); // Clone is ok as we have only primitive fields. } catch (CloneNotSupportedException ex) { throw new KryoException(ex); } } /** Sets the default value for {@link FieldSerializer.CachedField#setCanBeNull(boolean)}. * @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) { this.fieldsCanBeNull = fieldsCanBeNull; if (TRACE) trace("kryo", "FieldSerializerConfig fieldsCanBeNull: " + fieldsCanBeNull); } public boolean getFieldsCanBeNull () { return fieldsCanBeNull; } /** Controls which fields are serialized. * @param setFieldsAsAccessible If true, all non-transient fields (inlcuding private fields) will be serialized and * {@link java.lang.reflect.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) { this.setFieldsAsAccessible = setFieldsAsAccessible; if (TRACE) trace("kryo", "FieldSerializerConfig setFieldsAsAccessible: " + setFieldsAsAccessible); } public boolean getSetFieldsAsAccessible () { return setFieldsAsAccessible; } /** Controls if synthetic fields are serialized. Default is true. * @param ignoreSyntheticFields If true, only non-synthetic fields will be serialized. */ public void setIgnoreSyntheticFields (boolean ignoreSyntheticFields) { this.ignoreSyntheticFields = ignoreSyntheticFields; if (TRACE) trace("kryo", "FieldSerializerConfig ignoreSyntheticFields: " + ignoreSyntheticFields); } public boolean getIgnoreSyntheticFields () { return ignoreSyntheticFields; } /** Sets the default value for {@link FieldSerializer.CachedField#setValueClass(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. */ public void setFixedFieldTypes (boolean fixedFieldTypes) { this.fixedFieldTypes = fixedFieldTypes; if (TRACE) trace("kryo", "FieldSerializerConfig fixedFieldTypes: " + fixedFieldTypes); } public boolean getFixedFieldTypes () { return fixedFieldTypes; } /** If false, when {@link Kryo#copy(Object)} is called all transient fields that are accessible will be ignored from being * copied. Default is true. */ public void setCopyTransient (boolean copyTransient) { this.copyTransient = copyTransient; if (TRACE) trace("kryo", "FieldSerializerConfig copyTransient: " + copyTransient); } public boolean getCopyTransient () { return copyTransient; } /** If set, transient fields will be serialized. Default is false. */ public void setSerializeTransient (boolean serializeTransient) { this.serializeTransient = serializeTransient; if (TRACE) trace("kryo", "FieldSerializerConfig serializeTransient: " + serializeTransient); } public boolean getSerializeTransient () { return serializeTransient; } /** When true, variable length values are used for int and long fields. Default is true. * @see CachedField#setVariableLengthEncoding(boolean) * @see Output#setVariableLengthEncoding(boolean) * @see Input#setVariableLengthEncoding(boolean) */ public void setVariableLengthEncoding (boolean varEncoding) { this.varEncoding = varEncoding; if (TRACE) trace("kryo", "FieldSerializerConfig variable length encoding: " + varEncoding); } public boolean getVariableLengthEncoding () { return varEncoding; } /** When true, field names are prefixed by their declaring class. This can avoid conflicts when a subclass has a field with * the same name as a super class. Default is false. */ public void setExtendedFieldNames (boolean extendedFieldNames) { this.extendedFieldNames = extendedFieldNames; if (TRACE) trace("kryo", "FieldSerializerConfig extendedFieldNames: " + extendedFieldNames); } public boolean getExtendedFieldNames () { return extendedFieldNames; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy