Skip to content

Commit bb61ca1

Browse files
authored
Merge pull request #10 from ZorTik/development
Development
2 parents 24a227c + 2d5ac52 commit bb61ca1

13 files changed

Lines changed: 257 additions & 132 deletions

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package me.zort.sqllib.api;
2+
3+
import me.zort.sqllib.api.data.Row;
4+
import org.jetbrains.annotations.NotNull;
5+
6+
import java.lang.reflect.AnnotatedElement;
7+
import java.lang.reflect.Type;
8+
9+
public interface ObjectMapper {
10+
11+
void registerBackupValueResolver(@NotNull FieldValueResolver resolver);
12+
<T> T assignValues(Row row, Class<T> typeClass);
13+
14+
interface FieldValueResolver {
15+
Object obtainValue(SQLConnection connection,
16+
AnnotatedElement element,
17+
Row row,
18+
String fieldName,
19+
String convertedName,
20+
Type type);
21+
}
22+
}

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: 18 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
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.*;
5+
import me.zort.sqllib.api.ObjectMapper;
86
import me.zort.sqllib.api.Query;
97
import me.zort.sqllib.api.StatementFactory;
108
import me.zort.sqllib.api.data.QueryResult;
@@ -14,8 +12,10 @@
1412
import me.zort.sqllib.internal.Defaults;
1513
import me.zort.sqllib.internal.annotation.JsonField;
1614
import me.zort.sqllib.internal.factory.SQLConnectionFactory;
15+
import me.zort.sqllib.internal.fieldResolver.ConstructorParameterResolver;
1716
import me.zort.sqllib.internal.fieldResolver.LinkedOneFieldResolver;
1817
import me.zort.sqllib.internal.impl.DefaultNamingStrategy;
18+
import me.zort.sqllib.internal.impl.DefaultObjectMapper;
1919
import me.zort.sqllib.internal.impl.QueryResultImpl;
2020
import me.zort.sqllib.internal.query.*;
2121
import me.zort.sqllib.internal.query.part.SetStatement;
@@ -37,17 +37,19 @@
3737
*/
3838
public class SQLDatabaseConnectionImpl extends SQLDatabaseConnection {
3939

40+
// --***-- Default Constants --***--
41+
4042
public static boolean DEFAULT_AUTO_RECONNECT = true;
4143
public static boolean DEFAULT_DEBUG = false;
4244
public static boolean DEFAULT_LOG_SQL_ERRORS = true;
4345
public static NamingStrategy DEFAULT_NAMING_STRATEGY = new DefaultNamingStrategy();
4446
public static Gson DEFAULT_GSON = Defaults.DEFAULT_GSON;
4547

48+
// --***-- Options & Utilities --***--
49+
4650
@Getter
4751
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;
52+
private transient ObjectMapper objectMapper;
5153

5254
/**
5355
* Constructs new instance of this implementation with default
@@ -78,16 +80,21 @@ public SQLDatabaseConnectionImpl(SQLConnectionFactory connectionFactory, @Nullab
7880
);
7981

8082
this.options = options;
81-
this.backupValueResolvers = Collections.synchronizedList(new ArrayList<>());
83+
this.objectMapper = new DefaultObjectMapper(this);
8284

8385
// Default backup value resolvers.
8486
registerBackupValueResolver(new LinkedOneFieldResolver());
87+
registerBackupValueResolver(new ConstructorParameterResolver());
8588
}
8689

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

90-
backupValueResolvers.add(resolver);
93+
objectMapper.registerBackupValueResolver(resolver);
94+
}
95+
96+
public void setObjectMapper(@NotNull ObjectMapper objectMapper) {
97+
this.objectMapper = Objects.requireNonNull(objectMapper, "Object mapper cannot be null!");
9198
}
9299

93100
/**
@@ -162,7 +169,7 @@ public <T> QueryRowsResult<T> query(Query query, Class<T> typeClass) {
162169
QueryRowsResult<Row> resultRows = query(query.getAncestor());
163170
QueryRowsResult<T> result = new QueryRowsResult<>(resultRows.isSuccessful());
164171
for(Row row : resultRows) {
165-
Optional.ofNullable(assignValues(row, typeClass))
172+
Optional.ofNullable(objectMapper.assignValues(row, typeClass))
166173
.ifPresent(result::add);
167174
}
168175
return result;
@@ -219,95 +226,6 @@ public QueryResult exec(Query query) {
219226
}
220227
}
221228

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-
311229
private boolean handleAutoReconnect() {
312230
if(options.isAutoReconnect() && !isConnected()) {
313231
debug("Trying to make a new connection with the database!");
@@ -390,15 +308,6 @@ public PreparedStatement prepare(Connection connection) throws SQLException {
390308
}
391309
}
392310

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-
402311
@AllArgsConstructor
403312
@Data
404313
public static class UnknownValueWrapper {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public class SQLDatabaseOptions {
1616
private boolean autoReconnect = true;
1717
private boolean debug = false;
1818
private boolean logSqlErrors = true;
19-
private NamingStrategy namingStrategy = new DefaultNamingStrategy();
20-
private Gson gson = Defaults.DEFAULT_GSON;
19+
private transient NamingStrategy namingStrategy = new DefaultNamingStrategy();
20+
private transient Gson gson = Defaults.DEFAULT_GSON;
2121

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

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

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

33
import me.zort.sqllib.SQLDatabaseConnectionImpl;
4+
import me.zort.sqllib.api.ObjectMapper;
5+
import me.zort.sqllib.api.SQLConnection;
46
import me.zort.sqllib.api.data.Row;
57
import me.zort.sqllib.api.provider.Select;
68
import me.zort.sqllib.internal.annotation.LinkedOne;
@@ -16,15 +18,21 @@
1618
* @see LinkedOne
1719
* @author ZorTik
1820
*/
19-
public class LinkedOneFieldResolver implements SQLDatabaseConnectionImpl.FieldValueResolver {
21+
public class LinkedOneFieldResolver implements ObjectMapper.FieldValueResolver {
2022

2123
@Override
22-
public Object obtainValue(SQLDatabaseConnectionImpl connection,
24+
public Object obtainValue(SQLConnection _connection,
2325
AnnotatedElement element,
2426
Row row,
2527
String fieldName,
2628
String convertedName,
2729
Type type) {
30+
31+
if(!(_connection instanceof SQLDatabaseConnectionImpl))
32+
return null;
33+
34+
SQLDatabaseConnectionImpl connection = (SQLDatabaseConnectionImpl) _connection;
35+
2836
if(!element.isAnnotationPresent(LinkedOne.class)) {
2937
// This makes mapping function hop to the next resolver.
3038
return null;

0 commit comments

Comments
 (0)