This commit is contained in:
apucher
2023-01-08 14:45:03 +01:00
committed by wea_ondara
parent b805eed622
commit 74a8f56ab5
7 changed files with 682 additions and 213 deletions

View File

@@ -54,7 +54,7 @@ class ClassAnalyzerVisitor extends ClassVisitor {
var type = Type.getMethodType(descriptor);
var returnClassName = type.getReturnType().getClassName();
var parameterClassNames = Arrays.stream(type.getArgumentTypes()).map(Type::getClassName).toArray(String[]::new);
return new MethodAnalyzerVisitor(Opcodes.ASM9, className, name, returnClassName, parameterClassNames, entityAccess -> {
return new MethodAnalyzerVisitor(Opcodes.ASM9, className, name, access, returnClassName, parameterClassNames, entityAccess -> {
declaredConstructor.addAll(entityAccess.getDeclaredConstructors());
declaredGetters.addAll(entityAccess.getDeclaredGetters());
declaredSetters.addAll(entityAccess.getDeclaredSetters());

View File

@@ -6,6 +6,7 @@ import jef.asm.access.model.ConstructorParameterDescription;
import jef.asm.access.model.GetterDescription;
import jef.asm.access.model.SetterDescription;
import jef.asm.access.model.SuperConstructorParameterDescription;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;
@@ -14,6 +15,7 @@ import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.Stack;
@@ -22,14 +24,15 @@ import java.util.function.Consumer;
@Getter
public class MethodAnalyzerVisitor extends MethodVisitor {//TODO verify param count
private final Consumer<ClassDescription> callback;
private final String methodClassName;
private final String declaringClassName;
private final String methodName;
private final int access;
private final String returnClassName;
private final String[] parameterClassNames;
private List<ConstructorParameterDescription> constructorParameterDescriptions = new ArrayList<>();
private List<SuperConstructorParameterDescription> superConstructorParameterDescriptions = new ArrayList<>();
private Stack<Integer> varIndexStack = new Stack<>();
private Stack<Var> varStack = new Stack<>();
private String fieldDeclaringClassName = null;
private String fieldName = null;
@@ -69,12 +72,19 @@ public class MethodAnalyzerVisitor extends MethodVisitor {//TODO verify param co
INVALID
}
protected MethodAnalyzerVisitor(int api, String methodClassName, String methodName, String returnClassName, String[] parameterClassNames, Consumer<ClassDescription> callback) {
protected MethodAnalyzerVisitor(int api, String declaringClassName, String methodName, int access, String returnClassName, String[] parameterClassNames, Consumer<ClassDescription> callback) {
super(api);
this.methodClassName = methodClassName;
this.declaringClassName = declaringClassName;
this.methodName = methodName;
this.access = access;
this.returnClassName = returnClassName;
if (hasThis()) {
this.parameterClassNames = new String[parameterClassNames.length + 1];
System.arraycopy(parameterClassNames, 0, this.parameterClassNames, 1, parameterClassNames.length);
this.parameterClassNames[0] = declaringClassName;
} else {
this.parameterClassNames = parameterClassNames;
}
this.callback = callback;
}
@@ -122,7 +132,11 @@ public class MethodAnalyzerVisitor extends MethodVisitor {//TODO verify param co
@Override
public void visitVarInsn(int opcode, int varIndex) {
if (Set.of(Opcodes.ILOAD, Opcodes.LLOAD, Opcodes.FLOAD, Opcodes.DLOAD, Opcodes.ALOAD).contains(opcode)) {
varIndexStack.push(varIndex);
System.out.println(methodName + ": load_" + varIndex + ": " + (varIndex < parameterClassNames.length ? parameterClassNames[varIndex] : null));
if (varIndex == 5) {
int x = 0;
}
varStack.push(new Var(varIndex < parameterClassNames.length ? parameterClassNames[varIndex] : null, varIndex));
}
if (opcode == Opcodes.ALOAD && varIndex == 0) {
if (constructorStep == ConstructorSteps.CODE) {
@@ -160,6 +174,10 @@ public class MethodAnalyzerVisitor extends MethodVisitor {//TODO verify param co
super.visitVarInsn(opcode, varIndex);
}
private boolean hasThis() {
return (access & Opcodes.ACC_STATIC) == 0;
}
@Override
public void visitTypeInsn(int opcode, String type) {
constructorStep = ConstructorSteps.INVALID;
@@ -170,15 +188,15 @@ public class MethodAnalyzerVisitor extends MethodVisitor {//TODO verify param co
@Override
public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
var lastLoadedVarIndex = -1;
Var lastLoadedVar = null;
if (opcode == Opcodes.PUTFIELD) {
lastLoadedVarIndex = varIndexStack.pop();
varIndexStack.pop();
lastLoadedVar = varStack.pop();
varStack.pop();
constructorStep = constructorStep == ConstructorSteps.VAR_LOAD ? ConstructorSteps.PUT_FIELD : ConstructorSteps.INVALID;
getterStep = GetterSteps.INVALID;
setterStep = setterStep == SetterSteps.VAR_LOAD ? SetterSteps.PUT_FIELD : SetterSteps.INVALID;
} else if (opcode == Opcodes.GETFIELD) {
lastLoadedVarIndex = varIndexStack.pop();
lastLoadedVar = varStack.pop();
constructorStep = ConstructorSteps.INVALID;
getterStep = getterStep == GetterSteps.ALOAD_0 ? GetterSteps.GET_FIELD : GetterSteps.INVALID;
setterStep = SetterSteps.INVALID;
@@ -189,21 +207,21 @@ public class MethodAnalyzerVisitor extends MethodVisitor {//TODO verify param co
}
this.fieldDeclaringClassName = owner.replace("/", ".");
this.fieldName = name;
this.constructorParameterDescriptions.add(new ConstructorParameterDescription(lastLoadedVarIndex - 1, this.fieldDeclaringClassName, this.fieldName)); //lastLoadedVarIndex - 1 because arg 0 of an instance function is always this
this.constructorParameterDescriptions.add(new ConstructorParameterDescription(lastLoadedVar.getIndex() - 1, lastLoadedVar.getClassName(), this.fieldDeclaringClassName, this.fieldName)); //lastLoadedVar - 1 because arg 0 of an instance function is always this
super.visitFieldInsn(opcode, owner, name, descriptor);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
var argIndexes = new ArrayList<Integer>();
var args = new ArrayList<Var>();
for (var i = 0; i < Type.getArgumentTypes(descriptor).length; i++) {
argIndexes.add(0, varIndexStack.pop());
args.add(0, varStack.pop());
}
varIndexStack.pop(); // also pop ALOAD0
varStack.pop(); // also pop ALOAD0
if (opcode == Opcodes.INVOKESPECIAL && name.equals("<init>") //TODO does it have to be called '<init>' or is this just convention
&& Set.of(ConstructorSteps.PRE_SUPER_INIT_ALOAD_0, ConstructorSteps.PRE_SUPER_INIT_VAR_LOAD).contains(constructorStep)) {
for (int i = 0; i < argIndexes.size(); i++) {
superConstructorParameterDescriptions.add(new SuperConstructorParameterDescription(argIndexes.get(i), i));
for (int i = 0; i < args.size(); i++) {
superConstructorParameterDescriptions.add(new SuperConstructorParameterDescription(args.get(i).getIndex(), i));
}
constructorStep = ConstructorSteps.SUPER_INIT;
} else {
@@ -232,9 +250,6 @@ public class MethodAnalyzerVisitor extends MethodVisitor {//TODO verify param co
@Override
public void visitLabel(Label label) {
// constructorStep = ConstructorSteps.INVALID;
// getterStep = GetterSteps.INVALID;
// setterStep = SetterSteps.INVALID;
super.visitLabel(label);
}
@@ -300,9 +315,16 @@ public class MethodAnalyzerVisitor extends MethodVisitor {//TODO verify param co
public void visitEnd() {
super.visitEnd();
List<ConstructorDescription> constructors = constructorStep == ConstructorSteps.RETURN ? List.of(new ConstructorDescription(constructorParameterDescriptions, superConstructorParameterDescriptions)) : List.of();
List<GetterDescription> getters = getterStep == GetterSteps.RETURN ? List.of(new GetterDescription(methodClassName, methodName, returnClassName, parameterClassNames, fieldDeclaringClassName, fieldName)) : List.of();
List<SetterDescription> setters = setterStep == SetterSteps.RETURN ? List.of(new SetterDescription(methodClassName, methodName, returnClassName, parameterClassNames, fieldDeclaringClassName, fieldName)) : List.of();
List<GetterDescription> getters = getterStep == GetterSteps.RETURN ? List.of(new GetterDescription(declaringClassName, methodName, returnClassName, Arrays.stream(parameterClassNames).skip(hasThis() ? 1 : 0).toArray(String[]::new), fieldDeclaringClassName, fieldName)) : List.of();
List<SetterDescription> setters = setterStep == SetterSteps.RETURN ? List.of(new SetterDescription(declaringClassName, methodName, returnClassName, Arrays.stream(parameterClassNames).skip(hasThis() ? 1 : 0).toArray(String[]::new), fieldDeclaringClassName, fieldName)) : List.of();
var access = new ClassDescription(null, null, null, constructors, getters, setters);
callback.accept(access);
}
@AllArgsConstructor
@Getter
private static class Var {
private final String className;
private final int index;
}
}

View File

@@ -9,6 +9,7 @@ import lombok.ToString;
@ToString
public class ConstructorParameterDescription {
private final int parameterIndex;
private final String parameterClassName;
private final String declaringClassName;
private final String fieldName;
}

View File

@@ -15,7 +15,7 @@ public @interface Generated {
enum Type {
NONE,//no value is generated
IDENTITY,//value is generated on insert
// COMPUTED,//value is generated on insert and update
COMPUTED,//value is generated on insert and update
;
}
}

View File

@@ -4,6 +4,7 @@ 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.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -18,6 +19,7 @@ import java.sql.SQLException;
import java.sql.Types;
import java.util.Arrays;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -60,20 +62,11 @@ public abstract class ByteCodeHelper {
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);
private static void visitGetViaReflection(MethodVisitor mv, Field field, int varIndexEntity, String className, Runnable defineFieldCallback) {
defineFieldCallback.run();
// 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);
// field.get*(entity, value);
mv.visitFieldInsn(Opcodes.GETSTATIC, className.replace(".", "/"), makeReflectedFieldName(field), Type.getDescriptor(field.getType()));
mv.visitVarInsn(Opcodes.ALOAD, varIndexEntity);
var clazz = field.getType();
if (clazz == boolean.class) {
@@ -97,7 +90,7 @@ public abstract class ByteCodeHelper {
}
}
static void visitGetField(MethodVisitor mv, Field field, int varIndexEntity, int varIndexField, ClassDescription classDescription) {
static void visitGetField(MethodVisitor mv, Field field, int varIndexEntity, String className, Runnable defineFieldCallback, ClassDescription classDescription) {
//try direct member access
if (Modifier.isPublic(field.getModifiers())) {//TODO better check if publicly accessible
visitGetFieldDirectAccess(mv, field, varIndexEntity);
@@ -115,7 +108,7 @@ public abstract class ByteCodeHelper {
}
//use reflection
visitGetViaReflection(mv, field, varIndexEntity, varIndexField);
visitGetViaReflection(mv, field, varIndexEntity, className, defineFieldCallback);
}
//endregion
@@ -139,20 +132,11 @@ public abstract class ByteCodeHelper {
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);
private static void visitSetViaReflection(MethodVisitor mv, Field field, Consumer<MethodVisitor> value, int varIndexEntity, String className, Runnable defineFieldCallback) {
defineFieldCallback.run();
// field.set*(entity, value);
mv.visitVarInsn(Opcodes.ALOAD, varIndexField);
mv.visitFieldInsn(Opcodes.GETSTATIC, className.replace(".", "/"), makeReflectedFieldName(field), Type.getDescriptor(field.getType()));
mv.visitVarInsn(Opcodes.ALOAD, varIndexEntity);
value.accept(mv);
var clazz = field.getType();
@@ -177,7 +161,7 @@ public abstract class ByteCodeHelper {
}
}
static void visitSetField(MethodVisitor mv, Field field, Consumer<MethodVisitor> value, int varIndexEntity, int varIndexField, ClassDescription classDescription) {
static void visitSetField(MethodVisitor mv, Field field, Consumer<MethodVisitor> value, int varIndexEntity, String className, Runnable defineFieldCallback, 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);
@@ -195,7 +179,7 @@ public abstract class ByteCodeHelper {
}
//use reflection
visitSetViaReflection(mv, field, value, varIndexEntity, varIndexField);
visitSetViaReflection(mv, field, value, varIndexEntity, className, defineFieldCallback);
}
//endregion
@@ -383,6 +367,17 @@ public abstract class ByteCodeHelper {
mv.visitInsn(Opcodes.ATHROW);
}
public static void visitPushDefault(MethodVisitor mv, Class<?> type) {
if (Set.of(void.class, boolean.class, byte.class, char.class, short.class, int.class, float.class).contains(type)) {
mv.visitInsn(Opcodes.ICONST_0);
} else if (Set.of(double.class, long.class).contains(type)) {
mv.visitInsn(Opcodes.ICONST_0);
mv.visitInsn(Opcodes.ICONST_0);
} else {
mv.visitInsn(Opcodes.ACONST_NULL);
}
}
static String getDescriptorForClassName(String className) {
return switch (className) {
case "void" -> "V";
@@ -399,6 +394,33 @@ public abstract class ByteCodeHelper {
}
//endregion
//region field reflection
public static void visitReflectedFieldDefinition(ClassVisitor visitor, Field field, boolean synthetic) {
visitor.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | (synthetic ? Opcodes.ACC_SYNTHETIC : 0),
makeReflectedFieldName(field), T_FIELD.getDescriptor(), null, null);
}
public static void visitReflectedFieldInit(MethodVisitor mv, Field field, String className) {
className = className.replace(".", "/");
var fieldname = makeReflectedFieldName(field);
// 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.visitFieldInsn(Opcodes.PUTSTATIC, className, fieldname, T_FIELD.getDescriptor());
// field.setAccessible(true);
mv.visitFieldInsn(Opcodes.GETSTATIC, className, fieldname, Type.getDescriptor(field.getType()));
mv.visitInsn(Opcodes.ICONST_1);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setAccessible", Type.getMethodDescriptor(T_VOID, T_BOOL), false);
}
private static String makeReflectedFieldName(Field field) {
return field.getDeclaringClass().getName().replace(".", "_") + "_" + field.getName();
}
//endregion
//region var access
// private void visitLoadVariable(MethodVisitor mv, String className, int varIndex) {
// switch (className) {

View File

@@ -24,40 +24,53 @@ public abstract class MysqlEntityDbInterface<T extends SerializableObject> {
}
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 abstract void bindParamsAdd(PreparedStatement stmt, T entity) throws SQLException;
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);
protected abstract void readBackGeneratedValuesAdd(ResultSet res, T entity) throws SQLException;
public void update(Connection connection, T entity) throws SQLException {
try (var stmt = queryUpdate(connection)) {
bindParamsUpdate(stmt, entity);
var updated = stmt.executeUpdate();
if (updated != 1) {
throw new SQLException("Failed to update entity");
}
// public void update(Connection connection, T model);
//
// public void delete(Connection connection, T model);
//
// public T read(ResultSet res);
readBackGeneratedValuesUpdate(stmt, entity);
}
}
protected abstract PreparedStatement queryUpdate(Connection connection) throws SQLException;
protected abstract void bindParamsUpdate(PreparedStatement stmt, T entity) throws SQLException;
protected void readBackGeneratedValuesUpdate(PreparedStatement stmt, T entity) throws SQLException {
try (var res = stmt.getGeneratedKeys()) {
readBackGeneratedValuesUpdate(res, entity);
}
}
protected abstract void readBackGeneratedValuesUpdate(ResultSet res, T entity) throws SQLException;
public void delete(Connection connection, T entity) throws SQLException {
try (var stmt = queryUpdate(connection)) {
bindParamsDelete(stmt, entity);
var deleted = stmt.executeUpdate();
if (deleted != 1) {
throw new SQLException("Failed to delete entity");
}
}
}
protected abstract void bindParamsDelete(PreparedStatement stmt, T entity) throws SQLException;
public abstract T read(ResultSet res);
}

View File

@@ -17,8 +17,13 @@ import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -34,6 +39,7 @@ public class MysqlEntityDbInterfaceGenerator<T extends SerializableObject> {
//runtime
private ClassDescription classDescription;
private final Set<Field> fieldsToReflect = new HashSet<>();
//results
@Getter
@@ -45,7 +51,7 @@ public class MysqlEntityDbInterfaceGenerator<T extends SerializableObject> {
this.context = context;
this.entity = entity;
writer = new ClassWriter(Opcodes.ASM9);
visitor = new ClassVisitor(Opcodes.ASM9, writer) {//TODO definitely broken here
visitor = new ClassVisitor(Opcodes.ASM9, writer) {
};
}
@@ -57,15 +63,30 @@ public class MysqlEntityDbInterfaceGenerator<T extends SerializableObject> {
className = entity.getType().getSimpleName() + "" + MysqlEntityDbInterface.class.getSimpleName();
defineClass();
defineConstructor();
// defineAddFunction();
//add
defineQueryAddFunction();
defineBindParamsAddFunction();
defineReadBackGeneratedValuesAddPreparedStatement();
defineReadBackGeneratedValuesAddResultSet();
// defineUpdateFunction();
// defineDeleteFunction();
// defineReadFunction();
//update
defineQueryUpdateFunction();
defineBindParamsUpdateFunction();
defineReadBackGeneratedValuesUpdatePreparedStatement();
defineReadBackGeneratedValuesUpdateResultSet();
//delete
defineQueryDeleteFunction();
defineBindParamsDeleteFunction();
//read
defineReadFunction();
//other
defineReflectedFields();
defineClassConstructor();
defineConstructor();
visitor.visitEnd();
@@ -74,10 +95,7 @@ public class MysqlEntityDbInterfaceGenerator<T extends SerializableObject> {
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.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);
}
@@ -93,84 +111,39 @@ public class MysqlEntityDbInterfaceGenerator<T extends SerializableObject> {
mv.visitEnd();
}
private ColumnsAndValueBinders prepareColumnsAndValueBindersAdd() {
var columns = new ArrayList<String>();
var valueBinders = new ArrayList<ValueBinder>();
for (DbField<?> field : entity.getFields()) {
if (!field.isDatabaseField()) {
continue;
private void defineReflectedFields() {
for (Field field : fieldsToReflect) {
ByteCodeHelper.visitReflectedFieldDefinition(visitor, field, SET_SYNTHETIC_FLAG);
}
}
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);
//<clinit>
private void defineClassConstructor() {
var mv = visitor.visitMethod(Opcodes.ACC_STATIC | SYNTHETIC_FLAG, "<clinit>", "()V", null, new String[0]);
mv.visitCode();
//stmt.setNull(i, Types.INTEGER);
ByteCodeHelper.visitPreparedStatementSetNull(mv, field, varIndexStmt, index);
});
}
continue;
//init reflected fields
for (Field field : fieldsToReflect) {
ByteCodeHelper.visitReflectedFieldInit(mv, field, className);
}
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;
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(2, 0);
mv.visitEnd();
}
//region add
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()});
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 columnsAndValueBindersSet = prepareColumnsAndValueBindersSet();
var columnsString = columnsAndValueBinders.getColumns().stream().map(e -> "`" + e + "`").collect(Collectors.joining(","));
var placeholdersString = columnsAndValueBinders.getColumns().stream().map(e -> "?").collect(Collectors.joining(","));
var columnsString = columnsAndValueBindersSet.getColumns().stream().map(e -> "`" + e + "`").collect(Collectors.joining(","));
var placeholdersString = columnsAndValueBindersSet.getColumns().stream().map(e -> "?").collect(Collectors.joining(","));
var query = "INSERT INTO `" + entity.getName() + "` (" + columnsString + ") VALUES (" + placeholdersString + ")";
// variable scopes
@@ -189,8 +162,8 @@ public class MysqlEntityDbInterfaceGenerator<T extends SerializableObject> {
//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
if (entity.getFields().stream().anyMatch(e -> e.getGenerated() != Generated.Type.NONE)) {
ByteCodeHelper.visitIntInsn(mv, Statement.RETURN_GENERATED_KEYS);
} else {
mv.visitInsn(Opcodes.ICONST_0); //no flags
}
@@ -211,17 +184,15 @@ public class MysqlEntityDbInterfaceGenerator<T extends SerializableObject> {
}
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()});
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();
var columnsAndValueBindersSet = prepareColumnsAndValueBindersSet();
// variable scopes
var labelStartThis = new Label();
@@ -230,8 +201,6 @@ public class MysqlEntityDbInterfaceGenerator<T extends SerializableObject> {
var labelEndStmt = new Label();
var labelStartEntity = new Label();
var labelEndEntity = new Label();
var labelStartField = new Label();
var labelEndField = new Label();
// begin
mv.visitCode();
@@ -240,12 +209,11 @@ public class MysqlEntityDbInterfaceGenerator<T extends SerializableObject> {
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++);
for (var valueBinder : columnsAndValueBindersSet.getValueBinders()) {
valueBinder.bind(mv, varIndexEntity, varIndexStmt, i++);
}
//return
@@ -255,55 +223,503 @@ public class MysqlEntityDbInterfaceGenerator<T extends SerializableObject> {
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.visitMaxs(3, 3);
mv.visitEnd();
}
private void defineReadBackGeneratedValuesAddPreparedStatement() {
if (entity.getFields().stream().anyMatch(e -> e.getGenerated() != Generated.Type.NONE)) {
if (entity.getFields().stream().anyMatch(e -> e.getGenerated() == Generated.Type.IDENTITY)) {
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()});
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();
var labelStart = new Label();
var labelEnd = new Label();
mv.visitLabel(labelStart);
mv.visitCode();
mv.visitInsn(Opcodes.RETURN);
mv.visitLabel(labelEnd);
// 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.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, labelStart, labelEnd, 0);
mv.visitLocalVariable("stmt", ByteCodeHelper.T_PREPARED_STATEMENT.getDescriptor(), null, labelStart, labelEnd, 1);
mv.visitLocalVariable("entity", ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()), null, labelStart, labelEnd, 2);
mv.visitMaxs(0, 3);//TODO stack size
mv.visitMaxs(0, 3);
mv.visitEnd();
}
private void defineReadBackGeneratedValuesAddResultSet() {
if (entity.getFields().stream().noneMatch(e -> e.getGenerated() != Generated.Type.NONE)) {
var fieldsToRead = entity.getFields().stream().filter(e -> e.getGenerated() != Generated.Type.NONE).toList();
if (fieldsToRead.isEmpty()) {
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()});
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();
visitReadFieldsIntoEntityFunctionBody(mv, fieldsToRead, true);
}
//endregion
//region update
private void defineQueryUpdateFunction() {
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "queryUpdate", 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 columnsAndValueBindersSet = prepareColumnsAndValueBindersSet();
var columnsAndValueBindersWhere = prepareColumnsAndValueBindersWhere();
var updatesString = columnsAndValueBindersSet.getColumns().stream().map(e -> "`" + e + "` = ?").collect(Collectors.joining(", "));
var whereString = columnsAndValueBindersWhere.getColumns().stream().map(e -> "`" + e + "` = ?").collect(Collectors.joining(" AND "));
var query = "UPDATE `" + entity.getName() + "` SET " + updatesString + " WHERE " + whereString;
// 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.getFields().stream().anyMatch(e -> e.getGenerated() == Generated.Type.COMPUTED)) {
ByteCodeHelper.visitIntInsn(mv, Statement.RETURN_GENERATED_KEYS);
} 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 defineBindParamsUpdateFunction() {
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "bindParamsUpdate", 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;
//create lists for columns and value binders
var columnsAndValueBindersSet = prepareColumnsAndValueBindersSet();
var columnsAndValueBindersWhere = prepareColumnsAndValueBindersWhere();
// 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();
// begin
mv.visitCode();
// variable scopes
mv.visitLabel(labelStartThis);
mv.visitLabel(labelStartStmt);
mv.visitLabel(labelStartEntity);
//stmt.set* bind calls
var i = 1;
for (var valueBinder : columnsAndValueBindersSet.getValueBinders()) {
valueBinder.bind(mv, varIndexEntity, varIndexStmt, i++);
}
for (var valueBinder : columnsAndValueBindersWhere.getValueBinders()) {
valueBinder.bind(mv, varIndexEntity, varIndexStmt, i++);
}
//return
mv.visitInsn(Opcodes.RETURN);
// variable scopes
mv.visitLabel(labelEndThis);
mv.visitLabel(labelEndStmt);
mv.visitLabel(labelEndEntity);
// 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.visitMaxs(3, 3);
mv.visitEnd();
}
private void defineReadBackGeneratedValuesUpdatePreparedStatement() {
if (entity.getFields().stream().anyMatch(e -> e.getGenerated() == Generated.Type.COMPUTED)) {
return;
}
//stub method if no generated values
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "readBackGeneratedValuesUpdate", 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 labelStart = new Label();
var labelEnd = new Label();
mv.visitLabel(labelStart);
mv.visitCode();
mv.visitInsn(Opcodes.RETURN);
mv.visitLabel(labelEnd);
// debug symbols
mv.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, labelStart, labelEnd, 0);
mv.visitLocalVariable("stmt", ByteCodeHelper.T_PREPARED_STATEMENT.getDescriptor(), null, labelStart, labelEnd, 1);
mv.visitLocalVariable("entity", ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()), null, labelStart, labelEnd, 2);
mv.visitMaxs(0, 3);
mv.visitEnd();
}
private void defineReadBackGeneratedValuesUpdateResultSet() {
var fieldsToRead = entity.getFields().stream().filter(e -> e.getGenerated() == Generated.Type.COMPUTED).toList();
if (fieldsToRead.isEmpty()) {
return;
}
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "readBackGeneratedValuesUpdate", 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();
visitReadFieldsIntoEntityFunctionBody(mv, fieldsToRead, true);
}
//endregion
//region delete
private void defineQueryDeleteFunction() {
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "queryDelete", 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 columnsAndValueBindersWhere = prepareColumnsAndValueBindersWhere();
var whereString = columnsAndValueBindersWhere.getColumns().stream().map(e -> "`" + e + "` = ?").collect(Collectors.joining(" AND "));
var query = "DELETE FROM `" + entity.getName() + "` WHERE " + whereString;
// 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.getFields().stream().anyMatch(e -> e.getGenerated() != Generated.Type.NONE)) {
ByteCodeHelper.visitIntInsn(mv, Statement.RETURN_GENERATED_KEYS);
} 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 defineBindParamsDeleteFunction() {
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "bindParamsDelete", 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;
//create lists for columns and value binders
var columnsAndValueBindersWhere = prepareColumnsAndValueBindersWhere();
// 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();
// begin
mv.visitCode();
// variable scopes
mv.visitLabel(labelStartThis);
mv.visitLabel(labelStartStmt);
mv.visitLabel(labelStartEntity);
//stmt.set* bind calls
var i = 1;
for (var valueBinder : columnsAndValueBindersWhere.getValueBinders()) {
valueBinder.bind(mv, varIndexEntity, varIndexStmt, i++);
}
//return
mv.visitInsn(Opcodes.RETURN);
// variable scopes
mv.visitLabel(labelEndThis);
mv.visitLabel(labelEndStmt);
mv.visitLabel(labelEndEntity);
// 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.visitMaxs(3, 3);
mv.visitEnd();
}
//endregion
//region read
private void defineReadFunction() {
var fields = entity.getFields().stream().filter(e -> e.isDatabaseField()).toList();
var bestConstructor = classDescription.getDeclaredConstructors().stream().filter(e -> e.getSuperConstructorParameterDescriptions().size() == 0).map(ctor -> {
var matchingParamCount = ctor.getConstructorParameterDescriptions().stream()
.filter(param -> fields.stream().anyMatch(field -> field.getField().getDeclaringClass().getName().equals(param.getDeclaringClassName())
&& field.getField().getName().equals(param.getFieldName())))
.count();
return new AbstractMap.SimpleEntry<>(ctor, matchingParamCount);
}).sorted(Comparator.comparingLong(e -> -e.getValue())).findFirst();
if (!bestConstructor.isPresent()) {
throw new IllegalStateException("No suitable constructor found!");
}
var orderedFields = bestConstructor.orElseThrow().getKey().getConstructorParameterDescriptions().stream()
.sorted(Comparator.comparingInt(e -> e.getParameterIndex()))
.map(param -> new AbstractMap.SimpleEntry<>(param, fields.stream()
.filter(field -> field.getField().getDeclaringClass().getName().equals(param.getDeclaringClassName())
&& field.getField().getName().equals(param.getFieldName()))
.findFirst().orElse(null)))
.toList();
//begin
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "read", Type.getMethodDescriptor(Type.getType(ByteCodeHelper.getDescriptorForClassName(entity.getTypeName())), ByteCodeHelper.T_RESULT_SET), null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
var varIndexThis = 0;
var varIndexRes = 1;
// variable scopes
var labelStartThis = new Label();
var labelEndThis = new Label();
var labelStartRes = new Label();
var labelEndRes = new Label();
// begin
mv.visitCode();
// variable scopes
mv.visitLabel(labelStartThis);
mv.visitLabel(labelStartRes);
// push params
mv.visitLdcInsn(Type.getObjectType(entity.getTypeName().replace(".", "/")));
for (var paramAndField : orderedFields) {
if (paramAndField.getValue() == null) {
var fieldType = entity.getFields().stream()
.filter(field -> field.getField().getDeclaringClass().getName().equals(paramAndField.getKey().getDeclaringClassName())
&& field.getField().getName().equals(paramAndField.getKey().getFieldName()))
.findFirst().orElseThrow()
.getField().getType();
// PUSH null, 0, or false
ByteCodeHelper.visitPushDefault(mv, fieldType);
} else {
// PUSH res.get*(1);
ByteCodeHelper.visitResultSetGetAny(mv, paramAndField.getValue(), varIndexRes);
}
}
//new Entity(...)
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, entity.getTypeName().replace(".", "/"), "<init>",
Type.getMethodDescriptor(ByteCodeHelper.T_VOID, orderedFields.stream()
.map(paramAndField -> Type.getType(ByteCodeHelper.getDescriptorForClassName(paramAndField.getKey().getParameterClassName())))
.toArray(Type[]::new)), false);
//return
mv.visitInsn(Opcodes.ARETURN);
// variable scopes
mv.visitLabel(labelEndThis);
mv.visitLabel(labelEndRes);
// 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.visitMaxs(orderedFields.size() + 1, 2);
mv.visitEnd();
}
//endregion
//region util
private ColumnsAndValueBinders prepareColumnsAndValueBindersSet() {
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, 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, index) -> {
if (field.isNotNull()) {
//stmt.set*(i, value);
ByteCodeHelper.visitPreparedStatementSetAny(mv, field, varIndexStmt, index, (mv2) -> ByteCodeHelper.visitGetField(mv2, field.getField(), varIndexEntity, className, () -> fieldsToReflect.add(field.getField()), classDescription));
} else {
// getFieldValue(entity);
ByteCodeHelper.visitGetField(mv, f, varIndexEntity, className, () -> fieldsToReflect.add(field.getField()), 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, className, () -> fieldsToReflect.add(field.getField()), 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 ColumnsAndValueBinders prepareColumnsAndValueBindersWhere() {
var columns = new ArrayList<String>();
var valueBinders = new ArrayList<ValueBinder>();
for (DbField<?> field : entity.getPrimaryKey().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, 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, index) -> {
if (field.isNotNull()) {
//stmt.set*(i, value);
ByteCodeHelper.visitPreparedStatementSetAny(mv, field, varIndexStmt, index, (mv2) -> ByteCodeHelper.visitGetField(mv2, field.getField(), varIndexEntity, className, () -> fieldsToReflect.add(field.getField()), classDescription));
} else {
// getFieldValue(entity);
ByteCodeHelper.visitGetField(mv, f, varIndexEntity, className, () -> fieldsToReflect.add(field.getField()), 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, className, () -> fieldsToReflect.add(field.getField()), 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 void visitReadFieldsIntoEntityFunctionBody(MethodVisitor mv, List<DbField<?>> fields, boolean moveNext) {
var varIndexThis = 0;
var varIndexRes = 1;
var varIndexEntity = 2;
var varIndexField = 3;
// variable scopes
var labelStartThis = new Label();
@@ -312,8 +728,6 @@ public class MysqlEntityDbInterfaceGenerator<T extends SerializableObject> {
var labelEndRes = new Label();
var labelStartEntity = new Label();
var labelEndEntity = new Label();
var labelStartField = new Label();
var labelEndField = new Label();
// begin
mv.visitCode();
@@ -322,8 +736,8 @@ public class MysqlEntityDbInterfaceGenerator<T extends SerializableObject> {
mv.visitLabel(labelStartThis);
mv.visitLabel(labelStartRes);
mv.visitLabel(labelStartEntity);
mv.visitLabel(labelStartField);
if (moveNext) {
// if (!res.next()) {
// throw new SQLException("Missing generated keys for entity but are required");
// }
@@ -333,19 +747,16 @@ public class MysqlEntityDbInterfaceGenerator<T extends SerializableObject> {
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;
}
for (var field : fields) {
// 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);
ByteCodeHelper.visitSetField(mv, field.getField(), resGetValue, varIndexEntity, className, () -> fieldsToReflect.add(field.getField()), classDescription);
}
//return
@@ -355,26 +766,26 @@ public class MysqlEntityDbInterfaceGenerator<T extends SerializableObject> {
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.visitMaxs(3, 3);
mv.visitEnd();
}
private void defineUpdateFunction() {
private interface ValueBinder {
void bind(MethodVisitor mv, int varIndexEntity, int varIndexStmt, int index);
}
private void defineDeleteFunction() {
@AllArgsConstructor
@Getter
private static class ColumnsAndValueBinders {
private final List<String> columns;
private final List<ValueBinder> valueBinders;
}
private void defineReadFunction() {
}
//endregion
}