package rocks.inspectit.shared.all.serializer.impl; import static com.esotericsoftware.minlog.Log.TRACE; import static com.esotericsoftware.minlog.Log.trace; import java.lang.reflect.Field; import com.esotericsoftware.kryo.Kryo; import com.esotericsoftware.kryo.io.Input; import com.esotericsoftware.kryo.io.InputChunked; import com.esotericsoftware.kryo.io.Output; import com.esotericsoftware.kryo.io.OutputChunked; import com.esotericsoftware.kryo.serializers.CompatibleFieldSerializer; import com.esotericsoftware.kryo.serializers.FieldSerializer; import com.esotericsoftware.kryo.util.ObjectMap; import rocks.inspectit.shared.all.serializer.schema.ClassSchema; import rocks.inspectit.shared.all.serializer.schema.ClassSchemaManager; /** * This is the custom compatible {@link FieldSerializer} that uses our {@link ClassSchemaManager} to * read the integer marker for each class field. * <p> * This class is similar to the {@link com.esotericsoftware.kryo.serializers.TaggedFieldSerializer} * in the way fields are removed if they don't compile (in our case) with schema given for every * class. The writing and the reading of the fields is similar to the * {@link CompatibleFieldSerializer}. But instead of writing every field name prior to values (as it * is done in the mentioned serializer), we only write the marker for each field that is supplied * from the schema. So instead of having the following sequence of data: <br> * <br> * <i>[field1Name, field2Name, field1Value, field2Value]</i> <br> * <br> * here we do it as:<br> * <br> * <i>[1, 2, field1Value, field2Value]</i> <br> * <br> * This way we have the backward/forward compatibility based on the given schema and in addition * decrease the amount of binary data. This is because we are writing integers that are less than * 128 and occupy only 1 byte and not complete field names. * <p> * <b>IMPORTANT:</b> The class code is copied/taken/based from * <a href="https://github.com/EsotericSoftware/kryo">kryo</a>. Original author is Nathan Sweet. * License info can be found * <a href="https://github.com/EsotericSoftware/kryo/blob/master/license.txt">here</a>. * * @author Nathan Sweet <misc@n4te.com> * @author Ivan Senic * * @param <T> */ @SuppressWarnings({ "rawtypes", "unchecked" }) public class CustomCompatibleFieldSerializer<T> extends FieldSerializer<T> { /** * Markers that will be assigned to each field that should be serialized. */ private int[] fieldMarkers; /** * Class schema that will be use. */ private ClassSchema schema; /** * Default constructor. * * @param kryo * Kryo instance * @param type * Class to be serialized * @param schemaManager * {@link ClassSchemaManager} holding information about values. */ public CustomCompatibleFieldSerializer(Kryo kryo, Class<?> type, ClassSchemaManager schemaManager) { this(kryo, type, schemaManager, false); } /** * Default constructor. * * @param kryo * Kryo instance * @param type * Class to be serialized * @param schemaManager * {@link ClassSchemaManager} holding information about values. * @param useSuperclassSchema * If the superclass schema should be used if the one for the class is not available. */ public CustomCompatibleFieldSerializer(Kryo kryo, Class<?> type, ClassSchemaManager schemaManager, boolean useSuperclassSchema) { super(kryo, type); schema = schemaManager.getSchema(type.getName()); if (useSuperclassSchema && (null == schema)) { Class<?> superclass = type.getSuperclass(); while (null != superclass) { schema = schemaManager.getSchema(superclass.getName()); if (null != schema) { break; } superclass = superclass.getSuperclass(); } } if (schema == null) { throw new IllegalArgumentException("Schema for the class '" + type.getName() + "' does not exists in provided schema manager."); } initializeCachedFields(); } /** * {@inheritDoc} */ @Override protected final void initializeCachedFields() { if (null != schema) { CachedField<?>[] fields = getFields(); // Remove unwanted fields for (com.esotericsoftware.kryo.serializers.FieldSerializer.CachedField<?> field2 : fields) { Field field = field2.getField(); if (null == schema.getFieldMarker(field.getName())) { super.removeField(field.getName()); } } // Cache markers fields = getFields(); fieldMarkers = new int[fields.length]; for (int i = 0, n = fields.length; i < n; i++) { fieldMarkers[i] = schema.getFieldMarker(fields[i].getField().getName()).intValue(); } } } /** * {@inheritDoc} */ @Override public void write(Kryo kryo, Output output, T object) { CachedField[] fields = getFields(); ObjectMap context = kryo.getGraphContext(); if (!context.containsKey(this)) { context.put(this, null); if (TRACE) { trace("kryo", "Write " + fields.length + " field names."); } output.writeInt(fields.length, true); for (int i = 0, n = fields.length; i < n; i++) { // Changed by ISE output.writeInt(fieldMarkers[i], true); } } OutputChunked outputChunked = new OutputChunked(output, 1024); for (com.esotericsoftware.kryo.serializers.FieldSerializer.CachedField field : fields) { field.write(outputChunked, object); outputChunked.endChunks(); } } /** * {@inheritDoc} */ @Override public T read(Kryo kryo, Input input, Class<T> type) { T object = kryo.newInstance(type); kryo.reference(object); ObjectMap context = kryo.getGraphContext(); CachedField[] fields = (CachedField[]) context.get(this); if (fields == null) { int length = input.readInt(true); if (TRACE) { trace("kryo", "Read " + length + " field names."); } // Changed by ISE int[] markers = new int[length]; for (int i = 0; i < length; i++) { markers[i] = input.readInt(true); } fields = new CachedField[length]; CachedField[] allFields = getFields(); outer: for (int i = 0, n = markers.length; i < n; i++) { int fieldMarker = markers[i]; for (int ii = 0, nn = allFields.length; ii < nn; ii++) { if (fieldMarkers[ii] == fieldMarker) { fields[i] = allFields[ii]; continue outer; } } if (TRACE) { trace("kryo", "Ignoring obsolete field with marker: " + fieldMarker); } } context.put(this, fields); } InputChunked inputChunked = new InputChunked(input, 1024); for (com.esotericsoftware.kryo.serializers.FieldSerializer.CachedField cachedField : fields) { if (cachedField == null) { if (TRACE) { trace("kryo", "Skip obsolete field."); } inputChunked.nextChunks(); continue; } cachedField.read(inputChunked, object); inputChunked.nextChunks(); } return object; } }