wip
This commit is contained in:
@@ -4,19 +4,24 @@ import jef.expressions.Expression;
|
||||
import jef.expressions.SelectExpression;
|
||||
import jef.expressions.TableExpression;
|
||||
import jef.expressions.selectable.DatabaseSelectAllExpression;
|
||||
import jef.model.DbContext;
|
||||
import jef.serializable.SerializableObject;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Spliterator;
|
||||
|
||||
public class DbSet<T extends SerializableObject> implements Queryable<T> {
|
||||
@Getter
|
||||
private final DbContext context;
|
||||
@Getter
|
||||
private final Class<T> clazz;
|
||||
private final String table;
|
||||
|
||||
public DbSet(Class<T> clazz, String table) {
|
||||
public DbSet(DbContext context, Class<T> clazz, String table) {
|
||||
this.context = context;
|
||||
this.clazz = clazz;
|
||||
this.table = table;
|
||||
}
|
||||
@@ -36,6 +41,23 @@ public class DbSet<T extends SerializableObject> implements Queryable<T> {
|
||||
return "SELECT * FROM `" + table + "`";
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbSet<T> originalSet() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public void add(T entity) throws SQLException {
|
||||
context.getDatabase().add(context, clazz, entity);
|
||||
}
|
||||
|
||||
public void update(T entity) throws SQLException {
|
||||
context.getDatabase().update(context, clazz, entity);
|
||||
}
|
||||
|
||||
public void delete(T entity) throws SQLException {
|
||||
context.getDatabase().delete(context, clazz, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return null;
|
||||
|
||||
@@ -6,13 +6,15 @@ import jef.operations.FilterOp;
|
||||
import jef.operations.LimitOp;
|
||||
import jef.operations.SortOp;
|
||||
import jef.serializable.SerializableFunction;
|
||||
import jef.serializable.SerializableObject;
|
||||
import jef.serializable.SerializablePredicate;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Spliterator;
|
||||
|
||||
public interface Queryable<T extends Serializable> {
|
||||
public interface Queryable<T extends SerializableObject> {
|
||||
|
||||
//TODO documentation
|
||||
//TODO table alias thing still not ready
|
||||
@@ -22,6 +24,8 @@ public interface Queryable<T extends Serializable> {
|
||||
|
||||
String toString();
|
||||
|
||||
DbSet<T> originalSet();
|
||||
|
||||
//stream functions
|
||||
default Iterator<T> iterator() {
|
||||
return null;
|
||||
@@ -131,6 +135,9 @@ public interface Queryable<T extends Serializable> {
|
||||
// default Object[] toArray() {
|
||||
// return new Object[0];
|
||||
// }
|
||||
default List<T> toList() throws SQLException {
|
||||
return originalSet().getContext().getDatabase().queryExpression(originalSet().getContext(), originalSet().getClazz(), getExpression());
|
||||
}
|
||||
//
|
||||
// default <A> A[] toArray(IntFunction<A[]> intFunction) {
|
||||
// return null;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package jef;
|
||||
|
||||
import jef.expressions.Expression;
|
||||
import jef.serializable.SerializableObject;
|
||||
import jef.serializable.SerializablePredicate;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Iterator;
|
||||
import java.util.Spliterator;
|
||||
|
||||
public class QueryableProxy<T extends Serializable> implements Queryable<T> {
|
||||
public class QueryableProxy<T extends SerializableObject> implements Queryable<T> {
|
||||
private final Queryable<T> delegate;
|
||||
|
||||
public QueryableProxy(Queryable<T> delegate) {
|
||||
@@ -24,6 +24,11 @@ public class QueryableProxy<T extends Serializable> implements Queryable<T> {
|
||||
return delegate.getExpression();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbSet<T> originalSet() {
|
||||
return delegate.originalSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return delegate.iterator();
|
||||
|
||||
26
core/src/main/java/jef/asm/access/ClassAnalyzer.java
Normal file
26
core/src/main/java/jef/asm/access/ClassAnalyzer.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package jef.asm.access;
|
||||
|
||||
import jef.asm.access.model.ClassDescription;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ClassAnalyzer {
|
||||
public ClassDescription analyze(Class clazz) {
|
||||
return analyzeClass(clazz);
|
||||
}
|
||||
|
||||
private ClassDescription analyzeClass(Class clazz) {
|
||||
try (var is = clazz.getClassLoader().getResourceAsStream(clazz.getName().replace(".", "/") + ".class")) {
|
||||
var result = new ClassDescription[1];
|
||||
var byteCode = is.readAllBytes();
|
||||
var reader = new ClassReader(byteCode);
|
||||
var visitor = new ClassAnalyzerVisitor(Opcodes.ASM9, description -> result[0] = description);
|
||||
reader.accept(visitor, 0);
|
||||
return result[0];
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
69
core/src/main/java/jef/asm/access/ClassAnalyzerVisitor.java
Normal file
69
core/src/main/java/jef/asm/access/ClassAnalyzerVisitor.java
Normal file
@@ -0,0 +1,69 @@
|
||||
package jef.asm.access;
|
||||
|
||||
import jef.asm.access.model.ClassDescription;
|
||||
import jef.asm.access.model.ConstructorDescription;
|
||||
import jef.asm.access.model.FieldDescription;
|
||||
import jef.asm.access.model.GetterDescription;
|
||||
import jef.asm.access.model.SetterDescription;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.FieldVisitor;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
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.function.Consumer;
|
||||
|
||||
class ClassAnalyzerVisitor extends ClassVisitor {
|
||||
private final Consumer<ClassDescription> callback;
|
||||
|
||||
private String className;
|
||||
private String superClassName;
|
||||
private final List<FieldDescription> declaredFields = new ArrayList<>();
|
||||
private final List<ConstructorDescription> declaredConstructor = new ArrayList<>();
|
||||
private final List<GetterDescription> declaredGetters = new ArrayList<>();
|
||||
private final List<SetterDescription> declaredSetters = new ArrayList<>();
|
||||
|
||||
protected ClassAnalyzerVisitor(int api, Consumer<ClassDescription> callback) {
|
||||
super(api);
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
protected ClassAnalyzerVisitor(int api, ClassVisitor classVisitor, Consumer<ClassDescription> callback) {
|
||||
super(api, classVisitor);
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||
className = name.replace("/", ".");
|
||||
superClassName = superName.replace("/", ".");
|
||||
super.visit(version, access, name, signature, superName, interfaces);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
|
||||
declaredFields.add(new FieldDescription(className, name));
|
||||
return super.visitField(access, name, descriptor, signature, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
|
||||
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 -> {
|
||||
declaredConstructor.addAll(entityAccess.getDeclaredConstructors());
|
||||
declaredGetters.addAll(entityAccess.getDeclaredGetters());
|
||||
declaredSetters.addAll(entityAccess.getDeclaredSetters());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
super.visitEnd();
|
||||
callback.accept(new ClassDescription(className, superClassName, declaredFields, declaredConstructor, declaredGetters, declaredSetters));
|
||||
}
|
||||
}
|
||||
308
core/src/main/java/jef/asm/access/MethodAnalyzerVisitor.java
Normal file
308
core/src/main/java/jef/asm/access/MethodAnalyzerVisitor.java
Normal file
@@ -0,0 +1,308 @@
|
||||
package jef.asm.access;
|
||||
|
||||
import jef.asm.access.model.ClassDescription;
|
||||
import jef.asm.access.model.ConstructorDescription;
|
||||
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.Getter;
|
||||
import org.objectweb.asm.Handle;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
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 methodName;
|
||||
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 String fieldDeclaringClassName = null;
|
||||
private String fieldName = null;
|
||||
|
||||
private ConstructorSteps constructorStep = ConstructorSteps.INIT;
|
||||
private GetterSteps getterStep = GetterSteps.INIT;
|
||||
private SetterSteps setterStep = SetterSteps.INIT;
|
||||
|
||||
enum ConstructorSteps {
|
||||
INIT,
|
||||
CODE,
|
||||
PRE_SUPER_INIT_ALOAD_0,
|
||||
PRE_SUPER_INIT_VAR_LOAD,
|
||||
SUPER_INIT,
|
||||
ALOAD_0,
|
||||
VAR_LOAD,
|
||||
PUT_FIELD,
|
||||
RETURN,
|
||||
INVALID
|
||||
}
|
||||
|
||||
enum GetterSteps {
|
||||
INIT,
|
||||
CODE,
|
||||
ALOAD_0,
|
||||
GET_FIELD,
|
||||
RETURN,
|
||||
INVALID
|
||||
}
|
||||
|
||||
enum SetterSteps {
|
||||
INIT,
|
||||
CODE,
|
||||
ALOAD_0,
|
||||
VAR_LOAD,
|
||||
PUT_FIELD,
|
||||
RETURN,
|
||||
INVALID
|
||||
}
|
||||
|
||||
protected MethodAnalyzerVisitor(int api, String methodClassName, String methodName, String returnClassName, String[] parameterClassNames, Consumer<ClassDescription> callback) {
|
||||
super(api);
|
||||
this.methodClassName = methodClassName;
|
||||
this.methodName = methodName;
|
||||
this.returnClassName = returnClassName;
|
||||
this.parameterClassNames = parameterClassNames;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitCode() {
|
||||
constructorStep = constructorStep == ConstructorSteps.INIT ? ConstructorSteps.CODE : ConstructorSteps.INVALID;
|
||||
getterStep = getterStep == GetterSteps.INIT ? GetterSteps.CODE : GetterSteps.INVALID;
|
||||
setterStep = setterStep == SetterSteps.INIT ? SetterSteps.CODE : SetterSteps.INVALID;
|
||||
super.visitCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitFrame(int type, int numLocal, Object[] local, int numStack, Object[] stack) {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
getterStep = GetterSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
super.visitFrame(type, numLocal, local, numStack, stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInsn(int opcode) {
|
||||
if (Set.of(Opcodes.IRETURN, Opcodes.LRETURN, Opcodes.FRETURN, Opcodes.DRETURN, Opcodes.ARETURN).contains(opcode)) {
|
||||
getterStep = getterStep == GetterSteps.GET_FIELD ? GetterSteps.RETURN : GetterSteps.INVALID;
|
||||
} else {
|
||||
getterStep = GetterSteps.INVALID;
|
||||
}
|
||||
if (opcode == Opcodes.RETURN) {
|
||||
constructorStep = constructorStep == ConstructorSteps.PUT_FIELD ? ConstructorSteps.RETURN : ConstructorSteps.INVALID;
|
||||
setterStep = setterStep == SetterSteps.PUT_FIELD ? SetterSteps.RETURN : SetterSteps.INVALID;
|
||||
} else {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
}
|
||||
super.visitInsn(opcode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitIntInsn(int opcode, int operand) {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
getterStep = GetterSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
super.visitIntInsn(opcode, operand);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
if (opcode == Opcodes.ALOAD && varIndex == 0) {
|
||||
if (constructorStep == ConstructorSteps.CODE) {
|
||||
constructorStep = ConstructorSteps.PRE_SUPER_INIT_ALOAD_0;
|
||||
} else if (constructorStep == ConstructorSteps.SUPER_INIT || constructorStep == ConstructorSteps.PUT_FIELD) {
|
||||
constructorStep = ConstructorSteps.ALOAD_0;
|
||||
} else {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
}
|
||||
} else if (Set.of(Opcodes.ILOAD, Opcodes.LLOAD, Opcodes.FLOAD, Opcodes.DLOAD, Opcodes.ALOAD).contains(opcode)) {
|
||||
if (constructorStep == ConstructorSteps.PRE_SUPER_INIT_ALOAD_0) {
|
||||
constructorStep = ConstructorSteps.PRE_SUPER_INIT_VAR_LOAD;
|
||||
} else if (constructorStep == ConstructorSteps.PRE_SUPER_INIT_VAR_LOAD) {
|
||||
constructorStep = ConstructorSteps.PRE_SUPER_INIT_VAR_LOAD;
|
||||
} else if (constructorStep == ConstructorSteps.ALOAD_0) {
|
||||
constructorStep = ConstructorSteps.VAR_LOAD;
|
||||
} else {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
}
|
||||
} else {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
}
|
||||
if (opcode == Opcodes.ALOAD && varIndex == 0) {
|
||||
getterStep = getterStep == GetterSteps.CODE ? GetterSteps.ALOAD_0 : GetterSteps.INVALID;
|
||||
} else {
|
||||
getterStep = GetterSteps.INVALID;
|
||||
}
|
||||
if (opcode == Opcodes.ALOAD && varIndex == 0) {
|
||||
setterStep = setterStep == SetterSteps.CODE ? SetterSteps.ALOAD_0 : SetterSteps.INVALID;
|
||||
} else if (Set.of(Opcodes.ILOAD, Opcodes.LLOAD, Opcodes.FLOAD, Opcodes.DLOAD, Opcodes.ALOAD).contains(opcode)) {
|
||||
setterStep = setterStep == SetterSteps.ALOAD_0 ? SetterSteps.VAR_LOAD : SetterSteps.INVALID;
|
||||
} else {
|
||||
setterStep = SetterSteps.INVALID;
|
||||
}
|
||||
super.visitVarInsn(opcode, varIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTypeInsn(int opcode, String type) {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
getterStep = GetterSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
super.visitTypeInsn(opcode, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
|
||||
var lastLoadedVarIndex = -1;
|
||||
if (opcode == Opcodes.PUTFIELD) {
|
||||
lastLoadedVarIndex = varIndexStack.pop();
|
||||
varIndexStack.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();
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
getterStep = getterStep == GetterSteps.ALOAD_0 ? GetterSteps.GET_FIELD : GetterSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
} else {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
getterStep = GetterSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
}
|
||||
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
|
||||
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>();
|
||||
for (var i = 0; i < Type.getArgumentTypes(descriptor).length; i++) {
|
||||
argIndexes.add(0, varIndexStack.pop());
|
||||
}
|
||||
varIndexStack.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));
|
||||
}
|
||||
constructorStep = ConstructorSteps.SUPER_INIT;
|
||||
} else {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
}
|
||||
getterStep = GetterSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
getterStep = GetterSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitJumpInsn(int opcode, Label label) {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
getterStep = GetterSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
super.visitJumpInsn(opcode, label);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLabel(Label label) {
|
||||
// constructorStep = ConstructorSteps.INVALID;
|
||||
// getterStep = GetterSteps.INVALID;
|
||||
// setterStep = SetterSteps.INVALID;
|
||||
super.visitLabel(label);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLdcInsn(Object value) {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
getterStep = GetterSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
super.visitLdcInsn(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitIincInsn(int varIndex, int increment) {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
getterStep = GetterSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
super.visitIincInsn(varIndex, increment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
getterStep = GetterSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
super.visitTableSwitchInsn(min, max, dflt, labels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
getterStep = GetterSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
super.visitLookupSwitchInsn(dflt, keys, labels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMultiANewArrayInsn(String descriptor, int numDimensions) {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
getterStep = GetterSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
super.visitMultiANewArrayInsn(descriptor, numDimensions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
getterStep = GetterSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
super.visitTryCatchBlock(start, end, handler, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) {
|
||||
super.visitLocalVariable(name, descriptor, signature, start, end, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMaxs(int maxStack, int maxLocals) {
|
||||
super.visitMaxs(maxStack, maxLocals);
|
||||
}
|
||||
|
||||
@Override
|
||||
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();
|
||||
var access = new ClassDescription(null, null, null, constructors, getters, setters);
|
||||
callback.accept(access);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package jef.asm.access.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public class ClassDescription {
|
||||
private final String className;
|
||||
private final String superClassName;
|
||||
private final List<FieldDescription> declaredFields;
|
||||
private final List<ConstructorDescription> declaredConstructors;
|
||||
private final List<GetterDescription> declaredGetters;
|
||||
private final List<SetterDescription> declaredSetters;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ClassDescription{" +
|
||||
"className='" + className + '\'' +
|
||||
", superClassName='" + superClassName + '\'' +
|
||||
", declaredFields=" + declaredFields +
|
||||
", declaredConstructors=" + declaredConstructors +
|
||||
", declaredGetters=" + declaredGetters +
|
||||
", declaredSetters=" + declaredSetters +
|
||||
'}';
|
||||
}
|
||||
|
||||
public String toStringFriendly() {
|
||||
return "ClassAccessDescription{\n"
|
||||
+ " className: " + className + '\n'
|
||||
+ " superClassName: " + superClassName + '\n'
|
||||
+ " declaredFields: " + declaredFields + '\n'
|
||||
+ " declaredConstructors:\n" + declaredConstructors.stream().map(e -> " " + e.toString() + '\n').collect(Collectors.joining())
|
||||
+ " declaredGetters:\n" + declaredGetters.stream().map(e -> " " + e.toString() + '\n').collect(Collectors.joining())
|
||||
+ " declaredSetters:\n" + declaredSetters.stream().map(e -> " " + e.toString()+ '\n').collect(Collectors.joining())
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package jef.asm.access.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@ToString
|
||||
public class ConstructorDescription {
|
||||
private final List<ConstructorParameterDescription> constructorParameterDescriptions;
|
||||
private final List<SuperConstructorParameterDescription> superConstructorParameterDescriptions;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package jef.asm.access.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@ToString
|
||||
public class ConstructorParameterDescription {
|
||||
private final int parameterIndex;
|
||||
private final String declaringClassName;
|
||||
private final String fieldName;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package jef.asm.access.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@ToString
|
||||
public class FieldDescription {
|
||||
private final String fieldDeclaringClassName;
|
||||
private final String fieldName;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package jef.asm.access.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@ToString
|
||||
public class GetterDescription {
|
||||
private final String methodDeclaringClassName;
|
||||
private final String methodName;
|
||||
private final String methodReturnClassName;
|
||||
private final String[] methodParameterClassNames;
|
||||
private final String fieldDeclaringClassName;
|
||||
private final String fieldName;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package jef.asm.access.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@ToString
|
||||
public class SetterDescription {
|
||||
private final String methodDeclaringClassName;
|
||||
private final String methodName;
|
||||
private final String methodReturnClassName;
|
||||
private final String[] methodParameterClassNames;
|
||||
private final String fieldDeclaringClassName;
|
||||
private final String fieldName;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package jef.asm.access.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@ToString
|
||||
public class SuperConstructorParameterDescription {
|
||||
private final int constructorParameterIndex;
|
||||
private final int superConstructorParameterIndex;
|
||||
}
|
||||
@@ -40,7 +40,7 @@ public abstract class DbContext {
|
||||
f.setAccessible(true);
|
||||
Clazz anno = f.getAnnotation(Clazz.class);
|
||||
try {
|
||||
f.set(this, new DbSet(anno.value(), f.getName()));//TODO use table name from modelbuilder
|
||||
f.set(this, new DbSet(this, anno.value(), f.getName()));//TODO use table name from modelbuilder
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package jef.operations;
|
||||
|
||||
import jef.DbSet;
|
||||
import jef.Queryable;
|
||||
import jef.asm.AsmParseException;
|
||||
import jef.asm.OptimizedAsmParser;
|
||||
@@ -8,13 +9,13 @@ import jef.expressions.SelectExpression;
|
||||
import jef.expressions.WhereExpression;
|
||||
import jef.expressions.modifier.TableAliasInjector;
|
||||
import jef.expressions.selectable.DatabaseSelectAllExpression;
|
||||
import jef.serializable.SerializableObject;
|
||||
import jef.serializable.SerializablePredicate;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class FilterOp<T extends Serializable> implements Queryable<T>, Operation<T> {
|
||||
public class FilterOp<T extends SerializableObject> implements Queryable<T>, Operation<T> {
|
||||
|
||||
private final Queryable<T> queryable;
|
||||
private final Predicate<? super T> predicate;
|
||||
@@ -50,4 +51,9 @@ public class FilterOp<T extends Serializable> implements Queryable<T>, Operation
|
||||
public String toString() {
|
||||
return getExpression().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbSet<T> originalSet() {
|
||||
return queryable.originalSet();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
package jef.operations;
|
||||
|
||||
import jef.DbSet;
|
||||
import jef.Queryable;
|
||||
import jef.expressions.Expression;
|
||||
import jef.expressions.LimitExpression;
|
||||
import jef.expressions.SelectExpression;
|
||||
import jef.expressions.selectable.DatabaseSelectAllExpression;
|
||||
import jef.serializable.SerializableObject;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
public class LimitOp<T extends Serializable> implements Queryable<T> {
|
||||
public class LimitOp<T extends SerializableObject> implements Queryable<T> {
|
||||
private final Queryable<T> queryable;
|
||||
private Long start;
|
||||
private Long count;
|
||||
@@ -35,6 +36,11 @@ public class LimitOp<T extends Serializable> implements Queryable<T> {
|
||||
return getExpression().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbSet<T> originalSet() {
|
||||
return queryable.originalSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Queryable<T> limit(long l) {
|
||||
count = l;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package jef.operations;
|
||||
|
||||
import jef.Queryable;
|
||||
import jef.serializable.SerializableObject;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public interface Operation<T extends Serializable> extends Queryable<T> {
|
||||
public interface Operation<T extends SerializableObject> extends Queryable<T> {//TODO is this class required
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package jef.operations;
|
||||
|
||||
import jef.DbSet;
|
||||
import jef.Queryable;
|
||||
import jef.asm.AsmParseException;
|
||||
import jef.asm.AsmParser;
|
||||
@@ -13,14 +14,14 @@ import jef.expressions.modifier.TableAliasInjector;
|
||||
import jef.expressions.modifier.TernaryRewriter;
|
||||
import jef.expressions.selectable.DatabaseSelectAllExpression;
|
||||
import jef.serializable.SerializableFunction;
|
||||
import jef.serializable.SerializableObject;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class SortOp<T extends Serializable> implements Queryable<T> {
|
||||
public class SortOp<T extends SerializableObject> implements Queryable<T> {
|
||||
private final Queryable<T> queryable;
|
||||
private final List<OrderExpression.Sort> sorts = new ArrayList<>();
|
||||
|
||||
@@ -45,6 +46,11 @@ public class SortOp<T extends Serializable> implements Queryable<T> {
|
||||
return getExpression().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbSet<T> originalSet() {
|
||||
return queryable.originalSet();
|
||||
}
|
||||
|
||||
public SortOp<T> thenSorted(SerializableFunction<? super T, ?> fieldSelector) {
|
||||
var f = parseFunction(fieldSelector);
|
||||
this.sorts.add(new OrderExpression.Sort(f, OrderExpression.SortDirection.ASCENDING));
|
||||
|
||||
@@ -1,25 +1,96 @@
|
||||
package jef.platform.base;
|
||||
|
||||
import jef.MigrationException;
|
||||
import jef.expressions.Expression;
|
||||
import jef.model.DbContext;
|
||||
import jef.model.DbFieldBuilder;
|
||||
import jef.model.ModelBuilder;
|
||||
import jef.serializable.SerializableObject;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public abstract class Database {
|
||||
// private final DatabaseConfiguration config;
|
||||
protected final Connection connection;
|
||||
protected final DatabaseOptions options;
|
||||
|
||||
public abstract void migrate() throws MigrationException;
|
||||
|
||||
// public ResultSet executeRaw(String s) throws SQLException {
|
||||
// try (var stmt = connection.prepareStatement(s)) {
|
||||
// try (var res = stmt.executeQuery()) {
|
||||
// return res;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
public abstract <T extends SerializableObject> List<T> queryExpression(DbContext context, Class<T> clazz, Expression expression) throws SQLException;
|
||||
|
||||
public <T extends SerializableObject> List<T> queryRaw(DbContext context, Class<T> clazz, String query) throws SQLException {
|
||||
var modelBuilder = ModelBuilder.from(context.getClass());
|
||||
var entity = modelBuilder.entity(clazz);
|
||||
var fields = entity.fields();
|
||||
try (var stmt = connection.prepareStatement(query)) {
|
||||
return queryRaw(stmt, clazz, fields);
|
||||
}
|
||||
}
|
||||
|
||||
protected <T extends SerializableObject> List<T> queryRaw(PreparedStatement stmt, Class<T> clazz, List<DbFieldBuilder<?>> fields) throws SQLException {
|
||||
try (var result = stmt.executeQuery()) {
|
||||
var ret = new ArrayList<T>();
|
||||
while (result.next()) {
|
||||
ret.add(readRow(result, clazz, fields));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
protected <T extends SerializableObject> T readRow(ResultSet res, Class<T> clazz, List<DbFieldBuilder<?>> fields) throws SQLException {
|
||||
//TODO replace with by runtime generated class
|
||||
try {
|
||||
var model = (T) clazz.getConstructor().newInstance();
|
||||
for (DbFieldBuilder<?> field : fields) {
|
||||
if (!field.getField().isDatabaseField()) {
|
||||
continue;
|
||||
}
|
||||
Field f = field.getField().getField();
|
||||
if (f == null) {
|
||||
System.out.println("not class member for " + field.getField().getName());
|
||||
continue;
|
||||
}
|
||||
f.setAccessible(true);
|
||||
if (field.getField().getType() == String.class) {
|
||||
f.set(model, res.getString(f.getName()));
|
||||
} else if (field.getField().getType() == byte.class || field.getField().getType() == Byte.class) {
|
||||
f.set(model, res.getByte(f.getName()));
|
||||
} else if (field.getField().getType() == char.class || field.getField().getType() == Character.class) {
|
||||
// f.set(model, res.getString(f.getName()));//TODO handle char
|
||||
throw new UnsupportedOperationException("char");
|
||||
} else if (field.getField().getType() == short.class || field.getField().getType() == Short.class) {
|
||||
f.set(model, res.getShort(f.getName()));
|
||||
} else if (field.getField().getType() == int.class || field.getField().getType() == Integer.class) {
|
||||
f.set(model, res.getInt(f.getName()));
|
||||
} else if (field.getField().getType() == long.class || field.getField().getType() == Long.class) {
|
||||
f.set(model, res.getLong(f.getName()));
|
||||
} else if (field.getField().getType() == float.class || field.getField().getType() == Float.class) {
|
||||
f.set(model, res.getFloat(f.getName()));
|
||||
} else if (field.getField().getType() == double.class || field.getField().getType() == Double.class) {
|
||||
f.set(model, res.getDouble(f.getName()));
|
||||
} else {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
return model;
|
||||
} catch (IllegalAccessException | InvocationTargetException | InstantiationException | NoSuchMethodException e) {//TODO better handling
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract <T extends SerializableObject> void add(DbContext context, Class<T> clazz, T model) throws SQLException;
|
||||
|
||||
public abstract <T extends SerializableObject> void update(DbContext context, Class<T> clazz, T model) throws SQLException;
|
||||
|
||||
public abstract <T extends SerializableObject> void delete(DbContext context, Class<T> clazz, T model) throws SQLException;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
package jef.platform.dummy;
|
||||
|
||||
import jef.MigrationException;
|
||||
import jef.expressions.Expression;
|
||||
import jef.model.DbContext;
|
||||
import jef.platform.base.Database;
|
||||
import jef.serializable.SerializableObject;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
public class DummyDatabase extends Database {
|
||||
public DummyDatabase() {
|
||||
@@ -11,4 +17,28 @@ public class DummyDatabase extends Database {
|
||||
@Override
|
||||
public void migrate() throws MigrationException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends SerializableObject> List<T> queryExpression(DbContext context, Class<T> clazz, Expression expression) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends SerializableObject> List<T> queryRaw(DbContext context, Class<T> clazz, String query) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends SerializableObject> void add(DbContext context, Class<T> clazz, T entity) throws SQLException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends SerializableObject> void update(DbContext context, Class<T> clazz, T model) throws SQLException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends SerializableObject> void delete(DbContext context, Class<T> clazz, T model) throws SQLException {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,11 @@ public abstract class Util {
|
||||
R apply(T t, U u) throws Throwable;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ThrowableBiConsumer<T, U> {
|
||||
void accept(T t, U u) throws Throwable;
|
||||
}
|
||||
|
||||
public interface Equality<T> {
|
||||
boolean check(T t1, T t2);
|
||||
}
|
||||
|
||||
58
core/src/test/java/jef/asm/access/ClassAnalyzerTest.java
Normal file
58
core/src/test/java/jef/asm/access/ClassAnalyzerTest.java
Normal file
@@ -0,0 +1,58 @@
|
||||
package jef.asm.access;
|
||||
|
||||
import jef.model.ModelBuilder;
|
||||
import jef.model.annotations.ForeignKey;
|
||||
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.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
class ClassAnalyzerTest {
|
||||
|
||||
@Test
|
||||
void analyze() {
|
||||
var analyzer = new ClassAnalyzer();
|
||||
var result = analyzer.analyze(Employee.class);
|
||||
System.out.println(result.toStringFriendly());
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ public class FilterOpTest {
|
||||
@Test
|
||||
public void testSimple() {
|
||||
String act;
|
||||
act = new DbSet<>(TestClass.class, "table1")
|
||||
act = new DbSet<>(null, TestClass.class, "table1")
|
||||
.filter(e -> true)
|
||||
.toString();
|
||||
Assertions.assertEquals("SELECT * FROM (SELECT * FROM `table1`) a WHERE 1", act);
|
||||
@@ -23,7 +23,7 @@ public class FilterOpTest {
|
||||
public void testMultipleFilter() {
|
||||
String act;
|
||||
var s = List.of(1, 3);
|
||||
act = new DbSet<>(TestClass.class, "table1")
|
||||
act = new DbSet<>(null, TestClass.class, "table1")
|
||||
.filter(e -> s.contains(e.i))
|
||||
.filter(e -> e.i == 1337)
|
||||
.toString();
|
||||
|
||||
@@ -8,25 +8,25 @@ public class LimitOpTest {
|
||||
@Test
|
||||
public void test() {
|
||||
String act;
|
||||
act = new DbSet<>(FilterOpTest.TestClass.class, "table1")
|
||||
act = new DbSet<>(null, FilterOpTest.TestClass.class, "table1")
|
||||
.limit(10).skip(5)
|
||||
.toString();
|
||||
Assertions.assertEquals("SELECT * FROM (SELECT * FROM `table1`) a OFFSET 5 LIMIT 10", act);
|
||||
|
||||
|
||||
act = new DbSet<>(FilterOpTest.TestClass.class, "table1")
|
||||
act = new DbSet<>(null, FilterOpTest.TestClass.class, "table1")
|
||||
.skip(5).limit(10)
|
||||
.toString();
|
||||
Assertions.assertEquals("SELECT * FROM (SELECT * FROM `table1`) a OFFSET 5 LIMIT 10", act);
|
||||
|
||||
|
||||
act = new DbSet<>(FilterOpTest.TestClass.class, "table1")
|
||||
act = new DbSet<>(null, FilterOpTest.TestClass.class, "table1")
|
||||
.skip(5)
|
||||
.toString();
|
||||
Assertions.assertEquals("SELECT * FROM (SELECT * FROM `table1`) a OFFSET 5", act);
|
||||
|
||||
|
||||
act = new DbSet<>(FilterOpTest.TestClass.class, "table1")
|
||||
act = new DbSet<>(null, FilterOpTest.TestClass.class, "table1")
|
||||
.limit(10)
|
||||
.toString();
|
||||
Assertions.assertEquals("SELECT * FROM (SELECT * FROM `table1`) a LIMIT 10", act);
|
||||
|
||||
@@ -8,27 +8,27 @@ class SortOpTest {
|
||||
@Test
|
||||
public void test() {
|
||||
String act;
|
||||
act = new DbSet<>(FilterOpTest.TestClass.class, "table1")
|
||||
act = new DbSet<>(null, FilterOpTest.TestClass.class, "table1")
|
||||
.sorted(e -> e.getI()).thenSorted(e -> e.getD()).thenSorted(e -> e.getF()).thenSorted(e -> e.getL()).thenSorted(e -> e.getO())
|
||||
.toString();
|
||||
Assertions.assertEquals("SELECT * FROM (SELECT * FROM `table1`) a ORDER BY `a`.`i` ASC, `a`.`d` ASC, `a`.`f` ASC, `a`.`l` ASC, `a`.`o` ASC", act);
|
||||
|
||||
|
||||
act = new DbSet<>(FilterOpTest.TestClass.class, "table1")
|
||||
act = new DbSet<>(null, FilterOpTest.TestClass.class, "table1")
|
||||
.sortedDescending(e -> e.getI()).thenSortedDescending(e -> e.getD()).thenSortedDescending(e -> e.getF())
|
||||
/**/.thenSortedDescending(e -> e.getL()).thenSortedDescending(e -> e.getO())
|
||||
.toString();
|
||||
Assertions.assertEquals("SELECT * FROM (SELECT * FROM `table1`) a ORDER BY `a`.`i` DESC, `a`.`d` DESC, `a`.`f` DESC, `a`.`l` DESC, `a`.`o` DESC", act);
|
||||
|
||||
//alternating patterns
|
||||
act = new DbSet<>(FilterOpTest.TestClass.class, "table1")
|
||||
act = new DbSet<>(null, FilterOpTest.TestClass.class, "table1")
|
||||
.sortedDescending(e -> e.getI()).thenSorted(e -> e.getD()).thenSortedDescending(e -> e.getF()).thenSorted(e -> e.getL())
|
||||
/**/.thenSortedDescending(e -> e.getO())
|
||||
.toString();
|
||||
Assertions.assertEquals("SELECT * FROM (SELECT * FROM `table1`) a ORDER BY `a`.`i` DESC, `a`.`d` ASC, `a`.`f` DESC, `a`.`l` ASC, `a`.`o` DESC", act);
|
||||
|
||||
|
||||
act = new DbSet<>(FilterOpTest.TestClass.class, "table1")
|
||||
act = new DbSet<>(null, FilterOpTest.TestClass.class, "table1")
|
||||
.sorted(e -> e.getI()).thenSortedDescending(e -> e.getD()).thenSorted(e -> e.getF()).thenSortedDescending(e -> e.getL())
|
||||
/**/.thenSorted(e -> e.getO())
|
||||
.toString();
|
||||
|
||||
@@ -12,11 +12,11 @@ class DebugExpressionVisitorTest {
|
||||
@Test
|
||||
public void test() {
|
||||
var s = List.of(1, 3);
|
||||
Queryable<FilterOpTest.TestClass> q = new DbSet<>(FilterOpTest.TestClass.class, "table1")
|
||||
Queryable<FilterOpTest.TestClass> q = new DbSet<>(null, FilterOpTest.TestClass.class, "table1")
|
||||
.filter(e -> s.contains(e.i) && e.i == 1337 && e.i == 420);
|
||||
new DebugExpressionVisitor().visit(q.getExpression());
|
||||
|
||||
Queryable<FilterOpTest.TestClass> q2 = new DbSet<>(FilterOpTest.TestClass.class, "table1")
|
||||
Queryable<FilterOpTest.TestClass> q2 = new DbSet<>(null, FilterOpTest.TestClass.class, "table1")
|
||||
.filter(e -> s.contains(e.i) || e.i == 1337);
|
||||
new DebugExpressionVisitor().visit(q2.getExpression());
|
||||
}
|
||||
|
||||
@@ -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