28
28
29
29
import java .lang .reflect .Field ;
30
30
import java .lang .reflect .Modifier ;
31
+ import java .lang .reflect .ParameterizedType ;
32
+ import java .lang .reflect .Type ;
31
33
import java .time .Instant ;
32
34
import java .time .format .DateTimeFormatter ;
33
35
import java .time .format .DateTimeFormatterBuilder ;
@@ -50,8 +52,12 @@ public class InfluxDBResultMapper {
50
52
/**
51
53
* Data structure used to cache classes used as measurements.
52
54
*/
55
+ private static class ClassInfo {
56
+ ConcurrentMap <String , Field > fieldMap ;
57
+ ConcurrentMap <Field , TypeMapper > typeMappers ;
58
+ }
53
59
private static final
54
- ConcurrentMap <String , ConcurrentMap < String , Field >> CLASS_FIELD_CACHE = new ConcurrentHashMap <>();
60
+ ConcurrentMap <String , ClassInfo > CLASS_INFO_CACHE = new ConcurrentHashMap <>();
55
61
56
62
private static final int FRACTION_MIN_WIDTH = 0 ;
57
63
private static final int FRACTION_MAX_WIDTH = 9 ;
@@ -204,21 +210,19 @@ void throwExceptionIfResultWithError(final QueryResult queryResult) {
204
210
});
205
211
}
206
212
207
- ConcurrentMap <String , Field > getColNameAndFieldMap (final Class <?> clazz ) {
208
- return CLASS_FIELD_CACHE .get (clazz .getName ());
209
- }
210
-
211
213
void cacheMeasurementClass (final Class <?>... classVarAgrs ) {
212
214
for (Class <?> clazz : classVarAgrs ) {
213
- if (CLASS_FIELD_CACHE .containsKey (clazz .getName ())) {
215
+ if (CLASS_INFO_CACHE .containsKey (clazz .getName ())) {
214
216
continue ;
215
217
}
216
- ConcurrentMap <String , Field > influxColumnAndFieldMap = new ConcurrentHashMap <>();
218
+ ConcurrentMap <String , Field > fieldMap = new ConcurrentHashMap <>();
219
+ ConcurrentMap <Field , TypeMapper > typeMappers = new ConcurrentHashMap <>();
217
220
218
221
Measurement measurement = clazz .getAnnotation (Measurement .class );
219
222
boolean allFields = measurement != null && measurement .allFields ();
220
223
221
224
Class <?> c = clazz ;
225
+ TypeMapper typeMapper = TypeMapper .empty ();
222
226
while (c != null ) {
223
227
for (Field field : c .getDeclaredFields ()) {
224
228
Column colAnnotation = field .getAnnotation (Column .class );
@@ -227,11 +231,25 @@ void cacheMeasurementClass(final Class<?>... classVarAgrs) {
227
231
continue ;
228
232
}
229
233
230
- influxColumnAndFieldMap .put (getFieldName (field , colAnnotation ), field );
234
+ fieldMap .put (getFieldName (field , colAnnotation ), field );
235
+ typeMappers .put (field , typeMapper );
231
236
}
232
- c = c .getSuperclass ();
237
+
238
+ Class <?> superclass = c .getSuperclass ();
239
+ Type genericSuperclass = c .getGenericSuperclass ();
240
+ if (genericSuperclass instanceof ParameterizedType ) {
241
+ typeMapper = TypeMapper .of ((ParameterizedType ) genericSuperclass , superclass );
242
+ } else {
243
+ typeMapper = TypeMapper .empty ();
244
+ }
245
+
246
+ c = superclass ;
233
247
}
234
- CLASS_FIELD_CACHE .putIfAbsent (clazz .getName (), influxColumnAndFieldMap );
248
+
249
+ ClassInfo classInfo = new ClassInfo ();
250
+ classInfo .fieldMap = fieldMap ;
251
+ classInfo .typeMappers = typeMappers ;
252
+ CLASS_INFO_CACHE .putIfAbsent (clazz .getName (), classInfo );
235
253
}
236
254
}
237
255
@@ -255,28 +273,26 @@ String getRetentionPolicy(final Class<?> clazz) {
255
273
return ((Measurement ) clazz .getAnnotation (Measurement .class )).retentionPolicy ();
256
274
}
257
275
258
- TimeUnit getTimeUnit (final Class <?> clazz ) {
259
- return ((Measurement ) clazz .getAnnotation (Measurement .class )).timeUnit ();
260
- }
261
-
262
276
<T > List <T > parseSeriesAs (final QueryResult .Series series , final Class <T > clazz , final List <T > result ) {
263
277
return parseSeriesAs (series , clazz , result , TimeUnit .MILLISECONDS );
264
278
}
265
279
266
280
<T > List <T > parseSeriesAs (final QueryResult .Series series , final Class <T > clazz , final List <T > result ,
267
281
final TimeUnit precision ) {
268
282
int columnSize = series .getColumns ().size ();
269
- ConcurrentMap <String , Field > colNameAndFieldMap = CLASS_FIELD_CACHE .get (clazz .getName ());
283
+
284
+ ClassInfo classInfo = CLASS_INFO_CACHE .get (clazz .getName ());
270
285
try {
271
286
T object = null ;
272
287
for (List <Object > row : series .getValues ()) {
273
288
for (int i = 0 ; i < columnSize ; i ++) {
274
- Field correspondingField = colNameAndFieldMap .get (series .getColumns ().get (i )/*InfluxDB columnName*/ );
289
+ Field correspondingField = classInfo . fieldMap .get (series .getColumns ().get (i )/*InfluxDB columnName*/ );
275
290
if (correspondingField != null ) {
276
291
if (object == null ) {
277
292
object = clazz .newInstance ();
278
293
}
279
- setFieldValue (object , correspondingField , row .get (i ), precision );
294
+ setFieldValue (object , correspondingField , row .get (i ), precision ,
295
+ classInfo .typeMappers .get (correspondingField ));
280
296
}
281
297
}
282
298
// When the "GROUP BY" clause is used, "tags" are returned as Map<String,String> and
@@ -285,10 +301,11 @@ <T> List<T> parseSeriesAs(final QueryResult.Series series, final Class<T> clazz,
285
301
// "tag" values are always String.
286
302
if (series .getTags () != null && !series .getTags ().isEmpty ()) {
287
303
for (Entry <String , String > entry : series .getTags ().entrySet ()) {
288
- Field correspondingField = colNameAndFieldMap .get (entry .getKey ()/*InfluxDB columnName*/ );
304
+ Field correspondingField = classInfo . fieldMap .get (entry .getKey ()/*InfluxDB columnName*/ );
289
305
if (correspondingField != null ) {
290
306
// I don't think it is possible to reach here without a valid "object"
291
- setFieldValue (object , correspondingField , entry .getValue (), precision );
307
+ setFieldValue (object , correspondingField , entry .getValue (), precision ,
308
+ classInfo .typeMappers .get (correspondingField ));
292
309
}
293
310
}
294
311
}
@@ -309,104 +326,68 @@ <T> List<T> parseSeriesAs(final QueryResult.Series series, final Class<T> clazz,
309
326
* for more information.
310
327
*
311
328
*/
312
- private static <T > void setFieldValue (final T object , final Field field , final Object value , final TimeUnit precision )
329
+ private static <T > void setFieldValue (final T object , final Field field , final Object value , final TimeUnit precision ,
330
+ final TypeMapper typeMapper )
313
331
throws IllegalArgumentException , IllegalAccessException {
314
332
if (value == null ) {
315
333
return ;
316
334
}
317
- Class <?> fieldType = field .getType ();
335
+ Type fieldType = typeMapper .resolve (field .getGenericType ());
336
+ if (!field .isAccessible ()) {
337
+ field .setAccessible (true );
338
+ }
339
+ field .set (object , adaptValue ((Class <?>) fieldType , value , precision , field .getName (), object .getClass ().getName ()));
340
+ }
341
+
342
+ private static Object adaptValue (final Class <?> fieldType , final Object value , final TimeUnit precision ,
343
+ final String fieldName , final String className ) {
318
344
try {
319
- if (!field .isAccessible ()) {
320
- field .setAccessible (true );
345
+ if (String .class .isAssignableFrom (fieldType )) {
346
+ return String .valueOf (value );
347
+ }
348
+ if (Instant .class .isAssignableFrom (fieldType )) {
349
+ if (value instanceof String ) {
350
+ return Instant .from (RFC3339_FORMATTER .parse (String .valueOf (value )));
351
+ }
352
+ if (value instanceof Long ) {
353
+ return Instant .ofEpochMilli (toMillis ((long ) value , precision ));
354
+ }
355
+ if (value instanceof Double ) {
356
+ return Instant .ofEpochMilli (toMillis (((Double ) value ).longValue (), precision ));
357
+ }
358
+ if (value instanceof Integer ) {
359
+ return Instant .ofEpochMilli (toMillis (((Integer ) value ).longValue (), precision ));
360
+ }
361
+ throw new InfluxDBMapperException ("Unsupported type " + fieldType + " for field " + fieldName );
321
362
}
322
- if (fieldValueModified (fieldType , field , object , value , precision )
323
- || fieldValueForPrimitivesModified (fieldType , field , object , value )
324
- || fieldValueForPrimitiveWrappersModified (fieldType , field , object , value )) {
325
- return ;
363
+ if (Double .class .isAssignableFrom (fieldType ) || double .class .isAssignableFrom (fieldType )) {
364
+ return value ;
365
+ }
366
+ if (Long .class .isAssignableFrom (fieldType ) || long .class .isAssignableFrom (fieldType )) {
367
+ return ((Double ) value ).longValue ();
368
+ }
369
+ if (Integer .class .isAssignableFrom (fieldType ) || int .class .isAssignableFrom (fieldType )) {
370
+ return ((Double ) value ).intValue ();
371
+ }
372
+ if (Boolean .class .isAssignableFrom (fieldType ) || boolean .class .isAssignableFrom (fieldType )) {
373
+ return Boolean .valueOf (String .valueOf (value ));
374
+ }
375
+ if (Enum .class .isAssignableFrom (fieldType )) {
376
+ //noinspection unchecked
377
+ return Enum .valueOf ((Class <Enum >) fieldType , String .valueOf (value ));
326
378
}
327
- String msg = "Class '%s' field '%s' is from an unsupported type '%s'." ;
328
- throw new InfluxDBMapperException (
329
- String .format (msg , object .getClass ().getName (), field .getName (), field .getType ()));
330
379
} catch (ClassCastException e ) {
331
380
String msg = "Class '%s' field '%s' was defined with a different field type and caused a ClassCastException. "
332
381
+ "The correct type is '%s' (current field value: '%s')." ;
333
382
throw new InfluxDBMapperException (
334
- String .format (msg , object .getClass ().getName (), field .getName (), value .getClass ().getName (), value ));
335
- }
336
- }
337
-
338
- static <T > boolean fieldValueModified (final Class <?> fieldType , final Field field , final T object , final Object value ,
339
- final TimeUnit precision )
340
- throws IllegalArgumentException , IllegalAccessException {
341
- if (String .class .isAssignableFrom (fieldType )) {
342
- field .set (object , String .valueOf (value ));
343
- return true ;
344
- }
345
- if (Instant .class .isAssignableFrom (fieldType )) {
346
- Instant instant ;
347
- if (value instanceof String ) {
348
- instant = Instant .from (RFC3339_FORMATTER .parse (String .valueOf (value )));
349
- } else if (value instanceof Long ) {
350
- instant = Instant .ofEpochMilli (toMillis ((long ) value , precision ));
351
- } else if (value instanceof Double ) {
352
- instant = Instant .ofEpochMilli (toMillis (((Double ) value ).longValue (), precision ));
353
- } else if (value instanceof Integer ) {
354
- instant = Instant .ofEpochMilli (toMillis (((Integer ) value ).longValue (), precision ));
355
- } else {
356
- throw new InfluxDBMapperException ("Unsupported type " + field .getClass () + " for field " + field .getName ());
357
- }
358
- field .set (object , instant );
359
- return true ;
360
- }
361
- return false ;
362
- }
363
-
364
- static <T > boolean fieldValueForPrimitivesModified (final Class <?> fieldType , final Field field , final T object ,
365
- final Object value )
366
- throws IllegalArgumentException , IllegalAccessException {
367
- if (double .class .isAssignableFrom (fieldType )) {
368
- field .setDouble (object , ((Double ) value ).doubleValue ());
369
- return true ;
370
- }
371
- if (long .class .isAssignableFrom (fieldType )) {
372
- field .setLong (object , ((Double ) value ).longValue ());
373
- return true ;
374
- }
375
- if (int .class .isAssignableFrom (fieldType )) {
376
- field .setInt (object , ((Double ) value ).intValue ());
377
- return true ;
383
+ String .format (msg , className , fieldName , value .getClass ().getName (), value ));
378
384
}
379
- if (boolean .class .isAssignableFrom (fieldType )) {
380
- field .setBoolean (object , Boolean .valueOf (String .valueOf (value )).booleanValue ());
381
- return true ;
382
- }
383
- return false ;
384
- }
385
385
386
- static <T > boolean fieldValueForPrimitiveWrappersModified (final Class <?> fieldType , final Field field , final T object ,
387
- final Object value )
388
- throws IllegalArgumentException , IllegalAccessException {
389
- if (Double .class .isAssignableFrom (fieldType )) {
390
- field .set (object , value );
391
- return true ;
392
- }
393
- if (Long .class .isAssignableFrom (fieldType )) {
394
- field .set (object , Long .valueOf (((Double ) value ).longValue ()));
395
- return true ;
396
- }
397
- if (Integer .class .isAssignableFrom (fieldType )) {
398
- field .set (object , Integer .valueOf (((Double ) value ).intValue ()));
399
- return true ;
400
- }
401
- if (Boolean .class .isAssignableFrom (fieldType )) {
402
- field .set (object , Boolean .valueOf (String .valueOf (value )));
403
- return true ;
404
- }
405
- return false ;
386
+ throw new InfluxDBMapperException (
387
+ String .format ("Class '%s' field '%s' is from an unsupported type '%s'." , className , fieldName , fieldType ));
406
388
}
407
389
408
- private static Long toMillis (final long value , final TimeUnit precision ) {
409
-
390
+ private static long toMillis (final long value , final TimeUnit precision ) {
410
391
return TimeUnit .MILLISECONDS .convert (value , precision );
411
392
}
412
393
}
0 commit comments