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 }