Skip to content

Commit

Permalink
Fixed EsotericSoftware#180. Added support for field annotations.
Browse files Browse the repository at this point in the history
  • Loading branch information
romix committed Jan 25, 2014
1 parent 1603b8a commit c8b6367
Show file tree
Hide file tree
Showing 4 changed files with 374 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@

package com.esotericsoftware.kryo.serializers;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collection;

Expand Down Expand Up @@ -127,4 +131,34 @@ public Collection copy (Kryo kryo, Collection original) {
copy.add(kryo.copy(element));
return copy;
}

/**
* Used to annotate fields that are collections with specific Kryo serializers
* for their values.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindKryoSerializer {
/**
* Serializer to be used for values
*
* @return the class<? extends Serializer> used for values serialization
*/
@SuppressWarnings("rawtypes")
Class<? extends Serializer> elementSerializer() default Serializer.class;

/**
* Class used for elements
*
* @return the class used for elements
*/
Class<?> elementClass() default Object.class;

/**
* Indicates if elements can be null
*
* @return true, if elements can be null
*/
boolean elementsCanBeNull() default true;
}
}
125 changes: 124 additions & 1 deletion src/com/esotericsoftware/kryo/serializers/FieldSerializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
Expand All @@ -26,6 +27,7 @@
import com.esotericsoftware.kryo.NotNull;
import com.esotericsoftware.kryo.Registration;
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.util.IntArray;
Expand Down Expand Up @@ -224,6 +226,102 @@ protected void rebuildCachedFields () {

for (String fieldName : removedFields.keySet())
removeField(fieldName);

processAnnotatedFields();
}

/**
* Process annotated fields and set serializers according to the
* provided annotation.
*
* @see BindKryoSerializer
* @see CollectionSerializer.BindKryoSerializer
* @see MapSerializer.BindKryoSerializer
*/
protected void processAnnotatedFields () {
for (int i = 0, n = fields.length; i < n; i++) {
Field field = fields[i].getField();

// Set a specific serializer for a particular field
if (field.isAnnotationPresent(FieldSerializer.BindKryoSerializer.class)) {
Class<? extends Serializer> serializerClass = field.getAnnotation(FieldSerializer.BindKryoSerializer.class).value();
Serializer s = ReflectionSerializerFactory.makeSerializer(this.getKryo(), serializerClass, field.getClass());
fields[i].setSerializer(s);
}

if (field.isAnnotationPresent(CollectionSerializer.BindKryoSerializer.class)
&& field.isAnnotationPresent(MapSerializer.BindKryoSerializer.class)) {

}
// Set a specific collection serializer for a particular field
if (field.isAnnotationPresent(CollectionSerializer.BindKryoSerializer.class)) {
if (fields[i].serializer != null)
throw new RuntimeException("CollectionSerialier.BindKryoSerializer cannot be used with field "
+ fields[i].getField().getDeclaringClass().getName() + "." + fields[i].getField().getName()
+ ", because it has a serializer already.");
CollectionSerializer.BindKryoSerializer annotation = field
.getAnnotation(CollectionSerializer.BindKryoSerializer.class);
if (Collection.class.isAssignableFrom(fields[i].field.getType())) {
Class<? extends Serializer> elementSerializerClass = annotation.elementSerializer();
if (elementSerializerClass == Serializer.class) elementSerializerClass = null;
Serializer elementSerializer = (elementSerializerClass == null) ? null : ReflectionSerializerFactory
.makeSerializer(this.getKryo(), elementSerializerClass, field.getClass());
boolean elementsCanBeNull = annotation.elementsCanBeNull();
Class<?> elementClass = annotation.elementClass();
if (elementClass == Object.class) elementClass = null;
CollectionSerializer serializer = new CollectionSerializer();
serializer.setElementsCanBeNull(elementsCanBeNull);
serializer.setElementClass(elementClass, elementSerializer);
fields[i].setSerializer(serializer);
} else {
throw new RuntimeException(
"CollectionSerialier.BindKryoSerializer should be used only with fields implementing java.util.Collection, but field "
+ fields[i].getField().getDeclaringClass().getName() + "." + fields[i].getField().getName()
+ " does not implement it.");
}
}

// Set a specific map serializer for a particular field
if (field.isAnnotationPresent(MapSerializer.BindKryoSerializer.class)) {
if (fields[i].serializer != null)
throw new RuntimeException("MapSerialier.BindKryoSerializer cannot be used with field "
+ fields[i].getField().getDeclaringClass().getName() + "." + fields[i].getField().getName()
+ ", because it has a serializer already.");
MapSerializer.BindKryoSerializer annotation = field.getAnnotation(MapSerializer.BindKryoSerializer.class);
if (Map.class.isAssignableFrom(fields[i].field.getType())) {
Class<? extends Serializer> valueSerializerClass = annotation.valueSerializer();
Class<? extends Serializer> keySerializerClass = annotation.keySerializer();

if (valueSerializerClass == Serializer.class) valueSerializerClass = null;
if (keySerializerClass == Serializer.class) keySerializerClass = null;

Serializer valueSerializer = (valueSerializerClass == null) ? null : ReflectionSerializerFactory.makeSerializer(
this.getKryo(), valueSerializerClass, field.getClass());
Serializer keySerializer = (keySerializerClass == null) ? null : ReflectionSerializerFactory.makeSerializer(
this.getKryo(), keySerializerClass, field.getClass());
boolean valuesCanBeNull = annotation.valuesCanBeNull();
boolean keysCanBeNull = annotation.keysCanBeNull();
Class<?> keyClass = annotation.keyClass();
Class<?> valueClass = annotation.valueClass();

if (keyClass == Object.class) keyClass = null;
if (valueClass == Object.class) valueClass = null;

MapSerializer serializer = new MapSerializer();
serializer.setKeysCanBeNull(keysCanBeNull);
serializer.setValuesCanBeNull(valuesCanBeNull);
serializer.setKeyClass(keyClass, keySerializer);
serializer.setValueClass(valueClass, valueSerializer);
fields[i].setSerializer(serializer);
} else {
throw new RuntimeException(
"MapSerialier.BindKryoSerializer should be used only with fields implementing java.util.Map, but field "
+ fields[i].getField().getDeclaringClass().getName() + "." + fields[i].getField().getName()
+ " does not implement it.");
}
}

}
}

private List<Field> buildValidFields (boolean transientFields, List<Field> allFields, ObjectMap context, IntArray useAsm) {
Expand Down Expand Up @@ -524,10 +622,14 @@ public void removeField (String fieldName) {
throw new IllegalArgumentException("Field \"" + fieldName + "\" 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;
}

public Class getType () {
return type;
}
Expand Down Expand Up @@ -603,6 +705,10 @@ public void setClass (Class valueClass, Serializer serializer) {
public void setSerializer (Serializer serializer) {
this.serializer = serializer;
}

public Serializer getSerializer() {
return this.serializer;
}

public void setCanBeNull (boolean canBeNull) {
this.canBeNull = canBeNull;
Expand Down Expand Up @@ -637,4 +743,21 @@ public static interface CachedFieldFactory {
static public @interface Optional {
public String value();
}

/**
* 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();

}
}
57 changes: 57 additions & 0 deletions src/com/esotericsoftware/kryo/serializers/MapSerializer.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@

package com.esotericsoftware.kryo.serializers;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
Expand Down Expand Up @@ -154,4 +158,57 @@ public Map copy (Kryo kryo, Map original) {
}
return copy;
}

/**
* Used to annotate fields that are maps with specific Kryo serializers for
* their keys or values.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindKryoSerializer {

/**
* Serializer to be used for keys
*
* @return the class<? extends serializer> used for keys serialization
*/
@SuppressWarnings("rawtypes")
Class<? extends Serializer> keySerializer() default Serializer.class;

/**
* Serializer to be used for values
*
* @return the class<? extends serializer> used for values serialization
*/
@SuppressWarnings("rawtypes")
Class<? extends Serializer> valueSerializer() default Serializer.class;

/**
* Class used for keys
*
* @return the class used for keys
*/
Class<?> keyClass() default Object.class;

/**
* Class used for values
*
* @return the class used for values
*/
Class<?> valueClass() default Object.class;

/**
* Indicates if keys can be null
*
* @return true, if keys can be null
*/
boolean keysCanBeNull() default true;

/**
* Indicates if values can be null
*
* @return true, if values can be null
*/
boolean valuesCanBeNull() default true;
}
}
Loading

0 comments on commit c8b6367

Please sign in to comment.