19
19
20
20
package com .esotericsoftware .kryo .serializers ;
21
21
22
- import static com .esotericsoftware .minlog .Log .TRACE ;
23
- import static com .esotericsoftware .minlog .Log .trace ;
22
+ import static com .esotericsoftware .minlog .Log .*;
24
23
25
24
import com .esotericsoftware .kryo .Kryo ;
26
25
import com .esotericsoftware .kryo .KryoException ;
@@ -69,29 +68,43 @@ public class RecordSerializer<T> extends ImmutableSerializer<T> {
69
68
GET_TYPE = getType ;
70
69
}
71
70
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
+
72
83
private boolean fixedFieldTypes = false ;
73
84
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" );
75
92
}
76
93
77
94
@ Override
78
95
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 ())) {
84
97
final Class <?> type = rc .type ();
85
98
final String name = rc .name ();
86
99
try {
87
100
if (TRACE ) trace ("kryo" , "Write property: " + name + " (" + type .getName () + ")" );
88
101
if (type .isPrimitive ()) {
89
- kryo .writeObject (output , componentValue (object , rc ));
102
+ kryo .writeObject (output , rc . getValue (object ));
90
103
} else {
91
104
if (fixedFieldTypes || kryo .isFinal (type )) {
92
- kryo .writeObjectOrNull (output , componentValue (object , rc ), type );
105
+ kryo .writeObjectOrNull (output , rc . getValue (object ), type );
93
106
} else {
94
- kryo .writeClassAndObject (output , componentValue (object , rc ));
107
+ kryo .writeClassAndObject (output , rc . getValue (object ));
95
108
}
96
109
}
97
110
} catch (KryoException ex ) {
@@ -107,13 +120,10 @@ public void write (Kryo kryo, Output output, T object) {
107
120
108
121
@ Override
109
122
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 ];
117
127
final String name = rc .name ();
118
128
final Class <?> rcType = rc .type ();
119
129
try {
@@ -137,8 +147,7 @@ public T read (Kryo kryo, Input input, Class<? extends T> type) {
137
147
throw ex ;
138
148
}
139
149
}
140
- Arrays .sort (recordComponents , Comparator .comparing (RecordComponent ::index ));
141
- return invokeCanonicalConstructor (type , recordComponents , values );
150
+ return invokeCanonicalConstructor (type , values );
142
151
}
143
152
144
153
/** Returns true if, and only if, the given class is a record class. */
@@ -152,15 +161,29 @@ private boolean isRecord (Class<?> type) {
152
161
153
162
/** A record component, which has a name, a type and an index. The latter is the index of the record components in the class
154
163
* 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 ;
156
166
private final String name ;
157
167
private final Class <?> type ;
158
168
private final int index ;
169
+ private final Method getter ;
159
170
160
- RecordComponent (String name , Class <?> type , int index ) {
171
+ RecordComponent (Class <?> recordType , String name , Class <?> type , int index ) {
172
+ this .recordType = recordType ;
161
173
this .name = name ;
162
174
this .type = type ;
163
175
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
+ }
164
187
}
165
188
166
189
String name () {
@@ -174,6 +197,16 @@ Class<?> type () {
174
197
int index () {
175
198
return index ;
176
199
}
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
+ }
177
210
}
178
211
179
212
/** 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,
186
219
for (int i = 0 ; i < rawComponents .length ; i ++) {
187
220
final Object comp = rawComponents [i ];
188
221
recordComponents [i ] = new RecordComponent (
222
+ type ,
189
223
(String )GET_NAME .invoke (comp ),
190
224
(Class <?>)GET_TYPE .invoke (comp ), i );
191
225
}
@@ -198,46 +232,42 @@ private static <T> RecordComponent[] recordComponents (Class<T> type,
198
232
}
199
233
}
200
234
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 ) {
204
237
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 );
210
239
} catch (Throwable t ) {
211
240
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 () + ")" );
214
242
throw ex ;
215
243
}
216
244
}
217
245
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 ) {
222
247
try {
223
248
Class <?>[] paramTypes = Arrays .stream (recordComponents )
224
249
.map (RecordComponent ::type )
225
250
.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 );
234
252
} catch (Throwable t ) {
235
253
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 () + ")" );
237
255
throw ex ;
238
256
}
239
257
}
240
258
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
+
241
271
/** Tells the RecordSerializer that all field types are effectively final. This allows the serializer to be more efficient,
242
272
* since it knows field values will not be a subclass of their declared type. Default is false. */
243
273
public void setFixedFieldTypes (boolean fixedFieldTypes ) {
0 commit comments