Skip to content

Commit 304d84a

Browse files
committed
Restructure & constructor parameters support for mapping.
1 parent 8a4440d commit 304d84a

11 files changed

Lines changed: 214 additions & 129 deletions

File tree

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package me.zort.sqllib;
2+
3+
import com.google.gson.Gson;
4+
import lombok.AccessLevel;
5+
import lombok.Getter;
6+
import me.zort.sqllib.api.data.Row;
7+
import me.zort.sqllib.internal.annotation.JsonField;
8+
import org.jetbrains.annotations.Nullable;
9+
10+
import java.lang.reflect.*;
11+
import java.util.List;
12+
import java.util.concurrent.CopyOnWriteArrayList;
13+
14+
public class ObjectMapper {
15+
16+
@Getter(AccessLevel.PROTECTED)
17+
private final List<FieldValueResolver> backupValueResolvers;
18+
// Resolvers used after no value is found for the field
19+
// in mapped object as backup.
20+
private final SQLDatabaseConnectionImpl connectionWrapper;
21+
22+
public ObjectMapper(SQLDatabaseConnectionImpl connectionWrapper) {
23+
this.backupValueResolvers = new CopyOnWriteArrayList<>();
24+
this.connectionWrapper = connectionWrapper;
25+
}
26+
27+
@Nullable
28+
public <T> T assignValues(Row row, Class<T> typeClass) {
29+
T instance = null;
30+
try {
31+
try {
32+
Constructor<T> c = typeClass.getConstructor();
33+
c.setAccessible(true);
34+
instance = c.newInstance();
35+
} catch (NoSuchMethodException e) {
36+
for(Constructor<?> c : typeClass.getConstructors()) {
37+
if(c.getParameterCount() == row.size()) {
38+
Parameter[] params = c.getParameters();
39+
Object[] vals = new Object[c.getParameterCount()];
40+
for(int i = 0; i < row.size(); i++) {
41+
Parameter param = params[i];
42+
vals[i] = buildElementValue(param, row);
43+
}
44+
try {
45+
c.setAccessible(true);
46+
instance = (T) c.newInstance(vals);
47+
} catch(Exception ignored) {
48+
continue;
49+
}
50+
}
51+
}
52+
}
53+
for(Field field : typeClass.getDeclaredFields()) {
54+
55+
if(Modifier.isTransient(field.getModifiers()) || Modifier.isStatic(field.getModifiers())) {
56+
continue;
57+
}
58+
59+
try {
60+
field.setAccessible(true);
61+
field.set(instance, buildElementValue(field, row));
62+
} catch(SecurityException ignored) {
63+
debug(String.format("Field %s on class %s cannot be set accessible!",
64+
field.getName(),
65+
typeClass.getName()));
66+
} catch(Exception ignored) {
67+
continue;
68+
}
69+
}
70+
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
71+
debug("Cannot instantinate " + typeClass.getName() + " for assigning attributes from row!");
72+
e.printStackTrace();
73+
return null;
74+
}
75+
return instance;
76+
}
77+
78+
@Nullable
79+
private Object buildElementValue(AnnotatedElement element, Row row) {
80+
String name;
81+
Type type;
82+
if(element instanceof Field) {
83+
name = ((Field) element).getName();
84+
type = ((Field) element).getGenericType();
85+
} else if(element instanceof Parameter) { // TODO: Parameter names are arg[a-zA-Z0-9]+, use different strategy.
86+
name = ((Parameter) element).getName();
87+
type = ((Parameter) element).getType();
88+
} else {
89+
return null;
90+
}
91+
Object obj = row.get(name);
92+
if(obj == null) {
93+
String converted;
94+
if((obj = row.get(converted = connectionWrapper.getOptions().getNamingStrategy().fieldNameToColumn(name))) == null) {
95+
96+
// Now backup resolvers come.
97+
for(ObjectMapper.FieldValueResolver resolver : backupValueResolvers) {
98+
Object backupValue = resolver.obtainValue(connectionWrapper, element, row, name, converted, type);
99+
if(backupValue != null) {
100+
return backupValue;
101+
}
102+
}
103+
104+
debug(String.format("Cannot find column for target %s (%s)", name, converted));
105+
return null;
106+
}
107+
}
108+
if(element.isAnnotationPresent(JsonField.class) && obj instanceof String) {
109+
String jsonString = (String) obj;
110+
Gson gson = connectionWrapper.getOptions().getGson();
111+
return gson.fromJson(jsonString, type);
112+
} else {
113+
return obj;
114+
}
115+
}
116+
117+
private void debug(String message) {
118+
connectionWrapper.debug(message);
119+
}
120+
121+
public interface FieldValueResolver {
122+
Object obtainValue(SQLDatabaseConnectionImpl connection,
123+
AnnotatedElement element,
124+
Row row,
125+
String fieldName,
126+
String convertedName,
127+
Type type);
128+
}
129+
}

core/src/main/java/me/zort/sqllib/SQLDatabaseConnection.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public SQLDatabaseConnection(SQLConnectionFactory connectionFactory) {
5858
* query(() -> "SELECT * FROM players;");
5959
*
6060
* @param query The query to use while constructing query string.
61-
* @param typeClass Type class of object which will be instantinated and
61+
* @param typeClass Type class of object which will be instantiated and
6262
* populated with column values.
6363
* @param <T> Type of objects in result.
6464
*

core/src/main/java/me/zort/sqllib/SQLDatabaseConnectionImpl.java

Lines changed: 8 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
package me.zort.sqllib;
22

33
import com.google.gson.Gson;
4-
import lombok.AllArgsConstructor;
5-
import lombok.Data;
6-
import lombok.Getter;
7-
import lombok.RequiredArgsConstructor;
4+
import lombok.*;
85
import me.zort.sqllib.api.Query;
96
import me.zort.sqllib.api.StatementFactory;
107
import me.zort.sqllib.api.data.QueryResult;
@@ -14,6 +11,7 @@
1411
import me.zort.sqllib.internal.Defaults;
1512
import me.zort.sqllib.internal.annotation.JsonField;
1613
import me.zort.sqllib.internal.factory.SQLConnectionFactory;
14+
import me.zort.sqllib.internal.fieldResolver.ConstructorParameterResolver;
1715
import me.zort.sqllib.internal.fieldResolver.LinkedOneFieldResolver;
1816
import me.zort.sqllib.internal.impl.DefaultNamingStrategy;
1917
import me.zort.sqllib.internal.impl.QueryResultImpl;
@@ -45,9 +43,7 @@ public class SQLDatabaseConnectionImpl extends SQLDatabaseConnection {
4543

4644
@Getter
4745
private final SQLDatabaseOptions options;
48-
// Resolvers used after no value is found for the field
49-
// in mapped object as backup.
50-
private final List<FieldValueResolver> backupValueResolvers;
46+
private final ObjectMapper objectMapper;
5147

5248
/**
5349
* Constructs new instance of this implementation with default
@@ -78,16 +74,17 @@ public SQLDatabaseConnectionImpl(SQLConnectionFactory connectionFactory, @Nullab
7874
);
7975

8076
this.options = options;
81-
this.backupValueResolvers = Collections.synchronizedList(new ArrayList<>());
77+
this.objectMapper = new ObjectMapper(this);
8278

8379
// Default backup value resolvers.
8480
registerBackupValueResolver(new LinkedOneFieldResolver());
81+
registerBackupValueResolver(new ConstructorParameterResolver());
8582
}
8683

87-
public void registerBackupValueResolver(@NotNull FieldValueResolver resolver) {
84+
public void registerBackupValueResolver(@NotNull ObjectMapper.FieldValueResolver resolver) {
8885
Objects.requireNonNull(resolver, "Resolver cannot be null!");
8986

90-
backupValueResolvers.add(resolver);
87+
objectMapper.getBackupValueResolvers().add(resolver);
9188
}
9289

9390
/**
@@ -162,7 +159,7 @@ public <T> QueryRowsResult<T> query(Query query, Class<T> typeClass) {
162159
QueryRowsResult<Row> resultRows = query(query.getAncestor());
163160
QueryRowsResult<T> result = new QueryRowsResult<>(resultRows.isSuccessful());
164161
for(Row row : resultRows) {
165-
Optional.ofNullable(assignValues(row, typeClass))
162+
Optional.ofNullable(objectMapper.assignValues(row, typeClass))
166163
.ifPresent(result::add);
167164
}
168165
return result;
@@ -219,95 +216,6 @@ public QueryResult exec(Query query) {
219216
}
220217
}
221218

222-
@Nullable
223-
private <T> T assignValues(Row row, Class<T> typeClass) {
224-
T instance = null;
225-
try {
226-
try {
227-
Constructor<T> c = typeClass.getConstructor();
228-
c.setAccessible(true);
229-
instance = c.newInstance();
230-
} catch (NoSuchMethodException e) {
231-
for(Constructor<?> c : typeClass.getConstructors()) {
232-
if(c.getParameterCount() == row.size()) {
233-
Parameter[] params = c.getParameters();
234-
Object[] vals = new Object[c.getParameterCount()];
235-
for(int i = 0; i < row.size(); i++) {
236-
Parameter param = params[i];
237-
vals[i] = buildElementValue(param, row);
238-
}
239-
try {
240-
instance = (T) c.newInstance(vals);
241-
} catch(Exception ignored) {
242-
continue;
243-
}
244-
}
245-
}
246-
}
247-
for(Field field : typeClass.getDeclaredFields()) {
248-
249-
if(Modifier.isTransient(field.getModifiers()) || Modifier.isStatic(field.getModifiers())) {
250-
continue;
251-
}
252-
253-
try {
254-
field.setAccessible(true);
255-
field.set(instance, buildElementValue(field, row));
256-
} catch(SecurityException ignored) {
257-
debug(String.format("Field %s on class %s cannot be set accessible!",
258-
field.getName(),
259-
typeClass.getName()));
260-
} catch(Exception ignored) {
261-
continue;
262-
}
263-
}
264-
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
265-
debug("Cannot instantinate " + typeClass.getName() + " for assigning attributes from row!");
266-
e.printStackTrace();
267-
return null;
268-
}
269-
return instance;
270-
}
271-
272-
@Nullable
273-
private Object buildElementValue(AnnotatedElement element, Row row) {
274-
String name;
275-
Type type;
276-
if(element instanceof Field) {
277-
name = ((Field) element).getName();
278-
type = ((Field) element).getGenericType();
279-
} else if(element instanceof Parameter) { // TODO: Parameter names are arg[a-zA-Z0-9]+, use different strategy.
280-
name = ((Parameter) element).getName();
281-
type = ((Parameter) element).getType();
282-
} else {
283-
return null;
284-
}
285-
Object obj = row.get(name);
286-
if(obj == null) {
287-
String converted;
288-
if((obj = row.get(converted = options.getNamingStrategy().fieldNameToColumn(name))) == null) {
289-
290-
// Now backup resolvers come.
291-
for(FieldValueResolver resolver : backupValueResolvers) {
292-
Object backupValue = resolver.obtainValue(this, element, row, name, converted, type);
293-
if(backupValue != null) {
294-
return backupValue;
295-
}
296-
}
297-
298-
debug(String.format("Cannot find column for target %s (%s)", name, converted));
299-
return null;
300-
}
301-
}
302-
if(element.isAnnotationPresent(JsonField.class) && obj instanceof String) {
303-
String jsonString = (String) obj;
304-
Gson gson = options.getGson();
305-
return gson.fromJson(jsonString, type);
306-
} else {
307-
return obj;
308-
}
309-
}
310-
311219
private boolean handleAutoReconnect() {
312220
if(options.isAutoReconnect() && !isConnected()) {
313221
debug("Trying to make a new connection with the database!");
@@ -390,15 +298,6 @@ public PreparedStatement prepare(Connection connection) throws SQLException {
390298
}
391299
}
392300

393-
public interface FieldValueResolver {
394-
Object obtainValue(SQLDatabaseConnectionImpl connection,
395-
AnnotatedElement element,
396-
Row row,
397-
String fieldName,
398-
String convertedName,
399-
Type type);
400-
}
401-
402301
@AllArgsConstructor
403302
@Data
404303
public static class UnknownValueWrapper {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package me.zort.sqllib.internal.fieldResolver;
2+
3+
import me.zort.sqllib.ObjectMapper;
4+
import me.zort.sqllib.SQLDatabaseConnectionImpl;
5+
import me.zort.sqllib.api.data.Row;
6+
import me.zort.sqllib.util.Validator;
7+
import org.jetbrains.annotations.ApiStatus;
8+
9+
import java.lang.reflect.*;
10+
import java.util.regex.Matcher;
11+
import java.util.regex.Pattern;
12+
13+
@ApiStatus.AvailableSince("0.5.1")
14+
public class ConstructorParameterResolver implements ObjectMapper.FieldValueResolver {
15+
16+
private static final Pattern argumentPattern = Pattern.compile("(arg)(\\d+)");
17+
18+
@Override
19+
public Object obtainValue(SQLDatabaseConnectionImpl connection,
20+
AnnotatedElement element,
21+
Row row,
22+
String fieldName,
23+
String convertedName,
24+
Type type) {
25+
if (!(element instanceof Parameter) || !(((Parameter) element).getDeclaringExecutable() instanceof Constructor))
26+
return null;
27+
28+
Parameter p = (Parameter) element;
29+
Matcher matcher = argumentPattern.matcher(p.getName());
30+
31+
if (matcher.matches()) {
32+
try {
33+
Field[] fields = ((Constructor) p.getDeclaringExecutable()).getDeclaringClass().getDeclaredFields();
34+
int index = Integer.parseInt(matcher.group(2));
35+
if (index >= fields.length)
36+
return null;
37+
38+
int i = -1;
39+
for (Field field : fields) {
40+
if (Validator.validateAssignableField(field))
41+
i++;
42+
43+
if (i == index) {
44+
fieldName = field.getName();
45+
break;
46+
}
47+
}
48+
} catch (NumberFormatException ignored) {}
49+
}
50+
51+
return row.get(fieldName);
52+
}
53+
}

core/src/main/java/me/zort/sqllib/internal/fieldResolver/LinkedOneFieldResolver.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package me.zort.sqllib.internal.fieldResolver;
22

3+
import me.zort.sqllib.ObjectMapper;
34
import me.zort.sqllib.SQLDatabaseConnectionImpl;
45
import me.zort.sqllib.api.data.Row;
56
import me.zort.sqllib.api.provider.Select;
@@ -16,7 +17,7 @@
1617
* @see LinkedOne
1718
* @author ZorTik
1819
*/
19-
public class LinkedOneFieldResolver implements SQLDatabaseConnectionImpl.FieldValueResolver {
20+
public class LinkedOneFieldResolver implements ObjectMapper.FieldValueResolver {
2021

2122
@Override
2223
public Object obtainValue(SQLDatabaseConnectionImpl connection,

0 commit comments

Comments
 (0)