2 Commits

Author SHA1 Message Date
apucher
74a8f56ab5 wip 2023-01-08 14:46:03 +01:00
wea_ondara
b805eed622 wip 2022-12-22 10:20:44 +01:00
35 changed files with 2789 additions and 36 deletions

View File

@@ -4,19 +4,24 @@ import jef.expressions.Expression;
import jef.expressions.SelectExpression;
import jef.expressions.TableExpression;
import jef.expressions.selectable.DatabaseSelectAllExpression;
import jef.model.DbContext;
import jef.serializable.SerializableObject;
import lombok.Getter;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.List;
import java.util.Spliterator;
public class DbSet<T extends SerializableObject> implements Queryable<T> {
@Getter
private final DbContext context;
@Getter
private final Class<T> clazz;
private final String table;
public DbSet(Class<T> clazz, String table) {
public DbSet(DbContext context, Class<T> clazz, String table) {
this.context = context;
this.clazz = clazz;
this.table = table;
}
@@ -36,6 +41,23 @@ public class DbSet<T extends SerializableObject> implements Queryable<T> {
return "SELECT * FROM `" + table + "`";
}
@Override
public DbSet<T> originalSet() {
return this;
}
public void add(T entity) throws SQLException {
context.getDatabase().add(context, clazz, entity);
}
public void update(T entity) throws SQLException {
context.getDatabase().update(context, clazz, entity);
}
public void delete(T entity) throws SQLException {
context.getDatabase().delete(context, clazz, entity);
}
@Override
public Iterator<T> iterator() {
return null;

View File

@@ -6,13 +6,15 @@ import jef.operations.FilterOp;
import jef.operations.LimitOp;
import jef.operations.SortOp;
import jef.serializable.SerializableFunction;
import jef.serializable.SerializableObject;
import jef.serializable.SerializablePredicate;
import java.io.Serializable;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.List;
import java.util.Spliterator;
public interface Queryable<T extends Serializable> {
public interface Queryable<T extends SerializableObject> {
//TODO documentation
//TODO table alias thing still not ready
@@ -22,6 +24,8 @@ public interface Queryable<T extends Serializable> {
String toString();
DbSet<T> originalSet();
//stream functions
default Iterator<T> iterator() {
return null;
@@ -131,6 +135,9 @@ public interface Queryable<T extends Serializable> {
// default Object[] toArray() {
// return new Object[0];
// }
default List<T> toList() throws SQLException {
return originalSet().getContext().getDatabase().queryExpression(originalSet().getContext(), originalSet().getClazz(), getExpression());
}
//
// default <A> A[] toArray(IntFunction<A[]> intFunction) {
// return null;

View File

@@ -1,13 +1,13 @@
package jef;
import jef.expressions.Expression;
import jef.serializable.SerializableObject;
import jef.serializable.SerializablePredicate;
import java.io.Serializable;
import java.util.Iterator;
import java.util.Spliterator;
public class QueryableProxy<T extends Serializable> implements Queryable<T> {
public class QueryableProxy<T extends SerializableObject> implements Queryable<T> {
private final Queryable<T> delegate;
public QueryableProxy(Queryable<T> delegate) {
@@ -24,6 +24,11 @@ public class QueryableProxy<T extends Serializable> implements Queryable<T> {
return delegate.getExpression();
}
@Override
public DbSet<T> originalSet() {
return delegate.originalSet();
}
@Override
public Iterator<T> iterator() {
return delegate.iterator();

View 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);
}
}
}

View 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, access, 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));
}
}

View File

@@ -0,0 +1,330 @@
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.AllArgsConstructor;
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.Arrays;
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 declaringClassName;
private final String methodName;
private final int access;
private final String returnClassName;
private final String[] parameterClassNames;
private List<ConstructorParameterDescription> constructorParameterDescriptions = new ArrayList<>();
private List<SuperConstructorParameterDescription> superConstructorParameterDescriptions = new ArrayList<>();
private Stack<Var> varStack = 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 declaringClassName, String methodName, int access, String returnClassName, String[] parameterClassNames, Consumer<ClassDescription> callback) {
super(api);
this.declaringClassName = declaringClassName;
this.methodName = methodName;
this.access = access;
this.returnClassName = returnClassName;
if (hasThis()) {
this.parameterClassNames = new String[parameterClassNames.length + 1];
System.arraycopy(parameterClassNames, 0, this.parameterClassNames, 1, parameterClassNames.length);
this.parameterClassNames[0] = declaringClassName;
} else {
this.parameterClassNames = parameterClassNames;
}
this.callback = callback;
}
@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)) {
System.out.println(methodName + ": load_" + varIndex + ": " + (varIndex < parameterClassNames.length ? parameterClassNames[varIndex] : null));
if (varIndex == 5) {
int x = 0;
}
varStack.push(new Var(varIndex < parameterClassNames.length ? parameterClassNames[varIndex] : null, varIndex));
}
if (opcode == Opcodes.ALOAD && varIndex == 0) {
if (constructorStep == ConstructorSteps.CODE) {
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);
}
private boolean hasThis() {
return (access & Opcodes.ACC_STATIC) == 0;
}
@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 lastLoadedVar = null;
if (opcode == Opcodes.PUTFIELD) {
lastLoadedVar = varStack.pop();
varStack.pop();
constructorStep = constructorStep == ConstructorSteps.VAR_LOAD ? ConstructorSteps.PUT_FIELD : ConstructorSteps.INVALID;
getterStep = GetterSteps.INVALID;
setterStep = setterStep == SetterSteps.VAR_LOAD ? SetterSteps.PUT_FIELD : SetterSteps.INVALID;
} else if (opcode == Opcodes.GETFIELD) {
lastLoadedVar = varStack.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(lastLoadedVar.getIndex() - 1, lastLoadedVar.getClassName(), this.fieldDeclaringClassName, this.fieldName)); //lastLoadedVar - 1 because arg 0 of an instance function is always this
super.visitFieldInsn(opcode, owner, name, descriptor);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
var args = new ArrayList<Var>();
for (var i = 0; i < Type.getArgumentTypes(descriptor).length; i++) {
args.add(0, varStack.pop());
}
varStack.pop(); // also pop ALOAD0
if (opcode == Opcodes.INVOKESPECIAL && name.equals("<init>") //TODO does it have to be called '<init>' or is this just convention
&& Set.of(ConstructorSteps.PRE_SUPER_INIT_ALOAD_0, ConstructorSteps.PRE_SUPER_INIT_VAR_LOAD).contains(constructorStep)) {
for (int i = 0; i < args.size(); i++) {
superConstructorParameterDescriptions.add(new SuperConstructorParameterDescription(args.get(i).getIndex(), 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) {
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(declaringClassName, methodName, returnClassName, Arrays.stream(parameterClassNames).skip(hasThis() ? 1 : 0).toArray(String[]::new), fieldDeclaringClassName, fieldName)) : List.of();
List<SetterDescription> setters = setterStep == SetterSteps.RETURN ? List.of(new SetterDescription(declaringClassName, methodName, returnClassName, Arrays.stream(parameterClassNames).skip(hasThis() ? 1 : 0).toArray(String[]::new), fieldDeclaringClassName, fieldName)) : List.of();
var access = new ClassDescription(null, null, null, constructors, getters, setters);
callback.accept(access);
}
@AllArgsConstructor
@Getter
private static class Var {
private final String className;
private final int index;
}
}

View File

@@ -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())
+ '}';
}
}

View File

@@ -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;
}

View File

@@ -0,0 +1,15 @@
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 parameterClassName;
private final String declaringClassName;
private final String fieldName;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -40,7 +40,7 @@ public abstract class DbContext {
f.setAccessible(true);
Clazz anno = f.getAnnotation(Clazz.class);
try {
f.set(this, new DbSet(anno.value(), f.getName()));//TODO use table name from modelbuilder
f.set(this, new DbSet(this, anno.value(), f.getName()));//TODO use table name from modelbuilder
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}

View File

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

View File

@@ -1,5 +1,6 @@
package jef.operations;
import jef.DbSet;
import jef.Queryable;
import jef.asm.AsmParseException;
import jef.asm.OptimizedAsmParser;
@@ -8,13 +9,13 @@ import jef.expressions.SelectExpression;
import jef.expressions.WhereExpression;
import jef.expressions.modifier.TableAliasInjector;
import jef.expressions.selectable.DatabaseSelectAllExpression;
import jef.serializable.SerializableObject;
import jef.serializable.SerializablePredicate;
import java.io.Serializable;
import java.util.List;
import java.util.function.Predicate;
public class FilterOp<T extends Serializable> implements Queryable<T>, Operation<T> {
public class FilterOp<T extends SerializableObject> implements Queryable<T>, Operation<T> {
private final Queryable<T> queryable;
private final Predicate<? super T> predicate;
@@ -50,4 +51,9 @@ public class FilterOp<T extends Serializable> implements Queryable<T>, Operation
public String toString() {
return getExpression().toString();
}
@Override
public DbSet<T> originalSet() {
return queryable.originalSet();
}
}

View File

@@ -1,15 +1,16 @@
package jef.operations;
import jef.DbSet;
import jef.Queryable;
import jef.expressions.Expression;
import jef.expressions.LimitExpression;
import jef.expressions.SelectExpression;
import jef.expressions.selectable.DatabaseSelectAllExpression;
import jef.serializable.SerializableObject;
import java.io.Serializable;
import java.util.List;
public class LimitOp<T extends Serializable> implements Queryable<T> {
public class LimitOp<T extends SerializableObject> implements Queryable<T> {
private final Queryable<T> queryable;
private Long start;
private Long count;
@@ -35,6 +36,11 @@ public class LimitOp<T extends Serializable> implements Queryable<T> {
return getExpression().toString();
}
@Override
public DbSet<T> originalSet() {
return queryable.originalSet();
}
@Override
public Queryable<T> limit(long l) {
count = l;

View File

@@ -1,9 +1,8 @@
package jef.operations;
import jef.Queryable;
import jef.serializable.SerializableObject;
import java.io.Serializable;
public interface Operation<T extends Serializable> extends Queryable<T> {
public interface Operation<T extends SerializableObject> extends Queryable<T> {//TODO is this class required
}

View File

@@ -1,5 +1,6 @@
package jef.operations;
import jef.DbSet;
import jef.Queryable;
import jef.asm.AsmParseException;
import jef.asm.AsmParser;
@@ -13,14 +14,14 @@ import jef.expressions.modifier.TableAliasInjector;
import jef.expressions.modifier.TernaryRewriter;
import jef.expressions.selectable.DatabaseSelectAllExpression;
import jef.serializable.SerializableFunction;
import jef.serializable.SerializableObject;
import lombok.AllArgsConstructor;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@AllArgsConstructor
public class SortOp<T extends Serializable> implements Queryable<T> {
public class SortOp<T extends SerializableObject> implements Queryable<T> {
private final Queryable<T> queryable;
private final List<OrderExpression.Sort> sorts = new ArrayList<>();
@@ -45,6 +46,11 @@ public class SortOp<T extends Serializable> implements Queryable<T> {
return getExpression().toString();
}
@Override
public DbSet<T> originalSet() {
return queryable.originalSet();
}
public SortOp<T> thenSorted(SerializableFunction<? super T, ?> fieldSelector) {
var f = parseFunction(fieldSelector);
this.sorts.add(new OrderExpression.Sort(f, OrderExpression.SortDirection.ASCENDING));

View File

@@ -1,25 +1,96 @@
package jef.platform.base;
import jef.MigrationException;
import jef.expressions.Expression;
import jef.model.DbContext;
import jef.model.DbFieldBuilder;
import jef.model.ModelBuilder;
import jef.serializable.SerializableObject;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
@Getter
@AllArgsConstructor
public abstract class Database {
// private final DatabaseConfiguration config;
protected final Connection connection;
protected final DatabaseOptions options;
public abstract void migrate() throws MigrationException;
// public ResultSet executeRaw(String s) throws SQLException {
// try (var stmt = connection.prepareStatement(s)) {
// try (var res = stmt.executeQuery()) {
// return res;
// }
// }
// }
public abstract <T extends SerializableObject> List<T> queryExpression(DbContext context, Class<T> clazz, Expression expression) throws SQLException;
public <T extends SerializableObject> List<T> queryRaw(DbContext context, Class<T> clazz, String query) throws SQLException {
var modelBuilder = ModelBuilder.from(context.getClass());
var entity = modelBuilder.entity(clazz);
var fields = entity.fields();
try (var stmt = connection.prepareStatement(query)) {
return queryRaw(stmt, clazz, fields);
}
}
protected <T extends SerializableObject> List<T> queryRaw(PreparedStatement stmt, Class<T> clazz, List<DbFieldBuilder<?>> fields) throws SQLException {
try (var result = stmt.executeQuery()) {
var ret = new ArrayList<T>();
while (result.next()) {
ret.add(readRow(result, clazz, fields));
}
return ret;
}
}
protected <T extends SerializableObject> T readRow(ResultSet res, Class<T> clazz, List<DbFieldBuilder<?>> fields) throws SQLException {
//TODO replace with by runtime generated class
try {
var model = (T) clazz.getConstructor().newInstance();
for (DbFieldBuilder<?> field : fields) {
if (!field.getField().isDatabaseField()) {
continue;
}
Field f = field.getField().getField();
if (f == null) {
System.out.println("not class member for " + field.getField().getName());
continue;
}
f.setAccessible(true);
if (field.getField().getType() == String.class) {
f.set(model, res.getString(f.getName()));
} else if (field.getField().getType() == byte.class || field.getField().getType() == Byte.class) {
f.set(model, res.getByte(f.getName()));
} else if (field.getField().getType() == char.class || field.getField().getType() == Character.class) {
// f.set(model, res.getString(f.getName()));//TODO handle char
throw new UnsupportedOperationException("char");
} else if (field.getField().getType() == short.class || field.getField().getType() == Short.class) {
f.set(model, res.getShort(f.getName()));
} else if (field.getField().getType() == int.class || field.getField().getType() == Integer.class) {
f.set(model, res.getInt(f.getName()));
} else if (field.getField().getType() == long.class || field.getField().getType() == Long.class) {
f.set(model, res.getLong(f.getName()));
} else if (field.getField().getType() == float.class || field.getField().getType() == Float.class) {
f.set(model, res.getFloat(f.getName()));
} else if (field.getField().getType() == double.class || field.getField().getType() == Double.class) {
f.set(model, res.getDouble(f.getName()));
} else {
throw new UnsupportedOperationException();
}
}
return model;
} catch (IllegalAccessException | InvocationTargetException | InstantiationException | NoSuchMethodException e) {//TODO better handling
throw new RuntimeException(e);
}
}
public abstract <T extends SerializableObject> void add(DbContext context, Class<T> clazz, T model) throws SQLException;
public abstract <T extends SerializableObject> void update(DbContext context, Class<T> clazz, T model) throws SQLException;
public abstract <T extends SerializableObject> void delete(DbContext context, Class<T> clazz, T model) throws SQLException;
}

View File

@@ -1,7 +1,13 @@
package jef.platform.dummy;
import jef.MigrationException;
import jef.expressions.Expression;
import jef.model.DbContext;
import jef.platform.base.Database;
import jef.serializable.SerializableObject;
import java.sql.SQLException;
import java.util.List;
public class DummyDatabase extends Database {
public DummyDatabase() {
@@ -11,4 +17,28 @@ public class DummyDatabase extends Database {
@Override
public void migrate() throws MigrationException {
}
@Override
public <T extends SerializableObject> List<T> queryExpression(DbContext context, Class<T> clazz, Expression expression) {
return List.of();
}
@Override
public <T extends SerializableObject> List<T> queryRaw(DbContext context, Class<T> clazz, String query) {
return List.of();
}
@Override
public <T extends SerializableObject> void add(DbContext context, Class<T> clazz, T entity) throws SQLException {
}
@Override
public <T extends SerializableObject> void update(DbContext context, Class<T> clazz, T model) throws SQLException {
}
@Override
public <T extends SerializableObject> void delete(DbContext context, Class<T> clazz, T model) throws SQLException {
}
}

View File

@@ -26,6 +26,11 @@ public abstract class Util {
R apply(T t, U u) throws Throwable;
}
@FunctionalInterface
public interface ThrowableBiConsumer<T, U> {
void accept(T t, U u) throws Throwable;
}
public interface Equality<T> {
boolean check(T t1, T t2);
}

View 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;
}
}

View File

@@ -13,7 +13,7 @@ public class FilterOpTest {
@Test
public void testSimple() {
String act;
act = new DbSet<>(TestClass.class, "table1")
act = new DbSet<>(null, TestClass.class, "table1")
.filter(e -> true)
.toString();
Assertions.assertEquals("SELECT * FROM (SELECT * FROM `table1`) a WHERE 1", act);
@@ -23,7 +23,7 @@ public class FilterOpTest {
public void testMultipleFilter() {
String act;
var s = List.of(1, 3);
act = new DbSet<>(TestClass.class, "table1")
act = new DbSet<>(null, TestClass.class, "table1")
.filter(e -> s.contains(e.i))
.filter(e -> e.i == 1337)
.toString();

View File

@@ -8,25 +8,25 @@ public class LimitOpTest {
@Test
public void test() {
String act;
act = new DbSet<>(FilterOpTest.TestClass.class, "table1")
act = new DbSet<>(null, FilterOpTest.TestClass.class, "table1")
.limit(10).skip(5)
.toString();
Assertions.assertEquals("SELECT * FROM (SELECT * FROM `table1`) a OFFSET 5 LIMIT 10", act);
act = new DbSet<>(FilterOpTest.TestClass.class, "table1")
act = new DbSet<>(null, FilterOpTest.TestClass.class, "table1")
.skip(5).limit(10)
.toString();
Assertions.assertEquals("SELECT * FROM (SELECT * FROM `table1`) a OFFSET 5 LIMIT 10", act);
act = new DbSet<>(FilterOpTest.TestClass.class, "table1")
act = new DbSet<>(null, FilterOpTest.TestClass.class, "table1")
.skip(5)
.toString();
Assertions.assertEquals("SELECT * FROM (SELECT * FROM `table1`) a OFFSET 5", act);
act = new DbSet<>(FilterOpTest.TestClass.class, "table1")
act = new DbSet<>(null, FilterOpTest.TestClass.class, "table1")
.limit(10)
.toString();
Assertions.assertEquals("SELECT * FROM (SELECT * FROM `table1`) a LIMIT 10", act);

View File

@@ -8,27 +8,27 @@ class SortOpTest {
@Test
public void test() {
String act;
act = new DbSet<>(FilterOpTest.TestClass.class, "table1")
act = new DbSet<>(null, FilterOpTest.TestClass.class, "table1")
.sorted(e -> e.getI()).thenSorted(e -> e.getD()).thenSorted(e -> e.getF()).thenSorted(e -> e.getL()).thenSorted(e -> e.getO())
.toString();
Assertions.assertEquals("SELECT * FROM (SELECT * FROM `table1`) a ORDER BY `a`.`i` ASC, `a`.`d` ASC, `a`.`f` ASC, `a`.`l` ASC, `a`.`o` ASC", act);
act = new DbSet<>(FilterOpTest.TestClass.class, "table1")
act = new DbSet<>(null, FilterOpTest.TestClass.class, "table1")
.sortedDescending(e -> e.getI()).thenSortedDescending(e -> e.getD()).thenSortedDescending(e -> e.getF())
/**/.thenSortedDescending(e -> e.getL()).thenSortedDescending(e -> e.getO())
.toString();
Assertions.assertEquals("SELECT * FROM (SELECT * FROM `table1`) a ORDER BY `a`.`i` DESC, `a`.`d` DESC, `a`.`f` DESC, `a`.`l` DESC, `a`.`o` DESC", act);
//alternating patterns
act = new DbSet<>(FilterOpTest.TestClass.class, "table1")
act = new DbSet<>(null, FilterOpTest.TestClass.class, "table1")
.sortedDescending(e -> e.getI()).thenSorted(e -> e.getD()).thenSortedDescending(e -> e.getF()).thenSorted(e -> e.getL())
/**/.thenSortedDescending(e -> e.getO())
.toString();
Assertions.assertEquals("SELECT * FROM (SELECT * FROM `table1`) a ORDER BY `a`.`i` DESC, `a`.`d` ASC, `a`.`f` DESC, `a`.`l` ASC, `a`.`o` DESC", act);
act = new DbSet<>(FilterOpTest.TestClass.class, "table1")
act = new DbSet<>(null, FilterOpTest.TestClass.class, "table1")
.sorted(e -> e.getI()).thenSortedDescending(e -> e.getD()).thenSorted(e -> e.getF()).thenSortedDescending(e -> e.getL())
/**/.thenSorted(e -> e.getO())
.toString();

View File

@@ -12,11 +12,11 @@ class DebugExpressionVisitorTest {
@Test
public void test() {
var s = List.of(1, 3);
Queryable<FilterOpTest.TestClass> q = new DbSet<>(FilterOpTest.TestClass.class, "table1")
Queryable<FilterOpTest.TestClass> q = new DbSet<>(null, FilterOpTest.TestClass.class, "table1")
.filter(e -> s.contains(e.i) && e.i == 1337 && e.i == 420);
new DebugExpressionVisitor().visit(q.getExpression());
Queryable<FilterOpTest.TestClass> q2 = new DbSet<>(FilterOpTest.TestClass.class, "table1")
Queryable<FilterOpTest.TestClass> q2 = new DbSet<>(null, FilterOpTest.TestClass.class, "table1")
.filter(e -> s.contains(e.i) || e.i == 1337);
new DebugExpressionVisitor().visit(q2.getExpression());
}

View File

@@ -1,13 +1,30 @@
package jef.platform.mysql;
import jef.MigrationException;
import jef.expressions.Expression;
import jef.model.DbContext;
import jef.model.DbField;
import jef.model.DbFieldBuilder;
import jef.model.ModelBuilder;
import jef.platform.base.Database;
import jef.platform.base.DatabaseOptions;
import jef.platform.mysql.query.MysqlQueryBuilder;
import jef.serializable.SerializableObject;
import jef.util.Util;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class MysqlDatabase extends Database {
private final MysqlPlatform platform;
public MysqlDatabase(Connection connection, DatabaseOptions options, MysqlPlatform platform) {
super(connection, options);
this.platform = platform;
@@ -17,4 +34,300 @@ public class MysqlDatabase extends Database {
public void migrate() throws MigrationException {
platform.getMigrationApplier(connection, options).migrate();
}
@Override
public <T extends SerializableObject> List<T> queryExpression(DbContext context, Class<T> clazz, Expression expression) throws SQLException {
MysqlQueryBuilder queryBuilder = new MysqlQueryBuilder();
queryBuilder.visit(expression);
return queryRaw(context, clazz, queryBuilder.getQuery());
}
@Override
public <T extends SerializableObject> void add(DbContext context, Class<T> clazz, T model) throws SQLException {
var modelBuilder = ModelBuilder.from(context.getClass());
var entity = modelBuilder.entity(clazz);
var columns = new ArrayList<String>();
var valueBinders = new ArrayList<Util.ThrowableBiConsumer<PreparedStatement, Integer>>();
for (DbFieldBuilder<?> field : entity.fields()) {
Field f = field.getField().getField();
if (f == null) {
if (field.getField().isDatabaseField()) {
columns.add(field.getField().getName());
valueBinders.add((stmt, i) -> stmt.setNull(i, Types.INTEGER));//TODO set proper type
}
continue;
}
if (!field.getField().isDatabaseField()) {
continue;
}
f.setAccessible(true);
columns.add(field.getField().getName());
try {
if (f.get(model) == null) {
valueBinders.add((stmt, i) -> stmt.setNull(i, Types.INTEGER));
} else if (field.getField().getType() == String.class) {
valueBinders.add((stmt, i) -> stmt.setString(i, (String) f.get(model)));
} else if (field.getField().getType() == byte.class || field.getField().getType() == Byte.class) {
valueBinders.add((stmt, i) -> stmt.setByte(i, (byte) f.get(model)));
} else if (field.getField().getType() == char.class || field.getField().getType() == Character.class) {
// valueSetters.add((stmt, i) -> stmt.setString(i, (char) f.get(model)));//TODO handle char
throw new UnsupportedOperationException("char");
} else if (field.getField().getType() == short.class || field.getField().getType() == Short.class) {
valueBinders.add((stmt, i) -> stmt.setShort(i, (short) f.get(model)));
} else if (field.getField().getType() == int.class || field.getField().getType() == Integer.class) {
valueBinders.add((stmt, i) -> stmt.setInt(i, (int) f.get(model)));
} else if (field.getField().getType() == long.class || field.getField().getType() == Long.class) {
valueBinders.add((stmt, i) -> stmt.setLong(i, (long) f.get(model)));
} else if (field.getField().getType() == float.class || field.getField().getType() == Float.class) {
valueBinders.add((stmt, i) -> stmt.setFloat(i, (float) f.get(model)));
} else if (field.getField().getType() == double.class || field.getField().getType() == Double.class) {
valueBinders.add((stmt, i) -> stmt.setDouble(i, (double) f.get(model)));
} else {
throw new UnsupportedOperationException();
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
var columnsString = columns.stream().map(e -> "`" + e + "`").collect(Collectors.joining(","));
var placeholdersString = columns.stream().map(e -> "?").collect(Collectors.joining(","));
var query = "INSERT INTO `" + entity.name() + "` (" + columnsString + ") VALUES (" + placeholdersString + ")";
try (var stmt = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS)) {
var i = 1;
for (Util.ThrowableBiConsumer<PreparedStatement, Integer> valueBinder : valueBinders) {
try {
valueBinder.accept(stmt, i++);
} catch (Throwable e) {
if (e instanceof SQLException) {
throw (SQLException) e;
}
throw new IllegalStateException("This may not happen", e);
}
}
var added = stmt.executeUpdate();
if (added != 1) {
throw new SQLException("Failed to insert entity");
}
try (var res = stmt.getGeneratedKeys()) {
var primary = entity.getEntity().getPrimaryKey();
if (primary != null) {
if (!res.next()) {
throw new SQLException("Failed to add entity");
}
var idField = primary.getFields().get(0);//TODO this only supports primary keys, and only with one column
idField.getField().setAccessible(true);
if (idField.getField().getType() == int.class || idField.getField().getType() == Integer.class) {//TODO support more types
idField.getField().set(model, res.getInt(1));
} else if (idField.getField().getType() == long.class || idField.getField().getType() == Long.class) {
idField.getField().set(model, res.getLong(1));
} else {
throw new UnsupportedOperationException();
}
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
@Override
public <T extends SerializableObject> void update(DbContext context, Class<T> clazz, T model) throws SQLException {
var modelBuilder = ModelBuilder.from(context.getClass());
var entity = modelBuilder.entity(clazz);
if (entity.getEntity().getPrimaryKey() == null) {
throw new UnsupportedOperationException("Primary key required for updates");//at least for now
}
var columns = new ArrayList<String>();
var valueBinders = new ArrayList<Util.ThrowableBiConsumer<PreparedStatement, Integer>>();
for (DbFieldBuilder<?> field : entity.fields()) {
Field f = field.getField().getField();
if (f == null) {
if (field.getField().isDatabaseField()) {
columns.add(field.getField().getName());
valueBinders.add((stmt, i) -> stmt.setNull(i, Types.INTEGER));//TODO set proper type
}
continue;
}
if (!field.getField().isDatabaseField()) {
continue;
}
f.setAccessible(true);
columns.add(field.getField().getName());
try {
if (f.get(model) == null) {
valueBinders.add((stmt, i) -> stmt.setNull(i, Types.INTEGER));
} else if (field.getField().getType() == String.class) {
valueBinders.add((stmt, i) -> stmt.setString(i, (String) f.get(model)));
} else if (field.getField().getType() == byte.class || field.getField().getType() == Byte.class) {
valueBinders.add((stmt, i) -> stmt.setByte(i, (byte) f.get(model)));
} else if (field.getField().getType() == char.class || field.getField().getType() == Character.class) {
// valueSetters.add((stmt, i) -> stmt.setString(i, (char) f.get(model)));//TODO handle char
throw new UnsupportedOperationException("char");
} else if (field.getField().getType() == short.class || field.getField().getType() == Short.class) {
valueBinders.add((stmt, i) -> stmt.setShort(i, (short) f.get(model)));
} else if (field.getField().getType() == int.class || field.getField().getType() == Integer.class) {
valueBinders.add((stmt, i) -> stmt.setInt(i, (int) f.get(model)));
} else if (field.getField().getType() == long.class || field.getField().getType() == Long.class) {
valueBinders.add((stmt, i) -> stmt.setLong(i, (long) f.get(model)));
} else if (field.getField().getType() == float.class || field.getField().getType() == Float.class) {
valueBinders.add((stmt, i) -> stmt.setFloat(i, (float) f.get(model)));
} else if (field.getField().getType() == double.class || field.getField().getType() == Double.class) {
valueBinders.add((stmt, i) -> stmt.setDouble(i, (double) f.get(model)));
} else {
throw new UnsupportedOperationException();
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
var updatesString = columns.stream().map(e -> "`" + e + "` = ?").collect(Collectors.joining(", "));
var whereString = entity.getEntity().getPrimaryKey().getFields().stream().map(e -> "`" + e.getName() + "` = ?").collect(Collectors.joining(" AND "));
//add values for where clause
for (DbField<?> field : entity.getEntity().getPrimaryKey().getFields()) {
Field f = field.getField();
f.setAccessible(true);
try {
if (f.get(model) == null) {
valueBinders.add((stmt, i) -> stmt.setNull(i, Types.INTEGER));
} else if (field.getField().getType() == String.class) {
valueBinders.add((stmt, i) -> stmt.setString(i, (String) f.get(model)));
} else if (field.getField().getType() == byte.class || field.getField().getType() == Byte.class) {
valueBinders.add((stmt, i) -> stmt.setByte(i, (byte) f.get(model)));
} else if (field.getField().getType() == char.class || field.getField().getType() == Character.class) {
// valueSetters.add((stmt, i) -> stmt.setString(i, (char) f.get(model)));//TODO handle char
throw new UnsupportedOperationException("char");
} else if (field.getField().getType() == short.class || field.getField().getType() == Short.class) {
valueBinders.add((stmt, i) -> stmt.setShort(i, (short) f.get(model)));
} else if (field.getField().getType() == int.class || field.getField().getType() == Integer.class) {
valueBinders.add((stmt, i) -> stmt.setInt(i, (int) f.get(model)));
} else if (field.getField().getType() == long.class || field.getField().getType() == Long.class) {
valueBinders.add((stmt, i) -> stmt.setLong(i, (long) f.get(model)));
} else if (field.getField().getType() == float.class || field.getField().getType() == Float.class) {
valueBinders.add((stmt, i) -> stmt.setFloat(i, (float) f.get(model)));
} else if (field.getField().getType() == double.class || field.getField().getType() == Double.class) {
valueBinders.add((stmt, i) -> stmt.setDouble(i, (double) f.get(model)));
} else {
throw new UnsupportedOperationException();
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
var query = "UPDATE `" + entity.name() + "` SET " + updatesString + " WHERE " + whereString;
System.out.println(query);
try (var stmt = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS)) {
var i = 1;
for (Util.ThrowableBiConsumer<PreparedStatement, Integer> valueBinder : valueBinders) {
try {
valueBinder.accept(stmt, i++);
} catch (Throwable e) {
if (e instanceof SQLException) {
throw (SQLException) e;
}
throw new IllegalStateException("This may not happen", e);
}
}
var updated = stmt.executeUpdate();
if (updated != 1) {
throw new SQLException("Failed to update entity");
}
try (var res = stmt.getGeneratedKeys()) {
// var primary = entity.getEntity().getPrimaryKey();
// if (primary != null) {
// if (!res.next()) {
// throw new SQLException("Failed to add entity");
// }
// var idField = primary.getFields().get(0);//TODO this only supports primary keys, and only with one column
// idField.getField().setAccessible(true);
// if (idField.getField().getType() == int.class || idField.getField().getType() == Integer.class) {//TODO support more types
// idField.getField().set(model, res.getInt(1));
// } else if (idField.getField().getType() == long.class || idField.getField().getType() == Long.class) {
// idField.getField().set(model, res.getLong(1));
// } else {
// throw new UnsupportedOperationException();
// }
// }
// } catch (IllegalAccessException e) {
// throw new RuntimeException(e);
}
}
}
@Override
public <T extends SerializableObject> void delete(DbContext context, Class<T> clazz, T model) throws SQLException {
var modelBuilder = ModelBuilder.from(context.getClass());
var entity = modelBuilder.entity(clazz);
if (entity.getEntity().getPrimaryKey() == null) {
throw new UnsupportedOperationException("Primary key required for updates");//at least for now
}
var valueBinders = new ArrayList<Util.ThrowableBiConsumer<PreparedStatement, Integer>>();
var whereString = entity.getEntity().getPrimaryKey().getFields().stream().map(e -> "`" + e.getName() + "` = ?").collect(Collectors.joining(" AND "));
//add values for where clause
for (DbField<?> field : entity.getEntity().getPrimaryKey().getFields()) {
Field f = field.getField();
f.setAccessible(true);
try {
if (f.get(model) == null) {
valueBinders.add((stmt, i) -> stmt.setNull(i, Types.INTEGER));
} else if (field.getField().getType() == String.class) {
valueBinders.add((stmt, i) -> stmt.setString(i, (String) f.get(model)));
} else if (field.getField().getType() == byte.class || field.getField().getType() == Byte.class) {
valueBinders.add((stmt, i) -> stmt.setByte(i, (byte) f.get(model)));
} else if (field.getField().getType() == char.class || field.getField().getType() == Character.class) {
// valueSetters.add((stmt, i) -> stmt.setString(i, (char) f.get(model)));//TODO handle char
throw new UnsupportedOperationException("char");
} else if (field.getField().getType() == short.class || field.getField().getType() == Short.class) {
valueBinders.add((stmt, i) -> stmt.setShort(i, (short) f.get(model)));
} else if (field.getField().getType() == int.class || field.getField().getType() == Integer.class) {
valueBinders.add((stmt, i) -> stmt.setInt(i, (int) f.get(model)));
} else if (field.getField().getType() == long.class || field.getField().getType() == Long.class) {
valueBinders.add((stmt, i) -> stmt.setLong(i, (long) f.get(model)));
} else if (field.getField().getType() == float.class || field.getField().getType() == Float.class) {
valueBinders.add((stmt, i) -> stmt.setFloat(i, (float) f.get(model)));
} else if (field.getField().getType() == double.class || field.getField().getType() == Double.class) {
valueBinders.add((stmt, i) -> stmt.setDouble(i, (double) f.get(model)));
} else {
throw new UnsupportedOperationException();
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
var query = "DELETE FROM `" + entity.name() + "` WHERE " + whereString;
System.out.println(query);
try (var stmt = connection.prepareStatement(query)) {
var i = 1;
for (Util.ThrowableBiConsumer<PreparedStatement, Integer> valueBinder : valueBinders) {
try {
valueBinder.accept(stmt, i++);
} catch (Throwable e) {
if (e instanceof SQLException) {
throw (SQLException) e;
}
throw new IllegalStateException("This may not happen", e);
}
}
var deleted = stmt.executeUpdate();
if (deleted != 1) {
throw new SQLException("Failed to delete entity");
}
}
}
}

View File

@@ -0,0 +1,445 @@
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.ClassVisitor;
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.Set;
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, String className, Runnable defineFieldCallback) {
defineFieldCallback.run();
// field.get*(entity, value);
mv.visitFieldInsn(Opcodes.GETSTATIC, className.replace(".", "/"), makeReflectedFieldName(field), Type.getDescriptor(field.getType()));
mv.visitVarInsn(Opcodes.ALOAD, varIndexEntity);
var clazz = field.getType();
if (clazz == boolean.class) {
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, String className, Runnable defineFieldCallback, ClassDescription classDescription) {
//try direct member access
if (Modifier.isPublic(field.getModifiers())) {//TODO better check if publicly accessible
visitGetFieldDirectAccess(mv, field, varIndexEntity);
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, className, defineFieldCallback);
}
//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, String className, Runnable defineFieldCallback) {
defineFieldCallback.run();
// field.set*(entity, value);
mv.visitFieldInsn(Opcodes.GETSTATIC, className.replace(".", "/"), makeReflectedFieldName(field), Type.getDescriptor(field.getType()));
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, String className, Runnable defineFieldCallback, ClassDescription classDescription) {
//try direct member access
if (Modifier.isPublic(field.getModifiers()) && !Modifier.isFinal(field.getModifiers())) {//TODO better check if publicly accessible
visitSetFieldDirectAccess(mv, field, value, varIndexEntity);
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, className, defineFieldCallback);
}
//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);
}
public static void visitPushDefault(MethodVisitor mv, Class<?> type) {
if (Set.of(void.class, boolean.class, byte.class, char.class, short.class, int.class, float.class).contains(type)) {
mv.visitInsn(Opcodes.ICONST_0);
} else if (Set.of(double.class, long.class).contains(type)) {
mv.visitInsn(Opcodes.ICONST_0);
mv.visitInsn(Opcodes.ICONST_0);
} else {
mv.visitInsn(Opcodes.ACONST_NULL);
}
}
static String getDescriptorForClassName(String className) {
return switch (className) {
case "void" -> "V";
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 field reflection
public static void visitReflectedFieldDefinition(ClassVisitor visitor, Field field, boolean synthetic) {
visitor.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | (synthetic ? Opcodes.ACC_SYNTHETIC : 0),
makeReflectedFieldName(field), T_FIELD.getDescriptor(), null, null);
}
public static void visitReflectedFieldInit(MethodVisitor mv, Field field, String className) {
className = className.replace(".", "/");
var fieldname = makeReflectedFieldName(field);
// field = EntityClass.class.getDeclaredField("fieldname");
mv.visitLdcInsn(Type.getObjectType(field.getDeclaringClass().getName()));
mv.visitLdcInsn(field.getName());
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_CLASS.getInternalName(), "getDeclaredField", Type.getMethodDescriptor(T_FIELD, T_STRING), false);
mv.visitFieldInsn(Opcodes.PUTSTATIC, className, fieldname, T_FIELD.getDescriptor());
// field.setAccessible(true);
mv.visitFieldInsn(Opcodes.GETSTATIC, className, fieldname, Type.getDescriptor(field.getType()));
mv.visitInsn(Opcodes.ICONST_1);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setAccessible", Type.getMethodDescriptor(T_VOID, T_BOOL), false);
}
private static String makeReflectedFieldName(Field field) {
return field.getDeclaringClass().getName().replace(".", "_") + "_" + field.getName();
}
//endregion
//region var access
// private void visitLoadVariable(MethodVisitor mv, String className, int varIndex) {
// switch (className) {
// 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
}

View File

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

View File

@@ -0,0 +1,791 @@
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.sql.Statement;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
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;
private final Set<Field> fieldsToReflect = new HashSet<>();
//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) {
};
}
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();
//add
defineQueryAddFunction();
defineBindParamsAddFunction();
defineReadBackGeneratedValuesAddPreparedStatement();
defineReadBackGeneratedValuesAddResultSet();
//update
defineQueryUpdateFunction();
defineBindParamsUpdateFunction();
defineReadBackGeneratedValuesUpdatePreparedStatement();
defineReadBackGeneratedValuesUpdateResultSet();
//delete
defineQueryDeleteFunction();
defineBindParamsDeleteFunction();
//read
defineReadFunction();
//other
defineReflectedFields();
defineClassConstructor();
defineConstructor();
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 void defineReflectedFields() {
for (Field field : fieldsToReflect) {
ByteCodeHelper.visitReflectedFieldDefinition(visitor, field, SET_SYNTHETIC_FLAG);
}
}
//<clinit>
private void defineClassConstructor() {
var mv = visitor.visitMethod(Opcodes.ACC_STATIC | SYNTHETIC_FLAG, "<clinit>", "()V", null, new String[0]);
mv.visitCode();
//init reflected fields
for (Field field : fieldsToReflect) {
ByteCodeHelper.visitReflectedFieldInit(mv, field, className);
}
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(2, 0);
mv.visitEnd();
}
//region add
private void defineQueryAddFunction() {
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "queryAdd", Type.getMethodDescriptor(ByteCodeHelper.T_PREPARED_STATEMENT, ByteCodeHelper.T_CONNECTION), null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
var varIndexThis = 0;
var varIndexConnection = 1;
var columnsAndValueBindersSet = prepareColumnsAndValueBindersSet();
var columnsString = columnsAndValueBindersSet.getColumns().stream().map(e -> "`" + e + "`").collect(Collectors.joining(","));
var placeholdersString = columnsAndValueBindersSet.getColumns().stream().map(e -> "?").collect(Collectors.joining(","));
var query = "INSERT INTO `" + entity.getName() + "` (" + columnsString + ") VALUES (" + placeholdersString + ")";
// variable scopes
var labelStartThis = new Label();
var labelEndThis = new Label();
var labelStartConnection = new Label();
var labelEndConnection = new Label();
// begin
mv.visitCode();
// variable scopes
mv.visitLabel(labelStartThis);
mv.visitLabel(labelStartConnection);
//return connection.prepareStatement("query", Statement.RETURN_GENERATED_KEYS);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitLdcInsn(query);
if (entity.getFields().stream().anyMatch(e -> e.getGenerated() != Generated.Type.NONE)) {
ByteCodeHelper.visitIntInsn(mv, Statement.RETURN_GENERATED_KEYS);
} else {
mv.visitInsn(Opcodes.ICONST_0); //no flags
}
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, ByteCodeHelper.T_CONNECTION.getInternalName(), "prepareStatement", Type.getMethodDescriptor(ByteCodeHelper.T_PREPARED_STATEMENT, ByteCodeHelper.T_STRING, ByteCodeHelper.T_INT), true);
mv.visitInsn(Opcodes.ARETURN);
// variable scopes
mv.visitLabel(labelEndConnection);
mv.visitLabel(labelEndThis);
// debug symbols
mv.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, labelStartConnection, labelEndConnection, varIndexThis);
mv.visitLocalVariable("connection", ByteCodeHelper.T_CONNECTION.getDescriptor(), null, labelStartThis, labelEndThis, varIndexConnection);
mv.visitMaxs(3, 2);
mv.visitEnd();
}
private void 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;
//create lists for columns and value binders
var columnsAndValueBindersSet = prepareColumnsAndValueBindersSet();
// variable scopes
var labelStartThis = new Label();
var labelEndThis = new Label();
var labelStartStmt = new Label();
var labelEndStmt = new Label();
var labelStartEntity = new Label();
var labelEndEntity = new Label();
// begin
mv.visitCode();
// variable scopes
mv.visitLabel(labelStartThis);
mv.visitLabel(labelStartStmt);
mv.visitLabel(labelStartEntity);
//stmt.set* bind calls
var i = 1;
for (var valueBinder : columnsAndValueBindersSet.getValueBinders()) {
valueBinder.bind(mv, varIndexEntity, varIndexStmt, i++);
}
//return
mv.visitInsn(Opcodes.RETURN);
// variable scopes
mv.visitLabel(labelEndThis);
mv.visitLabel(labelEndStmt);
mv.visitLabel(labelEndEntity);
// debug symbols
mv.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, labelStartThis, labelEndThis, varIndexThis);
mv.visitLocalVariable("stmt", ByteCodeHelper.T_PREPARED_STATEMENT.getDescriptor(), null, labelStartStmt, labelEndStmt, varIndexStmt);
mv.visitLocalVariable("entity", ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()), null, labelStartEntity, labelEndEntity, varIndexEntity);
mv.visitMaxs(3, 3);
mv.visitEnd();
}
private void defineReadBackGeneratedValuesAddPreparedStatement() {
if (entity.getFields().stream().anyMatch(e -> e.getGenerated() == Generated.Type.IDENTITY)) {
return;
}
//stub method if no generated values
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "readBackGeneratedValuesAdd", Type.getMethodDescriptor(ByteCodeHelper.T_VOID, ByteCodeHelper.T_PREPARED_STATEMENT, Type.getType(ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()))), null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
var labelStart = new Label();
var labelEnd = new Label();
mv.visitLabel(labelStart);
mv.visitCode();
mv.visitInsn(Opcodes.RETURN);
mv.visitLabel(labelEnd);
// debug symbols
mv.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, labelStart, labelEnd, 0);
mv.visitLocalVariable("stmt", ByteCodeHelper.T_PREPARED_STATEMENT.getDescriptor(), null, labelStart, labelEnd, 1);
mv.visitLocalVariable("entity", ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()), null, labelStart, labelEnd, 2);
mv.visitMaxs(0, 3);
mv.visitEnd();
}
private void defineReadBackGeneratedValuesAddResultSet() {
var fieldsToRead = entity.getFields().stream().filter(e -> e.getGenerated() != Generated.Type.NONE).toList();
if (fieldsToRead.isEmpty()) {
return;
}
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "readBackGeneratedValuesAdd", Type.getMethodDescriptor(ByteCodeHelper.T_VOID, ByteCodeHelper.T_RESULT_SET, Type.getType(ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()))), null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
visitReadFieldsIntoEntityFunctionBody(mv, fieldsToRead, true);
}
//endregion
//region update
private void defineQueryUpdateFunction() {
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "queryUpdate", Type.getMethodDescriptor(ByteCodeHelper.T_PREPARED_STATEMENT, ByteCodeHelper.T_CONNECTION), null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
var varIndexThis = 0;
var varIndexConnection = 1;
var columnsAndValueBindersSet = prepareColumnsAndValueBindersSet();
var columnsAndValueBindersWhere = prepareColumnsAndValueBindersWhere();
var updatesString = columnsAndValueBindersSet.getColumns().stream().map(e -> "`" + e + "` = ?").collect(Collectors.joining(", "));
var whereString = columnsAndValueBindersWhere.getColumns().stream().map(e -> "`" + e + "` = ?").collect(Collectors.joining(" AND "));
var query = "UPDATE `" + entity.getName() + "` SET " + updatesString + " WHERE " + whereString;
// variable scopes
var labelStartThis = new Label();
var labelEndThis = new Label();
var labelStartConnection = new Label();
var labelEndConnection = new Label();
// begin
mv.visitCode();
// variable scopes
mv.visitLabel(labelStartThis);
mv.visitLabel(labelStartConnection);
//return connection.prepareStatement("query", Statement.RETURN_GENERATED_KEYS);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitLdcInsn(query);
if (entity.getFields().stream().anyMatch(e -> e.getGenerated() == Generated.Type.COMPUTED)) {
ByteCodeHelper.visitIntInsn(mv, Statement.RETURN_GENERATED_KEYS);
} else {
mv.visitInsn(Opcodes.ICONST_0); //no flags
}
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, ByteCodeHelper.T_CONNECTION.getInternalName(), "prepareStatement", Type.getMethodDescriptor(ByteCodeHelper.T_PREPARED_STATEMENT, ByteCodeHelper.T_STRING, ByteCodeHelper.T_INT), true);
mv.visitInsn(Opcodes.ARETURN);
// variable scopes
mv.visitLabel(labelEndConnection);
mv.visitLabel(labelEndThis);
// debug symbols
mv.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, labelStartConnection, labelEndConnection, varIndexThis);
mv.visitLocalVariable("connection", ByteCodeHelper.T_CONNECTION.getDescriptor(), null, labelStartThis, labelEndThis, varIndexConnection);
mv.visitMaxs(3, 2);
mv.visitEnd();
}
private void defineBindParamsUpdateFunction() {
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "bindParamsUpdate", Type.getMethodDescriptor(ByteCodeHelper.T_VOID, ByteCodeHelper.T_PREPARED_STATEMENT, Type.getType(ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()))), null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
var varIndexThis = 0;
var varIndexStmt = 1;
var varIndexEntity = 2;
//create lists for columns and value binders
var columnsAndValueBindersSet = prepareColumnsAndValueBindersSet();
var columnsAndValueBindersWhere = prepareColumnsAndValueBindersWhere();
// variable scopes
var labelStartThis = new Label();
var labelEndThis = new Label();
var labelStartStmt = new Label();
var labelEndStmt = new Label();
var labelStartEntity = new Label();
var labelEndEntity = new Label();
// begin
mv.visitCode();
// variable scopes
mv.visitLabel(labelStartThis);
mv.visitLabel(labelStartStmt);
mv.visitLabel(labelStartEntity);
//stmt.set* bind calls
var i = 1;
for (var valueBinder : columnsAndValueBindersSet.getValueBinders()) {
valueBinder.bind(mv, varIndexEntity, varIndexStmt, i++);
}
for (var valueBinder : columnsAndValueBindersWhere.getValueBinders()) {
valueBinder.bind(mv, varIndexEntity, varIndexStmt, i++);
}
//return
mv.visitInsn(Opcodes.RETURN);
// variable scopes
mv.visitLabel(labelEndThis);
mv.visitLabel(labelEndStmt);
mv.visitLabel(labelEndEntity);
// debug symbols
mv.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, labelStartThis, labelEndThis, varIndexThis);
mv.visitLocalVariable("stmt", ByteCodeHelper.T_PREPARED_STATEMENT.getDescriptor(), null, labelStartStmt, labelEndStmt, varIndexStmt);
mv.visitLocalVariable("entity", ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()), null, labelStartEntity, labelEndEntity, varIndexEntity);
mv.visitMaxs(3, 3);
mv.visitEnd();
}
private void defineReadBackGeneratedValuesUpdatePreparedStatement() {
if (entity.getFields().stream().anyMatch(e -> e.getGenerated() == Generated.Type.COMPUTED)) {
return;
}
//stub method if no generated values
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "readBackGeneratedValuesUpdate", Type.getMethodDescriptor(ByteCodeHelper.T_VOID, ByteCodeHelper.T_PREPARED_STATEMENT, Type.getType(ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()))), null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
var labelStart = new Label();
var labelEnd = new Label();
mv.visitLabel(labelStart);
mv.visitCode();
mv.visitInsn(Opcodes.RETURN);
mv.visitLabel(labelEnd);
// debug symbols
mv.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, labelStart, labelEnd, 0);
mv.visitLocalVariable("stmt", ByteCodeHelper.T_PREPARED_STATEMENT.getDescriptor(), null, labelStart, labelEnd, 1);
mv.visitLocalVariable("entity", ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()), null, labelStart, labelEnd, 2);
mv.visitMaxs(0, 3);
mv.visitEnd();
}
private void defineReadBackGeneratedValuesUpdateResultSet() {
var fieldsToRead = entity.getFields().stream().filter(e -> e.getGenerated() == Generated.Type.COMPUTED).toList();
if (fieldsToRead.isEmpty()) {
return;
}
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "readBackGeneratedValuesUpdate", Type.getMethodDescriptor(ByteCodeHelper.T_VOID, ByteCodeHelper.T_RESULT_SET, Type.getType(ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()))), null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
visitReadFieldsIntoEntityFunctionBody(mv, fieldsToRead, true);
}
//endregion
//region delete
private void defineQueryDeleteFunction() {
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "queryDelete", Type.getMethodDescriptor(ByteCodeHelper.T_PREPARED_STATEMENT, ByteCodeHelper.T_CONNECTION), null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
var varIndexThis = 0;
var varIndexConnection = 1;
var columnsAndValueBindersWhere = prepareColumnsAndValueBindersWhere();
var whereString = columnsAndValueBindersWhere.getColumns().stream().map(e -> "`" + e + "` = ?").collect(Collectors.joining(" AND "));
var query = "DELETE FROM `" + entity.getName() + "` WHERE " + whereString;
// variable scopes
var labelStartThis = new Label();
var labelEndThis = new Label();
var labelStartConnection = new Label();
var labelEndConnection = new Label();
// begin
mv.visitCode();
// variable scopes
mv.visitLabel(labelStartThis);
mv.visitLabel(labelStartConnection);
//return connection.prepareStatement("query", Statement.RETURN_GENERATED_KEYS);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitLdcInsn(query);
if (entity.getFields().stream().anyMatch(e -> e.getGenerated() != Generated.Type.NONE)) {
ByteCodeHelper.visitIntInsn(mv, Statement.RETURN_GENERATED_KEYS);
} else {
mv.visitInsn(Opcodes.ICONST_0); //no flags
}
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, ByteCodeHelper.T_CONNECTION.getInternalName(), "prepareStatement", Type.getMethodDescriptor(ByteCodeHelper.T_PREPARED_STATEMENT, ByteCodeHelper.T_STRING, ByteCodeHelper.T_INT), true);
mv.visitInsn(Opcodes.ARETURN);
// variable scopes
mv.visitLabel(labelEndConnection);
mv.visitLabel(labelEndThis);
// debug symbols
mv.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, labelStartConnection, labelEndConnection, varIndexThis);
mv.visitLocalVariable("connection", ByteCodeHelper.T_CONNECTION.getDescriptor(), null, labelStartThis, labelEndThis, varIndexConnection);
mv.visitMaxs(3, 2);
mv.visitEnd();
}
private void defineBindParamsDeleteFunction() {
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "bindParamsDelete", Type.getMethodDescriptor(ByteCodeHelper.T_VOID, ByteCodeHelper.T_PREPARED_STATEMENT, Type.getType(ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()))), null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
var varIndexThis = 0;
var varIndexStmt = 1;
var varIndexEntity = 2;
//create lists for columns and value binders
var columnsAndValueBindersWhere = prepareColumnsAndValueBindersWhere();
// variable scopes
var labelStartThis = new Label();
var labelEndThis = new Label();
var labelStartStmt = new Label();
var labelEndStmt = new Label();
var labelStartEntity = new Label();
var labelEndEntity = new Label();
// begin
mv.visitCode();
// variable scopes
mv.visitLabel(labelStartThis);
mv.visitLabel(labelStartStmt);
mv.visitLabel(labelStartEntity);
//stmt.set* bind calls
var i = 1;
for (var valueBinder : columnsAndValueBindersWhere.getValueBinders()) {
valueBinder.bind(mv, varIndexEntity, varIndexStmt, i++);
}
//return
mv.visitInsn(Opcodes.RETURN);
// variable scopes
mv.visitLabel(labelEndThis);
mv.visitLabel(labelEndStmt);
mv.visitLabel(labelEndEntity);
// debug symbols
mv.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, labelStartThis, labelEndThis, varIndexThis);
mv.visitLocalVariable("stmt", ByteCodeHelper.T_PREPARED_STATEMENT.getDescriptor(), null, labelStartStmt, labelEndStmt, varIndexStmt);
mv.visitLocalVariable("entity", ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()), null, labelStartEntity, labelEndEntity, varIndexEntity);
mv.visitMaxs(3, 3);
mv.visitEnd();
}
//endregion
//region read
private void defineReadFunction() {
var fields = entity.getFields().stream().filter(e -> e.isDatabaseField()).toList();
var bestConstructor = classDescription.getDeclaredConstructors().stream().filter(e -> e.getSuperConstructorParameterDescriptions().size() == 0).map(ctor -> {
var matchingParamCount = ctor.getConstructorParameterDescriptions().stream()
.filter(param -> fields.stream().anyMatch(field -> field.getField().getDeclaringClass().getName().equals(param.getDeclaringClassName())
&& field.getField().getName().equals(param.getFieldName())))
.count();
return new AbstractMap.SimpleEntry<>(ctor, matchingParamCount);
}).sorted(Comparator.comparingLong(e -> -e.getValue())).findFirst();
if (!bestConstructor.isPresent()) {
throw new IllegalStateException("No suitable constructor found!");
}
var orderedFields = bestConstructor.orElseThrow().getKey().getConstructorParameterDescriptions().stream()
.sorted(Comparator.comparingInt(e -> e.getParameterIndex()))
.map(param -> new AbstractMap.SimpleEntry<>(param, fields.stream()
.filter(field -> field.getField().getDeclaringClass().getName().equals(param.getDeclaringClassName())
&& field.getField().getName().equals(param.getFieldName()))
.findFirst().orElse(null)))
.toList();
//begin
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "read", Type.getMethodDescriptor(Type.getType(ByteCodeHelper.getDescriptorForClassName(entity.getTypeName())), ByteCodeHelper.T_RESULT_SET), null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
var varIndexThis = 0;
var varIndexRes = 1;
// variable scopes
var labelStartThis = new Label();
var labelEndThis = new Label();
var labelStartRes = new Label();
var labelEndRes = new Label();
// begin
mv.visitCode();
// variable scopes
mv.visitLabel(labelStartThis);
mv.visitLabel(labelStartRes);
// push params
mv.visitLdcInsn(Type.getObjectType(entity.getTypeName().replace(".", "/")));
for (var paramAndField : orderedFields) {
if (paramAndField.getValue() == null) {
var fieldType = entity.getFields().stream()
.filter(field -> field.getField().getDeclaringClass().getName().equals(paramAndField.getKey().getDeclaringClassName())
&& field.getField().getName().equals(paramAndField.getKey().getFieldName()))
.findFirst().orElseThrow()
.getField().getType();
// PUSH null, 0, or false
ByteCodeHelper.visitPushDefault(mv, fieldType);
} else {
// PUSH res.get*(1);
ByteCodeHelper.visitResultSetGetAny(mv, paramAndField.getValue(), varIndexRes);
}
}
//new Entity(...)
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, entity.getTypeName().replace(".", "/"), "<init>",
Type.getMethodDescriptor(ByteCodeHelper.T_VOID, orderedFields.stream()
.map(paramAndField -> Type.getType(ByteCodeHelper.getDescriptorForClassName(paramAndField.getKey().getParameterClassName())))
.toArray(Type[]::new)), false);
//return
mv.visitInsn(Opcodes.ARETURN);
// variable scopes
mv.visitLabel(labelEndThis);
mv.visitLabel(labelEndRes);
// debug symbols
mv.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, labelStartThis, labelEndThis, varIndexThis);
mv.visitLocalVariable("res", ByteCodeHelper.T_RESULT_SET.getDescriptor(), null, labelStartRes, labelEndRes, varIndexRes);
mv.visitMaxs(orderedFields.size() + 1, 2);
mv.visitEnd();
}
//endregion
//region util
private ColumnsAndValueBinders prepareColumnsAndValueBindersSet() {
var columns = new ArrayList<String>();
var valueBinders = new ArrayList<ValueBinder>();
for (DbField<?> field : entity.getFields()) {
if (!field.isDatabaseField()) {
continue;
}
Field f = field.getField();
if (f == null) {
if (field.isDatabaseField()) {//field defined but not present in entity class -> currently nothing to insert -> null
columns.add(field.getName());
valueBinders.add((mv, varIndexEntity, varIndexStmt, index) -> {
ByteCodeHelper.visitIntInsn(mv, index);
//stmt.setNull(i, Types.INTEGER);
ByteCodeHelper.visitPreparedStatementSetNull(mv, field, varIndexStmt, index);
});
}
continue;
}
columns.add(field.getName());
valueBinders.add((mv, varIndexEntity, varIndexStmt, index) -> {
if (field.isNotNull()) {
//stmt.set*(i, value);
ByteCodeHelper.visitPreparedStatementSetAny(mv, field, varIndexStmt, index, (mv2) -> ByteCodeHelper.visitGetField(mv2, field.getField(), varIndexEntity, className, () -> fieldsToReflect.add(field.getField()), classDescription));
} else {
// getFieldValue(entity);
ByteCodeHelper.visitGetField(mv, f, varIndexEntity, className, () -> fieldsToReflect.add(field.getField()), classDescription);
// if (value == null)
// stmt.setNull(i, Types.*);
// } else {
// stmt.set*(i, value);
// }
var labelAfter = new Label();
var labelElse = new Label();
mv.visitJumpInsn(Opcodes.IFNULL, labelElse);
ByteCodeHelper.visitPreparedStatementSetAny(mv, field, varIndexStmt, index, (mv2) -> ByteCodeHelper.visitGetField(mv2, field.getField(), varIndexEntity, className, () -> fieldsToReflect.add(field.getField()), classDescription));
mv.visitJumpInsn(Opcodes.GOTO, labelAfter);
mv.visitLabel(labelElse);
ByteCodeHelper.visitPreparedStatementSetNull(mv, field, varIndexStmt, index);
mv.visitLabel(labelAfter);
// mv.visitFrame(Opcodes.F_SAME, 0, new Object[0], 0, new Object[0]);
mv.visitFrame(Opcodes.F_FULL, 3, new Object[]{ByteCodeHelper.T_MYSQL_ENTITY_DB_INTERFACE.getInternalName(), ByteCodeHelper.T_PREPARED_STATEMENT.getInternalName(), entity.getTypeName().replace(".", "/")}, 0, new Object[0]);
}
});
}
return new ColumnsAndValueBinders(columns, valueBinders);
}
private ColumnsAndValueBinders prepareColumnsAndValueBindersWhere() {
var columns = new ArrayList<String>();
var valueBinders = new ArrayList<ValueBinder>();
for (DbField<?> field : entity.getPrimaryKey().getFields()) {
if (!field.isDatabaseField()) {
continue;
}
Field f = field.getField();
if (f == null) {
if (field.isDatabaseField()) {//field defined but not present in entity class -> currently nothing to insert -> null
columns.add(field.getName());
valueBinders.add((mv, varIndexEntity, varIndexStmt, index) -> {
ByteCodeHelper.visitIntInsn(mv, index);
//stmt.setNull(i, Types.INTEGER);
ByteCodeHelper.visitPreparedStatementSetNull(mv, field, varIndexStmt, index);
});
}
continue;
}
columns.add(field.getName());
valueBinders.add((mv, varIndexEntity, varIndexStmt, index) -> {
if (field.isNotNull()) {
//stmt.set*(i, value);
ByteCodeHelper.visitPreparedStatementSetAny(mv, field, varIndexStmt, index, (mv2) -> ByteCodeHelper.visitGetField(mv2, field.getField(), varIndexEntity, className, () -> fieldsToReflect.add(field.getField()), classDescription));
} else {
// getFieldValue(entity);
ByteCodeHelper.visitGetField(mv, f, varIndexEntity, className, () -> fieldsToReflect.add(field.getField()), classDescription);
// if (value == null)
// stmt.setNull(i, Types.*);
// } else {
// stmt.set*(i, value);
// }
var labelAfter = new Label();
var labelElse = new Label();
mv.visitJumpInsn(Opcodes.IFNULL, labelElse);
ByteCodeHelper.visitPreparedStatementSetAny(mv, field, varIndexStmt, index, (mv2) -> ByteCodeHelper.visitGetField(mv2, field.getField(), varIndexEntity, className, () -> fieldsToReflect.add(field.getField()), classDescription));
mv.visitJumpInsn(Opcodes.GOTO, labelAfter);
mv.visitLabel(labelElse);
ByteCodeHelper.visitPreparedStatementSetNull(mv, field, varIndexStmt, index);
mv.visitLabel(labelAfter);
// mv.visitFrame(Opcodes.F_SAME, 0, new Object[0], 0, new Object[0]);
mv.visitFrame(Opcodes.F_FULL, 3, new Object[]{ByteCodeHelper.T_MYSQL_ENTITY_DB_INTERFACE.getInternalName(), ByteCodeHelper.T_PREPARED_STATEMENT.getInternalName(), entity.getTypeName().replace(".", "/")}, 0, new Object[0]);
}
});
}
return new ColumnsAndValueBinders(columns, valueBinders);
}
private void visitReadFieldsIntoEntityFunctionBody(MethodVisitor mv, List<DbField<?>> fields, boolean moveNext) {
var varIndexThis = 0;
var varIndexRes = 1;
var varIndexEntity = 2;
// 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();
// begin
mv.visitCode();
// variable scopes
mv.visitLabel(labelStartThis);
mv.visitLabel(labelStartRes);
mv.visitLabel(labelStartEntity);
if (moveNext) {
// 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 : fields) {
// var value = res.get*(1);
Consumer<MethodVisitor> resGetValue = (mv2) -> {
ByteCodeHelper.visitResultSetGetAny(mv2, field, varIndexRes);
};
// *SET*(entity, field, value);
ByteCodeHelper.visitSetField(mv, field.getField(), resGetValue, varIndexEntity, className, () -> fieldsToReflect.add(field.getField()), classDescription);
}
//return
mv.visitInsn(Opcodes.RETURN);
// variable scopes
mv.visitLabel(labelEndThis);
mv.visitLabel(labelEndRes);
mv.visitLabel(labelEndEntity);
// 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.visitMaxs(3, 3);
mv.visitEnd();
}
private interface ValueBinder {
void bind(MethodVisitor mv, int varIndexEntity, int varIndexStmt, int index);
}
@AllArgsConstructor
@Getter
private static class ColumnsAndValueBinders {
private final List<String> columns;
private final List<ValueBinder> valueBinders;
}
//endregion
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}