34
34
import java .util .List ;
35
35
import java .util .Map ;
36
36
import java .util .Objects ;
37
+ import java .util .concurrent .TimeUnit ;
37
38
import java .util .stream .Collectors ;
38
39
import javax .annotation .Nonnull ;
39
40
import net .bytebuddy .description .type .TypeDescription .ForLoadedType ;
41
+ import net .bytebuddy .implementation .bytecode .Division ;
40
42
import net .bytebuddy .implementation .bytecode .Duplication ;
43
+ import net .bytebuddy .implementation .bytecode .Multiplication ;
41
44
import net .bytebuddy .implementation .bytecode .StackManipulation ;
42
45
import net .bytebuddy .implementation .bytecode .StackManipulation .Compound ;
43
46
import net .bytebuddy .implementation .bytecode .TypeCreation ;
44
47
import net .bytebuddy .implementation .bytecode .assign .TypeCasting ;
48
+ import net .bytebuddy .implementation .bytecode .constant .LongConstant ;
45
49
import net .bytebuddy .implementation .bytecode .member .MethodInvocation ;
46
50
import net .bytebuddy .matcher .ElementMatchers ;
47
51
import org .apache .avro .AvroRuntimeException ;
144
148
"rawtypes"
145
149
})
146
150
public class AvroUtils {
151
+
147
152
static {
148
153
// This works around a bug in the Avro library (AVRO-1891) around SpecificRecord's handling
149
154
// of DateTime types.
150
155
SpecificData .get ().addLogicalTypeConversion (new AvroCoder .JodaTimestampConversion ());
151
156
GenericData .get ().addLogicalTypeConversion (new AvroCoder .JodaTimestampConversion ());
152
157
}
153
158
159
+ private static final ForLoadedType BYTES = new ForLoadedType (byte [].class );
160
+ private static final ForLoadedType JAVA_INSTANT = new ForLoadedType (java .time .Instant .class );
161
+ private static final ForLoadedType JAVA_LOCALE_DATE =
162
+ new ForLoadedType (java .time .LocalDate .class );
163
+ private static final ForLoadedType JODA_READABLE_INSTANT =
164
+ new ForLoadedType (ReadableInstant .class );
165
+ private static final ForLoadedType JODA_INSTANT = new ForLoadedType (Instant .class );
166
+
154
167
// Unwrap an AVRO schema into the base type an whether it is nullable.
155
168
public static class TypeWithNullability {
156
169
public final org .apache .avro .Schema type ;
@@ -254,7 +267,10 @@ public AvroConvertType(boolean returnRawType) {
254
267
255
268
@ Override
256
269
protected java .lang .reflect .Type convertDefault (TypeDescriptor <?> type ) {
257
- if (type .isSubtypeOf (TypeDescriptor .of (GenericFixed .class ))) {
270
+ if (type .isSubtypeOf (TypeDescriptor .of (java .time .Instant .class ))
271
+ || type .isSubtypeOf (TypeDescriptor .of (java .time .LocalDate .class ))) {
272
+ return convertDateTime (type );
273
+ } else if (type .isSubtypeOf (TypeDescriptor .of (GenericFixed .class ))) {
258
274
return byte [].class ;
259
275
} else {
260
276
return super .convertDefault (type );
@@ -282,10 +298,46 @@ protected StackManipulation convertDefault(TypeDescriptor<?> type) {
282
298
MethodInvocation .invoke (
283
299
new ForLoadedType (GenericFixed .class )
284
300
.getDeclaredMethods ()
285
- .filter (
286
- ElementMatchers .named ("bytes" )
287
- .and (ElementMatchers .returns (new ForLoadedType (byte [].class ))))
301
+ .filter (ElementMatchers .named ("bytes" ).and (ElementMatchers .returns (BYTES )))
288
302
.getOnly ()));
303
+ } else if (java .time .Instant .class .isAssignableFrom (type .getRawType ())) {
304
+ // Generates the following code:
305
+ // return Instant.ofEpochMilli(value.toEpochMilli())
306
+ StackManipulation onNotNull =
307
+ new Compound (
308
+ readValue ,
309
+ MethodInvocation .invoke (
310
+ JAVA_INSTANT
311
+ .getDeclaredMethods ()
312
+ .filter (ElementMatchers .named ("toEpochMilli" ))
313
+ .getOnly ()),
314
+ MethodInvocation .invoke (
315
+ JODA_INSTANT
316
+ .getDeclaredMethods ()
317
+ .filter (
318
+ ElementMatchers .isStatic ().and (ElementMatchers .named ("ofEpochMilli" )))
319
+ .getOnly ()));
320
+ return shortCircuitReturnNull (readValue , onNotNull );
321
+ } else if (java .time .LocalDate .class .isAssignableFrom (type .getRawType ())) {
322
+ // Generates the following code:
323
+ // return Instant.ofEpochMilli(value.toEpochDay() * TimeUnit.DAYS.toMillis(1))
324
+ StackManipulation onNotNull =
325
+ new Compound (
326
+ readValue ,
327
+ MethodInvocation .invoke (
328
+ JAVA_LOCALE_DATE
329
+ .getDeclaredMethods ()
330
+ .filter (ElementMatchers .named ("toEpochDay" ))
331
+ .getOnly ()),
332
+ LongConstant .forValue (TimeUnit .DAYS .toMillis (1 )),
333
+ Multiplication .LONG ,
334
+ MethodInvocation .invoke (
335
+ JODA_INSTANT
336
+ .getDeclaredMethods ()
337
+ .filter (
338
+ ElementMatchers .isStatic ().and (ElementMatchers .named ("ofEpochMilli" )))
339
+ .getOnly ()));
340
+ return shortCircuitReturnNull (readValue , onNotNull );
289
341
}
290
342
return super .convertDefault (type );
291
343
}
@@ -303,25 +355,60 @@ protected TypeConversionsFactory getFactory() {
303
355
304
356
@ Override
305
357
protected StackManipulation convertDefault (TypeDescriptor <?> type ) {
306
- final ForLoadedType byteArrayType = new ForLoadedType (byte [].class );
307
358
if (type .isSubtypeOf (TypeDescriptor .of (GenericFixed .class ))) {
308
359
// Generate the following code:
309
- // return new T((byte[]) value);
360
+ // return new T((byte[]) value);
310
361
ForLoadedType loadedType = new ForLoadedType (type .getRawType ());
311
362
return new Compound (
312
363
TypeCreation .of (loadedType ),
313
364
Duplication .SINGLE ,
314
365
// Load the parameter and cast it to a byte[].
315
366
readValue ,
316
- TypeCasting .to (byteArrayType ),
367
+ TypeCasting .to (BYTES ),
317
368
// Create a new instance that wraps this byte[].
318
369
MethodInvocation .invoke (
319
370
loadedType
320
371
.getDeclaredMethods ()
321
372
.filter (
322
- ElementMatchers .isConstructor ()
323
- .and (ElementMatchers .takesArguments (byteArrayType )))
373
+ ElementMatchers .isConstructor ().and (ElementMatchers .takesArguments (BYTES )))
324
374
.getOnly ()));
375
+ } else if (java .time .Instant .class .isAssignableFrom (type .getRawType ())) {
376
+ // Generates the following code:
377
+ // return java.time.Instant.ofEpochMilli(value.getMillis())
378
+ StackManipulation onNotNull =
379
+ new Compound (
380
+ readValue ,
381
+ MethodInvocation .invoke (
382
+ JODA_READABLE_INSTANT
383
+ .getDeclaredMethods ()
384
+ .filter (ElementMatchers .named ("getMillis" ))
385
+ .getOnly ()),
386
+ MethodInvocation .invoke (
387
+ JAVA_INSTANT
388
+ .getDeclaredMethods ()
389
+ .filter (
390
+ ElementMatchers .isStatic ().and (ElementMatchers .named ("ofEpochMilli" )))
391
+ .getOnly ()));
392
+ return shortCircuitReturnNull (readValue , onNotNull );
393
+ } else if (java .time .LocalDate .class .isAssignableFrom (type .getRawType ())) {
394
+ // Generates the following code:
395
+ // return java.time.LocalDate.ofEpochDay(value.getMillis() / TimeUnit.DAYS.toMillis(1))
396
+ StackManipulation onNotNull =
397
+ new Compound (
398
+ readValue ,
399
+ MethodInvocation .invoke (
400
+ JODA_READABLE_INSTANT
401
+ .getDeclaredMethods ()
402
+ .filter (ElementMatchers .named ("getMillis" ))
403
+ .getOnly ()),
404
+ LongConstant .forValue (TimeUnit .DAYS .toMillis (1 )),
405
+ Division .LONG ,
406
+ MethodInvocation .invoke (
407
+ JAVA_LOCALE_DATE
408
+ .getDeclaredMethods ()
409
+ .filter (ElementMatchers .isStatic ().and (ElementMatchers .named ("ofEpochDay" )))
410
+ .getOnly ()));
411
+ return shortCircuitReturnNull (readValue , onNotNull );
325
412
}
326
413
return super .convertDefault (type );
327
414
}
0 commit comments