Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions api/src/main/java/me/zort/sqllib/api/ObjectMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package me.zort.sqllib.api;

import me.zort.sqllib.api.data.Row;
import org.jetbrains.annotations.NotNull;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Type;

public interface ObjectMapper {

void registerBackupValueResolver(@NotNull FieldValueResolver resolver);
<T> T assignValues(Row row, Class<T> typeClass);

interface FieldValueResolver {
Object obtainValue(SQLConnection connection,
AnnotatedElement element,
Row row,
String fieldName,
String convertedName,
Type type);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public SQLDatabaseConnection(SQLConnectionFactory connectionFactory) {
* query(() -> "SELECT * FROM players;");
*
* @param query The query to use while constructing query string.
* @param typeClass Type class of object which will be instantinated and
* @param typeClass Type class of object which will be instantiated and
* populated with column values.
* @param <T> Type of objects in result.
*
Expand Down
127 changes: 18 additions & 109 deletions core/src/main/java/me/zort/sqllib/SQLDatabaseConnectionImpl.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package me.zort.sqllib;

import com.google.gson.Gson;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.*;
import me.zort.sqllib.api.ObjectMapper;
import me.zort.sqllib.api.Query;
import me.zort.sqllib.api.StatementFactory;
import me.zort.sqllib.api.data.QueryResult;
Expand All @@ -14,8 +12,10 @@
import me.zort.sqllib.internal.Defaults;
import me.zort.sqllib.internal.annotation.JsonField;
import me.zort.sqllib.internal.factory.SQLConnectionFactory;
import me.zort.sqllib.internal.fieldResolver.ConstructorParameterResolver;
import me.zort.sqllib.internal.fieldResolver.LinkedOneFieldResolver;
import me.zort.sqllib.internal.impl.DefaultNamingStrategy;
import me.zort.sqllib.internal.impl.DefaultObjectMapper;
import me.zort.sqllib.internal.impl.QueryResultImpl;
import me.zort.sqllib.internal.query.*;
import me.zort.sqllib.internal.query.part.SetStatement;
Expand All @@ -37,17 +37,19 @@
*/
public class SQLDatabaseConnectionImpl extends SQLDatabaseConnection {

// --***-- Default Constants --***--

public static boolean DEFAULT_AUTO_RECONNECT = true;
public static boolean DEFAULT_DEBUG = false;
public static boolean DEFAULT_LOG_SQL_ERRORS = true;
public static NamingStrategy DEFAULT_NAMING_STRATEGY = new DefaultNamingStrategy();
public static Gson DEFAULT_GSON = Defaults.DEFAULT_GSON;

// --***-- Options & Utilities --***--

@Getter
private final SQLDatabaseOptions options;
// Resolvers used after no value is found for the field
// in mapped object as backup.
private final List<FieldValueResolver> backupValueResolvers;
private transient ObjectMapper objectMapper;

/**
* Constructs new instance of this implementation with default
Expand Down Expand Up @@ -78,16 +80,21 @@ public SQLDatabaseConnectionImpl(SQLConnectionFactory connectionFactory, @Nullab
);

this.options = options;
this.backupValueResolvers = Collections.synchronizedList(new ArrayList<>());
this.objectMapper = new DefaultObjectMapper(this);

// Default backup value resolvers.
registerBackupValueResolver(new LinkedOneFieldResolver());
registerBackupValueResolver(new ConstructorParameterResolver());
}

public void registerBackupValueResolver(@NotNull FieldValueResolver resolver) {
public void registerBackupValueResolver(@NotNull ObjectMapper.FieldValueResolver resolver) {
Objects.requireNonNull(resolver, "Resolver cannot be null!");

backupValueResolvers.add(resolver);
objectMapper.registerBackupValueResolver(resolver);
}

public void setObjectMapper(@NotNull ObjectMapper objectMapper) {
this.objectMapper = Objects.requireNonNull(objectMapper, "Object mapper cannot be null!");
}

/**
Expand Down Expand Up @@ -162,7 +169,7 @@ public <T> QueryRowsResult<T> query(Query query, Class<T> typeClass) {
QueryRowsResult<Row> resultRows = query(query.getAncestor());
QueryRowsResult<T> result = new QueryRowsResult<>(resultRows.isSuccessful());
for(Row row : resultRows) {
Optional.ofNullable(assignValues(row, typeClass))
Optional.ofNullable(objectMapper.assignValues(row, typeClass))
.ifPresent(result::add);
}
return result;
Expand Down Expand Up @@ -219,95 +226,6 @@ public QueryResult exec(Query query) {
}
}

@Nullable
private <T> T assignValues(Row row, Class<T> typeClass) {
T instance = null;
try {
try {
Constructor<T> c = typeClass.getConstructor();
c.setAccessible(true);
instance = c.newInstance();
} catch (NoSuchMethodException e) {
for(Constructor<?> c : typeClass.getConstructors()) {
if(c.getParameterCount() == row.size()) {
Parameter[] params = c.getParameters();
Object[] vals = new Object[c.getParameterCount()];
for(int i = 0; i < row.size(); i++) {
Parameter param = params[i];
vals[i] = buildElementValue(param, row);
}
try {
instance = (T) c.newInstance(vals);
} catch(Exception ignored) {
continue;
}
}
}
}
for(Field field : typeClass.getDeclaredFields()) {

if(Modifier.isTransient(field.getModifiers()) || Modifier.isStatic(field.getModifiers())) {
continue;
}

try {
field.setAccessible(true);
field.set(instance, buildElementValue(field, row));
} catch(SecurityException ignored) {
debug(String.format("Field %s on class %s cannot be set accessible!",
field.getName(),
typeClass.getName()));
} catch(Exception ignored) {
continue;
}
}
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
debug("Cannot instantinate " + typeClass.getName() + " for assigning attributes from row!");
e.printStackTrace();
return null;
}
return instance;
}

@Nullable
private Object buildElementValue(AnnotatedElement element, Row row) {
String name;
Type type;
if(element instanceof Field) {
name = ((Field) element).getName();
type = ((Field) element).getGenericType();
} else if(element instanceof Parameter) { // TODO: Parameter names are arg[a-zA-Z0-9]+, use different strategy.
name = ((Parameter) element).getName();
type = ((Parameter) element).getType();
} else {
return null;
}
Object obj = row.get(name);
if(obj == null) {
String converted;
if((obj = row.get(converted = options.getNamingStrategy().fieldNameToColumn(name))) == null) {

// Now backup resolvers come.
for(FieldValueResolver resolver : backupValueResolvers) {
Object backupValue = resolver.obtainValue(this, element, row, name, converted, type);
if(backupValue != null) {
return backupValue;
}
}

debug(String.format("Cannot find column for target %s (%s)", name, converted));
return null;
}
}
if(element.isAnnotationPresent(JsonField.class) && obj instanceof String) {
String jsonString = (String) obj;
Gson gson = options.getGson();
return gson.fromJson(jsonString, type);
} else {
return obj;
}
}

private boolean handleAutoReconnect() {
if(options.isAutoReconnect() && !isConnected()) {
debug("Trying to make a new connection with the database!");
Expand Down Expand Up @@ -390,15 +308,6 @@ public PreparedStatement prepare(Connection connection) throws SQLException {
}
}

public interface FieldValueResolver {
Object obtainValue(SQLDatabaseConnectionImpl connection,
AnnotatedElement element,
Row row,
String fieldName,
String convertedName,
Type type);
}

@AllArgsConstructor
@Data
public static class UnknownValueWrapper {
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/java/me/zort/sqllib/SQLDatabaseOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class SQLDatabaseOptions {
private boolean autoReconnect = true;
private boolean debug = false;
private boolean logSqlErrors = true;
private NamingStrategy namingStrategy = new DefaultNamingStrategy();
private Gson gson = Defaults.DEFAULT_GSON;
private transient NamingStrategy namingStrategy = new DefaultNamingStrategy();
private transient Gson gson = Defaults.DEFAULT_GSON;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package me.zort.sqllib.internal.fieldResolver;

import me.zort.sqllib.SQLDatabaseConnectionImpl;
import me.zort.sqllib.api.ObjectMapper;
import me.zort.sqllib.api.SQLConnection;
import me.zort.sqllib.api.data.Row;
import me.zort.sqllib.util.Validator;
import org.jetbrains.annotations.ApiStatus;

import java.lang.reflect.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@ApiStatus.AvailableSince("0.5.1")
public class ConstructorParameterResolver implements ObjectMapper.FieldValueResolver {

private static final Pattern argumentPattern = Pattern.compile("(arg)(\\d+)");

@Override
public Object obtainValue(SQLConnection connection,
AnnotatedElement element,
Row row,
String fieldName,
String convertedName,
Type type) {
if (!(element instanceof Parameter) || !(((Parameter) element).getDeclaringExecutable() instanceof Constructor))
return null;

Parameter p = (Parameter) element;
Matcher matcher = argumentPattern.matcher(p.getName());

if (matcher.matches()) {
try {
Field[] fields = ((Constructor) p.getDeclaringExecutable()).getDeclaringClass().getDeclaredFields();
int index = Integer.parseInt(matcher.group(2));
if (index >= fields.length)
return null;

int i = -1;
for (Field field : fields) {
if (Validator.validateAssignableField(field))
i++;

if (i == index) {
fieldName = field.getName();
break;
}
}
} catch (NumberFormatException ignored) {}
}

return row.get(fieldName);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package me.zort.sqllib.internal.fieldResolver;

import me.zort.sqllib.SQLDatabaseConnectionImpl;
import me.zort.sqllib.api.ObjectMapper;
import me.zort.sqllib.api.SQLConnection;
import me.zort.sqllib.api.data.Row;
import me.zort.sqllib.api.provider.Select;
import me.zort.sqllib.internal.annotation.LinkedOne;
Expand All @@ -16,15 +18,21 @@
* @see LinkedOne
* @author ZorTik
*/
public class LinkedOneFieldResolver implements SQLDatabaseConnectionImpl.FieldValueResolver {
public class LinkedOneFieldResolver implements ObjectMapper.FieldValueResolver {

@Override
public Object obtainValue(SQLDatabaseConnectionImpl connection,
public Object obtainValue(SQLConnection _connection,
AnnotatedElement element,
Row row,
String fieldName,
String convertedName,
Type type) {

if(!(_connection instanceof SQLDatabaseConnectionImpl))
return null;

SQLDatabaseConnectionImpl connection = (SQLDatabaseConnectionImpl) _connection;

if(!element.isAnnotationPresent(LinkedOne.class)) {
// This makes mapping function hop to the next resolver.
return null;
Expand Down
Loading