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 }