wip
This commit is contained in:
@@ -1,13 +1,30 @@
|
||||
package jef.platform.mysql;
|
||||
|
||||
import jef.MigrationException;
|
||||
import jef.expressions.Expression;
|
||||
import jef.model.DbContext;
|
||||
import jef.model.DbField;
|
||||
import jef.model.DbFieldBuilder;
|
||||
import jef.model.ModelBuilder;
|
||||
import jef.platform.base.Database;
|
||||
import jef.platform.base.DatabaseOptions;
|
||||
import jef.platform.mysql.query.MysqlQueryBuilder;
|
||||
import jef.serializable.SerializableObject;
|
||||
import jef.util.Util;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.sql.Types;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MysqlDatabase extends Database {
|
||||
private final MysqlPlatform platform;
|
||||
|
||||
public MysqlDatabase(Connection connection, DatabaseOptions options, MysqlPlatform platform) {
|
||||
super(connection, options);
|
||||
this.platform = platform;
|
||||
@@ -17,4 +34,300 @@ public class MysqlDatabase extends Database {
|
||||
public void migrate() throws MigrationException {
|
||||
platform.getMigrationApplier(connection, options).migrate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends SerializableObject> List<T> queryExpression(DbContext context, Class<T> clazz, Expression expression) throws SQLException {
|
||||
MysqlQueryBuilder queryBuilder = new MysqlQueryBuilder();
|
||||
queryBuilder.visit(expression);
|
||||
return queryRaw(context, clazz, queryBuilder.getQuery());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends SerializableObject> void add(DbContext context, Class<T> clazz, T model) throws SQLException {
|
||||
var modelBuilder = ModelBuilder.from(context.getClass());
|
||||
var entity = modelBuilder.entity(clazz);
|
||||
|
||||
var columns = new ArrayList<String>();
|
||||
var valueBinders = new ArrayList<Util.ThrowableBiConsumer<PreparedStatement, Integer>>();
|
||||
|
||||
for (DbFieldBuilder<?> field : entity.fields()) {
|
||||
Field f = field.getField().getField();
|
||||
if (f == null) {
|
||||
if (field.getField().isDatabaseField()) {
|
||||
columns.add(field.getField().getName());
|
||||
valueBinders.add((stmt, i) -> stmt.setNull(i, Types.INTEGER));//TODO set proper type
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!field.getField().isDatabaseField()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
f.setAccessible(true);
|
||||
|
||||
columns.add(field.getField().getName());
|
||||
try {
|
||||
if (f.get(model) == null) {
|
||||
valueBinders.add((stmt, i) -> stmt.setNull(i, Types.INTEGER));
|
||||
} else if (field.getField().getType() == String.class) {
|
||||
valueBinders.add((stmt, i) -> stmt.setString(i, (String) f.get(model)));
|
||||
} else if (field.getField().getType() == byte.class || field.getField().getType() == Byte.class) {
|
||||
valueBinders.add((stmt, i) -> stmt.setByte(i, (byte) f.get(model)));
|
||||
} else if (field.getField().getType() == char.class || field.getField().getType() == Character.class) {
|
||||
// valueSetters.add((stmt, i) -> stmt.setString(i, (char) f.get(model)));//TODO handle char
|
||||
throw new UnsupportedOperationException("char");
|
||||
} else if (field.getField().getType() == short.class || field.getField().getType() == Short.class) {
|
||||
valueBinders.add((stmt, i) -> stmt.setShort(i, (short) f.get(model)));
|
||||
} else if (field.getField().getType() == int.class || field.getField().getType() == Integer.class) {
|
||||
valueBinders.add((stmt, i) -> stmt.setInt(i, (int) f.get(model)));
|
||||
} else if (field.getField().getType() == long.class || field.getField().getType() == Long.class) {
|
||||
valueBinders.add((stmt, i) -> stmt.setLong(i, (long) f.get(model)));
|
||||
} else if (field.getField().getType() == float.class || field.getField().getType() == Float.class) {
|
||||
valueBinders.add((stmt, i) -> stmt.setFloat(i, (float) f.get(model)));
|
||||
} else if (field.getField().getType() == double.class || field.getField().getType() == Double.class) {
|
||||
valueBinders.add((stmt, i) -> stmt.setDouble(i, (double) f.get(model)));
|
||||
} else {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
var columnsString = columns.stream().map(e -> "`" + e + "`").collect(Collectors.joining(","));
|
||||
var placeholdersString = columns.stream().map(e -> "?").collect(Collectors.joining(","));
|
||||
var query = "INSERT INTO `" + entity.name() + "` (" + columnsString + ") VALUES (" + placeholdersString + ")";
|
||||
try (var stmt = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS)) {
|
||||
var i = 1;
|
||||
for (Util.ThrowableBiConsumer<PreparedStatement, Integer> valueBinder : valueBinders) {
|
||||
try {
|
||||
valueBinder.accept(stmt, i++);
|
||||
} catch (Throwable e) {
|
||||
if (e instanceof SQLException) {
|
||||
throw (SQLException) e;
|
||||
}
|
||||
throw new IllegalStateException("This may not happen", e);
|
||||
}
|
||||
}
|
||||
var added = stmt.executeUpdate();
|
||||
if (added != 1) {
|
||||
throw new SQLException("Failed to insert entity");
|
||||
}
|
||||
try (var res = stmt.getGeneratedKeys()) {
|
||||
var primary = entity.getEntity().getPrimaryKey();
|
||||
if (primary != null) {
|
||||
if (!res.next()) {
|
||||
throw new SQLException("Failed to add entity");
|
||||
}
|
||||
var idField = primary.getFields().get(0);//TODO this only supports primary keys, and only with one column
|
||||
idField.getField().setAccessible(true);
|
||||
if (idField.getField().getType() == int.class || idField.getField().getType() == Integer.class) {//TODO support more types
|
||||
idField.getField().set(model, res.getInt(1));
|
||||
} else if (idField.getField().getType() == long.class || idField.getField().getType() == Long.class) {
|
||||
idField.getField().set(model, res.getLong(1));
|
||||
} else {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends SerializableObject> void update(DbContext context, Class<T> clazz, T model) throws SQLException {
|
||||
var modelBuilder = ModelBuilder.from(context.getClass());
|
||||
var entity = modelBuilder.entity(clazz);
|
||||
if (entity.getEntity().getPrimaryKey() == null) {
|
||||
throw new UnsupportedOperationException("Primary key required for updates");//at least for now
|
||||
}
|
||||
|
||||
var columns = new ArrayList<String>();
|
||||
var valueBinders = new ArrayList<Util.ThrowableBiConsumer<PreparedStatement, Integer>>();
|
||||
|
||||
for (DbFieldBuilder<?> field : entity.fields()) {
|
||||
Field f = field.getField().getField();
|
||||
if (f == null) {
|
||||
if (field.getField().isDatabaseField()) {
|
||||
columns.add(field.getField().getName());
|
||||
valueBinders.add((stmt, i) -> stmt.setNull(i, Types.INTEGER));//TODO set proper type
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!field.getField().isDatabaseField()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
f.setAccessible(true);
|
||||
|
||||
columns.add(field.getField().getName());
|
||||
try {
|
||||
if (f.get(model) == null) {
|
||||
valueBinders.add((stmt, i) -> stmt.setNull(i, Types.INTEGER));
|
||||
} else if (field.getField().getType() == String.class) {
|
||||
valueBinders.add((stmt, i) -> stmt.setString(i, (String) f.get(model)));
|
||||
} else if (field.getField().getType() == byte.class || field.getField().getType() == Byte.class) {
|
||||
valueBinders.add((stmt, i) -> stmt.setByte(i, (byte) f.get(model)));
|
||||
} else if (field.getField().getType() == char.class || field.getField().getType() == Character.class) {
|
||||
// valueSetters.add((stmt, i) -> stmt.setString(i, (char) f.get(model)));//TODO handle char
|
||||
throw new UnsupportedOperationException("char");
|
||||
} else if (field.getField().getType() == short.class || field.getField().getType() == Short.class) {
|
||||
valueBinders.add((stmt, i) -> stmt.setShort(i, (short) f.get(model)));
|
||||
} else if (field.getField().getType() == int.class || field.getField().getType() == Integer.class) {
|
||||
valueBinders.add((stmt, i) -> stmt.setInt(i, (int) f.get(model)));
|
||||
} else if (field.getField().getType() == long.class || field.getField().getType() == Long.class) {
|
||||
valueBinders.add((stmt, i) -> stmt.setLong(i, (long) f.get(model)));
|
||||
} else if (field.getField().getType() == float.class || field.getField().getType() == Float.class) {
|
||||
valueBinders.add((stmt, i) -> stmt.setFloat(i, (float) f.get(model)));
|
||||
} else if (field.getField().getType() == double.class || field.getField().getType() == Double.class) {
|
||||
valueBinders.add((stmt, i) -> stmt.setDouble(i, (double) f.get(model)));
|
||||
} else {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
var updatesString = columns.stream().map(e -> "`" + e + "` = ?").collect(Collectors.joining(", "));
|
||||
var whereString = entity.getEntity().getPrimaryKey().getFields().stream().map(e -> "`" + e.getName() + "` = ?").collect(Collectors.joining(" AND "));
|
||||
|
||||
//add values for where clause
|
||||
for (DbField<?> field : entity.getEntity().getPrimaryKey().getFields()) {
|
||||
Field f = field.getField();
|
||||
f.setAccessible(true);
|
||||
try {
|
||||
if (f.get(model) == null) {
|
||||
valueBinders.add((stmt, i) -> stmt.setNull(i, Types.INTEGER));
|
||||
} else if (field.getField().getType() == String.class) {
|
||||
valueBinders.add((stmt, i) -> stmt.setString(i, (String) f.get(model)));
|
||||
} else if (field.getField().getType() == byte.class || field.getField().getType() == Byte.class) {
|
||||
valueBinders.add((stmt, i) -> stmt.setByte(i, (byte) f.get(model)));
|
||||
} else if (field.getField().getType() == char.class || field.getField().getType() == Character.class) {
|
||||
// valueSetters.add((stmt, i) -> stmt.setString(i, (char) f.get(model)));//TODO handle char
|
||||
throw new UnsupportedOperationException("char");
|
||||
} else if (field.getField().getType() == short.class || field.getField().getType() == Short.class) {
|
||||
valueBinders.add((stmt, i) -> stmt.setShort(i, (short) f.get(model)));
|
||||
} else if (field.getField().getType() == int.class || field.getField().getType() == Integer.class) {
|
||||
valueBinders.add((stmt, i) -> stmt.setInt(i, (int) f.get(model)));
|
||||
} else if (field.getField().getType() == long.class || field.getField().getType() == Long.class) {
|
||||
valueBinders.add((stmt, i) -> stmt.setLong(i, (long) f.get(model)));
|
||||
} else if (field.getField().getType() == float.class || field.getField().getType() == Float.class) {
|
||||
valueBinders.add((stmt, i) -> stmt.setFloat(i, (float) f.get(model)));
|
||||
} else if (field.getField().getType() == double.class || field.getField().getType() == Double.class) {
|
||||
valueBinders.add((stmt, i) -> stmt.setDouble(i, (double) f.get(model)));
|
||||
} else {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
var query = "UPDATE `" + entity.name() + "` SET " + updatesString + " WHERE " + whereString;
|
||||
System.out.println(query);
|
||||
try (var stmt = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS)) {
|
||||
var i = 1;
|
||||
for (Util.ThrowableBiConsumer<PreparedStatement, Integer> valueBinder : valueBinders) {
|
||||
try {
|
||||
valueBinder.accept(stmt, i++);
|
||||
} catch (Throwable e) {
|
||||
if (e instanceof SQLException) {
|
||||
throw (SQLException) e;
|
||||
}
|
||||
throw new IllegalStateException("This may not happen", e);
|
||||
}
|
||||
}
|
||||
var updated = stmt.executeUpdate();
|
||||
if (updated != 1) {
|
||||
throw new SQLException("Failed to update entity");
|
||||
}
|
||||
try (var res = stmt.getGeneratedKeys()) {
|
||||
// var primary = entity.getEntity().getPrimaryKey();
|
||||
// if (primary != null) {
|
||||
// if (!res.next()) {
|
||||
// throw new SQLException("Failed to add entity");
|
||||
// }
|
||||
// var idField = primary.getFields().get(0);//TODO this only supports primary keys, and only with one column
|
||||
// idField.getField().setAccessible(true);
|
||||
// if (idField.getField().getType() == int.class || idField.getField().getType() == Integer.class) {//TODO support more types
|
||||
// idField.getField().set(model, res.getInt(1));
|
||||
// } else if (idField.getField().getType() == long.class || idField.getField().getType() == Long.class) {
|
||||
// idField.getField().set(model, res.getLong(1));
|
||||
// } else {
|
||||
// throw new UnsupportedOperationException();
|
||||
// }
|
||||
// }
|
||||
// } catch (IllegalAccessException e) {
|
||||
// throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends SerializableObject> void delete(DbContext context, Class<T> clazz, T model) throws SQLException {
|
||||
var modelBuilder = ModelBuilder.from(context.getClass());
|
||||
var entity = modelBuilder.entity(clazz);
|
||||
if (entity.getEntity().getPrimaryKey() == null) {
|
||||
throw new UnsupportedOperationException("Primary key required for updates");//at least for now
|
||||
}
|
||||
|
||||
var valueBinders = new ArrayList<Util.ThrowableBiConsumer<PreparedStatement, Integer>>();
|
||||
var whereString = entity.getEntity().getPrimaryKey().getFields().stream().map(e -> "`" + e.getName() + "` = ?").collect(Collectors.joining(" AND "));
|
||||
|
||||
//add values for where clause
|
||||
for (DbField<?> field : entity.getEntity().getPrimaryKey().getFields()) {
|
||||
Field f = field.getField();
|
||||
f.setAccessible(true);
|
||||
try {
|
||||
if (f.get(model) == null) {
|
||||
valueBinders.add((stmt, i) -> stmt.setNull(i, Types.INTEGER));
|
||||
} else if (field.getField().getType() == String.class) {
|
||||
valueBinders.add((stmt, i) -> stmt.setString(i, (String) f.get(model)));
|
||||
} else if (field.getField().getType() == byte.class || field.getField().getType() == Byte.class) {
|
||||
valueBinders.add((stmt, i) -> stmt.setByte(i, (byte) f.get(model)));
|
||||
} else if (field.getField().getType() == char.class || field.getField().getType() == Character.class) {
|
||||
// valueSetters.add((stmt, i) -> stmt.setString(i, (char) f.get(model)));//TODO handle char
|
||||
throw new UnsupportedOperationException("char");
|
||||
} else if (field.getField().getType() == short.class || field.getField().getType() == Short.class) {
|
||||
valueBinders.add((stmt, i) -> stmt.setShort(i, (short) f.get(model)));
|
||||
} else if (field.getField().getType() == int.class || field.getField().getType() == Integer.class) {
|
||||
valueBinders.add((stmt, i) -> stmt.setInt(i, (int) f.get(model)));
|
||||
} else if (field.getField().getType() == long.class || field.getField().getType() == Long.class) {
|
||||
valueBinders.add((stmt, i) -> stmt.setLong(i, (long) f.get(model)));
|
||||
} else if (field.getField().getType() == float.class || field.getField().getType() == Float.class) {
|
||||
valueBinders.add((stmt, i) -> stmt.setFloat(i, (float) f.get(model)));
|
||||
} else if (field.getField().getType() == double.class || field.getField().getType() == Double.class) {
|
||||
valueBinders.add((stmt, i) -> stmt.setDouble(i, (double) f.get(model)));
|
||||
} else {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
var query = "DELETE FROM `" + entity.name() + "` WHERE " + whereString;
|
||||
System.out.println(query);
|
||||
try (var stmt = connection.prepareStatement(query)) {
|
||||
var i = 1;
|
||||
for (Util.ThrowableBiConsumer<PreparedStatement, Integer> valueBinder : valueBinders) {
|
||||
try {
|
||||
valueBinder.accept(stmt, i++);
|
||||
} catch (Throwable e) {
|
||||
if (e instanceof SQLException) {
|
||||
throw (SQLException) e;
|
||||
}
|
||||
throw new IllegalStateException("This may not happen", e);
|
||||
}
|
||||
}
|
||||
var deleted = stmt.executeUpdate();
|
||||
if (deleted != 1) {
|
||||
throw new SQLException("Failed to delete entity");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,423 @@
|
||||
package jef.platform.mysql.dbinterface;
|
||||
|
||||
import jef.asm.access.model.ClassDescription;
|
||||
import jef.asm.access.model.GetterDescription;
|
||||
import jef.asm.access.model.SetterDescription;
|
||||
import jef.model.DbField;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public abstract class ByteCodeHelper {
|
||||
public final static Type T_BOOL = Type.getType(boolean.class);
|
||||
public final static Type T_BYTE = Type.getType(byte.class);
|
||||
public final static Type T_CHAR = Type.getType(char.class);
|
||||
public final static Type T_CLASS = Type.getType(Class.class);
|
||||
public final static Type T_CONNECTION = Type.getType(Connection.class);
|
||||
public final static Type T_DOUBLE = Type.getType(double.class);
|
||||
public final static Type T_FIELD = Type.getType(Field.class);
|
||||
public final static Type T_FLOAT = Type.getType(float.class);
|
||||
public final static Type T_INT = Type.getType(int.class);
|
||||
public final static Type T_LONG = Type.getType(long.class);
|
||||
public final static Type T_MYSQL_ENTITY_DB_INTERFACE = Type.getType(MysqlEntityDbInterface.class);
|
||||
public final static Type T_OBJECT = Type.getType(Object.class);
|
||||
public final static Type T_OVERRIDE = Type.getType(Override.class);
|
||||
public final static Type T_PREPARED_STATEMENT = Type.getType(PreparedStatement.class);
|
||||
public final static Type T_RESULT_SET = Type.getType(ResultSet.class);
|
||||
public final static Type T_SHORT = Type.getType(short.class);
|
||||
public final static Type T_SQLEXCEPTION = Type.getType(SQLException.class);
|
||||
public final static Type T_STRING = Type.getType(String.class);
|
||||
public final static Type T_VOID = Type.getType(void.class);
|
||||
|
||||
//region getter
|
||||
private static void visitGetFieldDirectAccess(MethodVisitor mv, Field field, int varIndexEntity) {
|
||||
mv.visitVarInsn(Opcodes.ALOAD, varIndexEntity);
|
||||
mv.visitFieldInsn(Opcodes.GETFIELD, Type.getInternalName(field.getDeclaringClass()), field.getName(), Type.getInternalName(field.getType()));
|
||||
}
|
||||
|
||||
private static void visitGetter(MethodVisitor mv, GetterDescription getter, int varIndexEntity) {
|
||||
mv.visitVarInsn(Opcodes.ALOAD, varIndexEntity);
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
|
||||
getter.getMethodDeclaringClassName().replace(".", "/"),
|
||||
getter.getMethodName(),
|
||||
"(" + Arrays.stream(getter.getMethodParameterClassNames())
|
||||
.map(ByteCodeHelper::getDescriptorForClassName)
|
||||
.collect(Collectors.joining())
|
||||
+ ")" + getDescriptorForClassName(getter.getMethodReturnClassName()),
|
||||
false);
|
||||
}
|
||||
|
||||
private static void visitGetViaReflection(MethodVisitor mv, Field field, int varIndexEntity, int varIndexField) {
|
||||
// var field = EntityClass.class.getDeclaredField("fieldname");
|
||||
mv.visitLdcInsn(Type.getObjectType(field.getDeclaringClass().getName()));
|
||||
mv.visitLdcInsn(field.getName());
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_CLASS.getInternalName(), "getDeclaredField", Type.getMethodDescriptor(T_FIELD, T_STRING), false);
|
||||
mv.visitVarInsn(Opcodes.ASTORE, varIndexField);
|
||||
|
||||
// field.setAccessible(true);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, varIndexField);
|
||||
mv.visitInsn(Opcodes.ICONST_1);
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setAccessible", Type.getMethodDescriptor(T_VOID, T_BOOL), false);
|
||||
|
||||
// field.get*(entity);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, varIndexField);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, varIndexEntity);
|
||||
var clazz = field.getType();
|
||||
if (clazz == boolean.class) {
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "getBoolean", Type.getMethodDescriptor(T_BOOL, T_OBJECT), false);
|
||||
} else if (clazz == byte.class) {
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "getByte", Type.getMethodDescriptor(T_BYTE, T_OBJECT), false);
|
||||
} else if (clazz == char.class) {
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "getCharacter", Type.getMethodDescriptor(T_CHAR, T_OBJECT), false);
|
||||
} else if (clazz == short.class) {
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "getShort", Type.getMethodDescriptor(T_SHORT, T_OBJECT), false);
|
||||
} else if (clazz == int.class) {
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "getInt", Type.getMethodDescriptor(T_INT, T_OBJECT), false);
|
||||
} else if (clazz == long.class) {
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "getLong", Type.getMethodDescriptor(T_LONG, T_OBJECT), false);
|
||||
} else if (clazz == float.class) {
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "getFloat", Type.getMethodDescriptor(T_FLOAT, T_OBJECT), false);
|
||||
} else if (clazz == double.class) {
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "getDouble", Type.getMethodDescriptor(T_DOUBLE, T_OBJECT), false);
|
||||
} else {
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "get", Type.getMethodDescriptor(T_OBJECT, T_OBJECT), false);
|
||||
}
|
||||
}
|
||||
|
||||
static void visitGetField(MethodVisitor mv, Field field, int varIndexEntity, int varIndexField, ClassDescription classDescription) {
|
||||
//try direct member access
|
||||
if (Modifier.isPublic(field.getModifiers())) {//TODO better check if publicly accessible
|
||||
visitGetFieldDirectAccess(mv, field, varIndexEntity);
|
||||
return;
|
||||
}
|
||||
|
||||
//try finding getter
|
||||
Optional<GetterDescription> getter = classDescription.getDeclaredGetters().stream()
|
||||
.filter(e -> e.getFieldDeclaringClassName().equals(field.getDeclaringClass().getName())
|
||||
&& e.getFieldName().equals(field.getName()))
|
||||
.findFirst();
|
||||
if (getter.isPresent()) {
|
||||
visitGetter(mv, getter.get(), varIndexEntity);
|
||||
return;
|
||||
}
|
||||
|
||||
//use reflection
|
||||
visitGetViaReflection(mv, field, varIndexEntity, varIndexField);
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region setter
|
||||
private static void visitSetFieldDirectAccess(MethodVisitor mv, Field field, Consumer<MethodVisitor> value, int varIndexEntity) {
|
||||
mv.visitVarInsn(Opcodes.ALOAD, varIndexEntity);
|
||||
value.accept(mv);
|
||||
mv.visitFieldInsn(Opcodes.PUTFIELD, Type.getInternalName(field.getDeclaringClass()), field.getName(), Type.getInternalName(field.getType()));
|
||||
}
|
||||
|
||||
private static void visitSetter(MethodVisitor mv, SetterDescription setter, Consumer<MethodVisitor> value, int varIndexEntity) {
|
||||
mv.visitVarInsn(Opcodes.ALOAD, varIndexEntity);
|
||||
value.accept(mv);
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
|
||||
setter.getMethodDeclaringClassName().replace(".", "/"),
|
||||
setter.getMethodName(),
|
||||
"(" + Arrays.stream(setter.getMethodParameterClassNames())
|
||||
.map(ByteCodeHelper::getDescriptorForClassName)
|
||||
.collect(Collectors.joining())
|
||||
+ ")" + getDescriptorForClassName(setter.getMethodReturnClassName()),
|
||||
false);
|
||||
}
|
||||
|
||||
private static void visitSetViaReflection(MethodVisitor mv, Field field, Consumer<MethodVisitor> value, int varIndexEntity, int varIndexField) {
|
||||
// var field = EntityClass.class.getDeclaredField("fieldname");
|
||||
mv.visitLdcInsn(Type.getObjectType(field.getDeclaringClass().getName()));
|
||||
mv.visitLdcInsn(field.getName());
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_CLASS.getInternalName(), "getDeclaredField", Type.getMethodDescriptor(T_FIELD, T_STRING), false);
|
||||
mv.visitVarInsn(Opcodes.ASTORE, varIndexField);
|
||||
|
||||
// field.setAccessible(true);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, varIndexField);
|
||||
mv.visitInsn(Opcodes.ICONST_1);
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setAccessible", Type.getMethodDescriptor(T_VOID, T_BOOL), false);
|
||||
|
||||
// field.set*(entity, value);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, varIndexField);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, varIndexEntity);
|
||||
value.accept(mv);
|
||||
var clazz = field.getType();
|
||||
if (clazz == boolean.class) {
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setBoolean", Type.getMethodDescriptor(T_VOID, T_OBJECT, T_BOOL), false);
|
||||
} else if (clazz == byte.class) {
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setByte", Type.getMethodDescriptor(T_VOID, T_OBJECT, T_BYTE), true);
|
||||
} else if (clazz == char.class) {
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setCharacter", Type.getMethodDescriptor(T_VOID, T_OBJECT, T_CHAR), true);
|
||||
} else if (clazz == short.class) {
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setShort", Type.getMethodDescriptor(T_VOID, T_OBJECT, T_SHORT), true);
|
||||
} else if (clazz == int.class) {
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setInt", Type.getMethodDescriptor(T_VOID, T_OBJECT, T_INT), true);
|
||||
} else if (clazz == long.class) {
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setLong", Type.getMethodDescriptor(T_VOID, T_OBJECT, T_LONG), true);
|
||||
} else if (clazz == float.class) {
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setFloat", Type.getMethodDescriptor(T_VOID, T_OBJECT, T_FLOAT), true);
|
||||
} else if (clazz == double.class) {
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setDouble", Type.getMethodDescriptor(T_VOID, T_OBJECT, T_DOUBLE), true);
|
||||
} else {
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "set", Type.getMethodDescriptor(T_VOID, T_OBJECT, T_OBJECT), true);
|
||||
}
|
||||
}
|
||||
|
||||
static void visitSetField(MethodVisitor mv, Field field, Consumer<MethodVisitor> value, int varIndexEntity, int varIndexField, ClassDescription classDescription) {
|
||||
//try direct member access
|
||||
if (Modifier.isPublic(field.getModifiers()) && !Modifier.isFinal(field.getModifiers())) {//TODO better check if publicly accessible
|
||||
visitSetFieldDirectAccess(mv, field, value, varIndexEntity);
|
||||
return;
|
||||
}
|
||||
|
||||
//try finding getter
|
||||
Optional<SetterDescription> setter = classDescription.getDeclaredSetters().stream()
|
||||
.filter(e -> e.getFieldDeclaringClassName().equals(field.getDeclaringClass().getName())
|
||||
&& e.getFieldName().equals(field.getName()))
|
||||
.findFirst();
|
||||
if (setter.isPresent()) {
|
||||
visitSetter(mv, setter.get(), value, varIndexEntity);
|
||||
return;
|
||||
}
|
||||
|
||||
//use reflection
|
||||
visitSetViaReflection(mv, field, value, varIndexEntity, varIndexField);
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region PrepareStatement
|
||||
static void visitPreparedStatementSetNull(MethodVisitor mv, DbField<?> field, int varIndexStmt, int index) {
|
||||
//stmt.setNull(i, Types.*);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, varIndexStmt);//push stmt
|
||||
visitIntInsn(mv, index);//push index
|
||||
var clazz = field.getType();
|
||||
if (clazz == String.class) {
|
||||
visitIntInsn(mv, Types.VARCHAR);//push Types.VARCHAR
|
||||
} else if (clazz == boolean.class || clazz == Boolean.class) {
|
||||
visitIntInsn(mv, Types.BIT);//push Types.BIT
|
||||
} else if (clazz == byte.class || clazz == Byte.class) {
|
||||
visitIntInsn(mv, Types.TINYINT);//push Types.TINYINT
|
||||
} else if (clazz == char.class || clazz == Character.class) {
|
||||
visitIntInsn(mv, Types.CHAR);//push Types.CHAR
|
||||
} else if (clazz == short.class || clazz == Short.class) {
|
||||
visitIntInsn(mv, Types.SMALLINT);//push Types.SMALLINT
|
||||
} else if (clazz == int.class || clazz == Integer.class) {
|
||||
visitIntInsn(mv, Types.INTEGER);//push Types.INTEGER
|
||||
} else if (clazz == long.class || clazz == Long.class) {
|
||||
visitIntInsn(mv, Types.BIGINT);//push Types.BIGINT
|
||||
} else if (clazz == float.class || clazz == Float.class) {
|
||||
visitIntInsn(mv, Types.FLOAT);//push Types.FLOAT
|
||||
} else if (clazz == double.class || clazz == Double.class) {
|
||||
visitIntInsn(mv, Types.DOUBLE);//push Types.DOUBLE
|
||||
} else {
|
||||
throw new UnsupportedOperationException(clazz.getName());
|
||||
}
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_PREPARED_STATEMENT.getInternalName(), "setNull", "(II)V", false);
|
||||
}
|
||||
|
||||
static void visitPreparedStatementSetAny(MethodVisitor mv, DbField<?> field, int varIndexStmt, int index, Consumer<MethodVisitor> value) {
|
||||
var clazz = field.getType();
|
||||
//stmt.set*(i, value);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, varIndexStmt);//push stmt
|
||||
visitIntInsn(mv, index);//push index
|
||||
value.accept(mv);
|
||||
if (clazz == String.class) {
|
||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_PREPARED_STATEMENT.getInternalName(), "setString", "(ILjava.lang.String;)V", true);
|
||||
} else if (clazz == boolean.class || clazz == Boolean.class) {
|
||||
if (clazz == Boolean.class) {//unbox
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_BOOL.getInternalName(), "booleanValue", "()Z", true);
|
||||
}
|
||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_PREPARED_STATEMENT.getInternalName(), "setBoolean", "(IZ)V", true);
|
||||
} else if (clazz == byte.class || clazz == Byte.class) {
|
||||
if (clazz == Byte.class) {//unbox
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_BYTE.getInternalName(), "byteValue", "()B", true);
|
||||
}
|
||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_PREPARED_STATEMENT.getInternalName(), "setByte", "(IB)V", true);
|
||||
} else if (clazz == char.class || clazz == Character.class) {
|
||||
// if (clazz == Character.class) {//unbox
|
||||
// mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_CHAR.getInternalName(), "charValue", "()C", true);
|
||||
// }
|
||||
// mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_PREPARED_STATEMENT.getInternalName(), "setCharacter", "(IC)V", true);
|
||||
throw new UnsupportedOperationException("char");
|
||||
} else if (clazz == short.class || clazz == Short.class) {
|
||||
if (clazz == Short.class) {//unbox
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_SHORT.getInternalName(), "shortValue", "()S", true);
|
||||
}
|
||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_PREPARED_STATEMENT.getInternalName(), "setShort", "(IS)V", true);
|
||||
} else if (clazz == int.class || clazz == Integer.class) {
|
||||
if (clazz == Integer.class) {//unbox
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_INT.getInternalName(), "intValue", "()I", true);
|
||||
}
|
||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_PREPARED_STATEMENT.getInternalName(), "setInt", "(II)V", true);
|
||||
} else if (clazz == long.class || clazz == Long.class) {
|
||||
if (clazz == Long.class) {//unbox
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_LONG.getInternalName(), "longValue", "()J", true);
|
||||
}
|
||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_PREPARED_STATEMENT.getInternalName(), "setLong", "(IJ)V", true);
|
||||
} else if (clazz == float.class || clazz == Float.class) {
|
||||
if (clazz == Float.class) {//unbox
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FLOAT.getInternalName(), "floatValue", "()F", true);
|
||||
}
|
||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_PREPARED_STATEMENT.getInternalName(), "setFloat", "(IF)V", true);
|
||||
} else if (clazz == double.class || clazz == Double.class) {
|
||||
if (clazz == Double.class) {//unbox
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_DOUBLE.getInternalName(), "doubleValue", "()D", true);
|
||||
}
|
||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_PREPARED_STATEMENT.getInternalName(), "setDouble", "(ID)V", true);
|
||||
} else {
|
||||
throw new UnsupportedOperationException(clazz.getName());
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region ResultSet
|
||||
static void visitResultSetGetAny(MethodVisitor mv, DbField<?> field, int varIndexRes) {
|
||||
var clazz = field.getType();
|
||||
// res.get*(fieldname);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, varIndexRes);//push res
|
||||
mv.visitLdcInsn(field.getName());//push 1
|
||||
if (clazz == String.class) {
|
||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "getString", "(Ljava.lang.String;)Ljava.lang.String;", true);
|
||||
} else if (clazz == boolean.class || clazz == Boolean.class) {
|
||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "getBoolean", "(Ljava.lang.String;)Z", true);
|
||||
if (clazz == Boolean.class) {//box
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, T_BOOL.getInternalName(), "<init>", "(Z)V", true);
|
||||
}
|
||||
} else if (clazz == byte.class || clazz == Byte.class) {
|
||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "getByte", "(Ljava.lang.String;)B", true);
|
||||
if (clazz == Byte.class) {//box
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, T_BYTE.getInternalName(), "<init>", "(B)V", true);
|
||||
}
|
||||
} else if (clazz == char.class || clazz == Character.class) {
|
||||
// mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "getCharacter", "(Ljava.lang.String;)C", true);
|
||||
// if (clazz == Character.class) {//unbox
|
||||
// mv.visitMethodInsn(Opcodes.INVOKESPECIAL, T_CHAR.getInternalName(), "<init>", "(C)Ljava.lang.Character;", true);
|
||||
// }
|
||||
throw new UnsupportedOperationException("char");
|
||||
} else if (clazz == short.class || clazz == Short.class) {
|
||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "getShort", "(Ljava.lang.String;)S", true);
|
||||
if (clazz == Short.class) {//box
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, T_SHORT.getInternalName(), "<init>", "(S)V", true);
|
||||
}
|
||||
} else if (clazz == int.class || clazz == Integer.class) {
|
||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "getInt", "(Ljava.lang.String;)I", true);
|
||||
if (clazz == Integer.class) {//box
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, T_INT.getInternalName(), "<init>", "(I)V", true);
|
||||
}
|
||||
} else if (clazz == long.class || clazz == Long.class) {
|
||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "getLong", "(Ljava.lang.String;)J", true);
|
||||
if (clazz == Long.class) {//box
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, T_LONG.getInternalName(), "<init>", "(J)V", true);
|
||||
}
|
||||
} else if (clazz == float.class || clazz == Float.class) {
|
||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "getFloat", "(Ljava.lang.String;)F", true);
|
||||
if (clazz == Float.class) {//box
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, T_FLOAT.getInternalName(), "<init>", "(F)V", true);
|
||||
}
|
||||
} else if (clazz == double.class || clazz == Double.class) {
|
||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "getDouble", "(Ljava.lang.String;)D", true);
|
||||
if (clazz == Double.class) {//box
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, T_DOUBLE.getInternalName(), "<init>", "(D)V", true);
|
||||
}
|
||||
} else {
|
||||
throw new UnsupportedOperationException(clazz.getName());
|
||||
}
|
||||
|
||||
// if (res.wasNull()) {
|
||||
// POP
|
||||
// PUSH NULL
|
||||
// }
|
||||
if (!clazz.isPrimitive()) {
|
||||
var labelWasNotNull = new Label();
|
||||
mv.visitVarInsn(Opcodes.ALOAD, varIndexRes);//push res
|
||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "wasNull", "()Z", true);
|
||||
mv.visitJumpInsn(Opcodes.IFEQ, labelWasNotNull);
|
||||
mv.visitInsn(Opcodes.POP);//POP
|
||||
mv.visitInsn(Opcodes.ACONST_NULL);//PUSH NULL
|
||||
mv.visitLabel(labelWasNotNull);
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region util
|
||||
static void visitIntInsn(MethodVisitor mv, int value) {
|
||||
switch (value) {
|
||||
case -1 -> mv.visitInsn(Opcodes.ICONST_M1);
|
||||
case 0 -> mv.visitInsn(Opcodes.ICONST_0);
|
||||
case 1 -> mv.visitInsn(Opcodes.ICONST_1);
|
||||
case 2 -> mv.visitInsn(Opcodes.ICONST_2);
|
||||
case 3 -> mv.visitInsn(Opcodes.ICONST_3);
|
||||
case 4 -> mv.visitInsn(Opcodes.ICONST_4);
|
||||
case 5 -> mv.visitInsn(Opcodes.ICONST_5);
|
||||
default -> {
|
||||
if (Byte.MIN_VALUE <= value && value <= Byte.MAX_VALUE) {
|
||||
mv.visitIntInsn(Opcodes.BIPUSH, value);
|
||||
} else if (Short.MIN_VALUE <= value && value <= Short.MAX_VALUE) {
|
||||
mv.visitIntInsn(Opcodes.SIPUSH, value);
|
||||
} else {
|
||||
mv.visitLdcInsn(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void visitThrowSQLException(MethodVisitor mv, String message) {
|
||||
mv.visitTypeInsn(Opcodes.NEW, T_SQLEXCEPTION.getInternalName());
|
||||
mv.visitInsn(Opcodes.DUP);
|
||||
mv.visitLdcInsn(message);
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, T_SQLEXCEPTION.getInternalName(), "<init>", Type.getMethodDescriptor(T_VOID, T_STRING), false);
|
||||
mv.visitInsn(Opcodes.ATHROW);
|
||||
}
|
||||
|
||||
static String getDescriptorForClassName(String className) {
|
||||
return switch (className) {
|
||||
case "void" -> "V";
|
||||
case "boolean" -> "Z";
|
||||
case "byte" -> "B";
|
||||
case "char" -> "C";
|
||||
case "short" -> "S";
|
||||
case "int" -> "I";
|
||||
case "long" -> "J";
|
||||
case "float" -> "F";
|
||||
case "double" -> "D";
|
||||
default -> "L" + className.replace(".", "/") + ";";
|
||||
};
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region var access
|
||||
// private void visitLoadVariable(MethodVisitor mv, String className, int varIndex) {
|
||||
// switch (className) {
|
||||
// case "boolean", "byte", "char", "short", "int" -> mv.visitVarInsn(Opcodes.ILOAD, varIndex);
|
||||
// case "long" -> mv.visitVarInsn(Opcodes.LLOAD, varIndex);
|
||||
// case "float" -> mv.visitVarInsn(Opcodes.FLOAD, varIndex);
|
||||
// case "double" -> mv.visitVarInsn(Opcodes.DLOAD, varIndex);
|
||||
// default -> mv.visitVarInsn(Opcodes.ALOAD, varIndex);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private void visitStoreVariable(MethodVisitor mv, String className, int varIndex) {
|
||||
// switch (className) {
|
||||
// case "boolean", "byte", "char", "short", "int" -> mv.visitVarInsn(Opcodes.ISTORE, varIndex);
|
||||
// case "long" -> mv.visitVarInsn(Opcodes.LSTORE, varIndex);
|
||||
// case "float" -> mv.visitVarInsn(Opcodes.FSTORE, varIndex);
|
||||
// case "double" -> mv.visitVarInsn(Opcodes.DSTORE, varIndex);
|
||||
// default -> mv.visitVarInsn(Opcodes.ASTORE, varIndex);
|
||||
// }
|
||||
// }
|
||||
//endregion
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package jef.platform.mysql.dbinterface;
|
||||
|
||||
import jef.serializable.SerializableObject;
|
||||
|
||||
import javax.xml.transform.Result;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
|
||||
public abstract class MysqlEntityDbInterface<T extends SerializableObject> {
|
||||
public void add(Connection connection, T entity) throws SQLException {
|
||||
try (var stmt = queryAdd(connection)) {
|
||||
bindParamsAdd(stmt, entity);
|
||||
|
||||
var added = stmt.executeUpdate();
|
||||
if (added != 1) {
|
||||
throw new SQLException("Failed to insert entity");
|
||||
}
|
||||
|
||||
readBackGeneratedValuesAdd(stmt, entity);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract PreparedStatement queryAdd(Connection connection) throws SQLException;
|
||||
// protected abstract PreparedStatement queryAdd(Connection connection) throws SQLException {
|
||||
// return connection.prepareStatement("query", Statement.RETURN_GENERATED_KEYS);
|
||||
// }
|
||||
|
||||
// protected abstract void bindParamsAdd(PreparedStatement stmt, T entity) throws SQLException;
|
||||
protected void bindParamsAdd(PreparedStatement stmt, T entity) throws SQLException {
|
||||
stmt.setInt(1, 1);
|
||||
stmt.setInt(2, 2);
|
||||
var s = "string";
|
||||
if (s != null) {
|
||||
stmt.setString(3, "string");
|
||||
} else {
|
||||
stmt.setNull(3, Types.INTEGER);
|
||||
}
|
||||
stmt.setInt(4, 4);
|
||||
}
|
||||
|
||||
protected void readBackGeneratedValuesAdd(PreparedStatement stmt, T entity) throws SQLException {
|
||||
try (var res = stmt.getGeneratedKeys()) {
|
||||
readBackGeneratedValuesAdd(res, entity);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void readBackGeneratedValuesAdd(ResultSet res, T entity) throws Exception {
|
||||
var f = Exception.class.getDeclaredField("f");
|
||||
f.setAccessible(true);
|
||||
f.setInt(null, 12);
|
||||
}
|
||||
|
||||
// public void update(Connection connection, T model);
|
||||
//
|
||||
// public void delete(Connection connection, T model);
|
||||
//
|
||||
// public T read(ResultSet res);
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
package jef.platform.mysql.dbinterface;
|
||||
|
||||
import jef.asm.access.ClassAnalyzer;
|
||||
import jef.asm.access.model.ClassDescription;
|
||||
import jef.model.DbContext;
|
||||
import jef.model.DbEntity;
|
||||
import jef.model.DbField;
|
||||
import jef.model.annotations.Generated;
|
||||
import jef.serializable.SerializableObject;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MysqlEntityDbInterfaceGenerator<T extends SerializableObject> {
|
||||
private static final boolean SET_SYNTHETIC_FLAG = false;
|
||||
private static final int SYNTHETIC_FLAG = SET_SYNTHETIC_FLAG ? Opcodes.ACC_SYNTHETIC : 0;
|
||||
|
||||
private final Class<? extends DbContext> context;
|
||||
private final DbEntity<T> entity;
|
||||
|
||||
private final ClassWriter writer;
|
||||
private final ClassVisitor visitor;
|
||||
|
||||
//runtime
|
||||
private ClassDescription classDescription;
|
||||
|
||||
//results
|
||||
@Getter
|
||||
private String className;
|
||||
@Getter
|
||||
private byte[] byteCode;
|
||||
|
||||
public MysqlEntityDbInterfaceGenerator(Class<? extends DbContext> context, DbEntity<T> entity) {
|
||||
this.context = context;
|
||||
this.entity = entity;
|
||||
writer = new ClassWriter(Opcodes.ASM9);
|
||||
visitor = new ClassVisitor(Opcodes.ASM9, writer) {//TODO definitely broken here
|
||||
};
|
||||
}
|
||||
|
||||
public void generate() {
|
||||
classDescription = new ClassAnalyzer().analyze(entity.getType());
|
||||
|
||||
//follows template "<context full class name>$<entity classname>MysqlEntityDbInterface"
|
||||
// className = context.getName() + "$" + entity.getType().getSimpleName() + "" + MysqlEntityDbInterface.class.getSimpleName();
|
||||
className = entity.getType().getSimpleName() + "" + MysqlEntityDbInterface.class.getSimpleName();
|
||||
|
||||
defineClass();
|
||||
defineConstructor();
|
||||
// defineAddFunction();
|
||||
defineQueryAddFunction();
|
||||
defineBindParamsAddFunction();
|
||||
defineReadBackGeneratedValuesAddPreparedStatement();
|
||||
defineReadBackGeneratedValuesAddResultSet();
|
||||
// defineUpdateFunction();
|
||||
// defineDeleteFunction();
|
||||
// defineReadFunction();
|
||||
|
||||
visitor.visitEnd();
|
||||
|
||||
byteCode = writer.toByteArray();
|
||||
}
|
||||
|
||||
protected void defineClass() {
|
||||
var classSimpleName = className.substring(Math.max(className.lastIndexOf("."), className.lastIndexOf("$")) + 1);
|
||||
visitor.visit(Opcodes.V17, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER | SYNTHETIC_FLAG,
|
||||
classSimpleName,
|
||||
Type.getDescriptor(MysqlEntityDbInterface.class).replace(";", "<" + ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()) + ">;"),
|
||||
Type.getInternalName(MysqlEntityDbInterface.class), new String[]{});
|
||||
// visitor.visitNestHost(Type.getInternalName(context));
|
||||
// visitor.visitInnerClass(className.replace(".", "/"), Type.getInternalName(context), classSimpleName, Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_SUPER);
|
||||
}
|
||||
|
||||
//public <init>() {}
|
||||
private void defineConstructor() {
|
||||
var mv = visitor.visitMethod(Opcodes.ACC_PUBLIC | SYNTHETIC_FLAG, "<init>", "()V", null, new String[0]);
|
||||
mv.visitCode();
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, ByteCodeHelper.T_MYSQL_ENTITY_DB_INTERFACE.getInternalName(), "<init>", "()V", false);
|
||||
mv.visitInsn(Opcodes.RETURN);
|
||||
mv.visitMaxs(1, 1);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
private ColumnsAndValueBinders prepareColumnsAndValueBindersAdd() {
|
||||
var columns = new ArrayList<String>();
|
||||
var valueBinders = new ArrayList<ValueBinder>();
|
||||
|
||||
for (DbField<?> field : entity.getFields()) {
|
||||
if (!field.isDatabaseField()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Field f = field.getField();
|
||||
if (f == null) {
|
||||
if (field.isDatabaseField()) {//field defined but not present in entity class -> currently nothing to insert -> null
|
||||
columns.add(field.getName());
|
||||
valueBinders.add((mv, varIndexEntity, varIndexStmt, varIndexField, index) -> {
|
||||
ByteCodeHelper.visitIntInsn(mv, index);
|
||||
|
||||
//stmt.setNull(i, Types.INTEGER);
|
||||
ByteCodeHelper.visitPreparedStatementSetNull(mv, field, varIndexStmt, index);
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
columns.add(field.getName());
|
||||
valueBinders.add((mv, varIndexEntity, varIndexStmt, varIndexField, index) -> {
|
||||
if (field.isNotNull()) {
|
||||
//stmt.set*(i, value);
|
||||
ByteCodeHelper.visitPreparedStatementSetAny(mv, field, varIndexStmt, index, (mv2) -> ByteCodeHelper.visitGetField(mv2, field.getField(), varIndexEntity, varIndexField, classDescription));
|
||||
} else {
|
||||
// getFieldValue(entity);
|
||||
ByteCodeHelper.visitGetField(mv, f, varIndexEntity, varIndexField, classDescription);
|
||||
|
||||
// if (value == null)
|
||||
// stmt.setNull(i, Types.*);
|
||||
// } else {
|
||||
// stmt.set*(i, value);
|
||||
// }
|
||||
var labelAfter = new Label();
|
||||
var labelElse = new Label();
|
||||
mv.visitJumpInsn(Opcodes.IFNULL, labelElse);
|
||||
ByteCodeHelper.visitPreparedStatementSetAny(mv, field, varIndexStmt, index, (mv2) -> ByteCodeHelper.visitGetField(mv2, field.getField(), varIndexEntity, varIndexField, classDescription));
|
||||
mv.visitJumpInsn(Opcodes.GOTO, labelAfter);
|
||||
mv.visitLabel(labelElse);
|
||||
ByteCodeHelper.visitPreparedStatementSetNull(mv, field, varIndexStmt, index);
|
||||
mv.visitLabel(labelAfter);
|
||||
// mv.visitFrame(Opcodes.F_SAME, 0, new Object[0], 0, new Object[0]);
|
||||
mv.visitFrame(Opcodes.F_FULL, 3, new Object[]{
|
||||
ByteCodeHelper.T_MYSQL_ENTITY_DB_INTERFACE.getInternalName(), ByteCodeHelper.T_PREPARED_STATEMENT.getInternalName(), entity.getTypeName().replace(".", "/")
|
||||
}, 0, new Object[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
return new ColumnsAndValueBinders(columns, valueBinders);
|
||||
}
|
||||
|
||||
private interface ValueBinder {
|
||||
void bind(MethodVisitor mv, int varIndexEntity, int varIndexStmt, int varIndexField, int index);
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
private static class ColumnsAndValueBinders {
|
||||
private final List<String> columns;
|
||||
private final List<ValueBinder> valueBinders;
|
||||
}
|
||||
|
||||
private void defineQueryAddFunction() {
|
||||
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "queryAdd", Type.getMethodDescriptor(ByteCodeHelper.T_PREPARED_STATEMENT, ByteCodeHelper.T_CONNECTION),
|
||||
null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
|
||||
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
|
||||
|
||||
var varIndexThis = 0;
|
||||
var varIndexConnection = 1;
|
||||
|
||||
var columnsAndValueBinders = prepareColumnsAndValueBindersAdd();
|
||||
|
||||
var columnsString = columnsAndValueBinders.getColumns().stream().map(e -> "`" + e + "`").collect(Collectors.joining(","));
|
||||
var placeholdersString = columnsAndValueBinders.getColumns().stream().map(e -> "?").collect(Collectors.joining(","));
|
||||
var query = "INSERT INTO `" + entity.getName() + "` (" + columnsString + ") VALUES (" + placeholdersString + ")";
|
||||
|
||||
// variable scopes
|
||||
var labelStartThis = new Label();
|
||||
var labelEndThis = new Label();
|
||||
var labelStartConnection = new Label();
|
||||
var labelEndConnection = new Label();
|
||||
|
||||
// begin
|
||||
mv.visitCode();
|
||||
|
||||
// variable scopes
|
||||
mv.visitLabel(labelStartThis);
|
||||
mv.visitLabel(labelStartConnection);
|
||||
|
||||
//return connection.prepareStatement("query", Statement.RETURN_GENERATED_KEYS);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 1);
|
||||
mv.visitLdcInsn(query);
|
||||
if (entity.getPrimaryKey() != null) {
|
||||
mv.visitInsn(Opcodes.ICONST_1); //Statement.RETURN_GENERATED_KEYS == 1
|
||||
} else {
|
||||
mv.visitInsn(Opcodes.ICONST_0); //no flags
|
||||
}
|
||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, ByteCodeHelper.T_CONNECTION.getInternalName(), "prepareStatement", Type.getMethodDescriptor(ByteCodeHelper.T_PREPARED_STATEMENT, ByteCodeHelper.T_STRING, ByteCodeHelper.T_INT), true);
|
||||
mv.visitInsn(Opcodes.ARETURN);
|
||||
|
||||
// variable scopes
|
||||
mv.visitLabel(labelEndConnection);
|
||||
mv.visitLabel(labelEndThis);
|
||||
|
||||
// debug symbols
|
||||
mv.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, labelStartConnection, labelEndConnection, varIndexThis);
|
||||
mv.visitLocalVariable("connection", ByteCodeHelper.T_CONNECTION.getDescriptor(), null, labelStartThis, labelEndThis, varIndexConnection);
|
||||
|
||||
mv.visitMaxs(3, 2);
|
||||
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
private void defineBindParamsAddFunction() {
|
||||
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "bindParamsAdd", Type.getMethodDescriptor(ByteCodeHelper.T_VOID, ByteCodeHelper.T_PREPARED_STATEMENT, Type.getType(ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()))),
|
||||
null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
|
||||
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
|
||||
|
||||
var varIndexThis = 0;
|
||||
var varIndexStmt = 1;
|
||||
var varIndexEntity = 2;
|
||||
var varIndexField = 3;
|
||||
|
||||
//create lists for columns and value binders
|
||||
var columnsAndValueBinders = prepareColumnsAndValueBindersAdd();
|
||||
|
||||
// variable scopes
|
||||
var labelStartThis = new Label();
|
||||
var labelEndThis = new Label();
|
||||
var labelStartStmt = new Label();
|
||||
var labelEndStmt = new Label();
|
||||
var labelStartEntity = new Label();
|
||||
var labelEndEntity = new Label();
|
||||
var labelStartField = new Label();
|
||||
var labelEndField = new Label();
|
||||
|
||||
// begin
|
||||
mv.visitCode();
|
||||
|
||||
// variable scopes
|
||||
mv.visitLabel(labelStartThis);
|
||||
mv.visitLabel(labelStartStmt);
|
||||
mv.visitLabel(labelStartEntity);
|
||||
mv.visitLabel(labelStartField);
|
||||
|
||||
//stmt.set* bind calls
|
||||
var i = 1;
|
||||
for (var valueBinder : columnsAndValueBinders.getValueBinders()) {
|
||||
valueBinder.bind(mv, varIndexEntity, varIndexStmt, varIndexField, i++);
|
||||
}
|
||||
|
||||
//return
|
||||
mv.visitInsn(Opcodes.RETURN);
|
||||
|
||||
// variable scopes
|
||||
mv.visitLabel(labelEndThis);
|
||||
mv.visitLabel(labelEndStmt);
|
||||
mv.visitLabel(labelEndEntity);
|
||||
mv.visitLabel(labelEndField);
|
||||
|
||||
// debug symbols
|
||||
mv.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, labelStartThis, labelEndThis, varIndexThis);
|
||||
mv.visitLocalVariable("stmt", ByteCodeHelper.T_PREPARED_STATEMENT.getDescriptor(), null, labelStartStmt, labelEndStmt, varIndexStmt);
|
||||
mv.visitLocalVariable("entity", ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()), null, labelStartEntity, labelEndEntity, varIndexEntity);
|
||||
mv.visitLocalVariable("field", ByteCodeHelper.T_FIELD.getDescriptor(), null, labelStartField, labelEndField, varIndexField);
|
||||
|
||||
mv.visitMaxs(3, 4);
|
||||
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
private void defineReadBackGeneratedValuesAddPreparedStatement() {
|
||||
if (entity.getFields().stream().anyMatch(e -> e.getGenerated() != Generated.Type.NONE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
//stub method if no generated values
|
||||
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "readBackGeneratedValuesAdd", Type.getMethodDescriptor(ByteCodeHelper.T_VOID, ByteCodeHelper.T_PREPARED_STATEMENT, Type.getType(ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()))),
|
||||
null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
|
||||
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
|
||||
|
||||
var label = new Label();
|
||||
|
||||
mv.visitCode();
|
||||
|
||||
// debug symbols
|
||||
mv.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, label, label, 0);
|
||||
mv.visitLocalVariable("stmt", ByteCodeHelper.T_PREPARED_STATEMENT.getDescriptor(), null, label, label, 1);
|
||||
mv.visitLocalVariable("entity", ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()), null, label, label, 2);
|
||||
|
||||
mv.visitMaxs(0, 3);//TODO stack size
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
private void defineReadBackGeneratedValuesAddResultSet() {
|
||||
if (entity.getFields().stream().noneMatch(e -> e.getGenerated() != Generated.Type.NONE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "readBackGeneratedValuesAdd", Type.getMethodDescriptor(ByteCodeHelper.T_VOID, ByteCodeHelper.T_RESULT_SET, Type.getType(ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()))),
|
||||
null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
|
||||
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
|
||||
|
||||
var varIndexThis = 0;
|
||||
var varIndexRes = 1;
|
||||
var varIndexEntity = 2;
|
||||
var varIndexField = 3;
|
||||
|
||||
// variable scopes
|
||||
var labelStartThis = new Label();
|
||||
var labelEndThis = new Label();
|
||||
var labelStartRes = new Label();
|
||||
var labelEndRes = new Label();
|
||||
var labelStartEntity = new Label();
|
||||
var labelEndEntity = new Label();
|
||||
var labelStartField = new Label();
|
||||
var labelEndField = new Label();
|
||||
|
||||
// begin
|
||||
mv.visitCode();
|
||||
|
||||
// variable scopes
|
||||
mv.visitLabel(labelStartThis);
|
||||
mv.visitLabel(labelStartRes);
|
||||
mv.visitLabel(labelStartEntity);
|
||||
mv.visitLabel(labelStartField);
|
||||
|
||||
// if (!res.next()) {
|
||||
// throw new SQLException("Missing generated keys for entity but are required");
|
||||
// }
|
||||
var labelResHasNext = new Label();
|
||||
mv.visitVarInsn(Opcodes.ALOAD, varIndexRes);
|
||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, ByteCodeHelper.T_RESULT_SET.getInternalName(), "next", Type.getMethodDescriptor(ByteCodeHelper.T_BOOL), true);
|
||||
mv.visitJumpInsn(Opcodes.IFNE, labelResHasNext);
|
||||
ByteCodeHelper.visitThrowSQLException(mv, "Missing generated keys for entity but are required");
|
||||
mv.visitLabel(labelResHasNext);
|
||||
|
||||
for (var field : entity.getFields()) {
|
||||
if (field.getGenerated() == Generated.Type.NONE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// var value = res.get*(1);
|
||||
Consumer<MethodVisitor> resGetValue = (mv2) -> {
|
||||
ByteCodeHelper.visitResultSetGetAny(mv2, field, varIndexRes);
|
||||
};
|
||||
|
||||
// *SET*(entity, field, value);
|
||||
ByteCodeHelper.visitSetField(mv, field.getField(), resGetValue, varIndexEntity, varIndexField, classDescription);
|
||||
}
|
||||
|
||||
//return
|
||||
mv.visitInsn(Opcodes.RETURN);
|
||||
|
||||
// variable scopes
|
||||
mv.visitLabel(labelEndThis);
|
||||
mv.visitLabel(labelEndRes);
|
||||
mv.visitLabel(labelEndEntity);
|
||||
mv.visitLabel(labelEndField);
|
||||
|
||||
// debug symbols
|
||||
mv.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, labelStartThis, labelEndThis, varIndexThis);
|
||||
mv.visitLocalVariable("res", ByteCodeHelper.T_RESULT_SET.getDescriptor(), null, labelStartRes, labelEndRes, varIndexRes);
|
||||
mv.visitLocalVariable("entity", ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()), null, labelStartEntity, labelEndEntity, varIndexEntity);
|
||||
mv.visitLocalVariable("field", ByteCodeHelper.T_FIELD.getDescriptor(), null, labelStartField, labelEndField, varIndexField);
|
||||
|
||||
mv.visitMaxs(3, 4);
|
||||
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
private void defineUpdateFunction() {
|
||||
}
|
||||
|
||||
private void defineDeleteFunction() {
|
||||
}
|
||||
|
||||
private void defineReadFunction() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package jef.platform.mysql.dbinterface;
|
||||
|
||||
import jef.serializable.SerializableObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class MysqlInterfaceClassLoader extends ClassLoader {
|
||||
private final Map<String, byte[]> resources = new HashMap<>();
|
||||
|
||||
public MysqlInterfaceClassLoader(String name, ClassLoader parent) {
|
||||
super(name, parent);
|
||||
}
|
||||
|
||||
public MysqlInterfaceClassLoader(ClassLoader parent) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
public MysqlInterfaceClassLoader() {
|
||||
}
|
||||
|
||||
public <T extends SerializableObject> Class<? extends MysqlEntityDbInterface<T>> defineClass(String className, byte[] byteCode) {
|
||||
synchronized (resources) {
|
||||
var resourceName = className.replace(".", "/") + ".class";
|
||||
resources.put(resourceName, byteCode);
|
||||
}
|
||||
return (Class<? extends MysqlEntityDbInterface<T>>) super.defineClass(className, byteCode, 0, byteCode.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getResource(String name) {
|
||||
return super.getResource(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<URL> getResources(String name) throws IOException {
|
||||
return super.getResources(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getResourceAsStream(String name) {
|
||||
synchronized (resources) {
|
||||
return super.getResourceAsStream(name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<URL> resources(String name) {
|
||||
return super.resources(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected URL findResource(String moduleName, String name) throws IOException {
|
||||
return super.findResource(moduleName, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected URL findResource(String name) {
|
||||
return super.findResource(name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package jef.platform.mysql.dbinterface;
|
||||
|
||||
import jef.model.DbContext;
|
||||
import jef.model.DbEntity;
|
||||
import jef.serializable.SerializableObject;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class MysqlInterfaceFactory {
|
||||
private static MysqlInterfaceFactory getInstance(Class<DbContext> context) {
|
||||
return new MysqlInterfaceFactory(context);
|
||||
}
|
||||
|
||||
private final Class<DbContext> context;
|
||||
private final MysqlInterfaceClassLoader classLoader = new MysqlInterfaceClassLoader();
|
||||
private final Map<Class<? extends SerializableObject>, Class<? extends MysqlEntityDbInterface<? extends SerializableObject>>> cache = new HashMap<>();
|
||||
|
||||
public <T extends SerializableObject> Class<? extends MysqlEntityDbInterface<T>> getInterface(DbEntity<T> entity) {
|
||||
synchronized (cache) {
|
||||
return (Class<? extends MysqlEntityDbInterface<T>>) cache.computeIfAbsent(entity.getType(), ignored -> generateInterface(entity));
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends SerializableObject> Class<? extends MysqlEntityDbInterface<? extends SerializableObject>> generateInterface(DbEntity<T> entity) {
|
||||
var generator = new MysqlEntityDbInterfaceGenerator<T>(context, entity);
|
||||
generator.generate();
|
||||
var byteCode = generator.getByteCode();
|
||||
return classLoader.defineClass(generator.getClassName(), byteCode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package jef.platform.mysql;
|
||||
|
||||
import jef.DbSet;
|
||||
import jef.model.DbContext;
|
||||
import jef.model.DbContextOptions;
|
||||
import jef.model.annotations.Clazz;
|
||||
import jef.model.annotations.ForeignKey;
|
||||
import jef.platform.base.Database;
|
||||
import jef.platform.base.DatabaseOptions;
|
||||
import jef.serializable.SerializableObject;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.sql.DriverManager;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class MysqlSimpleIntegrativeTest {
|
||||
@Test
|
||||
void queryEntities() throws Exception {
|
||||
//setup
|
||||
Class.forName("com.mysql.cj.jdbc.Driver").getDeclaredConstructor().newInstance();
|
||||
var dboptions = new DatabaseOptions("jdbc:mysql://localhost/test", "test", "password", getClass().getSimpleName(), null);
|
||||
var ctxoptions = new DbContextOptions(dboptions);
|
||||
var conn = DriverManager.getConnection(dboptions.getUrl(), dboptions.getUser(), dboptions.getPassword());
|
||||
var sqlPlatform = new MysqlPlatform();
|
||||
var db = new MysqlDatabase(conn, dboptions, sqlPlatform);
|
||||
var ctx = new Ctx(db, ctxoptions);
|
||||
|
||||
//test
|
||||
var result = ctx.getCompanies().toList();
|
||||
System.out.println(result);
|
||||
|
||||
//assert
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void addEntity() throws Exception {
|
||||
//setup
|
||||
Class.forName("com.mysql.cj.jdbc.Driver").getDeclaredConstructor().newInstance();
|
||||
var dboptions = new DatabaseOptions("jdbc:mysql://localhost/test", "test", "password", getClass().getSimpleName(), null);
|
||||
var ctxoptions = new DbContextOptions(dboptions);
|
||||
var conn = DriverManager.getConnection(dboptions.getUrl(), dboptions.getUser(), dboptions.getPassword());
|
||||
var sqlPlatform = new MysqlPlatform();
|
||||
var db = new MysqlDatabase(conn, dboptions, sqlPlatform);
|
||||
var ctx = new Ctx(db, ctxoptions);
|
||||
|
||||
//test
|
||||
var entity = new Company();
|
||||
entity.setName("some company");
|
||||
ctx.getCompanies().add(entity);
|
||||
|
||||
//assert
|
||||
assertNotEquals(0, entity.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateEntity() throws Exception {
|
||||
//setup
|
||||
Class.forName("com.mysql.cj.jdbc.Driver").getDeclaredConstructor().newInstance();
|
||||
var dboptions = new DatabaseOptions("jdbc:mysql://localhost/test", "test", "password", getClass().getSimpleName(), null);
|
||||
var ctxoptions = new DbContextOptions(dboptions);
|
||||
var conn = DriverManager.getConnection(dboptions.getUrl(), dboptions.getUser(), dboptions.getPassword());
|
||||
var sqlPlatform = new MysqlPlatform();
|
||||
var db = new MysqlDatabase(conn, dboptions, sqlPlatform);
|
||||
var ctx = new Ctx(db, ctxoptions);
|
||||
|
||||
var entity = new Company();
|
||||
entity.setName("some company");
|
||||
ctx.getCompanies().add(entity);
|
||||
assertNotEquals(0, entity.getId());
|
||||
|
||||
//test
|
||||
entity.setName("other company");
|
||||
ctx.getCompanies().update(entity);
|
||||
|
||||
//assert
|
||||
var id = entity.getId();
|
||||
// var fromdb = ctx.getCompanies().filter(e -> e.id == entity.getId()).toList().stream().findFirst().orElseThrow();
|
||||
// var fromdb = ctx.getCompanies().filter(e -> e.id == entity.id).toList().stream().findFirst().orElseThrow();
|
||||
// var fromdb = ctx.getCompanies().filter(e -> e.id == id).toList().stream().findFirst().orElseThrow();
|
||||
var fromdb = ctx.getCompanies().toList().stream().filter(e -> e.id == id).findFirst().orElseThrow();
|
||||
// var fromdb = ctx.getCompanies().toList().stream().filter(e -> e.id == 5).findFirst().orElseThrow();
|
||||
assertEquals(entity, fromdb);
|
||||
assertNotSame(entity, fromdb);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteEntity() throws Exception {
|
||||
//setup
|
||||
Class.forName("com.mysql.cj.jdbc.Driver").getDeclaredConstructor().newInstance();
|
||||
var dboptions = new DatabaseOptions("jdbc:mysql://localhost/test", "test", "password", getClass().getSimpleName(), null);
|
||||
var ctxoptions = new DbContextOptions(dboptions);
|
||||
var conn = DriverManager.getConnection(dboptions.getUrl(), dboptions.getUser(), dboptions.getPassword());
|
||||
var sqlPlatform = new MysqlPlatform();
|
||||
var db = new MysqlDatabase(conn, dboptions, sqlPlatform);
|
||||
var ctx = new Ctx(db, ctxoptions);
|
||||
|
||||
var entity = new Company();
|
||||
entity.setName("some company");
|
||||
ctx.getCompanies().add(entity);
|
||||
assertNotEquals(0, entity.getId());
|
||||
|
||||
//test
|
||||
ctx.getCompanies().delete(entity);
|
||||
|
||||
//assert
|
||||
var id = entity.getId();
|
||||
// var fromdb = ctx.getCompanies().filter(e -> e.id == entity.getId()).toList().stream().findFirst().orElseThrow();//TODO get this to work too
|
||||
// var fromdb = ctx.getCompanies().filter(e -> e.id == entity.id).toList().stream().findFirst().orElseThrow();
|
||||
// var fromdb = ctx.getCompanies().filter(e -> e.id == id).toList().stream().findFirst().orElseThrow();
|
||||
var fromdb = ctx.getCompanies().toList().stream().noneMatch(e -> e.id == id);
|
||||
assertTrue(fromdb);
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class Ctx extends DbContext {
|
||||
@Clazz(Company.class)
|
||||
private DbSet<Company> companies;
|
||||
@Clazz(Employee.class)
|
||||
private DbSet<Employee> employees;
|
||||
|
||||
public Ctx() {
|
||||
}
|
||||
|
||||
public Ctx(Database database, DbContextOptions options) {
|
||||
super(database, options);
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@ToString
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public static class Company extends SerializableObject {
|
||||
private int id;
|
||||
private String name;
|
||||
|
||||
private Employee ceo;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@ToString
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public static class Employee extends SerializableObject {
|
||||
private int id;
|
||||
private String name;
|
||||
|
||||
private Employee boss;
|
||||
@ForeignKey(getterOrField = "boss")
|
||||
private int bossId;
|
||||
|
||||
private Company company;
|
||||
@ForeignKey(getterOrField = "company")
|
||||
private int companyId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package jef.platform.mysql.dbinterface;
|
||||
|
||||
import jef.DbSet;
|
||||
import jef.model.DbContext;
|
||||
import jef.model.DbContextOptions;
|
||||
import jef.model.ModelBuilder;
|
||||
import jef.model.annotations.Clazz;
|
||||
import jef.model.annotations.ForeignKey;
|
||||
import jef.platform.base.Database;
|
||||
import jef.serializable.SerializableObject;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
|
||||
class MysqlEntityDbInterfaceGeneratorTest {
|
||||
private static final String GENERATED_MIGRATIONS_DBINTERFACES = "target/test-generated-dbinterfaces/";
|
||||
// private static final String GENERATED_MIGRATIONS_FOLDER_SRC = GENERATED_MIGRATIONS_DBINTERFACES + "src/";
|
||||
private static final String GENERATED_MIGRATIONS_FOLDER_TARGET = GENERATED_MIGRATIONS_DBINTERFACES + "target/";
|
||||
|
||||
@Test
|
||||
void generate() throws IOException {
|
||||
var mb = ModelBuilder.from(Ctx.class);
|
||||
var generator = new MysqlEntityDbInterfaceGenerator<>(Ctx.class, mb.getEntity(Employee.class));
|
||||
generator.generate();
|
||||
// var path = Path.of(GENERATED_MIGRATIONS_FOLDER_TARGET, "MysqlEntityDbInterfaceGeneratorTest", "MysqlEntityDbInterfaceGeneratorTest$Ctx$EmployeeMysqlEntityDbInterface.class");
|
||||
var path = Path.of(GENERATED_MIGRATIONS_FOLDER_TARGET, "MysqlEntityDbInterfaceGeneratorTest", "EmployeeMysqlEntityDbInterface.class");
|
||||
path.toFile().getParentFile().mkdirs();
|
||||
Files.write(path, generator.getByteCode(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class Ctx extends DbContext {
|
||||
@Clazz(Company.class)
|
||||
private DbSet<Company> companies;
|
||||
@Clazz(Employee.class)
|
||||
private DbSet<Employee> employees;
|
||||
|
||||
public Ctx() {
|
||||
}
|
||||
|
||||
public Ctx(Database database, DbContextOptions options) {
|
||||
super(database, options);
|
||||
}
|
||||
}
|
||||
|
||||
// @Getter
|
||||
// @Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@ToString
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public static class Company extends SerializableObject {
|
||||
private int id;
|
||||
private String name;
|
||||
|
||||
private Employee ceo;
|
||||
}
|
||||
|
||||
// @Getter
|
||||
// @Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@ToString
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public static class Employee extends SerializableObject {
|
||||
private int id;
|
||||
private String name;
|
||||
|
||||
private Employee boss;
|
||||
@ForeignKey(getterOrField = "boss")
|
||||
private int bossId;
|
||||
|
||||
private Company company;
|
||||
@ForeignKey(getterOrField = "company")
|
||||
private int companyId;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user