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 }