wip
This commit is contained in:
@@ -4,19 +4,24 @@ import jef.expressions.Expression;
|
|||||||
import jef.expressions.SelectExpression;
|
import jef.expressions.SelectExpression;
|
||||||
import jef.expressions.TableExpression;
|
import jef.expressions.TableExpression;
|
||||||
import jef.expressions.selectable.DatabaseSelectAllExpression;
|
import jef.expressions.selectable.DatabaseSelectAllExpression;
|
||||||
|
import jef.model.DbContext;
|
||||||
import jef.serializable.SerializableObject;
|
import jef.serializable.SerializableObject;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Spliterator;
|
import java.util.Spliterator;
|
||||||
|
|
||||||
public class DbSet<T extends SerializableObject> implements Queryable<T> {
|
public class DbSet<T extends SerializableObject> implements Queryable<T> {
|
||||||
|
@Getter
|
||||||
|
private final DbContext context;
|
||||||
@Getter
|
@Getter
|
||||||
private final Class<T> clazz;
|
private final Class<T> clazz;
|
||||||
private final String table;
|
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.clazz = clazz;
|
||||||
this.table = table;
|
this.table = table;
|
||||||
}
|
}
|
||||||
@@ -36,6 +41,23 @@ public class DbSet<T extends SerializableObject> implements Queryable<T> {
|
|||||||
return "SELECT * FROM `" + table + "`";
|
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
|
@Override
|
||||||
public Iterator<T> iterator() {
|
public Iterator<T> iterator() {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -6,13 +6,15 @@ import jef.operations.FilterOp;
|
|||||||
import jef.operations.LimitOp;
|
import jef.operations.LimitOp;
|
||||||
import jef.operations.SortOp;
|
import jef.operations.SortOp;
|
||||||
import jef.serializable.SerializableFunction;
|
import jef.serializable.SerializableFunction;
|
||||||
|
import jef.serializable.SerializableObject;
|
||||||
import jef.serializable.SerializablePredicate;
|
import jef.serializable.SerializablePredicate;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.sql.SQLException;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Spliterator;
|
import java.util.Spliterator;
|
||||||
|
|
||||||
public interface Queryable<T extends Serializable> {
|
public interface Queryable<T extends SerializableObject> {
|
||||||
|
|
||||||
//TODO documentation
|
//TODO documentation
|
||||||
//TODO table alias thing still not ready
|
//TODO table alias thing still not ready
|
||||||
@@ -22,6 +24,8 @@ public interface Queryable<T extends Serializable> {
|
|||||||
|
|
||||||
String toString();
|
String toString();
|
||||||
|
|
||||||
|
DbSet<T> originalSet();
|
||||||
|
|
||||||
//stream functions
|
//stream functions
|
||||||
default Iterator<T> iterator() {
|
default Iterator<T> iterator() {
|
||||||
return null;
|
return null;
|
||||||
@@ -131,6 +135,9 @@ public interface Queryable<T extends Serializable> {
|
|||||||
// default Object[] toArray() {
|
// default Object[] toArray() {
|
||||||
// return new Object[0];
|
// 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) {
|
// default <A> A[] toArray(IntFunction<A[]> intFunction) {
|
||||||
// return null;
|
// return null;
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
package jef;
|
package jef;
|
||||||
|
|
||||||
import jef.expressions.Expression;
|
import jef.expressions.Expression;
|
||||||
|
import jef.serializable.SerializableObject;
|
||||||
import jef.serializable.SerializablePredicate;
|
import jef.serializable.SerializablePredicate;
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Spliterator;
|
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;
|
private final Queryable<T> delegate;
|
||||||
|
|
||||||
public QueryableProxy(Queryable<T> delegate) {
|
public QueryableProxy(Queryable<T> delegate) {
|
||||||
@@ -24,6 +24,11 @@ public class QueryableProxy<T extends Serializable> implements Queryable<T> {
|
|||||||
return delegate.getExpression();
|
return delegate.getExpression();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DbSet<T> originalSet() {
|
||||||
|
return delegate.originalSet();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<T> iterator() {
|
public Iterator<T> iterator() {
|
||||||
return delegate.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);
|
f.setAccessible(true);
|
||||||
Clazz anno = f.getAnnotation(Clazz.class);
|
Clazz anno = f.getAnnotation(Clazz.class);
|
||||||
try {
|
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) {
|
} catch (IllegalAccessException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package jef.operations;
|
package jef.operations;
|
||||||
|
|
||||||
|
import jef.DbSet;
|
||||||
import jef.Queryable;
|
import jef.Queryable;
|
||||||
import jef.asm.AsmParseException;
|
import jef.asm.AsmParseException;
|
||||||
import jef.asm.OptimizedAsmParser;
|
import jef.asm.OptimizedAsmParser;
|
||||||
@@ -8,13 +9,13 @@ import jef.expressions.SelectExpression;
|
|||||||
import jef.expressions.WhereExpression;
|
import jef.expressions.WhereExpression;
|
||||||
import jef.expressions.modifier.TableAliasInjector;
|
import jef.expressions.modifier.TableAliasInjector;
|
||||||
import jef.expressions.selectable.DatabaseSelectAllExpression;
|
import jef.expressions.selectable.DatabaseSelectAllExpression;
|
||||||
|
import jef.serializable.SerializableObject;
|
||||||
import jef.serializable.SerializablePredicate;
|
import jef.serializable.SerializablePredicate;
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Predicate;
|
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 Queryable<T> queryable;
|
||||||
private final Predicate<? super T> predicate;
|
private final Predicate<? super T> predicate;
|
||||||
@@ -50,4 +51,9 @@ public class FilterOp<T extends Serializable> implements Queryable<T>, Operation
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return getExpression().toString();
|
return getExpression().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DbSet<T> originalSet() {
|
||||||
|
return queryable.originalSet();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
package jef.operations;
|
package jef.operations;
|
||||||
|
|
||||||
|
import jef.DbSet;
|
||||||
import jef.Queryable;
|
import jef.Queryable;
|
||||||
import jef.expressions.Expression;
|
import jef.expressions.Expression;
|
||||||
import jef.expressions.LimitExpression;
|
import jef.expressions.LimitExpression;
|
||||||
import jef.expressions.SelectExpression;
|
import jef.expressions.SelectExpression;
|
||||||
import jef.expressions.selectable.DatabaseSelectAllExpression;
|
import jef.expressions.selectable.DatabaseSelectAllExpression;
|
||||||
|
import jef.serializable.SerializableObject;
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.List;
|
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 final Queryable<T> queryable;
|
||||||
private Long start;
|
private Long start;
|
||||||
private Long count;
|
private Long count;
|
||||||
@@ -35,6 +36,11 @@ public class LimitOp<T extends Serializable> implements Queryable<T> {
|
|||||||
return getExpression().toString();
|
return getExpression().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DbSet<T> originalSet() {
|
||||||
|
return queryable.originalSet();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Queryable<T> limit(long l) {
|
public Queryable<T> limit(long l) {
|
||||||
count = l;
|
count = l;
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
package jef.operations;
|
package jef.operations;
|
||||||
|
|
||||||
import jef.Queryable;
|
import jef.Queryable;
|
||||||
|
import jef.serializable.SerializableObject;
|
||||||
|
|
||||||
import java.io.Serializable;
|
public interface Operation<T extends SerializableObject> extends Queryable<T> {//TODO is this class required
|
||||||
|
|
||||||
public interface Operation<T extends Serializable> extends Queryable<T> {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package jef.operations;
|
package jef.operations;
|
||||||
|
|
||||||
|
import jef.DbSet;
|
||||||
import jef.Queryable;
|
import jef.Queryable;
|
||||||
import jef.asm.AsmParseException;
|
import jef.asm.AsmParseException;
|
||||||
import jef.asm.AsmParser;
|
import jef.asm.AsmParser;
|
||||||
@@ -13,14 +14,14 @@ import jef.expressions.modifier.TableAliasInjector;
|
|||||||
import jef.expressions.modifier.TernaryRewriter;
|
import jef.expressions.modifier.TernaryRewriter;
|
||||||
import jef.expressions.selectable.DatabaseSelectAllExpression;
|
import jef.expressions.selectable.DatabaseSelectAllExpression;
|
||||||
import jef.serializable.SerializableFunction;
|
import jef.serializable.SerializableFunction;
|
||||||
|
import jef.serializable.SerializableObject;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@AllArgsConstructor
|
@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 Queryable<T> queryable;
|
||||||
private final List<OrderExpression.Sort> sorts = new ArrayList<>();
|
private final List<OrderExpression.Sort> sorts = new ArrayList<>();
|
||||||
|
|
||||||
@@ -45,6 +46,11 @@ public class SortOp<T extends Serializable> implements Queryable<T> {
|
|||||||
return getExpression().toString();
|
return getExpression().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DbSet<T> originalSet() {
|
||||||
|
return queryable.originalSet();
|
||||||
|
}
|
||||||
|
|
||||||
public SortOp<T> thenSorted(SerializableFunction<? super T, ?> fieldSelector) {
|
public SortOp<T> thenSorted(SerializableFunction<? super T, ?> fieldSelector) {
|
||||||
var f = parseFunction(fieldSelector);
|
var f = parseFunction(fieldSelector);
|
||||||
this.sorts.add(new OrderExpression.Sort(f, OrderExpression.SortDirection.ASCENDING));
|
this.sorts.add(new OrderExpression.Sort(f, OrderExpression.SortDirection.ASCENDING));
|
||||||
|
|||||||
@@ -1,25 +1,96 @@
|
|||||||
package jef.platform.base;
|
package jef.platform.base;
|
||||||
|
|
||||||
import jef.MigrationException;
|
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.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.sql.Connection;
|
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
|
@Getter
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public abstract class Database {
|
public abstract class Database {
|
||||||
// private final DatabaseConfiguration config;
|
|
||||||
protected final Connection connection;
|
protected final Connection connection;
|
||||||
protected final DatabaseOptions options;
|
protected final DatabaseOptions options;
|
||||||
|
|
||||||
public abstract void migrate() throws MigrationException;
|
public abstract void migrate() throws MigrationException;
|
||||||
|
|
||||||
// public ResultSet executeRaw(String s) throws SQLException {
|
public abstract <T extends SerializableObject> List<T> queryExpression(DbContext context, Class<T> clazz, Expression expression) throws SQLException;
|
||||||
// try (var stmt = connection.prepareStatement(s)) {
|
|
||||||
// try (var res = stmt.executeQuery()) {
|
public <T extends SerializableObject> List<T> queryRaw(DbContext context, Class<T> clazz, String query) throws SQLException {
|
||||||
// return res;
|
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;
|
package jef.platform.dummy;
|
||||||
|
|
||||||
import jef.MigrationException;
|
import jef.MigrationException;
|
||||||
|
import jef.expressions.Expression;
|
||||||
|
import jef.model.DbContext;
|
||||||
import jef.platform.base.Database;
|
import jef.platform.base.Database;
|
||||||
|
import jef.serializable.SerializableObject;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class DummyDatabase extends Database {
|
public class DummyDatabase extends Database {
|
||||||
public DummyDatabase() {
|
public DummyDatabase() {
|
||||||
@@ -11,4 +17,28 @@ public class DummyDatabase extends Database {
|
|||||||
@Override
|
@Override
|
||||||
public void migrate() throws MigrationException {
|
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;
|
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> {
|
public interface Equality<T> {
|
||||||
boolean check(T t1, T t2);
|
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
|
@Test
|
||||||
public void testSimple() {
|
public void testSimple() {
|
||||||
String act;
|
String act;
|
||||||
act = new DbSet<>(TestClass.class, "table1")
|
act = new DbSet<>(null, TestClass.class, "table1")
|
||||||
.filter(e -> true)
|
.filter(e -> true)
|
||||||
.toString();
|
.toString();
|
||||||
Assertions.assertEquals("SELECT * FROM (SELECT * FROM `table1`) a WHERE 1", act);
|
Assertions.assertEquals("SELECT * FROM (SELECT * FROM `table1`) a WHERE 1", act);
|
||||||
@@ -23,7 +23,7 @@ public class FilterOpTest {
|
|||||||
public void testMultipleFilter() {
|
public void testMultipleFilter() {
|
||||||
String act;
|
String act;
|
||||||
var s = List.of(1, 3);
|
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 -> s.contains(e.i))
|
||||||
.filter(e -> e.i == 1337)
|
.filter(e -> e.i == 1337)
|
||||||
.toString();
|
.toString();
|
||||||
|
|||||||
@@ -8,25 +8,25 @@ public class LimitOpTest {
|
|||||||
@Test
|
@Test
|
||||||
public void test() {
|
public void test() {
|
||||||
String act;
|
String act;
|
||||||
act = new DbSet<>(FilterOpTest.TestClass.class, "table1")
|
act = new DbSet<>(null, FilterOpTest.TestClass.class, "table1")
|
||||||
.limit(10).skip(5)
|
.limit(10).skip(5)
|
||||||
.toString();
|
.toString();
|
||||||
Assertions.assertEquals("SELECT * FROM (SELECT * FROM `table1`) a OFFSET 5 LIMIT 10", act);
|
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)
|
.skip(5).limit(10)
|
||||||
.toString();
|
.toString();
|
||||||
Assertions.assertEquals("SELECT * FROM (SELECT * FROM `table1`) a OFFSET 5 LIMIT 10", act);
|
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)
|
.skip(5)
|
||||||
.toString();
|
.toString();
|
||||||
Assertions.assertEquals("SELECT * FROM (SELECT * FROM `table1`) a OFFSET 5", act);
|
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)
|
.limit(10)
|
||||||
.toString();
|
.toString();
|
||||||
Assertions.assertEquals("SELECT * FROM (SELECT * FROM `table1`) a LIMIT 10", act);
|
Assertions.assertEquals("SELECT * FROM (SELECT * FROM `table1`) a LIMIT 10", act);
|
||||||
|
|||||||
@@ -8,27 +8,27 @@ class SortOpTest {
|
|||||||
@Test
|
@Test
|
||||||
public void test() {
|
public void test() {
|
||||||
String act;
|
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())
|
.sorted(e -> e.getI()).thenSorted(e -> e.getD()).thenSorted(e -> e.getF()).thenSorted(e -> e.getL()).thenSorted(e -> e.getO())
|
||||||
.toString();
|
.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);
|
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())
|
.sortedDescending(e -> e.getI()).thenSortedDescending(e -> e.getD()).thenSortedDescending(e -> e.getF())
|
||||||
/**/.thenSortedDescending(e -> e.getL()).thenSortedDescending(e -> e.getO())
|
/**/.thenSortedDescending(e -> e.getL()).thenSortedDescending(e -> e.getO())
|
||||||
.toString();
|
.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);
|
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
|
//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())
|
.sortedDescending(e -> e.getI()).thenSorted(e -> e.getD()).thenSortedDescending(e -> e.getF()).thenSorted(e -> e.getL())
|
||||||
/**/.thenSortedDescending(e -> e.getO())
|
/**/.thenSortedDescending(e -> e.getO())
|
||||||
.toString();
|
.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);
|
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())
|
.sorted(e -> e.getI()).thenSortedDescending(e -> e.getD()).thenSorted(e -> e.getF()).thenSortedDescending(e -> e.getL())
|
||||||
/**/.thenSorted(e -> e.getO())
|
/**/.thenSorted(e -> e.getO())
|
||||||
.toString();
|
.toString();
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ class DebugExpressionVisitorTest {
|
|||||||
@Test
|
@Test
|
||||||
public void test() {
|
public void test() {
|
||||||
var s = List.of(1, 3);
|
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);
|
.filter(e -> s.contains(e.i) && e.i == 1337 && e.i == 420);
|
||||||
new DebugExpressionVisitor().visit(q.getExpression());
|
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);
|
.filter(e -> s.contains(e.i) || e.i == 1337);
|
||||||
new DebugExpressionVisitor().visit(q2.getExpression());
|
new DebugExpressionVisitor().visit(q2.getExpression());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,30 @@
|
|||||||
package jef.platform.mysql;
|
package jef.platform.mysql;
|
||||||
|
|
||||||
import jef.MigrationException;
|
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.Database;
|
||||||
import jef.platform.base.DatabaseOptions;
|
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.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 {
|
public class MysqlDatabase extends Database {
|
||||||
private final MysqlPlatform platform;
|
private final MysqlPlatform platform;
|
||||||
|
|
||||||
public MysqlDatabase(Connection connection, DatabaseOptions options, MysqlPlatform platform) {
|
public MysqlDatabase(Connection connection, DatabaseOptions options, MysqlPlatform platform) {
|
||||||
super(connection, options);
|
super(connection, options);
|
||||||
this.platform = platform;
|
this.platform = platform;
|
||||||
@@ -17,4 +34,300 @@ public class MysqlDatabase extends Database {
|
|||||||
public void migrate() throws MigrationException {
|
public void migrate() throws MigrationException {
|
||||||
platform.getMigrationApplier(connection, options).migrate();
|
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