Coverage Summary for Class: Reader (net.sf.persism)
| Class |
Method, %
|
Branch, %
|
Line, %
|
| Reader |
100%
(6/6)
|
92.6%
(88/95)
|
99.1%
(115/116)
|
| Reader$1 |
100%
(1/1)
|
100%
(1/1)
|
| Total |
100%
(7/7)
|
92.6%
(88/95)
|
99.1%
(116/117)
|
package net.sf.persism;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
final class Reader {
private static final Log log = Log.getLogger(Reader.class);
private static final Log blog = Log.getLogger("net.sf.persism.Benchmarks");
private final MetaData metaData;
private final Converter converter;
Reader(Session session) {
this.metaData = session.getMetaData();
this.converter = session.getConverter();
}
<T> T readObject(T object, Map<String, PropertyInfo> properties, ResultSet rs) throws SQLException, IOException {
Class<?> objectClass = object.getClass();
// We should never call this method with a primitive type.
assert JavaType.getType(objectClass) == null;
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
for (int j = 1; j <= columnCount; j++) {
String columnName = rsmd.getColumnLabel(j);
PropertyInfo columnProperty = getPropertyInfo(columnName, properties);
if (columnProperty != null) {
if (columnProperty.getter == null) {
throw new PersismException(Message.ClassHasNoGetterForProperty.message(object.getClass(), columnProperty.propertyName));
}
Class<?> returnType = columnProperty.getter.getReturnType();
Object value = readColumn(rs, j, rsmd.getColumnType(j), columnName, returnType);
if (value != null) {
try {
columnProperty.setValue(object, value);
} catch (IllegalArgumentException e) {
// A IllegalArgumentException (which is a RuntimeException) occurs if we're setting an unmatched ENUM
throw new PersismException(Message.IllegalArgumentReadingColumn.message(columnProperty.propertyName, objectClass, columnName, returnType, value.getClass(), value), e);
}
}
}
}
if (object instanceof Persistable<?> pojo) {
// Save this object initial state to later detect changed properties
pojo.saveReadState();
}
return (T) object;
}
<T> T readRecord(RecordInfo<T> recordInfo, ResultSet rs) throws SQLException, IOException, InvocationTargetException, InstantiationException, IllegalAccessException, IllegalArgumentException {
long now;
now = System.nanoTime();
ResultSetMetaData rsmd = rs.getMetaData();
List<Object> constructorParams = new ArrayList<>(recordInfo.propertyInfoByConstructorOrder().keySet().size());
for (String col : recordInfo.propertyInfoByConstructorOrder().keySet()) {
Class<?> returnType = recordInfo.propertyInfoByConstructorOrder().get(col).field.getType();
int ncol = recordInfo.ordinals().get(col);
Object value = readColumn(rs, ncol, rsmd.getColumnType(ncol), rsmd.getColumnLabel(ncol), returnType);
if (value == null && returnType.isPrimitive()) {
// Set null primitives to their default, otherwise the constructor will not be found
value = JavaType.getDefaultValue(returnType);
}
constructorParams.add(value);
}
try {
//noinspection
return recordInfo.constructor().newInstance(constructorParams.toArray());
} finally {
blog.debug("time to get readRecord: %s", (System.nanoTime() - now));
}
}
Object readColumn(ResultSet rs, int column, int sqlColumnType, String columnName, Class<?> returnType) throws SQLException, IOException {
long now = System.nanoTime();
if (returnType.isEnum()) {
// Some DBs may read an enum type as other 1111 - we can tell it here to read it as a string.
sqlColumnType = Types.CHAR;
}
JavaType columnType = JavaType.convert(sqlColumnType); // note this could be null if we can't match a type
Object value = null;
if (columnType != null) {
switch (columnType) {
case BooleanType:
case booleanType:
if (returnType == Boolean.class || returnType == boolean.class) {
value = rs.getBoolean(column);
} else {
value = rs.getByte(column);
}
break;
case TimestampType:
if (returnType == String.class) { // JTDS
value = rs.getString(column);
} else {
// work around to Oracle reading a oracle.sql.TIMESTAMP class with getObject
value = rs.getTimestamp(column);
}
break;
case ByteArrayType:
case byteArrayType:
value = rs.getBytes(column);
break;
case ClobType:
if (metaData.getConnectionType().supportsReadingFromClobType()) {
Clob clob = rs.getClob(column);
if (clob != null) {
try (InputStream in = clob.getAsciiStream()) {
StringWriter writer = new StringWriter();
int c = -1;
while ((c = in.read()) != -1) {
writer.write(c);
}
writer.flush();
value = writer.toString();
}
}
} else {
try (InputStream in = rs.getAsciiStream(column)) {
StringWriter writer = new StringWriter();
int c = -1;
while ((c = in.read()) != -1) {
writer.write(c);
}
writer.flush();
value = writer.toString();
}
}
break;
case BlobType:
byte[] buffer = new byte[1024];
if (metaData.getConnectionType().supportsReadingFromBlobType()) {
Blob blob = rs.getBlob(column);
if (blob != null) {
try (InputStream in = blob.getBinaryStream()) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
for (int len; (len = in.read(buffer)) != -1; ) {
bos.write(buffer, 0, len);
}
value = bos.toByteArray();
}
}
} else {
try (InputStream in = rs.getBinaryStream(column)) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
for (int len; (len = in.read(buffer)) != -1; ) {
bos.write(buffer, 0, len);
}
value = bos.toByteArray();
}
}
break;
case IntegerType:
// https://github.com/xerial/sqlite-jdbc/issues/604
// SQLite jdbc reports INT but the value is LONG for date types which can WRAP past Integer.MAX - Fixed in 3.39.3.0! Thanks!
if (metaData.getConnectionType() == ConnectionType.SQLite) {
value = rs.getObject(column);
if (value != null) {
if (value instanceof Long) {
value = rs.getLong(column);
} else {
value = rs.getInt(column);
}
}
} else {
value = rs.getObject(column) == null ? null : rs.getInt(column);
}
break;
case LongType:
value = rs.getObject(column) == null ? null : rs.getLong(column);
break;
case FloatType:
value = rs.getObject(column) == null ? null : rs.getFloat(column);
break;
case DoubleType:
value = rs.getObject(column) == null ? null : rs.getDouble(column);
break;
case BigIntegerType:
case BigDecimalType:
if (returnType == BigInteger.class) {
BigDecimal bd = rs.getBigDecimal(column);
if (bd != null) {
value = bd.toBigInteger();
}
} else {
value = rs.getBigDecimal(column);
}
break;
case TimeType:
value = rs.getTime(column);
break;
// We can't assume rs.getDate will work. SQLITE actually has a long value in here.
// We can live with rs.getObject and the convert method will handle it.
// case SQLDateType:
// case UtilDateType:
// value = rs.getDate(column);
// break;
case StringType:
if (returnType == Character.class || returnType == char.class) {
String s = rs.getString(column);
if (s != null && s.length() > 0) {
value = s.charAt(0);
}
break;
}
value = rs.getString(column);
break;
default:
value = rs.getObject(column);
}
} else {
value = rs.getObject(column);
String objType = "Unknown";
if (value != null) {
objType = value.getClass().getName();
}
log.warnNoDuplicates(Message.ColumnTypeNotKnownForSQLType.message(sqlColumnType, columnName, objType));
}
// If value is null or column type is unknown - no need to try to convert anything.
if (value != null && columnType != null) {
value = converter.convert(value, returnType, columnName);
}
blog.debug("time to readColumn: %s", (System.nanoTime() - now));
return value;
}
// Poor man's case-insensitive linked hash map ;)
PropertyInfo getPropertyInfo(String col, Map<String, PropertyInfo> properties) {
for (String key : properties.keySet()) {
if (key.equalsIgnoreCase(col)) {
return properties.get(key);
}
}
return null;
}
}