Coverage Summary for Class: Converter (net.sf.persism)
Class |
Method, %
|
Line, %
|
Converter |
100%
(7/7)
|
96%
(170/177)
|
Converter$1 |
100%
(1/1)
|
100%
(1/1)
|
Total |
100%
(8/8)
|
96.1%
(171/178)
|
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.*;
16
17 final class Converter {
18
19 private static final Log log = Log.getLogger(Converter.class);
20
21 // Make a sensible conversion of the value type from the DB and the property type defined
22 // on the Data class - or the value type from the property to the statement parameter.
23 Object convert(Object value, Class<?> targetType, String columnName) {
24 assert value != null;
25
26 Types valueType = Types.getType(value.getClass());
27
28 if (valueType == null) {
29 log.warn(Messages.NoConversionForUnknownType.message(value.getClass()));
30 return value;
31 }
32
33 Object returnValue = value;
34
35 // try to convert or cast the value to the proper type.
36 switch (valueType) {
37
38 case booleanType:
39 case BooleanType:
40 log.debug("BooleanType");
41 break;
42
43 case byteType:
44 case ByteType:
45 log.warnNoDuplicates(Messages.TinyIntMSSQL.message(columnName));
46 break;
47
48 case shortType:
49 case ShortType:
50 log.debug(valueType);
51 break;
52
53 case integerType:
54 case IntegerType:
55 // int to bool
56 if (targetType.equals(Boolean.class) || targetType.equals(boolean.class)) {
57 returnValue = Integer.valueOf("" + value) == 0 ? false : true;
58
59 } else if (targetType.equals(Time.class)) {
60 // SQLite when a Time is defined VIA a convert from LocalTime via Time.valueOf (see getContactForTest)
61 returnValue = new Time(((Integer) value).longValue());
62
63 } else if (targetType.equals(LocalTime.class)) {
64 // SQLite for Time SQLite sees Long, for LocalTime it sees Integer
65 returnValue = new Time((Integer) value).toLocalTime();
66
67 } else if (targetType.equals(Short.class) || targetType.equals(short.class)) {
68 returnValue = Short.parseShort("" + value);
69
70 } else if (targetType.equals(Byte.class) || targetType.equals(byte.class)) {
71 returnValue = Byte.parseByte("" + value);
72 }
73 break;
74
75 case longType:
76 case LongType:
77 long lval = Long.valueOf("" + value);
78 if (targetType.equals(java.sql.Date.class)) {
79 returnValue = new java.sql.Date(lval);
80
81 } else if (targetType.equals(java.util.Date.class)) {
82 returnValue = new java.util.Date(lval);
83
84 } else if (targetType.equals(Timestamp.class)) {
85 returnValue = new Timestamp(lval);
86
87 } else if (targetType.equals(Integer.class) || targetType.equals(int.class)) {
88 log.warnNoDuplicates(Messages.PossibleOverflow.message(columnName, "INT", "LONG"));
89 returnValue = Integer.parseInt("" + lval);
90
91 } else if (targetType.equals(LocalDate.class)) {
92 // SQLite reads long as date.....
93 returnValue = new Timestamp(lval).toLocalDateTime().toLocalDate();
94
95 } else if (targetType.equals(LocalDateTime.class)) {
96 returnValue = new Timestamp(lval).toLocalDateTime();
97
98 } else if (targetType.equals(Time.class)) {
99 // SQLite.... Again.....
100 returnValue = new Time((Long) value);
101 }
102 break;
103
104 case floatType:
105 case FloatType:
106 log.debug("FloatType");
107 break;
108
109 case doubleType:
110 case DoubleType:
111 Double dbl = (Double) value;
112 // float or doubles to BigDecimal
113 if (targetType.equals(BigDecimal.class)) {
114 returnValue = new BigDecimal("" + value);
115
116 } else if (targetType.equals(Float.class) || targetType.equals(float.class)) {
117 log.warnNoDuplicates(Messages.PossibleOverflow.message(columnName, "FLOAT", "DOUBLE"));
118 returnValue = dbl.floatValue();
119
120 } else if (targetType.equals(Integer.class) || targetType.equals(int.class)) {
121 log.warnNoDuplicates(Messages.PossibleOverflow.message(columnName, "INT", "DOUBLE"));
122 returnValue = dbl.intValue();
123
124 } else if (targetType.equals(Long.class) || targetType.equals(long.class)) {
125 log.warnNoDuplicates(Messages.PossibleOverflow.message(columnName, "LONG", "DOUBLE"));
126 returnValue = dbl.longValue();
127 }
128 break;
129
130 case BigDecimalType:
131 if (targetType.equals(Float.class) || targetType.equals(float.class)) {
132 returnValue = ((Number) value).floatValue();
133 log.warnNoDuplicates(Messages.PossibleOverflow.message(columnName, "Float", "BigDecimal"));
134
135 } else if (targetType.equals(Double.class) || targetType.equals(double.class)) {
136 returnValue = ((Number) value).doubleValue();
137 log.warnNoDuplicates(Messages.PossibleOverflow.message(columnName, "Double", "BigDecimal"));
138
139 } else if (targetType.equals(Long.class) || targetType.equals(long.class)) {
140 returnValue = ((Number) value).longValue();
141 log.warnNoDuplicates(Messages.PossibleOverflow.message(columnName, "Long", "BigDecimal"));
142
143 } else if (targetType.equals(Integer.class) || targetType.equals(int.class)) {
144 returnValue = ((Number) value).intValue();
145 log.warnNoDuplicates(Messages.PossibleOverflow.message(columnName, "Integer", "BigDecimal"));
146
147 } else if (targetType.equals(Short.class) || targetType.equals(short.class)) {
148 returnValue = ((Number) value).shortValue();
149 log.warnNoDuplicates(Messages.PossibleOverflow.message(columnName, "Short", "BigDecimal"));
150
151 } else if (targetType.equals(Boolean.class) || targetType.equals(boolean.class)) {
152 // BigDecimal to Boolean. Oracle (sigh) - Additional for a Char to Boolean as then (see TestOracle for links)
153 returnValue = ((Number) value).intValue() == 1;
154 log.warnNoDuplicates(Messages.PossibleOverflow.message(columnName, "Boolean", "BigDecimal"));
155
156 } else if (targetType.equals(String.class)) {
157 returnValue = (value).toString();
158 }
159 break;
160
161 case StringType:
162 java.util.Date dval;
163 String format;
164 if (("" + value).length() > "yyyy-MM-dd".length()) {
165 format = "yyyy-MM-dd hh:mm:ss";
166 } else {
167 format = "yyyy-MM-dd";
168 }
169 DateFormat df = new SimpleDateFormat(format);
170
171 // Read a string but we want a date
172 if (targetType.equals(java.util.Date.class) || targetType.equals(java.sql.Date.class)) {
173 // This condition occurs in SQLite when you have a datetime with default annotated
174 // the format returned is 2012-06-02 19:59:49
175 // Used for SQLite returning dates as Strings under some conditions
176 // SQL or others may return STRING yyyy-MM-dd for older legacy 'date' type.
177 // https://docs.microsoft.com/en-us/sql/t-sql/data-types/date-transact-sql?view=sql-server-ver15
178 dval = tryParseDate(value, targetType, columnName, df);
179
180 if (targetType.equals(java.sql.Date.class)) {
181 returnValue = new java.sql.Date(dval.getTime());
182 } else {
183 returnValue = dval;
184 }
185
186 } else if (targetType.equals(Timestamp.class)) {
187 returnValue = tryParseTimestamp(value, targetType, columnName);
188
189 } else if (targetType.equals(LocalDate.class)) {
190 // JTDS
191 dval = tryParseDate(value, targetType, columnName, df);
192 returnValue = dval.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
193
194 } else if (targetType.equals(LocalDateTime.class)) {
195 // JTDS
196 dval = tryParseDate(value, targetType, columnName, df);
197 returnValue = dval.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
198
199 } else if (targetType.isEnum()) {
200 // If this is an enum do a case insensitive comparison
201 Object[] enumConstants = targetType.getEnumConstants();
202 for (Object element : enumConstants) {
203 if (("" + value).equalsIgnoreCase(element.toString())) {
204 returnValue = element;
205 break;
206 }
207 }
208
209 } else if (targetType.equals(UUID.class)) {
210 returnValue = UUID.fromString("" + value);
211
212 } else if (targetType.equals(Boolean.class) || targetType.equals(boolean.class)) {
213 // String to Boolean - T or 1 - otherwise false (or null)
214 String bval = ("" + value).toUpperCase();
215 returnValue = bval.startsWith("T") || bval.startsWith("1");
216
217 } else if (targetType.equals(Time.class)) {
218 // MSSQL works, JTDS returns Varchar in format below with varying decimal numbers
219 // which won't format unless I use Exact so I chop of the milliseconds.
220 DateFormat timeFormat = new SimpleDateFormat("hh:mm:ss");
221 String sval = "" + value;
222 if (sval.indexOf('.') > -1) {
223 sval = sval.substring(0, sval.indexOf('.'));
224 }
225 dval = tryParseDate(sval, targetType, columnName, timeFormat);
226 returnValue = new Time(dval.getTime());
227
228 } else if (targetType.equals(LocalTime.class)) {
229 // JTDS Fails again...
230 returnValue = LocalTime.parse("" + value);
231 } else if (targetType.equals(BigDecimal.class)) {
232 try {
233 returnValue = new BigDecimal("" + value);
234 } catch (NumberFormatException e) {
235 throw new PersismException(Messages.NumberFormatException.message(columnName, targetType, value.getClass(), value), e);
236 }
237 }
238
239 break;
240
241 case characterType:
242 case CharacterType:
243 log.debug("CharacterType");
244 break;
245
246 case LocalDateType:
247 log.debug("LocalDateType");
248 returnValue = java.sql.Date.valueOf((LocalDate) value);
249 break;
250
251 case LocalDateTimeType:
252 log.debug("LocalDateTimeType");
253 returnValue = Timestamp.valueOf((LocalDateTime) value);
254 break;
255
256 case LocalTimeType:
257 log.debug("LocalTimeType");
258 returnValue = Time.valueOf((LocalTime) value);
259 break;
260
261 case UtilDateType:
262 case SQLDateType:
263 case TimestampType:
264 if (targetType.equals(java.util.Date.class)) {
265 returnValue = new java.util.Date(((Date) value).getTime());
266
267 } else if (targetType.equals(java.sql.Date.class)) {
268 returnValue = new java.sql.Date(((Date) value).getTime());
269
270 } else if (targetType.equals(Timestamp.class)) {
271 returnValue = new Timestamp(((Date) value).getTime());
272
273 } else if (targetType.equals(LocalDate.class)) {
274 Date dt = new Date(((Date) value).getTime());
275 returnValue = dt.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
276
277 } else if (targetType.equals(LocalDateTime.class)) {
278 Date dt = new Date(((Date) value).getTime());
279 returnValue = dt.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
280
281 } else if (targetType.equals(Time.class)) {
282 // Oracle doesn't seem to have Time so we use Timestamp
283 returnValue = new Time(((Date) value).getTime());
284
285 } else if (targetType.equals(LocalTime.class)) {
286 // Oracle.... Sigh
287 Date dt = (Date) value;
288 returnValue = dt.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().toLocalTime();
289 }
290 break;
291
292 case TimeType:
293 log.debug("TimeType");
294 if (targetType.equals(LocalTime.class)) {
295 returnValue = LocalTime.parse("" + value);
296 }
297 break;
298
299 case InstantType:
300 case OffsetDateTimeType:
301 case ZonedDateTimeType:
302 log.warn(Messages.ConverterValueTypeNotYetSupported.message(valueType.getJavaType()), new Throwable());
303 break;
304
305 case byteArrayType:
306 case ByteArrayType:
307 log.debug("ByteArrayType");
308 if (targetType.equals(UUID.class)) {
309 returnValue = asUuid((byte[]) value);
310 }
311 break;
312
313 case ClobType:
314 case BlobType:
315 log.warn(Messages.ConverterDoNotUseClobOrBlobAsAPropertyType.message(), new Throwable());
316 break;
317
318 case EnumType:
319 // No need to convert it here.
320 // If it's being used for the property setter then it's OK
321 // If it's being used by setParameters it's converted to String
322 // The String case above converts from the String to the Enum
323 log.debug("EnumType");
324 break;
325
326 case UUIDType:
327 log.debug("UUIDType");
328 if (targetType.equals(Blob.class) || targetType.equals(byte[].class) || targetType.equals(Byte[].class)) {
329 returnValue = asBytes((UUID) value);
330 }
331 break;
332
333 case ObjectType:
334 log.debug("ObjectType");
335 break;
336 }
337 return returnValue;
338 }
339
340 /*
341 * Used by convert for convenience - common possible parsing
342 */
343 static Date tryParseDate(Object value, Class<?> targetType, String columnName, DateFormat df) throws PersismException {
344 try {
345 return df.parse("" + value);
346 } catch (ParseException e) {
347 throw new PersismException(Messages.DateFormatException.message(e.getMessage(), columnName, targetType, value.getClass(), value), e); }
348 }
349
350 Timestamp tryParseTimestamp(Object value, Class<?> targetType, String columnName) throws PersismException {
351 try {
352 return Timestamp.valueOf("" + value);
353 } catch (IllegalArgumentException e) {
354 throw new PersismException(Messages.DateFormatException.message(e.getMessage(), columnName, targetType, value.getClass(), value), e); }
355 }
356
357 // https://stackoverflow.com/questions/17893609/convert-uuid-to-byte-that-works-when-using-uuid-nameuuidfrombytesb
358 // THANKS!
359 static UUID asUuid(byte[] bytes) {
360 ByteBuffer bb = ByteBuffer.wrap(bytes);
361 long firstLong = bb.getLong();
362 long secondLong = bb.getLong();
363 return new UUID(firstLong, secondLong);
364 }
365
366 static byte[] asBytes(UUID uuid) {
367 ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
368 bb.putLong(uuid.getMostSignificantBits());
369 bb.putLong(uuid.getLeastSignificantBits());
370 return bb.array();
371 }
372
373
374 }