2828
2929import java .lang .reflect .Field ;
3030import java .lang .reflect .Modifier ;
31+ import java .lang .reflect .ParameterizedType ;
32+ import java .lang .reflect .Type ;
3133import java .time .Instant ;
3234import java .time .format .DateTimeFormatter ;
3335import java .time .format .DateTimeFormatterBuilder ;
@@ -50,8 +52,12 @@ public class InfluxDBResultMapper {
5052 /**
5153 * Data structure used to cache classes used as measurements.
5254 */
55+ private static class ClassInfo {
56+ ConcurrentMap <String , Field > fieldMap ;
57+ ConcurrentMap <Field , TypeMapper > typeMappers ;
58+ }
5359 private static final
54- ConcurrentMap <String , ConcurrentMap < String , Field >> CLASS_FIELD_CACHE = new ConcurrentHashMap <>();
60+ ConcurrentMap <String , ClassInfo > CLASS_INFO_CACHE = new ConcurrentHashMap <>();
5561
5662 private static final int FRACTION_MIN_WIDTH = 0 ;
5763 private static final int FRACTION_MAX_WIDTH = 9 ;
@@ -204,21 +210,19 @@ void throwExceptionIfResultWithError(final QueryResult queryResult) {
204210 });
205211 }
206212
207- ConcurrentMap <String , Field > getColNameAndFieldMap (final Class <?> clazz ) {
208- return CLASS_FIELD_CACHE .get (clazz .getName ());
209- }
210-
211213 void cacheMeasurementClass (final Class <?>... classVarAgrs ) {
212214 for (Class <?> clazz : classVarAgrs ) {
213- if (CLASS_FIELD_CACHE .containsKey (clazz .getName ())) {
215+ if (CLASS_INFO_CACHE .containsKey (clazz .getName ())) {
214216 continue ;
215217 }
216- ConcurrentMap <String , Field > influxColumnAndFieldMap = new ConcurrentHashMap <>();
218+ ConcurrentMap <String , Field > fieldMap = new ConcurrentHashMap <>();
219+ ConcurrentMap <Field , TypeMapper > typeMappers = new ConcurrentHashMap <>();
217220
218221 Measurement measurement = clazz .getAnnotation (Measurement .class );
219222 boolean allFields = measurement != null && measurement .allFields ();
220223
221224 Class <?> c = clazz ;
225+ TypeMapper typeMapper = TypeMapper .empty ();
222226 while (c != null ) {
223227 for (Field field : c .getDeclaredFields ()) {
224228 Column colAnnotation = field .getAnnotation (Column .class );
@@ -227,11 +231,25 @@ void cacheMeasurementClass(final Class<?>... classVarAgrs) {
227231 continue ;
228232 }
229233
230- influxColumnAndFieldMap .put (getFieldName (field , colAnnotation ), field );
234+ fieldMap .put (getFieldName (field , colAnnotation ), field );
235+ typeMappers .put (field , typeMapper );
231236 }
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 ;
233247 }
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 );
235253 }
236254 }
237255
@@ -255,28 +273,26 @@ String getRetentionPolicy(final Class<?> clazz) {
255273 return ((Measurement ) clazz .getAnnotation (Measurement .class )).retentionPolicy ();
256274 }
257275
258- TimeUnit getTimeUnit (final Class <?> clazz ) {
259- return ((Measurement ) clazz .getAnnotation (Measurement .class )).timeUnit ();
260- }
261-
262276 <T > List <T > parseSeriesAs (final QueryResult .Series series , final Class <T > clazz , final List <T > result ) {
263277 return parseSeriesAs (series , clazz , result , TimeUnit .MILLISECONDS );
264278 }
265279
266280 <T > List <T > parseSeriesAs (final QueryResult .Series series , final Class <T > clazz , final List <T > result ,
267281 final TimeUnit precision ) {
268282 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 ());
270285 try {
271286 T object = null ;
272287 for (List <Object > row : series .getValues ()) {
273288 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*/ );
275290 if (correspondingField != null ) {
276291 if (object == null ) {
277292 object = clazz .newInstance ();
278293 }
279- setFieldValue (object , correspondingField , row .get (i ), precision );
294+ setFieldValue (object , correspondingField , row .get (i ), precision ,
295+ classInfo .typeMappers .get (correspondingField ));
280296 }
281297 }
282298 // 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,
285301 // "tag" values are always String.
286302 if (series .getTags () != null && !series .getTags ().isEmpty ()) {
287303 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*/ );
289305 if (correspondingField != null ) {
290306 // 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 ));
292309 }
293310 }
294311 }
@@ -309,104 +326,68 @@ <T> List<T> parseSeriesAs(final QueryResult.Series series, final Class<T> clazz,
309326 * for more information.
310327 *
311328 */
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 )
313331 throws IllegalArgumentException , IllegalAccessException {
314332 if (value == null ) {
315333 return ;
316334 }
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 ) {
318344 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 );
321362 }
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 ));
326378 }
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 ()));
330379 } catch (ClassCastException e ) {
331380 String msg = "Class '%s' field '%s' was defined with a different field type and caused a ClassCastException. "
332381 + "The correct type is '%s' (current field value: '%s')." ;
333382 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 ));
378384 }
379- if (boolean .class .isAssignableFrom (fieldType )) {
380- field .setBoolean (object , Boolean .valueOf (String .valueOf (value )).booleanValue ());
381- return true ;
382- }
383- return false ;
384- }
385385
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 ));
406388 }
407389
408- private static Long toMillis (final long value , final TimeUnit precision ) {
409-
390+ private static long toMillis (final long value , final TimeUnit precision ) {
410391 return TimeUnit .MILLISECONDS .convert (value , precision );
411392 }
412393}
0 commit comments