package de.javakaffee.kryoserializers;
import static com.esotericsoftware.minlog.Log.TRACE;
import static com.esotericsoftware.minlog.Log.trace;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.factories.SerializerFactory;
import com.esotericsoftware.kryo.serializers.FieldSerializer;
/**
* A kryo {@link FieldSerializer} that allows to exclusively include or exclude fields that
* are attributed with user-specific annotations. This can be for example useful when serializing beans that carry
* references to a dependency injection framework. As an example for Spring:
* <p/>
* <pre>
* {@code
* Set<Class<? extends Annotation>> marks = new HashSet<>();
* marks.add(Autowired.class);
* SerializerFactory disregardingFactory = new FieldAnnotationAwareSerializer.Factory(marks, true);
* Kryo kryo = new Kryo();
* kryo.setDefaultSerializer(factory);
* }
* </pre>
* <p/>
* The resulting {@link Kryo} instance would ignore all fields that are annotated with Spring's {@code @Autowired}
* annotation.
* <p/>
* Similarly, it is possible to created a serializer which does the opposite such that the resulting serializer
* would only serialize fields that are annotated with the specified annotations.
*
* @author <a href="mailto:rafael.wth@web.de">Rafael Winterhalter</a>
* @author <a href="mailto:martin.grotzke@javakaffee.de">Martin Grotzke</a>
*/
public class FieldAnnotationAwareSerializer<T> extends FieldSerializer<T> {
/**
* A factory for creating instances of {@link FieldAnnotationAwareSerializer}.
*/
public static class Factory implements SerializerFactory {
private final Collection<Class<? extends Annotation>> marked;
private final boolean disregarding;
/**
* Creates a new factory. See {@link FieldAnnotationAwareSerializer#FieldAnnotationAwareSerializer(
*com.esotericsoftware.kryo.Kryo, Class, java.util.Collection, boolean)}
* for additional information on the constructor parameters.
*
* @param marked The annotations that will be considered of the resulting converter.
* @param disregarding If {@code true}, the serializer will ignore all annotated fields,
* if set to {@code false} it will exclusively look at annotated fields.
*/
public Factory(final Collection<Class<? extends Annotation>> marked, final boolean disregarding) {
this.marked = marked;
this.disregarding = disregarding;
}
@Override
public Serializer<?> makeSerializer(final Kryo kryo, final Class<?> type) {
return new FieldAnnotationAwareSerializer<Object>(kryo, type, marked, disregarding);
}
}
private final Set<Class<? extends Annotation>> marked;
/**
* Determines whether annotated fields should be excluded from serialization.
* <p/>
* {@code true} if annotated fields should be excluded from serialization,
* {@code false} if only annotated fields should be included from serialization.
*/
private final boolean disregarding;
/**
* Creates a new field annotation aware serializer.
*
* @param kryo The {@link Kryo} instace.
* @param type The type of the class being serialized.
* @param marked The annotations this serializer considers for its serialization process. Be aware tha
* a serializer with {@code disregarding} set to {@code false} will never be able to
* serialize fields that are not annotated with any of these annotations since it is not
* possible to add fields to a {@link FieldSerializer} once it is created. See the
* documentation to {@link FieldAnnotationAwareSerializer#addAnnotation(Class)} and
* {@link FieldAnnotationAwareSerializer#removeAnnotation(Class)} for further information.
* @param disregarding If {@code true}, the serializer will ignore all annotated fields,
* if set to {@code false} it will exclusively look at annotated fields.
*/
public FieldAnnotationAwareSerializer(final Kryo kryo,
final Class<?> type,
final Collection<Class<? extends Annotation>> marked,
final boolean disregarding) {
super(kryo, type);
this.disregarding = disregarding;
this.marked = new HashSet<Class<? extends Annotation>>(marked);
rebuildCachedFields();
}
@Override
protected void rebuildCachedFields() {
// In order to avoid rebuilding the cached fields twice, the super constructor's call
// to this method will be suppressed. This can be done by a simple check of the initialization
// state of a property of this subclass.
if (marked == null) {
return;
}
super.rebuildCachedFields();
removeFields();
}
private void removeFields() {
final CachedField<?>[] cachedFields = getFields();
for (final CachedField<?> cachedField : cachedFields) {
final Field field = cachedField.getField();
if (isRemove(field)) {
if (TRACE) {
trace("kryo", String.format("Ignoring field %s tag: %s", disregarding ? "without" : "with", cachedField));
}
super.removeField(field.getName());
}
}
}
private boolean isRemove(final Field field) {
return !isMarked(field) ^ disregarding;
}
private boolean isMarked(final Field field) {
for (final Annotation annotation : field.getAnnotations()) {
final Class<? extends Annotation> annotationType = annotation.annotationType();
if (marked.contains(annotationType)) {
return true;
}
}
return false;
}
/**
* Adds an annotation to the annotations that are considered by this serializer.
* <p/>
* <b>Important</b>: This will not have an effect if the serializer was configured
* to exclusively serialize annotated fields by setting {@code disregarding} to
* {@code false}. This is similar to the contract of this serializer's superclass
* {@link FieldSerializer} which does not allow to add fields that were formerly
* removed. If this was possible, instances that were serialized before this field
* was added could not longer be properly deserialized. In order to make this contract
* break explicit, you need to create a new instance of this serializer if you want to
* include new fields to a serializer that exclusively serializes annotated fields.
*
* @param clazz The annotation class to be added.
* @return {@code true} if the method call had an effect.
*/
public boolean addAnnotation(final Class<? extends Annotation> clazz) {
if (disregarding && marked.add(clazz)) {
initializeCachedFields();
return true;
}
return false;
}
/**
* Removes an annotation to the annotations that are considered by this serializer.
* <p/>
* <b>Important</b>: This will not have an effect if the serializer was configured
* to not serialize annotated fields by setting {@code disregarding} to
* {@code true}. This is similar to the contract of this serializer's superclass
* {@link FieldSerializer} which does not allow to add fields that were formerly
* removed. If this was possible, instances that were serialized before this field
* was added could not longer be properly deserialized. In order to make this contract
* break explicit, you need to create a new instance of this serializer if you want to
* include new fields to a serializer that ignores annotated fields for serialization.
*
* @param clazz The annotation class to be removed.
* @return {@code true} if the method call had an effect.
*/
public boolean removeAnnotation(final Class<? extends Annotation> clazz) {
if (!disregarding && marked.remove(clazz)) {
initializeCachedFields();
return true;
}
return false;
}
}