From 4064918242e2457189a4adac23aab7a5022faaae Mon Sep 17 00:00:00 2001 From: Thomas Heigl Date: Thu, 29 Jul 2021 11:28:41 +0200 Subject: [PATCH] #847 Ensure that `RecordSerializer` can deal with subtypes --- .../kryo/serializers/RecordSerializer.java | 28 ++++++++++++++++--- .../esotericsoftware/kryo/TestDataJava14.java | 4 +-- .../serializers/RecordSerializerTest.java | 19 ++++++++++++- 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/com/esotericsoftware/kryo/serializers/RecordSerializer.java b/src/com/esotericsoftware/kryo/serializers/RecordSerializer.java index 2cfa5ece9..82b4da9b8 100644 --- a/src/com/esotericsoftware/kryo/serializers/RecordSerializer.java +++ b/src/com/esotericsoftware/kryo/serializers/RecordSerializer.java @@ -69,6 +69,8 @@ public class RecordSerializer extends ImmutableSerializer { GET_TYPE = getType; } + private boolean fixedFieldTypes = false; + public RecordSerializer () { } @@ -83,10 +85,14 @@ public void write (Kryo kryo, Output output, T object) { final String name = rc.name(); try { if (TRACE) trace("kryo", "Write property: " + name + " (" + type.getName() + ")"); - if (rc.type().isPrimitive()) { + if (type.isPrimitive()) { kryo.writeObject(output, componentValue(object, rc)); } else { - kryo.writeObjectOrNull(output, componentValue(object, rc), type); + if (fixedFieldTypes || kryo.isFinal(type)) { + kryo.writeObjectOrNull(output, componentValue(object, rc), type); + } else { + kryo.writeClassAndObject(output, componentValue(object, rc)); + } } } catch (KryoException ex) { ex.addTrace(name + " (" + type.getName() + ")"); @@ -109,11 +115,19 @@ public T read (Kryo kryo, Input input, Class type) { for (int i = 0; i < recordComponents.length; i++) { final RecordComponent rc = recordComponents[i]; final String name = rc.name(); + final Class rcType = rc.type(); try { if (TRACE) trace("kryo", "Read property: " + name + " (" + type.getName() + ")"); // Populate values in the order required by the canonical constructor - values[rc.index()] = rc.type().isPrimitive() ? kryo.readObject(input, rc.type()) - : kryo.readObjectOrNull(input, rc.type()); + if (rcType.isPrimitive()) { + values[rc.index()] = kryo.readObject(input, rcType); + } else { + if (fixedFieldTypes || kryo.isFinal(rcType)) { + values[rc.index()] = kryo.readObjectOrNull(input, rcType); + } else { + values[rc.index()] = kryo.readClassAndObject(input); + } + } } catch (KryoException ex) { ex.addTrace(name + " (" + type.getName() + ")"); throw ex; @@ -214,4 +228,10 @@ private static T invokeCanonicalConstructor (Class recordType, throw ex; } } + + /** Tells the RecordSerializer that all field types are effectively final. This allows the serializer to be more efficient, + * since it knows field values will not be a subclass of their declared type. Default is false. */ + public void setFixedFieldTypes (boolean fixedFieldTypes) { + this.fixedFieldTypes = fixedFieldTypes; + } } diff --git a/test-jdk14/com/esotericsoftware/kryo/TestDataJava14.java b/test-jdk14/com/esotericsoftware/kryo/TestDataJava14.java index dba04c874..945bc414f 100644 --- a/test-jdk14/com/esotericsoftware/kryo/TestDataJava14.java +++ b/test-jdk14/com/esotericsoftware/kryo/TestDataJava14.java @@ -26,11 +26,11 @@ * @author Chris Hegarty */ public class TestDataJava14 extends SerializationCompatTestData.TestData { - public record Rec (byte b, short s, int i, long l, float f, double d, boolean bool, char c, String str, Number[] n) { }; + public record Rec (byte b, short s, int i, long l, float f, double d, boolean bool, char c, String str, Integer[] n) { }; private Rec rec; public TestDataJava14() { - rec = new Rec("b".getBytes()[0], (short)1, 2, 3L, 4.0f, 5.0d, true, 'c', "foo", new Number[]{1,2,3}); + rec = new Rec("b".getBytes()[0], (short)1, 2, 3L, 4.0f, 5.0d, true, 'c', "foo", new Integer[]{1,2,3}); } } diff --git a/test-jdk14/com/esotericsoftware/kryo/serializers/RecordSerializerTest.java b/test-jdk14/com/esotericsoftware/kryo/serializers/RecordSerializerTest.java index a6573a313..861c0f0d3 100644 --- a/test-jdk14/com/esotericsoftware/kryo/serializers/RecordSerializerTest.java +++ b/test-jdk14/com/esotericsoftware/kryo/serializers/RecordSerializerTest.java @@ -19,7 +19,7 @@ package com.esotericsoftware.kryo.serializers; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.*; import com.esotericsoftware.kryo.KryoException; import com.esotericsoftware.kryo.KryoTestCase; @@ -301,4 +301,21 @@ public void testRecordWithParametersReordered2() { roundTrip(6, r2); } + + public static record RecordWithSuperType(Number n) {} + + @Test + void testRecordWithSuperType() { + var rc = new RecordSerializer(); + kryo.register(RecordWithSuperType.class, rc); + + final var r = new RecordWithSuperType(1L); + final var output = new Output(32); + kryo.writeObject(output, r); + final var input = new Input(output.getBuffer(), 0, output.position()); + final var r2 = kryo.readObject(input, RecordWithSuperType.class); + + roundTrip(3, r2); + } + }