Skip to content

Commit 7f789b7

Browse files
authored
Cache components, getters and constructors in RecordSerializer (#927)
* Cache components, getters and constructors in `RecordSerializer` * Use `ClassValue` and restore default constructor for backwards compatibility
1 parent 4785d75 commit 7f789b7

File tree

2 files changed

+76
-46
lines changed

2 files changed

+76
-46
lines changed

src/com/esotericsoftware/kryo/serializers/RecordSerializer.java

Lines changed: 75 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@
1919

2020
package com.esotericsoftware.kryo.serializers;
2121

22-
import static com.esotericsoftware.minlog.Log.TRACE;
23-
import static com.esotericsoftware.minlog.Log.trace;
22+
import static com.esotericsoftware.minlog.Log.*;
2423

2524
import com.esotericsoftware.kryo.Kryo;
2625
import com.esotericsoftware.kryo.KryoException;
@@ -69,29 +68,43 @@ public class RecordSerializer<T> extends ImmutableSerializer<T> {
6968
GET_TYPE = getType;
7069
}
7170

71+
private static final ClassValue<Constructor<?>> CONSTRUCTOR = new ClassValue<Constructor<?>>() {
72+
protected Constructor<?> computeValue(Class<?> clazz) {
73+
final RecordComponent[] components = recordComponents(clazz, Comparator.comparing(RecordComponent::index));
74+
return getCanonicalConstructor(clazz, components);
75+
}
76+
};
77+
private static final ClassValue<RecordComponent[]> RECORD_COMPONENTS = new ClassValue<RecordComponent[]>() {
78+
protected RecordComponent[] computeValue(Class<?> type) {
79+
return recordComponents(type, Comparator.comparing(RecordComponent::name));
80+
}
81+
};
82+
7283
private boolean fixedFieldTypes = false;
7384

74-
public RecordSerializer () {
85+
/** @deprecated use {@link #RecordSerializer(Class) instead} */
86+
@Deprecated(forRemoval = true)
87+
public RecordSerializer() {
88+
}
89+
90+
public RecordSerializer (Class<T> clazz) {
91+
if (!isRecord(clazz)) throw new KryoException(clazz + " is not a record");
7592
}
7693

7794
@Override
7895
public void write (Kryo kryo, Output output, T object) {
79-
final Class<?> cls = object.getClass();
80-
if (!isRecord(cls)) {
81-
throw new KryoException(object + " is not a record");
82-
}
83-
for (RecordComponent rc : recordComponents(cls, Comparator.comparing(RecordComponent::name))) {
96+
for (RecordComponent rc : RECORD_COMPONENTS.get(object.getClass())) {
8497
final Class<?> type = rc.type();
8598
final String name = rc.name();
8699
try {
87100
if (TRACE) trace("kryo", "Write property: " + name + " (" + type.getName() + ")");
88101
if (type.isPrimitive()) {
89-
kryo.writeObject(output, componentValue(object, rc));
102+
kryo.writeObject(output, rc.getValue(object));
90103
} else {
91104
if (fixedFieldTypes || kryo.isFinal(type)) {
92-
kryo.writeObjectOrNull(output, componentValue(object, rc), type);
105+
kryo.writeObjectOrNull(output, rc.getValue(object), type);
93106
} else {
94-
kryo.writeClassAndObject(output, componentValue(object, rc));
107+
kryo.writeClassAndObject(output, rc.getValue(object));
95108
}
96109
}
97110
} catch (KryoException ex) {
@@ -107,13 +120,10 @@ public void write (Kryo kryo, Output output, T object) {
107120

108121
@Override
109122
public T read (Kryo kryo, Input input, Class<? extends T> type) {
110-
if (!isRecord(type)) {
111-
throw new KryoException("Not a record (" + type + ")");
112-
}
113-
final RecordComponent[] recordComponents = recordComponents(type, Comparator.comparing(RecordComponent::name));
114-
final Object[] values = new Object[recordComponents.length];
115-
for (int i = 0; i < recordComponents.length; i++) {
116-
final RecordComponent rc = recordComponents[i];
123+
final RecordComponent[] components = RECORD_COMPONENTS.get(type);
124+
final Object[] values = new Object[components.length];
125+
for (int i = 0; i < components.length; i++) {
126+
final RecordComponent rc = components[i];
117127
final String name = rc.name();
118128
final Class<?> rcType = rc.type();
119129
try {
@@ -137,8 +147,7 @@ public T read (Kryo kryo, Input input, Class<? extends T> type) {
137147
throw ex;
138148
}
139149
}
140-
Arrays.sort(recordComponents, Comparator.comparing(RecordComponent::index));
141-
return invokeCanonicalConstructor(type, recordComponents, values);
150+
return invokeCanonicalConstructor(type, values);
142151
}
143152

144153
/** Returns true if, and only if, the given class is a record class. */
@@ -152,15 +161,29 @@ private boolean isRecord (Class<?> type) {
152161

153162
/** A record component, which has a name, a type and an index. The latter is the index of the record components in the class
154163
* file's record attribute, required to invoke the record's canonical constructor . */
155-
final static class RecordComponent {
164+
static final class RecordComponent {
165+
private final Class<?> recordType;
156166
private final String name;
157167
private final Class<?> type;
158168
private final int index;
169+
private final Method getter;
159170

160-
RecordComponent (String name, Class<?> type, int index) {
171+
RecordComponent (Class<?> recordType, String name, Class<?> type, int index) {
172+
this.recordType = recordType;
161173
this.name = name;
162174
this.type = type;
163175
this.index = index;
176+
177+
try {
178+
getter = recordType.getDeclaredMethod(name);
179+
if (!getter.isAccessible()) {
180+
getter.setAccessible(true);
181+
}
182+
} catch (Exception t) {
183+
KryoException ex = new KryoException(t);
184+
ex.addTrace("Could not retrieve record component getter (" + recordType.getName() + ")");
185+
throw ex;
186+
}
164187
}
165188

166189
String name () {
@@ -174,6 +197,16 @@ Class<?> type () {
174197
int index () {
175198
return index;
176199
}
200+
201+
Object getValue (Object recordObject) {
202+
try {
203+
return getter.invoke(recordObject);
204+
} catch (Exception t) {
205+
KryoException ex = new KryoException(t);
206+
ex.addTrace("Could not retrieve record component value (" + recordType.getName() + ")");
207+
throw ex;
208+
}
209+
}
177210
}
178211

179212
/** Returns an ordered array of the record components for the given record class. The order is imposed by the given comparator.
@@ -186,6 +219,7 @@ private static <T> RecordComponent[] recordComponents (Class<T> type,
186219
for (int i = 0; i < rawComponents.length; i++) {
187220
final Object comp = rawComponents[i];
188221
recordComponents[i] = new RecordComponent(
222+
type,
189223
(String)GET_NAME.invoke(comp),
190224
(Class<?>)GET_TYPE.invoke(comp), i);
191225
}
@@ -198,46 +232,42 @@ private static <T> RecordComponent[] recordComponents (Class<T> type,
198232
}
199233
}
200234

201-
/** Retrieves the value of the record component for the given record object. */
202-
private static Object componentValue (Object recordObject,
203-
RecordComponent recordComponent) {
235+
/** Invokes the canonical constructor of a record class with the given argument values. */
236+
private T invokeCanonicalConstructor (Class<? extends T> recordType, Object[] args) {
204237
try {
205-
Method get = recordObject.getClass().getDeclaredMethod(recordComponent.name());
206-
if (!get.canAccess(recordObject)) {
207-
get.setAccessible(true);
208-
}
209-
return get.invoke(recordObject);
238+
return (T) CONSTRUCTOR.get(recordType).newInstance(args);
210239
} catch (Throwable t) {
211240
KryoException ex = new KryoException(t);
212-
ex.addTrace("Could not retrieve record components ("
213-
+ recordObject.getClass().getName() + ")");
241+
ex.addTrace("Could not construct type (" + recordType.getName() + ")");
214242
throw ex;
215243
}
216244
}
217245

218-
/** Invokes the canonical constructor of a record class with the given argument values. */
219-
private static <T> T invokeCanonicalConstructor (Class<T> recordType,
220-
RecordComponent[] recordComponents,
221-
Object[] args) {
246+
private static <T> Constructor<T> getCanonicalConstructor (Class<T> recordType, RecordComponent[] recordComponents) {
222247
try {
223248
Class<?>[] paramTypes = Arrays.stream(recordComponents)
224249
.map(RecordComponent::type)
225250
.toArray(Class<?>[]::new);
226-
Constructor<T> canonicalConstructor;
227-
try {
228-
canonicalConstructor = recordType.getConstructor(paramTypes);
229-
} catch (NoSuchMethodException e) {
230-
canonicalConstructor = recordType.getDeclaredConstructor(paramTypes);
231-
canonicalConstructor.setAccessible(true);
232-
}
233-
return canonicalConstructor.newInstance(args);
251+
return getCanonicalConstructor(recordType, paramTypes);
234252
} catch (Throwable t) {
235253
KryoException ex = new KryoException(t);
236-
ex.addTrace("Could not construct type (" + recordType.getName() + ")");
254+
ex.addTrace("Could not retrieve record canonical constructor (" + recordType.getName() + ")");
237255
throw ex;
238256
}
239257
}
240258

259+
private static <T> Constructor<T> getCanonicalConstructor (Class<T> recordType, Class<?>[] paramTypes)
260+
throws NoSuchMethodException {
261+
Constructor<T> canonicalConstructor;
262+
try {
263+
canonicalConstructor = recordType.getConstructor(paramTypes);
264+
} catch (NoSuchMethodException e) {
265+
canonicalConstructor = recordType.getDeclaredConstructor(paramTypes);
266+
canonicalConstructor.setAccessible(true);
267+
}
268+
return canonicalConstructor;
269+
}
270+
241271
/** Tells the RecordSerializer that all field types are effectively final. This allows the serializer to be more efficient,
242272
* since it knows field values will not be a subclass of their declared type. Default is false. */
243273
public void setFixedFieldTypes (boolean fixedFieldTypes) {

test-jdk14/com/esotericsoftware/kryo/serializers/RecordSerializerTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ public static record RecordWithSuperType(Number n) {}
306306

307307
@Test
308308
void testRecordWithSuperType() {
309-
var rc = new RecordSerializer<RecordWithSuperType>();
309+
var rc = new RecordSerializer<>(RecordWithSuperType.class);
310310
kryo.register(RecordWithSuperType.class, rc);
311311

312312
final var r = new RecordWithSuperType(1L);

0 commit comments

Comments
 (0)