diff --git a/src/com/esotericsoftware/kryo/serializers/CompatibleFieldSerializer.java b/src/com/esotericsoftware/kryo/serializers/CompatibleFieldSerializer.java index 6d9c4c81a..0db3ab386 100644 --- a/src/com/esotericsoftware/kryo/serializers/CompatibleFieldSerializer.java +++ b/src/com/esotericsoftware/kryo/serializers/CompatibleFieldSerializer.java @@ -32,6 +32,9 @@ import com.esotericsoftware.kryo.util.ObjectMap; import com.esotericsoftware.kryo.util.Util; +import java.lang.reflect.Field; +import java.util.HashSet; + /** Serializes objects using direct field assignment, providing both forward and backward compatibility. This means fields can be * added or removed without invalidating previously serialized bytes. Renaming or changing the type of a field is not supported. * Like {@link FieldSerializer}, it can serialize most classes without needing annotations. @@ -58,6 +61,24 @@ public CompatibleFieldSerializer (Kryo kryo, Class type, CompatibleFieldSerializ this.config = config; } + @Override + protected void initializeCachedFields() { + if (config != null && !config.extendedFieldNames) { + final HashSet hashSet = new HashSet(); + CachedField[] fields = cachedFields.fields; + for (int i = 0, n = fields.length; i < n; i++) { + final Field field = fields[i].field; + if (!hashSet.add(field.getName())) { + if (WARN) + warn("Detected duplicate field " + field.getName() + " in class hierarchy " + + field.getDeclaringClass() + + ". Consider enabling FieldSerializerConfig.extendedFieldNames"); + break; + } + } + } + } + @Override public void write (Kryo kryo, Output output, T object) { int pop = pushTypeVariables(); @@ -289,6 +310,12 @@ public boolean getChunkedEncoding () { return chunked; } + @Override + public void setExtendedFieldNames(boolean extendedFieldNames) { + this.extendedFieldNames = extendedFieldNames; + if (TRACE) trace("kryo", "CompatibleFieldSerializerConfig extendedFieldNames: " + extendedFieldNames); + } + /** The maximum size of each chunk for chunked encoding. Default is 1024. */ public void setChunkSize (int chunkSize) { this.chunkSize = chunkSize; diff --git a/test/com/esotericsoftware/kryo/WarnUnregisteredClassesTest.java b/test/com/esotericsoftware/kryo/WarnUnregisteredClassesTest.java index c8df3003d..e3aba5f0e 100644 --- a/test/com/esotericsoftware/kryo/WarnUnregisteredClassesTest.java +++ b/test/com/esotericsoftware/kryo/WarnUnregisteredClassesTest.java @@ -125,7 +125,7 @@ public void write (Kryo kryo, Object object) { output.flush(); } - class LoggerStub extends Logger { + public static class LoggerStub extends Logger { public List levels = new ArrayList(); public List messages = new ArrayList(); diff --git a/test/com/esotericsoftware/kryo/serializers/CompatibleFieldSerializerTest.java b/test/com/esotericsoftware/kryo/serializers/CompatibleFieldSerializerTest.java index 8796ed822..e708772e6 100644 --- a/test/com/esotericsoftware/kryo/serializers/CompatibleFieldSerializerTest.java +++ b/test/com/esotericsoftware/kryo/serializers/CompatibleFieldSerializerTest.java @@ -33,7 +33,10 @@ import java.util.List; import java.util.Objects; +import com.esotericsoftware.kryo.WarnUnregisteredClassesTest; +import com.esotericsoftware.minlog.Log; import org.apache.commons.lang.builder.EqualsBuilder; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -44,6 +47,16 @@ public class CompatibleFieldSerializerTest extends KryoTestCase { supportsCopy = true; } + WarnUnregisteredClassesTest.LoggerStub log; + + @Before + public void setUp() throws Exception { + log = new WarnUnregisteredClassesTest.LoggerStub(); + Log.setLogger(log); + Log.INFO(); + kryo = new Kryo(); + } + @Rule public ExpectedException exceptionRule = ExpectedException.none(); @Test @@ -684,6 +697,47 @@ public int hashCode () { } } + @Test + public void testLogWarningOnDuplicateFieldInClassHierarchy() { + kryo.setReferences(true); + CompatibleFieldSerializer serializer = new CompatibleFieldSerializer(kryo, ClassWithDuplicateField.class); + serializer.getCompatibleFieldSerializerConfig().setChunkedEncoding(true); + serializer.getCompatibleFieldSerializerConfig().setExtendedFieldNames(false); + serializer.updateFields(); + kryo.register(ClassWithDuplicateField.class, serializer); + + final ClassWithDuplicateField duplicateField = new ClassWithDuplicateField(); + roundTrip(31, duplicateField); + assertEquals(1, log.messages.size()); + } + + static class ClassWithDuplicateField extends SuperClassWithDuplicateField { + private Boolean customNote = true; + } + + static class SuperClassWithDuplicateField implements Serializable { + private Boolean customNote = false; + + public SuperClassWithDuplicateField() {} + + public SuperClassWithDuplicateField(Boolean customNote) { + this.customNote = customNote; + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SuperClassWithDuplicateField other = (SuperClassWithDuplicateField)obj; + if (customNote != other.customNote) + return false; + return true; + } + } + public static class ClassWithObjectField { Object value;