Coverage Summary for Class: Reader (net.sf.persism)

Class Method, % Line, %
Reader 100% (6/6) 99.3% (139/140)
Reader$1 100% (1/1) 100% (1/1)
Total 100% (7/7) 99.3% (140/141)


1 package net.sf.persism; 2  3 import java.io.ByteArrayOutputStream; 4 import java.io.IOException; 5 import java.io.InputStream; 6 import java.io.StringWriter; 7 import java.lang.reflect.InvocationTargetException; 8 import java.math.BigDecimal; 9 import java.math.BigInteger; 10 import java.sql.*; 11 import java.util.ArrayList; 12 import java.util.List; 13 import java.util.Map; 14  15 final class Reader { 16  17  private static final Log log = Log.getLogger(Reader.class); 18  private static final Log blog = Log.getLogger("net.sf.persism.Benchmarks"); 19  20  private final MetaData metaData; 21  private final Converter converter; 22  23  Reader(Session session) { 24  this.metaData = session.getMetaData(); 25  this.converter = session.getConverter(); 26  } 27  28  <T> T readObject(T object, Map<String, PropertyInfo> properties, ResultSet rs) throws SQLException, IOException { 29  Class<?> objectClass = object.getClass(); 30  31  // We should never call this method with a primitive type. 32  assert JavaType.getType(objectClass) == null; 33  34  ResultSetMetaData rsmd = rs.getMetaData(); 35  int columnCount = rsmd.getColumnCount(); 36  37  for (int j = 1; j <= columnCount; j++) { 38  39  String columnName = rsmd.getColumnLabel(j); 40  41  PropertyInfo columnProperty = getPropertyInfo(columnName, properties); 42  43  if (columnProperty != null) { 44  45  if (columnProperty.getter == null) { 46  throw new PersismException(Message.ClassHasNoGetterForProperty.message(object.getClass(), columnProperty.propertyName)); 47  } 48  49  Class<?> returnType = columnProperty.getter.getReturnType(); 50  51  Object value = readColumn(rs, j, rsmd.getColumnType(j), columnName, returnType); 52  53  if (value != null) { 54  try { 55  columnProperty.setValue(object, value); 56  } catch (IllegalArgumentException e) { 57  // A IllegalArgumentException (which is a RuntimeException) occurs if we're setting an unmatched ENUM 58  throw new PersismException(Message.IllegalArgumentReadingColumn.message(columnProperty.propertyName, objectClass, columnName, returnType, value.getClass(), value), e); 59  } 60  } 61  } 62  } 63  64  if (object instanceof Persistable<?> pojo) { 65  // Save this object initial state to later detect changed properties 66  pojo.saveReadState(); 67  } 68  return (T) object; 69  } 70  71  <T> T readRecord(RecordInfo<T> recordInfo, ResultSet rs) throws SQLException, IOException, InvocationTargetException, InstantiationException, IllegalAccessException { 72  73  long now; 74  now = System.nanoTime(); 75  76  ResultSetMetaData rsmd = rs.getMetaData(); 77  List<Object> constructorParams = new ArrayList<>(recordInfo.propertyInfoByConstructorOrder().keySet().size()); 78  79  for (String col : recordInfo.propertyInfoByConstructorOrder().keySet()) { 80  Class<?> returnType = recordInfo.propertyInfoByConstructorOrder().get(col).field.getType(); 81  82  int ncol = recordInfo.ordinals().get(col); 83  Object value = readColumn(rs, ncol, rsmd.getColumnType(ncol), rsmd.getColumnLabel(ncol), returnType); 84  if (value == null && returnType.isPrimitive()) { 85  // Set null primitives to their default, otherwise the constructor will not be found 86  value = JavaType.getDefaultValue(returnType); 87  } 88  89  constructorParams.add(value); 90  } 91  92  93  try { 94  //noinspection 95  return recordInfo.constructor().newInstance(constructorParams.toArray()); 96  } finally { 97  blog.debug("time to get readRecord: %s", (System.nanoTime() - now)); 98  } 99  } 100  101  102  Object readColumn(ResultSet rs, int column, int sqlColumnType, String columnName, Class<?> returnType) throws SQLException, IOException { 103  long now = System.nanoTime(); 104  105  if (returnType.isEnum()) { 106  // Some DBs may read an enum type as other 1111 - we can tell it here to read it as a string. 107  sqlColumnType = java.sql.Types.CHAR; 108  } 109  110  JavaType columnType = JavaType.convert(sqlColumnType); // note this could be null if we can't match a type 111  112  Object value = null; 113  114  if (columnType != null) { 115  116  switch (columnType) { 117  118  case BooleanType: 119  case booleanType: 120  if (returnType == Boolean.class || returnType == boolean.class) { 121  value = rs.getBoolean(column); 122  } else { 123  value = rs.getByte(column); 124  } 125  break; 126  127  case TimestampType: 128  if (returnType == String.class) { // JTDS 129  value = rs.getString(column); 130  } else { 131  // work around to Oracle reading a oracle.sql.TIMESTAMP class with getObject 132  value = rs.getTimestamp(column); 133  } 134  break; 135  136  case ByteArrayType: 137  case byteArrayType: 138  value = rs.getBytes(column); 139  break; 140  141  case ClobType: 142  if (metaData.getConnectionType().supportsReadingFromClobType()) { 143  Clob clob = rs.getClob(column); 144  if (clob != null) { 145  try (InputStream in = clob.getAsciiStream()) { 146  StringWriter writer = new StringWriter(); 147  148  int c = -1; 149  while ((c = in.read()) != -1) { 150  writer.write(c); 151  } 152  writer.flush(); 153  value = writer.toString(); 154  } 155  } 156  } else { 157  try (InputStream in = rs.getAsciiStream(column)) { 158  StringWriter writer = new StringWriter(); 159  160  int c = -1; 161  while ((c = in.read()) != -1) { 162  writer.write(c); 163  } 164  writer.flush(); 165  value = writer.toString(); 166  } 167  } 168  break; 169  170  case BlobType: 171  byte[] buffer = new byte[1024]; 172  if (metaData.getConnectionType().supportsReadingFromBlobType()) { 173  Blob blob = rs.getBlob(column); 174  if (blob != null) { 175  try (InputStream in = blob.getBinaryStream()) { 176  ByteArrayOutputStream bos = new ByteArrayOutputStream(); 177  for (int len; (len = in.read(buffer)) != -1; ) { 178  bos.write(buffer, 0, len); 179  } 180  value = bos.toByteArray(); 181  } 182  } 183  } else { 184  try (InputStream in = rs.getBinaryStream(column)) { 185  ByteArrayOutputStream bos = new ByteArrayOutputStream(); 186  for (int len; (len = in.read(buffer)) != -1; ) { 187  bos.write(buffer, 0, len); 188  } 189  value = bos.toByteArray(); 190  } 191  } 192  break; 193  194  case IntegerType: 195  // https://github.com/xerial/sqlite-jdbc/issues/604 196  // 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! 197  if (metaData.getConnectionType() == ConnectionType.SQLite) { 198  value = rs.getObject(column); 199  if (value != null) { 200  if (value instanceof Long) { 201  value = rs.getLong(column); 202  } else { 203  value = rs.getInt(column); 204  } 205  } 206  } else { 207  value = rs.getObject(column) == null ? null : rs.getInt(column); 208  } 209  break; 210  211  case LongType: 212  value = rs.getObject(column) == null ? null : rs.getLong(column); 213  break; 214  215  case FloatType: 216  value = rs.getObject(column) == null ? null : rs.getFloat(column); 217  break; 218  219  case DoubleType: 220  value = rs.getObject(column) == null ? null : rs.getDouble(column); 221  break; 222  223  case BigIntegerType: 224  case BigDecimalType: 225  if (returnType == BigInteger.class) { 226  BigDecimal bd = rs.getBigDecimal(column); 227  if (bd != null) { 228  value = bd.toBigInteger(); 229  } 230  } else { 231  value = rs.getBigDecimal(column); 232  } 233  break; 234  235  case TimeType: 236  value = rs.getTime(column); 237  break; 238  239 // We can't assume rs.getDate will work. SQLITE actually has a long value in here. 240 // We can live with rs.getObject and the convert method will handle it. 241 // case SQLDateType: 242 // case UtilDateType: 243 // value = rs.getDate(column); 244 // break; 245  246  case StringType: 247  if (returnType == Character.class || returnType == char.class) { 248  String s = rs.getString(column); 249  if (s != null && s.length() > 0) { 250  value = s.charAt(0); 251  } 252  break; 253  } 254  255  value = rs.getString(column); 256  break; 257  258  default: 259  value = rs.getObject(column); 260  } 261  262  } else { 263  value = rs.getObject(column); 264  String objType = "Unknown"; 265  if (value != null) { 266  objType = value.getClass().getName(); 267  } 268  log.warnNoDuplicates(Message.ColumnTypeNotKnownForSQLType.message(sqlColumnType, columnName, objType)); 269  } 270  271  // If value is null or column type is unknown - no need to try to convert anything. 272  if (value != null && columnType != null) { 273  value = converter.convert(value, returnType, columnName); 274  } 275  276  blog.debug("time to readColumn: %s", (System.nanoTime() - now)); 277  return value; 278  } 279  280  281  // Poor man's case-insensitive linked hash map ;) 282  PropertyInfo getPropertyInfo(String col, Map<String, PropertyInfo> properties) { 283  for (String key : properties.keySet()) { 284  if (key.equalsIgnoreCase(col)) { 285  return properties.get(key); 286  } 287  } 288  return null; 289  } 290  291 }