diff --git a/src/com/esotericsoftware/kryo/serializers/CachedFields.java b/src/com/esotericsoftware/kryo/serializers/CachedFields.java index 1364a591c..ebb75bc2b 100644 --- a/src/com/esotericsoftware/kryo/serializers/CachedFields.java +++ b/src/com/esotericsoftware/kryo/serializers/CachedFields.java @@ -127,8 +127,11 @@ private void addField (Field field, boolean asm, ArrayList fields, } } - Optional optional = field.getAnnotation(Optional.class); - if (optional != null && !serializer.kryo.getContext().containsKey(optional.value())) return; + Optional[] optionals = field.getAnnotationsByType(Optional.class); + if (optionals.length > 0 && Arrays.stream(optionals).noneMatch( + optional -> serializer.kryo.getContext().containsKey(optional.value()))) { + return; + } if (removedFields.contains(field)) return; diff --git a/src/com/esotericsoftware/kryo/serializers/FieldSerializer.java b/src/com/esotericsoftware/kryo/serializers/FieldSerializer.java index 1bf68d6f1..5e20beac5 100644 --- a/src/com/esotericsoftware/kryo/serializers/FieldSerializer.java +++ b/src/com/esotericsoftware/kryo/serializers/FieldSerializer.java @@ -34,6 +34,7 @@ import com.esotericsoftware.reflectasm.FieldAccess; import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -350,15 +351,25 @@ public String toString () { } + /** Indicates a field should be ignored when its declaring class is registered unless the {@link Kryo#getContext() context} has + * a value set for a key specified by at least one of the {@link Optional} annotations. */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface Optionals { + Optional[] value(); + } + /** 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. + * If a field has multiple of this annotation, then the field is serialized if at least one of the keys is present in the context. * @author Nathan Sweet */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) + @Repeatable(Optionals.class) public @interface Optional { - public String value(); + String value(); } /** Used to annotate fields with a specific Kryo serializer. diff --git a/test/com/esotericsoftware/kryo/serializers/FieldSerializerTest.java b/test/com/esotericsoftware/kryo/serializers/FieldSerializerTest.java index 592f7952b..72e3f8e7a 100644 --- a/test/com/esotericsoftware/kryo/serializers/FieldSerializerTest.java +++ b/test/com/esotericsoftware/kryo/serializers/FieldSerializerTest.java @@ -364,10 +364,35 @@ void testOptionalAnnotation () { kryo.setRegistrationRequired(false); kryo.setReferences(true); roundTrip(82, new HasOptionalAnnotation()); + + kryo = new Kryo(); + kryo.setRegistrationRequired(false); + kryo.setReferences(true); + kryo.getContext().put("smurf", null); + roundTrip(83, new HasOptionalAnnotation()); + + // To verify that more than one matching key is fine + kryo = new Kryo(); + kryo.setRegistrationRequired(false); + kryo.setReferences(true); + kryo.getContext().put("smurf", null); + kryo.getContext().put("another smurf", null); + roundTrip(83, new HasOptionalAnnotation()); + + // To verify that additional keys do not matter kryo = new Kryo(); kryo.setRegistrationRequired(false); kryo.setReferences(true); kryo.getContext().put("smurf", null); + kryo.getContext().put("another smurf", null); + kryo.getContext().put("has nothing to do with it", null); + roundTrip(83, new HasOptionalAnnotation()); + + // To verify that the order of Optional annotations does not matter + kryo = new Kryo(); + kryo.setRegistrationRequired(false); + kryo.setReferences(true); + kryo.getContext().put("another smurf", null); roundTrip(83, new HasOptionalAnnotation()); } @@ -1026,7 +1051,7 @@ public boolean equals (Object obj) { } public static class HasOptionalAnnotation { - @Optional("smurf") int moo; + @Optional("smurf") @Optional("another smurf") int moo; public boolean equals (Object obj) { if (this == obj) return true;