Coverage Summary for Class: Converter (net.sf.persism)
Class |
Method, %
|
Line, %
|
Converter |
100%
(11/11)
|
98.3%
(232/236)
|
Converter$1 |
100%
(1/1)
|
100%
(1/1)
|
Total |
100%
(12/12)
|
98.3%
(233/237)
|
1 package net.sf.persism;
2
3 import java.math.BigDecimal;
4 import java.nio.ByteBuffer;
5 import java.sql.Blob;
6 import java.sql.Time;
7 import java.sql.Timestamp;
8 import java.text.DateFormat;
9 import java.text.ParseException;
10 import java.text.SimpleDateFormat;
11 import java.time.LocalDate;
12 import java.time.LocalDateTime;
13 import java.time.LocalTime;
14 import java.time.ZoneId;
15 import java.util.Date;
16 import java.util.UUID;
17
18 final class Converter {
19
20 private static final Log log = Log.getLogger(Converter.class);
21
22 // https://stackoverflow.com/questions/2409657/call-to-method-of-static-java-text-dateformat-not-advisable
23 // https://www.javacodegeeks.com/2010/07/java-best-practices-dateformat-in.html
24 private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT1 =
25 ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"));
26
27 private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT2 =
28 ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
29
30 private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT3 =
31 ThreadLocal.withInitial(() -> new SimpleDateFormat("hh:mm:ss"));
32
33 // Make a sensible conversion of the value type from the DB and the property type defined
34 // on the Data class - or the value type from the property to the statement parameter.
35 Object convert(Object value, Class<?> targetType, String columnName) {
36 assert value != null;
37
38 JavaType valueType = JavaType.getType(value.getClass());
39
40 if (valueType == null) {
41 log.warn(Message.NoConversionForUnknownType.message(value.getClass()));
42 return value;
43 }
44
45 Object returnValue = value;
46
47 // try to convert or cast (no cast) the value to the proper type.
48 switch (valueType) {
49
50 case booleanType:
51 case BooleanType:
52 break;
53
54 case byteType:
55 case ByteType:
56 log.warnNoDuplicates(Message.TinyIntMSSQL.message(columnName));
57 break;
58
59 case shortType:
60 case ShortType:
61 break;
62
63 case integerType:
64 case IntegerType:
65
66 if (targetType == Integer.class || targetType == int.class) {
67 break;
68 }
69
70 // int to bool
71 if (targetType == Boolean.class || targetType == boolean.class) {
72 returnValue = Integer.parseInt("" + value) != 0;
73 break;
74 }
75
76 if (targetType == Short.class || targetType == short.class) {
77 log.warnNoDuplicates(Message.PossibleOverflow.message(columnName, "SHORT", "INT"));
78 returnValue = Short.parseShort("" + value);
79 break;
80 }
81
82 if (targetType == Byte.class || targetType == byte.class) {
83 log.warnNoDuplicates(Message.PossibleOverflow.message(columnName, "BYTE", "INT"));
84 returnValue = Byte.parseByte("" + value);
85 break;
86 }
87
88 if (targetType == Time.class) {
89 // SQLite when a Time is defined VIA a convert from LocalTime via Time.valueOf (see getContactForTest)
90 returnValue = new Time(((Integer) value).longValue());
91 break;
92 }
93
94 if (targetType == LocalTime.class) {
95 // SQLite for Time SQLite sees Long, for LocalTime it sees Integer
96 returnValue = new Time((Integer) value).toLocalTime();
97 break;
98 }
99
100 break;
101
102 case longType:
103 case LongType:
104
105 if (targetType == Long.class || targetType == long.class) {
106 break;
107 }
108
109 long lval = Long.parseLong("" + value);
110 if (targetType == java.sql.Date.class) {
111 returnValue = new java.sql.Date(lval);
112 break;
113 }
114
115 if (targetType == Date.class) {
116 returnValue = new java.util.Date(lval);
117 break;
118 }
119
120 if (targetType == Timestamp.class) {
121 returnValue = new Timestamp(lval);
122 break;
123 }
124
125 if (targetType == Integer.class || targetType == int.class) {
126 log.warnNoDuplicates(Message.PossibleOverflow.message(columnName, "INT", "LONG"));
127 returnValue = Integer.parseInt("" + lval);
128 break;
129 }
130
131 if (targetType == LocalDateTime.class) {
132 returnValue = new Timestamp(lval).toLocalDateTime();
133 break;
134 }
135
136 if (targetType == LocalDate.class) {
137 // SQLite reads long as date.....
138 returnValue = new Timestamp(lval).toLocalDateTime().toLocalDate();
139 break;
140 }
141
142 if (targetType == Time.class) {
143 // SQLite.... Again.....
144 returnValue = new Time((Long) value);
145 break;
146 }
147 break;
148
149 case floatType:
150 case FloatType:
151 if (targetType == Float.class || targetType == float.class) {
152 break;
153 }
154
155 Float flt = (Float) value;
156 if (targetType == Integer.class || targetType == int.class) {
157 log.warnNoDuplicates(Message.PossibleOverflow.message(columnName, "INT", "DOUBLE"));
158 returnValue = flt.intValue();
159 break;
160 }
161
162 if (targetType == Long.class || targetType == long.class) {
163 log.warnNoDuplicates(Message.PossibleOverflow.message(columnName, "LONG", "DOUBLE"));
164 returnValue = flt.longValue();
165 break;
166 }
167 break;
168
169 case doubleType:
170 case DoubleType:
171 if (targetType == Double.class || targetType == double.class) {
172 break;
173 }
174
175 Double dbl = (Double) value;
176 // float or doubles to BigDecimal
177 if (targetType == BigDecimal.class) {
178 returnValue = new BigDecimal("" + value);
179 break;
180 }
181
182 if (targetType == Float.class || targetType == float.class) {
183 log.warnNoDuplicates(Message.PossibleOverflow.message(columnName, "FLOAT", "DOUBLE"));
184 returnValue = dbl.floatValue();
185 break;
186 }
187
188 if (targetType == Integer.class || targetType == int.class) {
189 log.warnNoDuplicates(Message.PossibleOverflow.message(columnName, "INT", "DOUBLE"));
190 returnValue = dbl.intValue();
191 break;
192 }
193
194 if (targetType == Long.class || targetType == long.class) {
195 log.warnNoDuplicates(Message.PossibleOverflow.message(columnName, "LONG", "DOUBLE"));
196 returnValue = dbl.longValue();
197 break;
198 }
199 break;
200
201 case BigDecimalType:
202 if (targetType == BigDecimal.class) {
203 break;
204 }
205
206 if (targetType == Float.class || targetType == float.class) {
207 returnValue = ((Number) value).floatValue();
208 log.warnNoDuplicates(Message.PossibleOverflow.message(columnName, "FLOAT", "BigDecimal"));
209 break;
210 }
211
212 if (targetType == Double.class || targetType == double.class) {
213 returnValue = ((Number) value).doubleValue();
214 log.warnNoDuplicates(Message.PossibleOverflow.message(columnName, "DOUBLE", "BigDecimal"));
215 break;
216 }
217
218 if (targetType == Long.class || targetType == long.class) {
219 returnValue = ((Number) value).longValue();
220 log.warnNoDuplicates(Message.PossibleOverflow.message(columnName, "LONG", "BigDecimal"));
221 break;
222 }
223
224 if (targetType == String.class) {
225 returnValue = (value).toString();
226 break;
227 }
228
229 if (targetType == Integer.class || targetType == int.class) {
230 returnValue = ((Number) value).intValue();
231 log.warnNoDuplicates(Message.PossibleOverflow.message(columnName, "INT", "BigDecimal"));
232 break;
233 }
234
235 if (targetType == Short.class || targetType == short.class) {
236 returnValue = ((Number) value).shortValue();
237 log.warnNoDuplicates(Message.PossibleOverflow.message(columnName, "SHORT", "BigDecimal"));
238 break;
239 }
240
241 if (targetType == Boolean.class || targetType == boolean.class) {
242 // BigDecimal to Boolean. Oracle (sigh) - Additional for a Char to Boolean as then (see TestOracle for links)
243 returnValue = ((Number) value).intValue() == 1;
244 log.warnNoDuplicates(Message.PossibleOverflow.message(columnName, "BOOLEAN", "BigDecimal"));
245 break;
246 }
247
248 break;
249
250 case StringType:
251 if (targetType == String.class) {
252 break;
253 }
254
255 Date dval;
256 DateFormat df;
257 if (("" + value).length() > "yyyy-MM-dd".length()) {
258 df = DATE_FORMAT1.get();
259 } else {
260 df = DATE_FORMAT2.get();
261 }
262
263 // Read a string but we want a date
264 if (targetType == Date.class) {
265 // This condition occurs in SQLite when you have a datetime with default annotated
266 // the format returned is 2012-06-02 19:59:49
267 // Used for SQLite returning dates as Strings under some conditions
268 // SQL or others may return STRING yyyy-MM-dd for older legacy 'date' type.
269 // https://docs.microsoft.com/en-us/sql/t-sql/data-types/date-transact-sql?view=sql-server-ver15
270 returnValue = tryParseDate(value, targetType, columnName, df);
271 break;
272 }
273
274 if (targetType == Timestamp.class) {
275 returnValue = tryParseTimestamp(value, targetType, columnName);
276 break;
277 }
278
279 if (targetType == LocalDate.class) {
280 // SQLite
281 dval = tryParseDate(value, targetType, columnName, df);
282 returnValue = dval.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
283 break;
284 }
285
286 if (targetType == LocalDateTime.class) {
287 // SQLite
288 dval = tryParseDate(value, targetType, columnName, df);
289 returnValue = dval.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
290 break;
291 }
292
293 if (targetType.isEnum()) {
294 // If this is an enum do a case-insensitive comparison
295 Object[] enumConstants = targetType.getEnumConstants();
296 for (Object element : enumConstants) {
297 if (("" + value).equalsIgnoreCase(element.toString())) {
298 returnValue = element;
299 break;
300 }
301 }
302 break;
303 }
304
305 if (targetType == UUID.class) {
306 returnValue = UUID.fromString("" + value);
307 break;
308 }
309
310 if (targetType == Boolean.class || targetType == boolean.class) {
311 // String to Boolean - T or 1 - otherwise false (or null)
312 String bval = ("" + value).toUpperCase();
313 returnValue = bval.startsWith("T") || bval.startsWith("1");
314 break;
315 }
316
317 if (targetType == BigDecimal.class) {
318 try {
319 returnValue = new BigDecimal("" + value);
320 } catch (NumberFormatException e) {
321 throw new PersismException(Message.NumberFormatException.message(columnName, targetType, value.getClass(), value), e);
322 }
323 break;
324 }
325 break;
326
327 case characterType:
328 case CharacterType:
329 break;
330
331 case LocalDateType:
332 returnValue = java.sql.Date.valueOf((LocalDate) value);
333 break;
334
335 case LocalDateTimeType:
336 returnValue = Timestamp.valueOf((LocalDateTime) value);
337 break;
338
339 case LocalTimeType:
340 returnValue = Time.valueOf((LocalTime) value);
341 break;
342
343 case UtilDateType:
344 if (targetType == java.sql.Date.class) {
345 returnValue = new java.sql.Date(((Date) value).getTime());
346 break;
347 }
348
349 if (targetType == Timestamp.class) {
350 returnValue = new Timestamp(((Date) value).getTime());
351 break;
352 }
353 break;
354
355 case SQLDateType:
356 if (targetType == java.sql.Date.class) {
357 break;
358 }
359
360 if (targetType == Date.class) {
361 returnValue = new java.util.Date(((Date) value).getTime());
362 break;
363 }
364
365 if (targetType == Timestamp.class) {
366 returnValue = new Timestamp(((Date) value).getTime());
367 break;
368 }
369
370 if (targetType == LocalDate.class) {
371 Date dt = new Date(((Date) value).getTime());
372 returnValue = dt.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
373 break;
374 }
375
376 if (targetType == LocalDateTime.class) {
377 Date dt = new Date(((Date) value).getTime());
378 returnValue = dt.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
379 break;
380 }
381 break;
382
383 case TimestampType:
384 if (targetType == Timestamp.class) {
385 break;
386 }
387
388 if (targetType == Date.class) {
389 returnValue = new java.util.Date(((Date) value).getTime());
390 break;
391 }
392
393 if (targetType == java.sql.Date.class) {
394 returnValue = new java.sql.Date(((Date) value).getTime());
395 break;
396 }
397
398 if (targetType == LocalDate.class) {
399 Date dt = new Date(((Date) value).getTime());
400 returnValue = dt.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
401 break;
402 }
403
404 if (targetType == LocalDateTime.class) {
405 Date dt = new Date(((Date) value).getTime());
406 returnValue = dt.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
407 break;
408 }
409
410 if (targetType == Time.class) {
411 // Oracle doesn't seem to have Time so we use Timestamp
412 returnValue = new Time(((Date) value).getTime());
413 break;
414 }
415
416 if (targetType == LocalTime.class) {
417 // Oracle.... Sigh
418 Date dt = (Date) value;
419 returnValue = dt.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().toLocalTime();
420 break;
421 }
422 break;
423
424 case TimeType:
425 if (targetType == LocalTime.class) {
426 returnValue = LocalTime.parse("" + value);
427 }
428 break;
429
430 case InstantType:
431 case OffsetDateTimeType:
432 case ZonedDateTimeType:
433 log.warn(Message.ConverterValueTypeNotYetSupported.message(valueType.getJavaType()), new Throwable());
434 break;
435
436 case byteArrayType:
437 case ByteArrayType:
438 if (targetType == UUID.class) {
439 returnValue = asUuid((byte[]) value);
440 }
441 break;
442
443 case ClobType:
444 case BlobType:
445 log.warn(Message.ConverterDoNotUseClobOrBlobAsAPropertyType.message(), new Throwable());
446 break;
447
448 case EnumType:
449 // No need to convert it here.
450 // If it's being used for the property setter then it's OK
451 // If it's being used by setParameters it's converted to String
452 // The String case above converts from the String to the Enum
453 log.debug("EnumType");
454 break;
455
456 case UUIDType:
457 if (targetType == Blob.class || targetType == byte[].class || targetType == Byte[].class) {
458 returnValue = asBytes((UUID) value);
459 }
460 break;
461
462 case ObjectType:
463 break;
464 }
465 return returnValue;
466 }
467
468 /*
469 * Used by convert for convenience - common possible parsing
470 */
471 static Date tryParseDate(Object value, Class<?> targetType, String columnName, DateFormat df) throws PersismException {
472 try {
473 return df.parse("" + value);
474 } catch (ParseException e) {
475 throw new PersismException(Message.DateFormatException.message(e.getMessage(), columnName, targetType, value.getClass(), value), e);
476 }
477 }
478
479 static Timestamp tryParseTimestamp(Object value, Class<?> targetType, String columnName) throws PersismException {
480 try {
481 return Timestamp.valueOf("" + value);
482 } catch (IllegalArgumentException e) {
483 throw new PersismException(Message.DateFormatException.message(e.getMessage(), columnName, targetType, value.getClass(), value), e);
484 }
485 }
486
487 // todo document UUID usage for supported and non-supported DBs. Point out this URL to get converter code - otherwise we could expose these 2 static methods somewhere (which we dont want to do)
488 // https://stackoverflow.com/questions/17893609/convert-uuid-to-byte-that-works-when-using-uuid-nameuuidfrombytesb
489 // THANKS!
490 static UUID asUuid(byte[] bytes) {
491 ByteBuffer bb = ByteBuffer.wrap(bytes);
492 long firstLong = bb.getLong();
493 long secondLong = bb.getLong();
494 return new UUID(firstLong, secondLong);
495 }
496
497 static byte[] asBytes(UUID uuid) {
498 ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
499 bb.putLong(uuid.getMostSignificantBits());
500 bb.putLong(uuid.getLeastSignificantBits());
501 return bb.array();
502 }
503
504 static Object asBytesFromUUID(UUID uuid) {
505 return asBytes(uuid);
506 }
507
508
509 }