Coverage Summary for Class: RecordInfo (net.sf.persism)
Class |
Class, %
|
Method, %
|
Line, %
|
RecordInfo |
100%
(1/1)
|
100%
(6/6)
|
100%
(50/50)
|
1 package net.sf.persism;
2
3 import java.beans.ConstructorProperties;
4 import java.lang.reflect.Constructor;
5 import java.lang.reflect.Parameter;
6 import java.sql.ResultSet;
7 import java.sql.ResultSetMetaData;
8 import java.sql.SQLException;
9 import java.util.*;
10 import java.util.stream.Collectors;
11
12 import static net.sf.persism.Util.listEqualsIgnoreOrder;
13
14 // todo Cache RecordInfo
15 class RecordInfo<T> {
16
17 private final Map<String, PropertyInfo> propertyInfoByConstructorOrder;
18 private final Constructor<T> constructor;
19 private final Map<String, Integer> ordinals;
20
21 public RecordInfo(Class<T> objectClass, Map<String, PropertyInfo> properties, ResultSet rs) throws SQLException {
22 // resultset may not have columns in the proper order
23 // resultset may not have all columns
24 // get column order based on which properties are found
25 // get matching constructor
26
27 List<String> propertyNames = new ArrayList<>(properties.values().stream().map(propertyInfo -> propertyInfo.propertyName).toList());
28 Constructor<T> selectedConstructor = findConstructor(objectClass, propertyNames);
29
30 // now re-arrange by property order
31 propertyInfoByConstructorOrder = new LinkedHashMap<>(selectedConstructor.getParameterCount());
32 for (String paramName : propertyNames) {
33 for (String col : properties.keySet()) {
34 if (paramName.equals(properties.get(col).field.getName())) {
35 propertyInfoByConstructorOrder.put(col, properties.get(col));
36 }
37 }
38 }
39
40 List<Class<?>> constructorTypes = new ArrayList<>(12);
41 // put into ordinals the order to read from to match the constructor
42 ResultSetMetaData rsmd = rs.getMetaData();
43 ordinals = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
44 for (int j = 1; j <= rsmd.getColumnCount(); j++) {
45 ordinals.put(rsmd.getColumnLabel(j), j);
46 }
47
48 for (String col : propertyInfoByConstructorOrder.keySet()) {
49 if (ordinals.containsKey(col)) {
50 constructorTypes.add(propertyInfoByConstructorOrder.get(col).field.getType());
51 } else {
52 // can happen if a user manually constructs the SQL and misses a column
53 throw new PersismException(Message.ReadRecordColumnNotFound.message(objectClass, col));
54 }
55 }
56
57 try {
58 constructor = objectClass.getConstructor(constructorTypes.toArray(new Class<?>[0]));
59 assert constructor.equals(selectedConstructor);
60 } catch (NoSuchMethodException e) {
61 throw new PersismException(Message.ReadRecordCouldNotInstantiate.message(objectClass, constructorTypes));
62 }
63 }
64
65 private Constructor<T> findConstructor(Class<T> objectClass, List<String> propertyNames) {
66
67 //noinspection unchecked
68 Constructor<T>[] constructors = (Constructor<T>[]) objectClass.getConstructors();
69 Constructor<T> selectedConstructor = null;
70
71 for (Constructor<T> constructor : constructors) {
72 // Check with canonical or maybe -parameters
73 List<String> parameterNames = Arrays.stream(constructor.getParameters()).
74 map(Parameter::getName).collect(Collectors.toList());
75
76 // why don't I just use the parameterNames instead of modifying property list?
77 if (listEqualsIgnoreOrder(propertyNames, parameterNames)) {
78 // re-arrange the propertyNames to match parameterNames
79 propertyNames.clear();
80 propertyNames.addAll(parameterNames);
81 selectedConstructor = constructor;
82 break;
83 }
84
85 // Check with ConstructorProperties
86 ConstructorProperties constructorProperties = constructor.getAnnotation(ConstructorProperties.class);
87 if (constructorProperties != null) {
88 parameterNames = Arrays.asList(constructorProperties.value());
89 if (listEqualsIgnoreOrder(propertyNames, parameterNames)) {
90 // re-arrange the propertyNames to match parameterNames
91 propertyNames.clear();
92 propertyNames.addAll(parameterNames);
93 selectedConstructor = constructor;
94 break;
95 }
96 }
97 }
98
99 if (selectedConstructor == null) {
100 throw new PersismException(Message.CouldNotFindConstructorForRecord.message(objectClass.getName(), propertyNames));
101 }
102 return selectedConstructor;
103
104 }
105
106 public Map<String, PropertyInfo> propertyInfoByConstructorOrder() {
107 return propertyInfoByConstructorOrder;
108 }
109
110 public Constructor<T> constructor() {
111 return constructor;
112 }
113
114 public Map<String, Integer> ordinals() {
115 return ordinals;
116 }
117
118 }