Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to Tag and Bind specific serializers to fields via annotations #180

Closed
pheer opened this issue Jan 18, 2014 · 5 comments
Closed

Comments

@pheer
Copy link

pheer commented Jan 18, 2014

Like TaggedFieldSerializer, but instead also allow an annotation to set a specific serializer annotation.

Something like a change to TaggedFieldSerializer#initializeCachedFields:

protected void initializeCachedFields() {
    CachedField[] fields = getFields();
    // Remove unwanted fields.
    for (int i = 0, n = fields.length; i < n; i++) {
        Field field = fields[i].getField();
        Tag tag = field.getAnnotation(Tag.class);
        Deprecated deprecated = field.getAnnotation(Deprecated.class);
        if (tag == null || deprecated != null) {
            super.removeField(field.getName());
            continue;
        }

        if (field.isAnnotationPresent(BindSerializer.class)) {
            Class<? extends Serializer> serializerClass = field.getAnnotation(BindSerializer.class).value();
            try {
                Constructor<?> constructor = serializerClass.getDeclaredConstructor((Class<?>[]) null);
                constructor.setAccessible(true);
                Serializer s = (Serializer) constructor.newInstance();
                fields[i].setSerializer(s);
            } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
                    | InvocationTargetException | NoSuchMethodException | SecurityException e) {
                logger.error("Unable to create instance of serializer {}", serializerClass);
                continue;
            }

        }
    }
    // Cache tags.
    fields = getFields();
    tags = new int[fields.length];
    for (int i = 0, n = fields.length; i < n; i++)
        tags[i] = fields[i].getField().getAnnotation(Tag.class).value();
}

Use:

@DefaultSerializer(TaggedBindableFieldSerializer.class)
public class Foo {
@tag(0)
@BindSerializer(UriSerializer.class)
private URI sourceLabel;

@Tag(1)
@BindSerializer(KryoUriSerializer.class)
private URI dataDescriptor;

}

Also, it would be nice to actually annotate an interface with @DefaultSerializer. Basically the Kryo#getDefaultSerializer can be updated to traverse up the interfaces. I'd be happy to help with this. let me know your thoughts.

@NathanSweet
Copy link
Member

If we allow fields to be annoted to specify the serializer for that field, probably it should be done for FieldSerializer and all similar classes. We'd probably accept a PR for this. I unfortunately don't have a lot of time to work on Kryo. :(

@romix
Copy link
Collaborator

romix commented Jan 24, 2014

I agree. It is FieldSerializer and classes derived from it, who are affected.

For collection classes we already have APIs to set serializers for their elements and keys/values. So, adding this possibility to the FieldSerializer would make this approach more uniform and symmetric.

We may also think about adding special annotations for fields that are collections or maps. Internally they would be mapped to the mentioned existing APIs. E.g. something like

@BindSerializer(keySerializer=KeySerializer.class, valueSerializer=ValueSerializer.class)
 private Map<A, B> myMap;

@NathanSweet
Copy link
Member

In case someone works on this, what name do we want for the annotation? BindSerializer is ok I suppose. Serializer, FieldSerializer, DefaultSerializer are taken. Well, we could allow DefaultSerializer to also target fields, would that make more sense? Or maybe FieldSerializer.BindSerializer (or FieldSerializer.DefaultSerializer?) and MapSerializer.BindSerializer? They aren't the same since MapSerializer needs two.

@magro
Copy link
Collaborator

magro commented Jan 24, 2014

I think Field/MapSerializer.BindSerializer is fine, I just want to throw in the idea to make the relation to kryo more clear in the name - thinking of @XmlJavaTypeAdapter or @JsonSerialize it could be e.g. @KryoSerializer or @KryoSerialization.

@pheer
Copy link
Author

pheer commented Jan 24, 2014

Yeah I agree it should be part of the FieldSerializer. FWIW below is what we ended up doing in our code since we are on Kryo 2.22. we needed backward compatibility support as well. The class is TagBindFieldSerializer. open to better name(s) or other updates. Thanks,
-Sonny

import java.lang.reflect.Field;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.KryoException;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.factories.ReflectionSerializerFactory;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.serializers.FieldSerializer;
import com.esotericsoftware.kryo.serializers.TaggedFieldSerializer;

/**
 * The Class TagBindFieldSerializer. Ported from {@link TaggedFieldSerializer}.
 * An alternative default serializer for Kryo that also processes {@link Tag}
 * and {@link BindKryoSerializer} annotations.
 * 
 * @see FieldSerializer
 * @see TaggedFieldSerializer
 * 
 * @param <T>
 *            the generic type being processed
 */
@SuppressWarnings("rawtypes")
public class TagBindFieldSerializer<T> extends FieldSerializer<T> {

    /** The tag indexes of this type T. */
    private int[] tags;

    /**
     * Instantiates a new tag bind field serializer.
     * 
     * @param kryo
     *            the kryo instance used
     * @param type
     *            the type being processed
     */
    public TagBindFieldSerializer(Kryo kryo, Class type) {
        super(kryo, type);
    }

    /**
     * Handle {@link Tag} and {@link BindKryoSerializer} annotations. Modifies
     * backing fields by removing unwanted fields and setting custom serializers
     * if set by {@link BindKryoSerializer}
     * 
     * @see com.esotericsoftware.kryo.serializers.FieldSerializer#initializeCachedFields
     *      ()
     */
    protected void initializeCachedFields() {
        CachedField[] fields = getFields();
        // Remove unwanted fields.
        for (int i = 0, n = fields.length; i < n; i++) {
            Field field = fields[i].getField();
            Tag tag = field.getAnnotation(Tag.class);
            Deprecated deprecated = field.getAnnotation(Deprecated.class);
            if (tag == null || deprecated != null) {
                super.removeField(field.getName());
                continue;
            }

            // here we capture a specific serializer for a particular field
            if (field.isAnnotationPresent(BindKryoSerializer.class)) {
                Class<? extends Serializer> serializerClass = field.getAnnotation(BindKryoSerializer.class).value();
                Serializer s = ReflectionSerializerFactory.makeSerializer(super.getKryo(), serializerClass,
                        field.getClass());
                fields[i].setSerializer(s);
            }
        }
        // Cache tags.
        fields = getFields();
        tags = new int[fields.length];
        for (int i = 0, n = fields.length; i < n; i++)
            tags[i] = fields[i].getField().getAnnotation(Tag.class).value();
    }

    /**
     * Writes the object by inspected modified backing fields. write tag index
     * in the beginning.
     * 
     * @see com.esotericsoftware.kryo.serializers.FieldSerializer#write(com.
     *      esotericsoftware.kryo.Kryo, com.esotericsoftware.kryo.io.Output,
     *      java.lang.Object)
     */
    public void write(Kryo kryo, Output output, T object) {
        CachedField[] fields = getFields();
        output.writeInt(fields.length, true);
        for (int i = 0, n = fields.length; i < n; i++) {
            output.writeInt(tags[i], true);
            fields[i].write(output, object);
        }
    }

    /**
     * Process reads based on modified backing fields. Read tag index and match
     * with field tag.
     * 
     * @see com.esotericsoftware.kryo.serializers.FieldSerializer#read(com.
     *      esotericsoftware.kryo.Kryo, com.esotericsoftware.kryo.io.Input,
     *      java.lang.Class)
     */
    public T read(Kryo kryo, Input input, Class<T> type) {
        T object = kryo.newInstance(type);
        kryo.reference(object);
        int fieldCount = input.readInt(true);
        int[] tags = this.tags;
        CachedField[] fields = getFields();
        for (int i = 0, n = fieldCount; i < n; i++) {
            int tag = input.readInt(true);
            CachedField cachedField = null;
            for (int ii = 0, nn = tags.length; ii < nn; ii++) {
                if (tags[ii] == tag) {
                    cachedField = fields[ii];
                    break;
                }
            }
            if (cachedField == null)
                throw new KryoException("Unknown field tag: " + tag + " (" + getType().getName() + ")");
            cachedField.read(input, object);
        }
        return object;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.esotericsoftware.kryo.serializers.FieldSerializer#removeField(java
     * .lang.String)
     */
    public void removeField(String fieldName) {
        super.removeField(fieldName);
        initializeCachedFields();
    }

}

and the annotations

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import com.esotericsoftware.kryo.Serializer;

/**
 * The Annotation BindKryoSerializer. Used to annotate fields with a specific
 * Kryo serializer.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindKryoSerializer {

    /**
     * Value.
     * 
     * @return the class<? extends serializer> used for this field
     */
    @SuppressWarnings("rawtypes")
    Class<? extends Serializer> value();

}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * The Interface Tag. Used to annotate fields with an integer index.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Tag {

    /**
     * Value.
     * 
     * @return the index of this field
     */
    int value();
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

4 participants