Coverage Summary for Class: Converter (net.sf.persism)
| Class |
Method, %
|
Branch, %
|
Line, %
|
| Converter |
100%
(11/11)
|
86.3%
(177/205)
|
98.8%
(170/172)
|
| Converter$1 |
100%
(1/1)
|
100%
(1/1)
|
| Total |
100%
(12/12)
|
86.3%
(177/205)
|
98.8%
(171/173)
|
package net.sf.persism;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.sql.Blob;
import java.sql.Time;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.UUID;
final class Converter {
private static final Log log = Log.getLogger(Converter.class);
// https://stackoverflow.com/questions/2409657/call-to-method-of-static-java-text-dateformat-not-advisable
// https://www.javacodegeeks.com/2010/07/java-best-practices-dateformat-in.html
private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT1 =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"));
private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT2 =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT3 =
ThreadLocal.withInitial(() -> new SimpleDateFormat("hh:mm:ss"));
// Make a sensible conversion of the value type from the DB and the property type defined
// on the Data class - or the value type from the property to the statement parameter.
Object convert(Object value, Class<?> targetType, String columnName) {
assert value != null;
JavaType valueType = JavaType.getType(value.getClass());
if (valueType == null) {
log.warn(Message.NoConversionForUnknownType.message(value.getClass()));
return value;
}
Object returnValue = value;
// try to convert or cast (no cast) the value to the proper type.
switch (valueType) {
case booleanType:
case BooleanType:
break;
case byteType:
case ByteType:
log.warnNoDuplicates(Message.TinyIntMSSQL.message(columnName));
break;
case shortType:
case ShortType:
break;
case integerType:
case IntegerType:
if (targetType == Integer.class || targetType == int.class) {
break;
}
// int to bool
if (targetType == Boolean.class || targetType == boolean.class) {
returnValue = Integer.parseInt("" + value) != 0;
break;
}
if (targetType == Short.class || targetType == short.class) {
log.warnNoDuplicates(Message.PossibleOverflow.message(columnName, "SHORT", "INT"));
returnValue = Short.parseShort("" + value);
break;
}
if (targetType == Byte.class || targetType == byte.class) {
log.warnNoDuplicates(Message.PossibleOverflow.message(columnName, "BYTE", "INT"));
returnValue = Byte.parseByte("" + value);
break;
}
if (targetType == Time.class) {
// SQLite when a Time is defined VIA a convert from LocalTime via Time.valueOf (see getContactForTest)
returnValue = new Time(((Integer) value).longValue());
break;
}
if (targetType == LocalTime.class) {
// SQLite for Time SQLite sees Long, for LocalTime it sees Integer
returnValue = new Time((Integer) value).toLocalTime();
break;
}
break;
case longType:
case LongType:
if (targetType == Long.class || targetType == long.class) {
break;
}
long lval = Long.parseLong("" + value);
if (targetType == java.sql.Date.class) {
returnValue = new java.sql.Date(lval);
break;
}
if (targetType == Date.class) {
returnValue = new java.util.Date(lval);
break;
}
if (targetType == Timestamp.class) {
returnValue = new Timestamp(lval);
break;
}
if (targetType == Integer.class || targetType == int.class) {
log.warnNoDuplicates(Message.PossibleOverflow.message(columnName, "INT", "LONG"));
returnValue = Integer.parseInt("" + lval);
break;
}
if (targetType == LocalDateTime.class) {
returnValue = new Timestamp(lval).toLocalDateTime();
break;
}
if (targetType == LocalDate.class) {
// SQLite reads long as date.....
returnValue = new Timestamp(lval).toLocalDateTime().toLocalDate();
break;
}
if (targetType == Time.class) {
// SQLite.... Again.....
returnValue = new Time((Long) value);
break;
}
break;
case floatType:
case FloatType:
if (targetType == Float.class || targetType == float.class) {
break;
}
Float flt = (Float) value;
if (targetType == Integer.class || targetType == int.class) {
log.warnNoDuplicates(Message.PossibleOverflow.message(columnName, "INT", "DOUBLE"));
returnValue = flt.intValue();
break;
}
if (targetType == Long.class || targetType == long.class) {
log.warnNoDuplicates(Message.PossibleOverflow.message(columnName, "LONG", "DOUBLE"));
returnValue = flt.longValue();
break;
}
break;
case doubleType:
case DoubleType:
if (targetType == Double.class || targetType == double.class) {
break;
}
Double dbl = (Double) value;
// float or doubles to BigDecimal
if (targetType == BigDecimal.class) {
returnValue = new BigDecimal("" + value);
break;
}
if (targetType == Float.class || targetType == float.class) {
log.warnNoDuplicates(Message.PossibleOverflow.message(columnName, "FLOAT", "DOUBLE"));
returnValue = dbl.floatValue();
break;
}
if (targetType == Integer.class || targetType == int.class) {
log.warnNoDuplicates(Message.PossibleOverflow.message(columnName, "INT", "DOUBLE"));
returnValue = dbl.intValue();
break;
}
if (targetType == Long.class || targetType == long.class) {
log.warnNoDuplicates(Message.PossibleOverflow.message(columnName, "LONG", "DOUBLE"));
returnValue = dbl.longValue();
break;
}
break;
case BigDecimalType:
if (targetType == BigDecimal.class) {
break;
}
if (targetType == Float.class || targetType == float.class) {
returnValue = ((Number) value).floatValue();
log.warnNoDuplicates(Message.PossibleOverflow.message(columnName, "FLOAT", "BigDecimal"));
break;
}
if (targetType == Double.class || targetType == double.class) {
returnValue = ((Number) value).doubleValue();
log.warnNoDuplicates(Message.PossibleOverflow.message(columnName, "DOUBLE", "BigDecimal"));
break;
}
if (targetType == Long.class || targetType == long.class) {
returnValue = ((Number) value).longValue();
log.warnNoDuplicates(Message.PossibleOverflow.message(columnName, "LONG", "BigDecimal"));
break;
}
if (targetType == String.class) {
returnValue = (value).toString();
break;
}
if (targetType == Integer.class || targetType == int.class) {
returnValue = ((Number) value).intValue();
log.warnNoDuplicates(Message.PossibleOverflow.message(columnName, "INT", "BigDecimal"));
break;
}
if (targetType == Short.class || targetType == short.class) {
returnValue = ((Number) value).shortValue();
log.warnNoDuplicates(Message.PossibleOverflow.message(columnName, "SHORT", "BigDecimal"));
break;
}
if (targetType == Boolean.class || targetType == boolean.class) {
// BigDecimal to Boolean. Oracle (sigh) - Additional for a Char to Boolean as then (see TestOracle for links)
returnValue = ((Number) value).intValue() == 1;
log.warnNoDuplicates(Message.PossibleOverflow.message(columnName, "BOOLEAN", "BigDecimal"));
break;
}
break;
case StringType:
if (targetType == String.class) {
break;
}
Date dval;
DateFormat df;
if (("" + value).length() > "yyyy-MM-dd".length()) {
df = DATE_FORMAT1.get();
} else {
df = DATE_FORMAT2.get();
}
// Read a string but we want a date
if (targetType == Date.class) {
// This condition occurs in SQLite when you have a datetime with default annotated
// the format returned is 2012-06-02 19:59:49
// Used for SQLite returning dates as Strings under some conditions
// SQL or others may return STRING yyyy-MM-dd for older legacy 'date' type.
// https://docs.microsoft.com/en-us/sql/t-sql/data-types/date-transact-sql?view=sql-server-ver15
returnValue = tryParseDate(value, targetType, columnName, df);
break;
}
if (targetType == Timestamp.class) {
returnValue = tryParseTimestamp(value, targetType, columnName);
break;
}
if (targetType == LocalDate.class) {
// SQLite
dval = tryParseDate(value, targetType, columnName, df);
returnValue = dval.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
break;
}
if (targetType == LocalDateTime.class) {
// SQLite
dval = tryParseDate(value, targetType, columnName, df);
returnValue = dval.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
break;
}
if (targetType.isEnum()) {
// If this is an enum do a case-insensitive comparison
Object[] enumConstants = targetType.getEnumConstants();
for (Object element : enumConstants) {
if (("" + value).equalsIgnoreCase(element.toString())) {
returnValue = element;
break;
}
}
break;
}
if (targetType == UUID.class) {
returnValue = UUID.fromString("" + value);
break;
}
if (targetType == Boolean.class || targetType == boolean.class) {
// String to Boolean - T or 1 - otherwise false (or null)
String bval = ("" + value).toUpperCase();
returnValue = bval.startsWith("T") || bval.startsWith("1");
break;
}
if (targetType == BigDecimal.class) {
try {
returnValue = new BigDecimal("" + value);
} catch (NumberFormatException e) {
throw new PersismException(Message.NumberFormatException.message(columnName, targetType, value.getClass(), value), e);
}
break;
}
break;
case characterType:
case CharacterType:
break;
case LocalDateType:
returnValue = java.sql.Date.valueOf((LocalDate) value);
break;
case LocalDateTimeType:
returnValue = Timestamp.valueOf((LocalDateTime) value);
break;
case LocalTimeType:
returnValue = Time.valueOf((LocalTime) value);
break;
case UtilDateType:
if (targetType == java.sql.Date.class) {
returnValue = new java.sql.Date(((Date) value).getTime());
break;
}
if (targetType == Timestamp.class) {
returnValue = new Timestamp(((Date) value).getTime());
break;
}
break;
case SQLDateType:
if (targetType == java.sql.Date.class) {
break;
}
if (targetType == Date.class) {
returnValue = new java.util.Date(((Date) value).getTime());
break;
}
if (targetType == Timestamp.class) {
returnValue = new Timestamp(((Date) value).getTime());
break;
}
if (targetType == LocalDate.class) {
Date dt = new Date(((Date) value).getTime());
returnValue = dt.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
break;
}
if (targetType == LocalDateTime.class) {
Date dt = new Date(((Date) value).getTime());
returnValue = dt.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
break;
}
break;
case TimestampType:
if (targetType == Timestamp.class) {
break;
}
if (targetType == Date.class) {
returnValue = new java.util.Date(((Date) value).getTime());
break;
}
if (targetType == java.sql.Date.class) {
returnValue = new java.sql.Date(((Date) value).getTime());
break;
}
if (targetType == LocalDate.class) {
Date dt = new Date(((Date) value).getTime());
returnValue = dt.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
break;
}
if (targetType == LocalDateTime.class) {
Date dt = new Date(((Date) value).getTime());
returnValue = dt.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
break;
}
if (targetType == Time.class) {
// Oracle doesn't seem to have Time so we use Timestamp
returnValue = new Time(((Date) value).getTime());
break;
}
if (targetType == LocalTime.class) {
// Oracle.... Sigh
Date dt = (Date) value;
returnValue = dt.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().toLocalTime();
break;
}
break;
case TimeType:
if (targetType == LocalTime.class) {
returnValue = LocalTime.parse("" + value);
}
break;
case InstantType:
case OffsetDateTimeType:
case ZonedDateTimeType:
log.warn(Message.ConverterValueTypeNotYetSupported.message(valueType.getJavaType()), new Throwable());
break;
case byteArrayType:
case ByteArrayType:
if (targetType == UUID.class) {
returnValue = asUuid((byte[]) value);
}
break;
case ClobType:
case BlobType:
log.warn(Message.ConverterDoNotUseClobOrBlobAsAPropertyType.message(), new Throwable());
break;
case EnumType:
// No need to convert it here.
// If it's being used for the property setter then it's OK
// If it's being used by setParameters it's converted to String
// The String case above converts from the String to the Enum
log.debug("EnumType");
break;
case UUIDType:
if (targetType == Blob.class || targetType == byte[].class || targetType == Byte[].class) {
returnValue = asBytes((UUID) value);
}
break;
case ObjectType:
break;
}
return returnValue;
}
/*
* Used by convert for convenience - common possible parsing
*/
static Date tryParseDate(Object value, Class<?> targetType, String columnName, DateFormat df) throws PersismException {
try {
return df.parse("" + value);
} catch (ParseException e) {
throw new PersismException(Message.DateFormatException.message(e.getMessage(), columnName, targetType, value.getClass(), value), e);
}
}
static Timestamp tryParseTimestamp(Object value, Class<?> targetType, String columnName) throws PersismException {
try {
return Timestamp.valueOf("" + value);
} catch (IllegalArgumentException e) {
throw new PersismException(Message.DateFormatException.message(e.getMessage(), columnName, targetType, value.getClass(), value), e);
}
}
// 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)
// https://stackoverflow.com/questions/17893609/convert-uuid-to-byte-that-works-when-using-uuid-nameuuidfrombytesb
// THANKS!
static UUID asUuid(byte[] bytes) {
ByteBuffer bb = ByteBuffer.wrap(bytes);
long firstLong = bb.getLong();
long secondLong = bb.getLong();
return new UUID(firstLong, secondLong);
}
static byte[] asBytes(UUID uuid) {
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
bb.putLong(uuid.getMostSignificantBits());
bb.putLong(uuid.getLeastSignificantBits());
return bb.array();
}
static Object asBytesFromUUID(UUID uuid) {
return asBytes(uuid);
}
}