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 }