Compare commits
1 Commits
wip-byteco
...
count
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2876c6d2e7 |
@@ -8,15 +8,13 @@
|
||||
- references to platform specific impls
|
||||
|
||||
## Asm
|
||||
- method references
|
||||
- equals function for primitive and string + String::equalsIgnoreCase
|
||||
- equals function for registered primitive conversion types
|
||||
- IConst0Fixer: IConst0/1 on its own => false/true
|
||||
- IConst0Fixer: IConst0/1 false/true depending on compared field type
|
||||
- actually parse getter
|
||||
- resolve Predicate functions (not, and, or)
|
||||
- don't bind external vars in lambdas, instead make information (parameter and intercepted values) available in a context object
|
||||
- expose more stuff as result (e.g. accessed members/functions, ...)
|
||||
- cleanup exception handling
|
||||
- cleanup if else trees
|
||||
|
||||
## ModelBuilder
|
||||
- add registrable conversion types e.g. UUID, Date, Timestamp, Calendar, LocalTime, Instant,...
|
||||
@@ -24,9 +22,6 @@
|
||||
- field length
|
||||
- field precision
|
||||
- sql type
|
||||
- ignore() entity/field for db
|
||||
- object inlining
|
||||
- entity() with callback
|
||||
|
||||
## Annotations
|
||||
- db type
|
||||
|
||||
45
cli/pom.xml
45
cli/pom.xml
@@ -1,45 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>jef</artifactId>
|
||||
<groupId>jef</groupId>
|
||||
<version>0.1</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>cli</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<addClasspath>true</addClasspath>
|
||||
<mainClass>jef.main.Main</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>jef</groupId>
|
||||
<artifactId>migration-creator</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,27 +0,0 @@
|
||||
package jef.main;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
if (args.length == 0) {
|
||||
printHelp();
|
||||
System.exit(1);
|
||||
}
|
||||
switch (args[0].toLowerCase()) {
|
||||
case "help":
|
||||
printHelp();
|
||||
case "migration":
|
||||
MigrationCommandHandler.handleMigration(Arrays.copyOfRange(args, 1, args.length));
|
||||
|
||||
default:
|
||||
printHelp();
|
||||
}
|
||||
}
|
||||
|
||||
static void printHelp() {
|
||||
System.out.println("Usage: java -jar thisfile <command> [options]");
|
||||
MigrationCommandHandler.printHelp();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -1,279 +0,0 @@
|
||||
package jef.main;
|
||||
|
||||
import jef.model.DbContext;
|
||||
import jef.model.ModelBuilder;
|
||||
import jef.model.migration.creator.MigrationCreator;
|
||||
import jef.platform.dummy.DummyPlatform;
|
||||
import jef.util.Util;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.JarURLConnection;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.OpenOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
public class MigrationCommandHandler {
|
||||
static void handleMigration(String[] args) {
|
||||
if (args.length == 0) {
|
||||
Main.printHelp();
|
||||
}
|
||||
|
||||
try {
|
||||
switch (args[0].toLowerCase()) {
|
||||
case "add":
|
||||
handleMigrationAdd(Arrays.copyOfRange(args, 1, args.length));
|
||||
case "remove":
|
||||
handleMigrationRemove(Arrays.copyOfRange(args, 1, args.length));
|
||||
case "list":
|
||||
handleMigrationList(Arrays.copyOfRange(args, 1, args.length));
|
||||
default:
|
||||
Main.printHelp();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
private static void handleMigrationList(String[] args) {
|
||||
}
|
||||
|
||||
private static void handleMigrationRemove(String[] args) {
|
||||
}
|
||||
|
||||
private static void handleMigrationAdd(String[] args) throws Exception {
|
||||
String contextName = null;
|
||||
String migrationPackage = null;
|
||||
String targetFolder = null;
|
||||
String name = null;
|
||||
String platformType = null;
|
||||
var classPath = new ArrayList<String>();
|
||||
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
switch (args[i].toLowerCase()) {
|
||||
case "--cp":
|
||||
if (i + 1 >= args.length) {
|
||||
Main.printHelp();
|
||||
}
|
||||
classPath.addAll(List.of(args[++i].split(":")));//TODO does not work well on windows (:)
|
||||
break;
|
||||
case "--context":
|
||||
case "-c":
|
||||
if (i + 1 >= args.length) {
|
||||
Main.printHelp();
|
||||
}
|
||||
contextName = args[++i];
|
||||
break;
|
||||
case "--output":
|
||||
case "-o":
|
||||
if (i + 1 >= args.length) {
|
||||
Main.printHelp();
|
||||
}
|
||||
targetFolder = args[++i];
|
||||
break;
|
||||
case "--platform":
|
||||
case "-p":
|
||||
if (i + 1 >= args.length) {
|
||||
Main.printHelp();
|
||||
}
|
||||
platformType = args[++i];
|
||||
break;
|
||||
default:
|
||||
if (name != null) {
|
||||
Main.printHelp();
|
||||
}
|
||||
name = args[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if (Set.of(contextName, targetFolder, name).contains(null)) {
|
||||
// Main.printUsage();
|
||||
// }
|
||||
|
||||
if (name == null) {
|
||||
System.err.println("The migration requires a name.");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
//find data locations
|
||||
var urls = classPath.stream().map(e -> Util.tryGet(() -> new File(e).toURI().toURL()).orElseThrow()).toArray(URL[]::new);
|
||||
var cl = new URLClassLoader(urls, MigrationCommandHandler.class.getClassLoader());
|
||||
var context = contextName == null ? findAnyContext(cl) : findContextByName(cl, contextName);
|
||||
targetFolder = targetFolder == null ? System.getProperty("user.dir") : targetFolder;
|
||||
var targetFolderFile = new File(targetFolder);
|
||||
targetFolderFile.mkdirs();
|
||||
migrationPackage = findMigrationPackageName(targetFolder);
|
||||
var sqlPlatform = new DummyPlatform();//TODO find out from migrations or -p param
|
||||
|
||||
//find data
|
||||
var currentSnapshotFile = new File(targetFolderFile, "CurrentSnapshot.java");
|
||||
var from = !currentSnapshotFile.isFile()
|
||||
? new ModelBuilder()
|
||||
: (ModelBuilder) cl.loadClass((migrationPackage != null ? migrationPackage + "." : "") + "CurrentSnapshot").getConstructor().newInstance();
|
||||
var to = ModelBuilder.from(context.orElseThrow().getClass());
|
||||
// context.orElseThrow().onModelCreate(to);
|
||||
|
||||
//begin
|
||||
var date = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
|
||||
var className = "M" + date + "_" + name;
|
||||
|
||||
var creator = new MigrationCreator(sqlPlatform, from, to, className, migrationPackage, currentSnapshotFile.isFile() ? Files.readString(currentSnapshotFile.toPath()) : null);
|
||||
var result = creator.createMigration();
|
||||
|
||||
var fileOptions = new OpenOption[]{StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE};
|
||||
Files.writeString(new File(targetFolder, result.getMigrationClassName() + ".java").toPath(), result.getMigration(), fileOptions);
|
||||
Files.writeString(new File(targetFolder, result.getMigrationSnapshotClassName() + ".java").toPath(), result.getMigrationSnapshot(), fileOptions);
|
||||
Files.writeString(new File(targetFolder, result.getCurrentSnapshotClassName() + ".java").toPath(), result.getCurrentSnapshot(), fileOptions);
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
private static Optional<DbContext> findAnyContext(URLClassLoader cl) {
|
||||
return findContextByName(cl, "");
|
||||
}
|
||||
|
||||
private static Optional<DbContext> findContextByName(URLClassLoader cl, String contextName) {
|
||||
Util.ThrowableFunction<String, DbContext> finder = (String entryName) -> {
|
||||
var cls = (Class<? extends DbContext>) cl.loadClass(entryName.substring(0, entryName.length() - ".class".length()).replace("/", "."));
|
||||
var parent = (Class<?>) cls;
|
||||
while (parent != Object.class && parent != DbContext.class) {
|
||||
parent = parent.getSuperclass();
|
||||
}
|
||||
if (parent == DbContext.class) {
|
||||
return Util.tryGet(() -> cls.getDeclaredConstructor().newInstance()).orElse(null);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
return Arrays.stream(cl.getURLs()).map(url -> {
|
||||
URLConnection conn;
|
||||
try {
|
||||
conn = url.openConnection();
|
||||
} catch (IOException ignored) {
|
||||
return null;
|
||||
}
|
||||
if (conn instanceof JarURLConnection) {
|
||||
try (var zip = new ZipInputStream(conn.getInputStream())) {
|
||||
ZipEntry entry;
|
||||
while ((entry = zip.getNextEntry()) != null) {
|
||||
if (entry.isDirectory() || !entry.getName().endsWith(contextName + ".class")) {
|
||||
continue;
|
||||
}
|
||||
return finder.apply(entry.getName());
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
} else {
|
||||
var search = new ArrayList<URL>();
|
||||
search.add(url);
|
||||
while (search.size() > 0) {
|
||||
try {
|
||||
var currentUrl = search.remove(0);
|
||||
conn = currentUrl.openConnection();
|
||||
try (var is = conn.getInputStream();
|
||||
var isr = new InputStreamReader(is);
|
||||
var br = new BufferedReader(isr)) {
|
||||
String entry;
|
||||
while ((entry = br.readLine()) != null) {
|
||||
// System.out.println(entry);
|
||||
var newUri = currentUrl.toURI().toString();
|
||||
newUri = newUri.endsWith("/") ? newUri : newUri + "/";
|
||||
newUri += entry;
|
||||
search.add(new URI(newUri).toURL());
|
||||
if (!entry.endsWith(contextName + ".class")) {//entry.isDirectory() ||
|
||||
continue;
|
||||
}
|
||||
return finder.apply(new URI(newUri).getPath().substring(url.toURI().getPath().length()));
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
private static String findMigrationPackageName(String dir) {
|
||||
//search for java file in dir and take package name from there
|
||||
var javaFiles = new File(dir).listFiles(file -> file.isFile() && file.getName().endsWith(".java"));
|
||||
if (javaFiles.length > 0) {
|
||||
try {
|
||||
var contextFileString = Files.readAllLines(javaFiles[0].toPath()).stream().collect(Collectors.joining(" "));
|
||||
var pattern = Pattern.compile("\\s*package\\s+(.*?);");
|
||||
var matcher = pattern.matcher(contextFileString);
|
||||
if (matcher.find()) {
|
||||
return matcher.group(1);
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
// //try finding out from dir path
|
||||
var path = new File(dir).getAbsolutePath();
|
||||
var i = path.indexOf("/src/");
|
||||
if (i >= 0) {
|
||||
path = path.substring(i + "/src/".length());
|
||||
if (path.startsWith("main/")) {
|
||||
path = path.substring("main/".length());
|
||||
if (path.startsWith("java/")) {
|
||||
path = path.substring("java/".length());
|
||||
}
|
||||
}
|
||||
}
|
||||
i = path.indexOf("/target/");
|
||||
if (i >= 0) {
|
||||
path = path.substring(i + "/target/".length());
|
||||
if (path.startsWith("generated-migrations/")) {
|
||||
path = path.substring("generated-migrations/".length());
|
||||
if (path.startsWith("src/")) {
|
||||
path = path.substring("src/".length());
|
||||
}
|
||||
}
|
||||
}
|
||||
return path.replace("/", ".");
|
||||
// return null;
|
||||
}
|
||||
|
||||
public static void printHelp() {
|
||||
System.out.println("migration add --cp <classpath> [--context/-c <context class name>] [--output/-o <folder for migrations>] <name>");
|
||||
System.out.println("migration remove --cp <classpath> [--context/-c <context class name>] [--output/-o <folder for migrations>] <name>");
|
||||
System.out.println("migration list");
|
||||
}
|
||||
|
||||
// private static String findMigrationSnapshot(String dir) {
|
||||
// var javaFiles = new File(dir).listFiles(file -> file.isFile() && file.getName().endsWith(".java"));
|
||||
// if (javaFiles.length > 0) {
|
||||
// try {
|
||||
// var contextFileString = Files.readAllLines(javaFiles[0].toPath()).stream().collect(Collectors.joining(" "));
|
||||
// var pattern = Pattern.compile("\\s*package\\s+(.*?);");
|
||||
// var matcher = pattern.matcher(contextFileString);
|
||||
// if (matcher.find()) {
|
||||
// return matcher.group(1);
|
||||
// }
|
||||
// } catch (IOException ignored) {
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
36
core/pom.xml
36
core/pom.xml
@@ -1,36 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>jef</groupId>
|
||||
<artifactId>jef</artifactId>
|
||||
<version>0.1</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>core</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin><!-- generate tests jar so that test utils can be used in downstream projects -->
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>test-jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -1,18 +0,0 @@
|
||||
package jef;
|
||||
|
||||
public class MigrationException extends Exception {
|
||||
public MigrationException() {
|
||||
}
|
||||
|
||||
public MigrationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public MigrationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public MigrationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package jef.asm;
|
||||
|
||||
import jef.expressions.Expression;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Set;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public class AsmParseResult {
|
||||
private final Expression expression;
|
||||
private final Set<Field> accessedFields;
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package jef.asm;
|
||||
|
||||
import jef.serializable.SerializableFunction;
|
||||
import jef.serializable.SerializablePredicate;
|
||||
import jef.util.Check;
|
||||
import lombok.Getter;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.lang.invoke.SerializedLambda;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
@Getter
|
||||
public class AsmParser {
|
||||
private final Serializable lambda;
|
||||
private final Method method;
|
||||
|
||||
public AsmParser(SerializablePredicate<?> predicate) {
|
||||
this.lambda = Check.notNull(predicate, "predicate");
|
||||
this.method = null;
|
||||
}
|
||||
|
||||
public AsmParser(SerializableFunction<?, ?> function) {
|
||||
this.lambda = Check.notNull(function, "function");
|
||||
this.method = null;
|
||||
}
|
||||
|
||||
public AsmParser(Method method) {
|
||||
this.lambda = null;
|
||||
this.method = Check.notNull(method, "method");
|
||||
}
|
||||
|
||||
public AsmParseResult parse() throws AsmParseException {
|
||||
try {
|
||||
if (this.lambda != null) {
|
||||
return parseLambdaExpression();
|
||||
} else if (this.method != null) {
|
||||
return parseMethodExpression();
|
||||
}
|
||||
throw new IllegalStateException("Illegal state");
|
||||
} catch (Exception e) {
|
||||
throw new AsmParseException("PredicateParser: failed to parse expression: " + e.getLocalizedMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private AsmParseResult parseLambdaExpression() throws Exception {
|
||||
var cls = (Class) lambda.getClass();
|
||||
var loader = cls.getClassLoader();
|
||||
var writeReplace = cls.getDeclaredMethod("writeReplace");
|
||||
writeReplace.setAccessible(true);
|
||||
var serlambda = (SerializedLambda) writeReplace.invoke(lambda);
|
||||
Object[] args = IntStream.range(0, serlambda.getCapturedArgCount()).mapToObj(serlambda::getCapturedArg).toArray();
|
||||
|
||||
var classname = serlambda.getImplClass();
|
||||
var lambdaname = serlambda.getImplMethodName();
|
||||
cls = Class.forName(classname.replace("/", "."));
|
||||
var is = loader.getResourceAsStream(cls.getName().replace(".", "/") + ".class");
|
||||
|
||||
return parseCommon(is, lambdaname, args);
|
||||
}
|
||||
|
||||
private AsmParseResult parseMethodExpression() throws Exception {
|
||||
var cls = method.getDeclaringClass();
|
||||
var loader = cls.getClassLoader();
|
||||
InputStream is = loader.getResourceAsStream(cls.getName().replace(".", "/") + ".class");
|
||||
Object[] args = new Object[0];//TODO capturing args here? or maybe not supported since this will only be user by getter evaluation
|
||||
|
||||
return parseCommon(is, method.getName(), args);
|
||||
}
|
||||
|
||||
private AsmParseResult parseCommon(InputStream classIs, String methodname, Object[] args) throws Exception {
|
||||
var cr = new ClassReader(classIs);
|
||||
var visiter = new FilterClassVisitor(Opcodes.ASM9, methodname, args);
|
||||
cr.accept(visiter, 0);
|
||||
|
||||
return visiter.getResult();
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package jef.asm;
|
||||
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
class FilterClassVisitor extends ClassVisitor {
|
||||
private final int api;
|
||||
private final String lambdaname;
|
||||
private final Object[] args;
|
||||
|
||||
private String className;
|
||||
private FilterMethodVisitor mv;
|
||||
|
||||
protected FilterClassVisitor(int api, String lambdaname, Object[] args) {
|
||||
super(api);
|
||||
this.api = api;
|
||||
this.lambdaname = lambdaname;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||
super.visit(version, access, name, signature, superName, interfaces);
|
||||
this.className = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
|
||||
if (!name.equals(lambdaname)) {
|
||||
return super.visitMethod(access, name, descriptor, signature, exceptions);
|
||||
}
|
||||
|
||||
if (mv != null) {
|
||||
throw new IllegalStateException("multiple lambda with same name found: " + lambdaname);
|
||||
}
|
||||
return mv = new FilterMethodVisitor(api, className, descriptor, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
super.visitEnd();
|
||||
|
||||
//finish
|
||||
if (mv == null) {
|
||||
throw new IllegalStateException("lambda not found: " + lambdaname);
|
||||
}
|
||||
}
|
||||
|
||||
public AsmParseResult getResult() {
|
||||
return Optional.ofNullable(mv).map(e -> e.getResult()).orElse(null);
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -1,330 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
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())
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package jef.expressions;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
public class ConstantExpression implements Expression {
|
||||
public static final ConstantExpression V0 = new ConstantExpression(0);
|
||||
public static final ConstantExpression V1 = new ConstantExpression(1);
|
||||
public static final ConstantExpression V2 = new ConstantExpression(2);
|
||||
public static final ConstantExpression V3 = new ConstantExpression(3);
|
||||
public static final ConstantExpression V4 = new ConstantExpression(4);
|
||||
public static final ConstantExpression V5 = new ConstantExpression(5);
|
||||
|
||||
protected final Object value;
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.CONSTANT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Priority getPriority() {
|
||||
return Priority.CONSTANT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (value instanceof String) {
|
||||
return "\"" + value + "\"";
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package jef.expressions;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public class FunctionExpression implements Expression {
|
||||
public static final String TOLOWER = "TOLOWER";
|
||||
private final String function;
|
||||
private final List<Expression> parameters;
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.FUNCTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Priority getPriority() {
|
||||
return Priority.UNARY_PRE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return function + "(" + parameters.stream().map(Expression::toString).collect(Collectors.joining(", ")) + ")";
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.platform.base.DatabaseOptions;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
public class DbContextOptions {
|
||||
private final DatabaseOptions databaseOptions;
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.asm.AsmParser;
|
||||
import jef.serializable.SerializableFunction;
|
||||
import jef.serializable.SerializableObject;
|
||||
import jef.util.Check;
|
||||
import jef.util.Util;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class DbEntityBuilder<T extends SerializableObject> {
|
||||
private final ModelBuilder modelBuilder;
|
||||
private final DbEntity<T> entity;
|
||||
|
||||
public DbEntityBuilder<T> name(String name) {
|
||||
entity.setName(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
public <R> DbFieldBuilder<R> field(SerializableFunction<T, R> getter) {
|
||||
try {
|
||||
var prop = entity.getField(getter);
|
||||
if (prop == null) {
|
||||
var name = entity.extractFieldName(getter);
|
||||
var field = ReflectionUtil.getFieldsRecursive(type().get()).stream().filter(f -> f.getName().equals(name)).findFirst().orElse(null);
|
||||
if (field == null) {
|
||||
throw new RuntimeException("Field not found: " + name);
|
||||
}
|
||||
prop = new DbField<>(entity, (Class<R>) field.getType(), field);
|
||||
entity.addField(prop);
|
||||
}
|
||||
return new DbFieldBuilder<>(prop);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Invalid expression", e);
|
||||
}
|
||||
}
|
||||
|
||||
public <R> DbFieldBuilder<R> field(Field field) {
|
||||
var prop = (DbField<R>) entity.getFields().stream().filter(e -> e.getField().equals(field)).findFirst().orElse(null);
|
||||
if (prop == null) {
|
||||
prop = new DbField<>(entity, (Class<R>) field.getType(), field);
|
||||
entity.addField(prop);
|
||||
}
|
||||
return new DbFieldBuilder<>(prop);
|
||||
}
|
||||
|
||||
public <R> DbFieldBuilder<R> field(String name, String typeName) {
|
||||
var prop = (DbField<R>) entity.getFields().stream().filter(e -> e.getName().equals(name)).findFirst().orElse(null);
|
||||
if (prop == null) {
|
||||
prop = new DbField<>(entity, name, typeName);
|
||||
entity.addField(prop);
|
||||
}
|
||||
return new DbFieldBuilder<>(prop);
|
||||
}
|
||||
|
||||
//keys
|
||||
public KeyBuilder<T> hasOne(SerializableFunction<T, ?> getter) {
|
||||
Check.notNull(getter, "getter");
|
||||
var set = new AsmParser(getter).parse().getAccessedFields();
|
||||
var fields = entity.getFields().stream().filter(e -> set.contains(e.getField())).toList();
|
||||
return new KeyBuilder<>(modelBuilder, entity, fields);
|
||||
}
|
||||
|
||||
public KeyBuilder<T> hasOne(String... getterOrFieldNames) {
|
||||
Check.notNull(getterOrFieldNames, "getterOrFieldNames");
|
||||
var set = new HashSet<>(Arrays.asList(getterOrFieldNames));
|
||||
var fields = entity.getFields().stream().filter(e -> set.contains(e.getName())).toList();
|
||||
return new KeyBuilder<>(modelBuilder, entity, fields);
|
||||
}
|
||||
|
||||
public KeyBuilder<T> hasMany(SerializableFunction<T, ?> getter) {
|
||||
Check.notNull(getter, "getter");
|
||||
var set = new AsmParser(getter).parse().getAccessedFields();
|
||||
var fields = entity.getFields().stream().filter(e -> set.contains(e.getField())).toList();
|
||||
return new KeyBuilder<>(modelBuilder, entity, fields);
|
||||
}
|
||||
|
||||
public KeyBuilder<T> hasMany(String... getterOrFieldNames) {
|
||||
Check.notNull(getterOrFieldNames, "getterOrFieldNames");
|
||||
var set = new HashSet<>(Arrays.asList(getterOrFieldNames));
|
||||
var fields = entity.getFields().stream().filter(e -> set.contains(e.getName())).toList();
|
||||
return new KeyBuilder<>(modelBuilder, entity, fields);
|
||||
}
|
||||
|
||||
//getters
|
||||
public String name() {
|
||||
return entity.getName();
|
||||
}
|
||||
|
||||
public List<DbFieldBuilder<?>> fields() {
|
||||
return (List) entity.getFields().stream().map(e -> new DbFieldBuilder(e)).toList();
|
||||
}
|
||||
|
||||
public Optional<Class<T>> type() {
|
||||
return Optional.ofNullable(entity.getType()).or(() -> Util.tryGet(() -> (Class<T>) Class.forName(typeName())));
|
||||
}
|
||||
|
||||
public String typeName() {
|
||||
return entity.getTypeName();
|
||||
}
|
||||
|
||||
public String className() {
|
||||
var s = entity.getTypeName().replace("$", ".").split("\\.");
|
||||
return s[s.length - 1];
|
||||
}
|
||||
|
||||
public DbEntity<T> getEntity() { //TODO make this libaray private somehow
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return entity.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.model.annotations.Generated;
|
||||
import jef.model.constraints.IndexConstraint;
|
||||
import jef.model.constraints.KeyConstraint;
|
||||
import jef.model.constraints.UniqueKeyConstraint;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class DbFieldBuilder<T> {
|
||||
private final DbField<T> field;
|
||||
|
||||
public DbFieldBuilder<T> name(String name) {
|
||||
field.setName(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
public DbFieldBuilder<T> isDatabaseField(boolean isDatabase) {
|
||||
field.setDatabaseField(isDatabase);
|
||||
return this;
|
||||
}
|
||||
|
||||
public DbFieldBuilder<T> isModelField(boolean isModel) {
|
||||
field.setModelField(isModel);
|
||||
return this;
|
||||
}
|
||||
|
||||
public DbFieldBuilder<T> sqlType(String sqlType) {
|
||||
field.setSqlType(sqlType);
|
||||
return this;
|
||||
}
|
||||
|
||||
public DbFieldBuilder<T> isNotNull() {
|
||||
return isNotNull(true);
|
||||
}
|
||||
|
||||
public DbFieldBuilder<T> isNotNull(boolean notnull) {
|
||||
field.setNotNull(notnull);
|
||||
return this;
|
||||
}
|
||||
|
||||
public DbFieldBuilder<T> generated(Generated.Type type) {
|
||||
field.setGenerated(type);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void isUnique() {
|
||||
isUnique(true);
|
||||
}
|
||||
|
||||
public void isUnique(boolean unique) {
|
||||
var entity = field.getEntity();
|
||||
var constr = entity.getUniqueKeys().stream().filter(u -> u.getFields().size() == 1 && u.getFields().get(0) == field).findFirst();
|
||||
if (!constr.isPresent() && unique) {
|
||||
entity.addUniqueKey(new UniqueKeyConstraint(entity, new ArrayList<>(List.of(field))));
|
||||
} else if (constr.isPresent() && !unique) {
|
||||
entity.dropUniqueKey(constr.get());
|
||||
} //else do nothing
|
||||
}
|
||||
|
||||
public void isKey() {
|
||||
isKey(true);
|
||||
}
|
||||
|
||||
public void isKey(boolean key) {
|
||||
var entity = field.getEntity();
|
||||
var constr = entity.getKeys().stream().filter(u -> u.getFields().size() == 1 && u.getFields().get(0) == field).findFirst();
|
||||
if (!constr.isPresent() && key) {
|
||||
entity.addKey(new KeyConstraint(entity, new ArrayList<>(List.of(field))));
|
||||
} else if (constr.isPresent() && !key) {
|
||||
entity.dropKey(constr.get());
|
||||
} //else do nothing
|
||||
}
|
||||
|
||||
public void isIndex() {
|
||||
isIndex(true);
|
||||
}
|
||||
|
||||
public void isIndex(boolean index) {
|
||||
var entity = field.getEntity();
|
||||
var constr = entity.getIndexes().stream().filter(u -> u.getFields().size() == 1 && u.getFields().get(0) == field).findFirst();
|
||||
if (!constr.isPresent() && index) {
|
||||
entity.addIndex(new IndexConstraint(entity, new ArrayList<>(List.of(field))));
|
||||
} else if (constr.isPresent() && !index) {
|
||||
entity.dropIndex(constr.get());
|
||||
} //else do nothing
|
||||
}
|
||||
|
||||
public DbField<T> getField() {//TODO make this library private
|
||||
return field;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.util.Log;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class EntityDefaultConstructorChecker {
|
||||
static void checkEntities(ModelBuilder mb) {
|
||||
for (DbEntityBuilder<?> entity : mb.entities()) {
|
||||
checkEntity(entity);
|
||||
}
|
||||
}
|
||||
|
||||
static void checkEntity(DbEntityBuilder<?> entity) {
|
||||
Log.debug("Checking default constructor exists for entity '" + entity.name() + "' of type " + entity.className());
|
||||
|
||||
//check no arg constructor
|
||||
Class<?> clazz = entity.type().orElseThrow();
|
||||
if (Arrays.stream(clazz.getDeclaredConstructors()).noneMatch(e -> e.getParameterCount() == 0)) {
|
||||
throw new ModelException("Class '" + clazz.getSimpleName() + "' does not have a default constructor!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.DbSet;
|
||||
import jef.model.annotations.Clazz;
|
||||
import jef.model.annotations.Transient;
|
||||
import jef.serializable.SerializableObject;
|
||||
import jef.util.Log;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
|
||||
class EntityInitializer {
|
||||
static void initEntities(ModelBuilder mb, Class<? extends DbContext> context) {
|
||||
for (Field ctxField : context.getDeclaredFields()) {
|
||||
if (!DbSet.class.isAssignableFrom(ctxField.getType())) {
|
||||
continue;
|
||||
}
|
||||
Clazz clazzAnnotation = ctxField.getAnnotation(Clazz.class);
|
||||
if (clazzAnnotation == null) {
|
||||
throw new ModelException("DbSet " + ctxField.getName() + " is missing the " + Clazz.class.getSimpleName() + " annotation");
|
||||
}
|
||||
if (!SerializableObject.class.isAssignableFrom(clazzAnnotation.value())) {
|
||||
throw new ModelException("DbSet " + ctxField.getName() + " has a class in its " + Clazz.class.getSimpleName() + " annotation that does not inherit from " + SerializableObject.class.getSimpleName() + ": " + clazzAnnotation.value().getSimpleName());
|
||||
}
|
||||
var dbSetClazz = (Class<? extends SerializableObject>) clazzAnnotation.value();
|
||||
initEntity(mb, dbSetClazz, ctxField.getName(), true);
|
||||
}
|
||||
}
|
||||
|
||||
static void initEntity(ModelBuilder mb, Class<? extends SerializableObject> clazz, String name, boolean overrideName) {
|
||||
var existingEntity = mb.getEntity(clazz);
|
||||
if (existingEntity != null) {
|
||||
//allow DbSets to override the name
|
||||
if (overrideName) {
|
||||
existingEntity.setName(name);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Log.debug("Initializing entity '" + name + "' of type " + clazz.getName());
|
||||
var entity = mb.entity(clazz);
|
||||
entity.name(name);
|
||||
|
||||
var fields = ReflectionUtil.getFieldsRecursive(clazz);
|
||||
for (var f : fields) {
|
||||
if (f.getAnnotationsByType(Transient.class).length > 0) {
|
||||
continue;
|
||||
}
|
||||
Log.debug("Initializing field '" + f.getName() + "' with type " + f.getType().getName());
|
||||
if (Collection.class.isAssignableFrom(f.getType())) {
|
||||
//find a Collection field with the same Model
|
||||
//e.g. class Entity { @Clazz(Entity2.class) List<Entity2> ent; @Clazz(Entity2.class) Set<Entity2> ent2; }
|
||||
var clazzAnnotation = f.getAnnotation(Clazz.class);
|
||||
if (clazzAnnotation == null) {
|
||||
throw new ModelException("Field " + clazz.getSimpleName() + "::" + f.getName() + " is missing the " + Clazz.class.getSimpleName() + " annotation");
|
||||
}
|
||||
var fClazz = clazzAnnotation.value();
|
||||
if (!SerializableObject.class.isAssignableFrom(fClazz)) {
|
||||
throw new ModelException("Field " + clazz.getSimpleName() + "::" + f.getName() + " has a class in its " + Clazz.class.getSimpleName() + " annotation that does not inherit from " + SerializableObject.class.getSimpleName() + ": " + fClazz.getSimpleName());
|
||||
}
|
||||
var foundCollection = entity.fields().stream()
|
||||
.filter(e -> Collection.class.isAssignableFrom(e.getField().getType())
|
||||
&& e.getField().isModelField()
|
||||
&& e.getField().getField().getAnnotationsByType(Clazz.class).length > 0
|
||||
&& e.getField().getField().getAnnotationsByType(Clazz.class)[0].value() == fClazz)
|
||||
.findFirst();
|
||||
if (foundCollection.isPresent()) {
|
||||
throw new ModelException("Model " + entity.className() + " contains multiple 1 to N relations with type " + fClazz.getSimpleName());
|
||||
}
|
||||
entity.field(f);
|
||||
initEntity(mb, (Class<? extends SerializableObject>) fClazz, fClazz.getSimpleName(), false);
|
||||
} else if (SerializableObject.class.isAssignableFrom(f.getType())) {
|
||||
entity.field(f);
|
||||
initEntity(mb, (Class<? extends SerializableObject>) f.getType(), f.getType().getSimpleName(), false);
|
||||
} else {
|
||||
var dbField = entity.field(f);
|
||||
if (f.getType().isPrimitive()) {
|
||||
dbField.isNotNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.model.constraints.ForeignKeyConstraint;
|
||||
import jef.serializable.SerializableObject;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class ForeignKeyBuilder<T extends SerializableObject, R extends SerializableObject> {
|
||||
private final ForeignKeyConstraint foreignKey;
|
||||
|
||||
public ForeignKeyBuilder<T, R> onUpdate(ForeignKeyConstraint.Action onUpdate) {
|
||||
foreignKey.setOnUpdate(onUpdate);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ForeignKeyBuilder<T, R> onDelete(ForeignKeyConstraint.Action onDelete) {
|
||||
foreignKey.setOnDelete(onDelete);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.asm.OptimizedAsmParser;
|
||||
import jef.model.annotations.ForeignKey;
|
||||
import jef.serializable.SerializableObject;
|
||||
import jef.util.Util;
|
||||
|
||||
class ForeignKeyExposeInitializer {
|
||||
static void initForeignKeyExposures(ModelBuilder mb) {
|
||||
var entities = mb.entities();
|
||||
for (int i = 0; i < entities.size(); i++) {
|
||||
var entity = entities.get(i);
|
||||
initForeignKeyExposures(mb, entity);
|
||||
}
|
||||
}
|
||||
|
||||
static void initForeignKeyExposures(ModelBuilder mb, DbEntityBuilder<?> entityBuilder) {
|
||||
var fields = entityBuilder.fields();
|
||||
for (var f : fields) {
|
||||
if (SerializableObject.class.isAssignableFrom(f.getField().getType())) {
|
||||
var idFields = fields.stream()
|
||||
.filter(e -> {
|
||||
//ignore self
|
||||
if (e.getField().getName().equals(f.getField().getName())) {
|
||||
return false;
|
||||
}
|
||||
//match with name in annotation
|
||||
var anno = e.getField().getField().getAnnotation(ForeignKey.class);
|
||||
if (anno == null) {
|
||||
return false;
|
||||
}
|
||||
if (anno.entity() != SerializableObject.class) { // not a foreign key exposure, ignore
|
||||
return false;
|
||||
}
|
||||
var fieldname = anno.getterOrField();
|
||||
|
||||
//if getter, extract getter field
|
||||
var method = Util.tryGet(() -> entityBuilder.type().orElseThrow().getMethod(anno.getterOrField()));
|
||||
if (method.isPresent()) {
|
||||
var res = new OptimizedAsmParser(method.get()).parse();
|
||||
fieldname = res.getAccessedFields().stream().findFirst().orElseThrow().getName();
|
||||
}
|
||||
if (!SerializableObject.class.isAssignableFrom(entityBuilder.getEntity().getField(fieldname).getType())) {
|
||||
throw new ModelException("The getter/field in " + entityBuilder.className() + "::" + f.getField().getField().getName() + " is not a " + SerializableObject.class.getSimpleName() + " (via @ForeignKey in TestClass::testFk)");
|
||||
}
|
||||
return f.getField().getName().equals(fieldname);
|
||||
})
|
||||
.toList();
|
||||
idFields.forEach(e -> {
|
||||
e.getField().setExposingForeignKeyOf(f.getField());
|
||||
e.getField().setDatabaseField(idFields.stream().findFirst().orElseThrow() == e);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.model.annotations.Clazz;
|
||||
import jef.model.annotations.ForeignKey;
|
||||
import jef.model.annotations.Transient;
|
||||
import jef.serializable.SerializableObject;
|
||||
import jef.util.Log;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
|
||||
class ForeignKeyInitializer {
|
||||
static void initForeignKeys(ModelBuilder mb) {
|
||||
var entities = mb.entities();
|
||||
for (int i = 0; i < entities.size(); i++) {
|
||||
var entity = entities.get(i);
|
||||
initForeignKeys(mb, entity);
|
||||
}
|
||||
}
|
||||
|
||||
static void initForeignKeys(ModelBuilder mb, DbEntityBuilder<?> entityBuilder) {
|
||||
var entity = entityBuilder.getEntity();
|
||||
var fields = ReflectionUtil.getFieldsRecursive(entityBuilder.type().orElseThrow(() -> new IllegalStateException("Class not found: " + entityBuilder.typeName())));
|
||||
for (var f : fields) {
|
||||
if (f.getAnnotationsByType(Transient.class).length > 0) {
|
||||
continue;
|
||||
}
|
||||
if (Collection.class.isAssignableFrom(f.getType())) {
|
||||
do1toN(mb, entityBuilder, entity, f);
|
||||
} else if (SerializableObject.class.isAssignableFrom(f.getType())) {//1 to 1 relation
|
||||
do1to1(mb, entityBuilder, entity, f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void do1toN(ModelBuilder mb, DbEntityBuilder<?> entityBuilder, DbEntity<?> entity, Field f) {
|
||||
Clazz clazzAnnotation = f.getAnnotation(Clazz.class);
|
||||
if (clazzAnnotation == null) {
|
||||
throw new ModelException("Collection " + entityBuilder.className() + "." + f.getName() + " is missing the " + Clazz.class.getSimpleName() + " annotation");
|
||||
}
|
||||
var otherEntity = mb.getEntity((Class<? extends SerializableObject>) clazzAnnotation.value());
|
||||
if (otherEntity == null) {
|
||||
//all types must be initialized by EntityInitializer
|
||||
throw new IllegalStateException(ForeignKeyInitializer.class.getSimpleName() + ": Entity of type " + clazzAnnotation.value() + " does not exist");
|
||||
// EntityInitializer.initEntity(mb, (Class<? extends SerializableObject>) clazzAnnotation.value(), f.getName());
|
||||
// otherEntity = mb.getEntity((Class<? extends SerializableObject>) clazzAnnotation.value());
|
||||
// PrimaryKeyInitializer.initPrimaryKeys(mb, otherEntity.builder(mb));
|
||||
}
|
||||
var primary = entityBuilder.getEntity().getPrimaryKey();
|
||||
if (primary == null) {
|
||||
throw new ModelException("Entity " + entityBuilder.className() + " is missing a primary key and therefore cannot be referenced by " + otherEntity.builder(mb).className());
|
||||
}
|
||||
var otherEntityF = otherEntity;
|
||||
var otherFields = primary.getFields().stream()
|
||||
.map(e -> {
|
||||
//find list<object> in other entity (N to N relation)
|
||||
if (otherEntityF != entity) { //ignore on recursive models
|
||||
var otherEntityListField = otherEntityF.getFields().stream()
|
||||
.filter(oef -> Collection.class.isAssignableFrom(oef.getType())
|
||||
&& oef.getField().getAnnotationsByType(Clazz.class).length > 0
|
||||
&& oef.getField().getAnnotationsByType(Clazz.class)[0].value().getName().equals(entity.getTypeName())).findFirst();
|
||||
if (otherEntityListField.isPresent()) {
|
||||
throw new ModelException("N to N relations need to explicitly defined via a mapping model ("
|
||||
+ otherEntityF.builder(mb).className() + "::" + otherEntityListField.get().getTypeName() + " and "
|
||||
+ entityBuilder.className() + "::" + f.getType().getName() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
//find object in other entity (1 to N relation)
|
||||
var otherEntityObjectField = otherEntityF.getFields().stream().filter(oef -> oef.getTypeName().equals(entity.getTypeName())).findFirst();
|
||||
if (otherEntityObjectField.isPresent()) {
|
||||
return new FieldSearchResult(otherEntityObjectField.get(), false);
|
||||
}
|
||||
|
||||
//find objectId in other entity (1 to N relation)
|
||||
// var idFieldName = entity.getType().getSimpleName().substring(0, 1).toLowerCase(Locale.ROOT)
|
||||
// + entity.getType().getSimpleName().substring(1)
|
||||
// + e.getName().substring(0, 1).toUpperCase(Locale.ROOT)
|
||||
// + e.getName().substring(1);
|
||||
var idFieldName = f.getName()
|
||||
+ e.getName().substring(0, 1).toUpperCase(Locale.ROOT)
|
||||
+ e.getName().substring(1);
|
||||
var otherEntityIdField = otherEntityF.getFields().stream().filter(oef -> oef.getName().equals(idFieldName)).findFirst();
|
||||
if (otherEntityIdField.isPresent()) {
|
||||
return new FieldSearchResult(otherEntityIdField.get(), otherEntityIdField.get().getField().getAnnotationsByType(ForeignKey.class).length == 0);
|
||||
}
|
||||
|
||||
// //add shadow field
|
||||
// var entityField = new DbField<>(otherEntityF, f.getDeclaringClass(), null, f.getName());
|
||||
// entityField.setNotNull(true);
|
||||
// entityField = otherEntityF.addIfAbsent(entityField);
|
||||
|
||||
//add shadow id field
|
||||
var entityIdField = new DbField<>(otherEntityF, entityBuilder.type().orElseThrow(), null, idFieldName);
|
||||
entityIdField.setNotNull(true);
|
||||
entityIdField.setDatabaseField(true);
|
||||
entityIdField = otherEntityF.addIfAbsent(entityIdField);
|
||||
// if (entityIdField.getExposingForeignKeyOf() == null) {
|
||||
// entityIdField.setExposingForeignKeyOf(entityField);
|
||||
// }
|
||||
|
||||
return new FieldSearchResult(entityIdField, true);
|
||||
})
|
||||
.toList();
|
||||
if (otherFields.stream().anyMatch(FieldSearchResult::isCreateForeignKey)) {
|
||||
otherEntity.builder(mb)
|
||||
.hasOne(otherFields.stream().map(e -> e.getField().getName()).toArray(String[]::new))
|
||||
.withMany(entity.getTypeName(), primary.getFields().stream().map(DbField::getName).toArray(String[]::new));
|
||||
// otherEntity.addForeignKey(new ForeignKeyConstraint(otherEntity, (List) otherFields.stream().map(FieldSearchResult::getField).toList(),
|
||||
// entity, primary.getFields(), ForeignKeyConstraint.Action.RESTRICT, ForeignKeyConstraint.Action.CASCADE));
|
||||
}
|
||||
}
|
||||
|
||||
private static void do1to1(ModelBuilder mb, DbEntityBuilder<?> entityBuilder, DbEntity<?> entity, Field f) {
|
||||
Log.debug("Initializing foreign key for " + entityBuilder.className() + "::" + f.getName());
|
||||
var otherEntity = mb.getEntity((Class<? extends SerializableObject>) f.getType());
|
||||
if (otherEntity == null) {
|
||||
//all types must be initialized by EntityInitializer
|
||||
throw new IllegalStateException(ForeignKeyInitializer.class.getSimpleName() + ": Entity of type " + f.getType().getName() + " does not exist");
|
||||
// EntityInitializer.initEntity(mb, (Class<? extends SerializableObject>) f.getType(), f.getName());
|
||||
// otherEntity = mb.getEntity((Class<? extends SerializableObject>) f.getType());
|
||||
// PrimaryKeyInitializer.initPrimaryKeys(mb, otherEntity.builder(mb));
|
||||
}
|
||||
var primary = otherEntity.getPrimaryKey();
|
||||
if (primary == null) {
|
||||
throw new ModelException("Entity " + otherEntity.builder(mb).className() + " is missing a primary key and therefore cannot be referenced by " + entityBuilder.className());
|
||||
}
|
||||
var otherEntityF = otherEntity;
|
||||
var otherFields = primary.getFields().stream()
|
||||
.map(otherPrimaryField -> {
|
||||
// //find list<object> in other entity (N to 1 relation)
|
||||
// var entityListField = entity.getFields().stream()
|
||||
// .filter(oef -> Collection.class.isAssignableFrom(oef.getType())
|
||||
// && oef.getField().getAnnotationsByType(Clazz.class).length > 0
|
||||
// && oef.getField().getAnnotationsByType(Clazz.class)[0].value().getName().equals(otherEntityF.getTypeName())).toList();
|
||||
//// if (entityListField.size() == 1) {
|
||||
//// return new FieldSearchResult(entityListField.get(0), true);
|
||||
//// }
|
||||
|
||||
//find object in other entity (1 to 1 relation)
|
||||
// var entityObjectFields = entity.getFields().stream().filter(oef -> oef.getTypeName().equals(otherEntityF.getTypeName())).toList();
|
||||
// if (entityObjectField.isPresent()) {
|
||||
// return new FieldSearchResult(entityObjectField.get(), false);
|
||||
// }
|
||||
|
||||
//find objectId in other entity (1 to 1 relation)
|
||||
//search for exposing foreign key field first
|
||||
var entityIdField = entity.getFields().stream()
|
||||
.filter(oef -> oef.getExposingForeignKeyOf() != null && oef.getExposingForeignKeyOf().getField().equals(f))
|
||||
.findFirst();
|
||||
|
||||
//search for field with name object field name + primary field name
|
||||
var idFieldName = f.getName()
|
||||
+ otherPrimaryField.getName().substring(0, 1).toUpperCase(Locale.ROOT)
|
||||
+ otherPrimaryField.getName().substring(1);
|
||||
if (!entityIdField.isPresent()) {
|
||||
entityIdField = entity.getFields().stream().filter(oef -> oef.getName().equals(idFieldName)).findFirst();
|
||||
}
|
||||
if (entityIdField.isPresent()) {
|
||||
return new FieldSearchResult(entityIdField.get(), true);//entityIdField.get().getField().getAnnotationsByType(ForeignKey.class).length == 0);
|
||||
}
|
||||
|
||||
var field = new DbField<>(entity, otherPrimaryField.getType(), null, idFieldName);
|
||||
field.setExposingForeignKeyOf(entity.getField(f.getName()));
|
||||
field = entity.addIfAbsent(field);
|
||||
// if (entityListField.size() == 1) {
|
||||
//// field.setForeignKeyObjectField(entityListField.get(0));
|
||||
//// entityListField.get(0).setForeignKeyObjectField(field);
|
||||
// } else
|
||||
// if (entityObjectFields.size() == 1) {
|
||||
// field.setExposingForeignKeyOf(entityObjectFields.get(0));
|
||||
// entityObjectFields.get(0).setForeignKeyObjectField(field);
|
||||
// }
|
||||
return new FieldSearchResult(field, true);
|
||||
// return new DbField<>(entity, e.getType(), null, f.getName() + e.getName().substring(0, 1).toUpperCase(Locale.ROOT) + e.getName().substring(1))
|
||||
})
|
||||
.toList();
|
||||
if (otherFields.stream().anyMatch(FieldSearchResult::isCreateForeignKey)) {
|
||||
entityBuilder
|
||||
.hasOne(otherFields.stream().map(e -> e.getField().getName()).toArray(String[]::new))
|
||||
.withOne(otherEntity.getTypeName(), primary.getFields().stream().map(DbField::getName).toArray(String[]::new));
|
||||
// entity.addForeignKey(new ForeignKeyConstraint(entity, (List) otherFields.stream().map(FieldSearchResult::getField).toList(),
|
||||
// otherEntity, primary.getFields(), ForeignKeyConstraint.Action.RESTRICT, ForeignKeyConstraint.Action.CASCADE));
|
||||
}
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
private static class FieldSearchResult {
|
||||
private final DbField<?> field;
|
||||
private final boolean createForeignKey;
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.asm.AsmParser;
|
||||
import jef.model.constraints.ForeignKeyConstraint;
|
||||
import jef.model.constraints.IndexConstraint;
|
||||
import jef.model.constraints.KeyConstraint;
|
||||
import jef.model.constraints.PrimaryKeyConstraint;
|
||||
import jef.model.constraints.UniqueKeyConstraint;
|
||||
import jef.serializable.SerializableFunction;
|
||||
import jef.serializable.SerializableObject;
|
||||
import jef.util.Check;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class KeyBuilder<T extends SerializableObject> {
|
||||
private final ModelBuilder modelBuilder;
|
||||
private final DbEntity<T> entity;
|
||||
private final List<DbField<?>> fields;
|
||||
|
||||
public <R extends SerializableObject> ForeignKeyBuilder<T, R> withOne(SerializableFunction<T, R> getter) {
|
||||
Check.notNull(getter, "getter");
|
||||
var set = new AsmParser(getter).parse().getAccessedFields();
|
||||
// if (set.isEmpty()) throw new IllegalArgumentException("no fields found");
|
||||
// else if (set.size() > 1) throw new IllegalArgumentException("more than 1 field found");
|
||||
// var refFields = entity.getFields().stream().filter(e -> set.contains(e.getField())).toList();
|
||||
// var refEntities = refFields.stream().map(e -> modelBuilder.getEntity(e.getTypeName())).toList();
|
||||
// var existingFk = entity.getForeignKeys().stream().filter(fk -> fk.getEntity() == this.entity
|
||||
// && fk.getFields().equals(this.fields)
|
||||
// && fk.getReferencedEntity() == refEntities.get(0)
|
||||
// && fk.getReferencedFields().equals(refFields))
|
||||
// .findFirst().orElse(null);
|
||||
// if (existingFk == null) {
|
||||
// existingFk = new ForeignKeyConstraint(this.entity, this.fields, refEntities.get(0), refFields, ForeignKeyConstraint.Action.RESTRICT, ForeignKeyConstraint.Action.CASCADE);
|
||||
// this.entity.addForeignKey(existingFk);
|
||||
// }
|
||||
// return new ForeignKeyBuilder<>(existingFk);
|
||||
// var rClassName = getter.getClass().
|
||||
return withOne("", set.stream().map(Field::getName).toArray(String[]::new));
|
||||
}
|
||||
|
||||
public <R extends SerializableObject> ForeignKeyBuilder<T, R> withOne(String referencedClass, String... getterOrFieldNames) {
|
||||
Check.notNull(getterOrFieldNames, "getterOrFieldNames");
|
||||
// var set = new HashSet<>(Arrays.asList(getterOrFieldNames));
|
||||
// if (set.isEmpty()) throw new IllegalArgumentException("no fields found");
|
||||
// else if (set.size() > 1) throw new IllegalArgumentException("more than 1 field found");
|
||||
// var refFields = entity.getFields().stream().filter(e -> set.contains(e.getName())).toList();
|
||||
// var refEntities = refFields.stream().map(e -> modelBuilder.getEntity(e.getTypeName())).toList();
|
||||
var refEntities = List.of(modelBuilder.getEntity(referencedClass));
|
||||
var refFields = IntStream.range(0, getterOrFieldNames.length)
|
||||
.mapToObj(i -> {
|
||||
// var field = fields.get(i);
|
||||
// if (field.getExposingForeignKeyOf() != null) {
|
||||
// field = field.getExposingForeignKeyOf();
|
||||
// }
|
||||
return refEntities.get(0).getField(getterOrFieldNames[i]);
|
||||
})
|
||||
.toList();
|
||||
// var refEntities = refFields.stream().map(e -> e.getEntity()).toList();
|
||||
var existingFk = entity.getForeignKeys().stream().filter(fk -> fk.getEntity() == this.entity
|
||||
&& fk.getFields().equals(this.fields)
|
||||
&& fk.getReferencedEntity() == refEntities.get(0)
|
||||
&& fk.getReferencedFields().equals(refFields))
|
||||
.findFirst().orElse(null);
|
||||
if (existingFk == null) {
|
||||
existingFk = new ForeignKeyConstraint(this.entity, this.fields, refEntities.get(0), (List) refFields, ForeignKeyConstraint.Action.RESTRICT, ForeignKeyConstraint.Action.CASCADE);
|
||||
this.entity.addForeignKey(existingFk);
|
||||
}
|
||||
return new ForeignKeyBuilder<>(existingFk);
|
||||
}
|
||||
|
||||
public <R extends SerializableObject> ForeignKeyBuilder<T, R> withMany(SerializableFunction<T, R> getter) {
|
||||
Check.notNull(getter, "getter");
|
||||
var set = new AsmParser(getter).parse().getAccessedFields();
|
||||
// if (set.isEmpty()) throw new IllegalArgumentException("no fields found");
|
||||
// else if (set.size() > 1) throw new IllegalArgumentException("more than 1 field found");
|
||||
// var refFields = entity.getFields().stream().filter(e -> set.contains(e.getField())).toList();
|
||||
// var refEntities = refFields.stream().map(e -> modelBuilder.getEntity(e.getTypeName())).toList();
|
||||
// var existingFk = entity.getForeignKeys().stream().filter(fk -> fk.getEntity() == this.entity
|
||||
// && fk.getFields().equals(this.fields)
|
||||
// && fk.getReferencedEntity() == refEntities.get(0)
|
||||
// && fk.getReferencedFields().equals(refFields))
|
||||
// .findFirst().orElse(null);
|
||||
// if (existingFk == null) {
|
||||
// existingFk = new ForeignKeyConstraint(this.entity, this.fields, refEntities.get(0), refFields, ForeignKeyConstraint.Action.RESTRICT, ForeignKeyConstraint.Action.CASCADE);
|
||||
// this.entity.addForeignKey(existingFk);
|
||||
// }
|
||||
// return new ForeignKeyBuilder<>(existingFk);
|
||||
return withMany("", set.stream().map(Field::getName).toArray(String[]::new));
|
||||
}
|
||||
|
||||
public <R extends SerializableObject> ForeignKeyBuilder<T, R> withMany(String referencedClass, String... getterOrFieldNames) {
|
||||
Check.notNull(getterOrFieldNames, "getterOrFieldNames");
|
||||
// var set = new HashSet<>(Arrays.asList(getterOrFieldNames));
|
||||
// var refFields = entity.getFields().stream().filter(e -> set.contains(e.getName())).toList();
|
||||
// var refEntities = refFields.stream().map(e -> modelBuilder.getEntity(e.getTypeName())).toList();
|
||||
var refEntities = List.of(modelBuilder.getEntity(referencedClass));
|
||||
var refFields = IntStream.range(0, getterOrFieldNames.length)
|
||||
.mapToObj(i -> {
|
||||
// var field = fields.get(i);
|
||||
// if (field.getExposingForeignKeyOf() != null) {
|
||||
// field = field.getExposingForeignKeyOf();
|
||||
// }
|
||||
// var type = (Class<? extends SerializableObject>) field.getType();
|
||||
// return modelBuilder.getEntity(type).getField(getterOrFieldNames[i]);
|
||||
return refEntities.get(0).getField(getterOrFieldNames[i]);
|
||||
})
|
||||
.toList();
|
||||
// var refEntities = refFields.stream().map(e -> e.getEntity()).toList();
|
||||
var existingFk = entity.getForeignKeys().stream().filter(fk -> fk.getEntity() == this.entity
|
||||
&& fk.getFields().equals(this.fields)
|
||||
&& fk.getReferencedEntity() == refEntities.get(0)
|
||||
&& fk.getReferencedFields().equals(refFields))
|
||||
.findFirst().orElse(null);
|
||||
if (existingFk == null) {
|
||||
existingFk = new ForeignKeyConstraint(this.entity, this.fields, refEntities.get(0), (List) refFields, ForeignKeyConstraint.Action.RESTRICT, ForeignKeyConstraint.Action.CASCADE);
|
||||
this.entity.addForeignKey(existingFk);
|
||||
}
|
||||
return new ForeignKeyBuilder<>(existingFk);
|
||||
}
|
||||
|
||||
public KeyBuilder<T> notNull(boolean notNull) {
|
||||
//TODO
|
||||
return this;
|
||||
}
|
||||
|
||||
public void isUnique() {
|
||||
isUnique(true);
|
||||
}
|
||||
|
||||
public void isUnique(boolean unique) {
|
||||
var key = new UniqueKeyConstraint(entity, fields);
|
||||
if (unique) {
|
||||
entity.addUniqueKey(key);
|
||||
} else {
|
||||
entity.dropUniqueKey(key);
|
||||
}
|
||||
}
|
||||
|
||||
public void isPrimaryKey() {
|
||||
isPrimaryKey(true);
|
||||
}
|
||||
|
||||
public void isPrimaryKey(boolean primary) {
|
||||
if (primary) {
|
||||
entity.setPrimaryKey(new PrimaryKeyConstraint(entity, fields));
|
||||
} else {
|
||||
entity.setPrimaryKey(null);
|
||||
|
||||
//drop referencing foreign keys
|
||||
modelBuilder.entities().forEach(e -> {
|
||||
var remove = e.getEntity().getForeignKeys().stream().filter(fk -> fk.getReferencedEntity() == this.entity).toList();
|
||||
remove.forEach(fk -> e.getEntity().dropForeignKey(fk));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void isKey() {
|
||||
isKey(true);
|
||||
}
|
||||
|
||||
public void isKey(boolean key) {
|
||||
var k = new KeyConstraint(entity, fields);
|
||||
if (key) {
|
||||
entity.addKey(k);
|
||||
} else {
|
||||
entity.dropKey(k);
|
||||
}
|
||||
}
|
||||
|
||||
public void isIndex() {
|
||||
isIndex(true);
|
||||
}
|
||||
|
||||
public void isIndex(boolean index) {
|
||||
var i = new IndexConstraint(entity, fields);
|
||||
if (index) {
|
||||
entity.addIndex(i);
|
||||
} else {
|
||||
entity.dropIndex(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.model.annotations.processors.AnnotationProcessor;
|
||||
import jef.model.annotations.processors.ForeignKeyProcessor;
|
||||
import jef.model.annotations.processors.GeneratedProcessor;
|
||||
import jef.model.annotations.processors.IndexProcessor;
|
||||
import jef.model.annotations.processors.KeyProcessor;
|
||||
import jef.model.annotations.processors.NotNullProcessor;
|
||||
import jef.model.annotations.processors.UniqueProcessor;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
public class ModelBuilderOptions {
|
||||
private List<AnnotationProcessor> annotationProcessors;
|
||||
private DbContextOptions contextOptions;
|
||||
|
||||
public ModelBuilderOptions() {
|
||||
this.annotationProcessors = new ArrayList<>(List.of(
|
||||
NotNullProcessor.INSTANCE,
|
||||
UniqueProcessor.INSTANCE,
|
||||
IndexProcessor.INSTANCE,
|
||||
KeyProcessor.INSTANCE,
|
||||
ForeignKeyProcessor.INSTANCE,
|
||||
GeneratedProcessor.INSTANCE
|
||||
));
|
||||
this.contextOptions = new DbContextOptions(null);//TODO
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package jef.model.annotations;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Documented
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Generated {
|
||||
Type value() default Type.IDENTITY;
|
||||
|
||||
enum Type {
|
||||
NONE,//no value is generated
|
||||
IDENTITY,//value is generated on insert
|
||||
COMPUTED,//value is generated on insert and update
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package jef.model.annotations.processors;
|
||||
|
||||
import jef.model.DbEntityBuilder;
|
||||
import jef.model.DbFieldBuilder;
|
||||
import jef.model.ModelBuilder;
|
||||
import jef.model.annotations.Generated;
|
||||
import jef.serializable.SerializableObject;
|
||||
|
||||
public class GeneratedProcessor implements AnnotationProcessor {
|
||||
public static final GeneratedProcessor INSTANCE = new GeneratedProcessor();
|
||||
|
||||
@Override
|
||||
public void apply(ModelBuilder mb) {
|
||||
for (DbEntityBuilder<? extends SerializableObject> entity : mb.entities()) {
|
||||
for (DbFieldBuilder<?> field : entity.fields()) {
|
||||
if (field.getField().getField() == null) {
|
||||
continue;
|
||||
}
|
||||
var annotation = field.getField().getField().getAnnotation(Generated.class);
|
||||
if (annotation == null) {
|
||||
continue;
|
||||
}
|
||||
field.generated(annotation.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package jef.operations;
|
||||
|
||||
import jef.Queryable;
|
||||
import jef.serializable.SerializableObject;
|
||||
|
||||
public interface Operation<T extends SerializableObject> extends Queryable<T> {//TODO is this class required
|
||||
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package jef.platform;
|
||||
|
||||
import jef.platform.base.SqlTypeMapper;
|
||||
import jef.platform.base.DatabaseOptions;
|
||||
import jef.platform.base.migration.MigrationApplier;
|
||||
import jef.platform.base.migration.MigrationOperationTranslator;
|
||||
|
||||
import java.sql.Connection;
|
||||
|
||||
public interface SqlPlatform {
|
||||
MigrationOperationTranslator getTranslator();
|
||||
SqlTypeMapper getTypeMapper();
|
||||
MigrationApplier getMigrationApplier(Connection connection, DatabaseOptions options);
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
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 {
|
||||
protected final Connection connection;
|
||||
protected final DatabaseOptions options;
|
||||
|
||||
public abstract void migrate() throws MigrationException;
|
||||
|
||||
public abstract <T extends SerializableObject> List<T> queryExpression(DbContext context, Class<T> clazz, Expression expression) throws SQLException;
|
||||
|
||||
public <T extends SerializableObject> List<T> queryRaw(DbContext context, Class<T> clazz, String query) throws SQLException {
|
||||
var modelBuilder = ModelBuilder.from(context.getClass());
|
||||
var entity = modelBuilder.entity(clazz);
|
||||
var fields = entity.fields();
|
||||
try (var stmt = connection.prepareStatement(query)) {
|
||||
return queryRaw(stmt, clazz, fields);
|
||||
}
|
||||
}
|
||||
|
||||
protected <T extends SerializableObject> List<T> queryRaw(PreparedStatement stmt, Class<T> clazz, List<DbFieldBuilder<?>> fields) throws SQLException {
|
||||
try (var result = stmt.executeQuery()) {
|
||||
var ret = new ArrayList<T>();
|
||||
while (result.next()) {
|
||||
ret.add(readRow(result, clazz, fields));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
protected <T extends SerializableObject> T readRow(ResultSet res, Class<T> clazz, List<DbFieldBuilder<?>> fields) throws SQLException {
|
||||
//TODO replace with by runtime generated class
|
||||
try {
|
||||
var model = (T) clazz.getConstructor().newInstance();
|
||||
for (DbFieldBuilder<?> field : fields) {
|
||||
if (!field.getField().isDatabaseField()) {
|
||||
continue;
|
||||
}
|
||||
Field f = field.getField().getField();
|
||||
if (f == null) {
|
||||
System.out.println("not class member for " + field.getField().getName());
|
||||
continue;
|
||||
}
|
||||
f.setAccessible(true);
|
||||
if (field.getField().getType() == String.class) {
|
||||
f.set(model, res.getString(f.getName()));
|
||||
} else if (field.getField().getType() == byte.class || field.getField().getType() == Byte.class) {
|
||||
f.set(model, res.getByte(f.getName()));
|
||||
} else if (field.getField().getType() == char.class || field.getField().getType() == Character.class) {
|
||||
// f.set(model, res.getString(f.getName()));//TODO handle char
|
||||
throw new UnsupportedOperationException("char");
|
||||
} else if (field.getField().getType() == short.class || field.getField().getType() == Short.class) {
|
||||
f.set(model, res.getShort(f.getName()));
|
||||
} else if (field.getField().getType() == int.class || field.getField().getType() == Integer.class) {
|
||||
f.set(model, res.getInt(f.getName()));
|
||||
} else if (field.getField().getType() == long.class || field.getField().getType() == Long.class) {
|
||||
f.set(model, res.getLong(f.getName()));
|
||||
} else if (field.getField().getType() == float.class || field.getField().getType() == Float.class) {
|
||||
f.set(model, res.getFloat(f.getName()));
|
||||
} else if (field.getField().getType() == double.class || field.getField().getType() == Double.class) {
|
||||
f.set(model, res.getDouble(f.getName()));
|
||||
} else {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
return model;
|
||||
} catch (IllegalAccessException | InvocationTargetException | InstantiationException | NoSuchMethodException e) {//TODO better handling
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract <T extends SerializableObject> void add(DbContext context, Class<T> clazz, T model) throws SQLException;
|
||||
|
||||
public abstract <T extends SerializableObject> void update(DbContext context, Class<T> clazz, T model) throws SQLException;
|
||||
|
||||
public abstract <T extends SerializableObject> void delete(DbContext context, Class<T> clazz, T model) throws SQLException;
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package jef.platform.base;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Getter
|
||||
public class DatabaseOptions {
|
||||
protected final String url;
|
||||
protected final String user;
|
||||
protected final String password;
|
||||
|
||||
protected final String migrationsPackage;
|
||||
protected final String migrationsTableName;//TODO verify sql injection
|
||||
|
||||
protected final String host;//TODO get rid of this
|
||||
protected final String database;//TODO get rid of this
|
||||
|
||||
public DatabaseOptions(String url, String user, String password, String migrationsPackage, String migrationsTableName) {
|
||||
this.url = url;
|
||||
this.user = user;
|
||||
this.password = password;
|
||||
|
||||
host = extractHost(url);
|
||||
database = extractDatabase(url);
|
||||
this.migrationsPackage = migrationsPackage;
|
||||
this.migrationsTableName = migrationsTableName == null || migrationsTableName.isBlank() ? "__jef_migration_log" : migrationsTableName;
|
||||
}
|
||||
|
||||
private static String extractHost(String url) {
|
||||
var pattern = Pattern.compile("^.*?//(.*?)/.*$");
|
||||
var matcher = pattern.matcher(url);
|
||||
if (!matcher.matches()) {
|
||||
throw new RuntimeException(new MalformedURLException("Could not extract host for url: " + url));
|
||||
}
|
||||
return matcher.group(1);
|
||||
}
|
||||
|
||||
private static String extractDatabase(String url) {
|
||||
var pattern = Pattern.compile("^.*?//.*?/(.*?)([?/].*)?$");
|
||||
var matcher = pattern.matcher(url);
|
||||
if (!matcher.matches()) {
|
||||
throw new RuntimeException(new MalformedURLException("Could not extract database for url: " + url));
|
||||
}
|
||||
return matcher.group(1);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package jef.platform.base;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class SqlTypeMapper {
|
||||
public Optional<String> map(String typeName) {
|
||||
if (typeName == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.ofNullable(switch (typeName) {
|
||||
case "java.lang.String" -> "VARCHAR(255)";//TODO add length and precision as param
|
||||
case "int" -> "INT(11)";
|
||||
case "float" -> "FLOAT";
|
||||
case "double" -> "DOUBLE";
|
||||
case "boolean" -> "INT(11)";
|
||||
case "short" -> "INT(11)";
|
||||
case "long" -> "BIGINT";
|
||||
default -> null;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package jef.platform.base.migration;
|
||||
|
||||
import jef.model.migration.Migration;
|
||||
import jef.model.migration.MigrationBuilder;
|
||||
import jef.model.migration.operation.AddFieldOperation;
|
||||
import jef.platform.base.SqlTypeMapper;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode//needed for test
|
||||
public class M00010101000000_MigrationsLogTableMigration implements Migration {
|
||||
private final SqlTypeMapper typeMapper;
|
||||
private final String tableName;
|
||||
|
||||
@Override
|
||||
public void up(MigrationBuilder migrationBuilder) {//TODO add timestamp later
|
||||
migrationBuilder.addTable(tableName, List.of(
|
||||
new AddFieldOperation.Builder(tableName, "migration").notNull(true).sqlType(typeMapper.map(String.class.getName()).orElseThrow()),
|
||||
new AddFieldOperation.Builder(tableName, "version").notNull(true).sqlType(typeMapper.map(String.class.getName()).orElseThrow())
|
||||
), null);
|
||||
migrationBuilder.addUniqueKey("U_" + tableName + "_migration", tableName, List.of("migration"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void down(MigrationBuilder migrationBuilder) {
|
||||
migrationBuilder.dropConstraint("U_" + tableName + "_migration", tableName);
|
||||
migrationBuilder.dropTable(tableName);
|
||||
}
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
package jef.platform.base.migration;
|
||||
|
||||
import jef.MigrationException;
|
||||
import jef.model.migration.Migration;
|
||||
import jef.model.migration.MigrationBuilder;
|
||||
import jef.model.migration.operation.MigrationOperation;
|
||||
import jef.platform.SqlPlatform;
|
||||
import jef.platform.base.DatabaseOptions;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public abstract class MigrationApplier {
|
||||
protected final Connection connection;
|
||||
protected final DatabaseOptions options;
|
||||
protected final SqlPlatform sqlPlatform;
|
||||
|
||||
public void migrate() throws MigrationException {
|
||||
var migrations = new ArrayList<>(findMigrations(options.getMigrationsPackage())); //TODO find all migrations, support multiple db contexts
|
||||
try {
|
||||
ensureMigrationsTableExists();
|
||||
getAppliedMigrations().forEach(a -> migrations.removeIf(m -> m.getClass().getSimpleName().equals(a)));
|
||||
for (Migration migration : migrations) {
|
||||
applyMigration(migration);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new MigrationException("Error while applying migrations: " + e.getLocalizedMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void applyMigration(Migration m) throws MigrationException {//TODO verify transactions work
|
||||
var mb = new MigrationBuilder();
|
||||
m.up(mb);
|
||||
var operations = mb.getOperations().stream().map(MigrationOperation.Builder::build).toList();
|
||||
|
||||
try {
|
||||
connection.setAutoCommit(false);
|
||||
try {
|
||||
for (MigrationOperation op : operations) {
|
||||
applyMigrationOperation(op);
|
||||
}
|
||||
insertMigrationLog(m);
|
||||
connection.commit();
|
||||
} catch (SQLException e) {
|
||||
connection.rollback();
|
||||
throw e;
|
||||
} finally {
|
||||
connection.setAutoCommit(true);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new MigrationException("Failed to apply migration '" + m.getClass().getSimpleName() + "' to the database: " + e.getLocalizedMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void applyMigrationOperation(MigrationOperation operation) throws SQLException {
|
||||
PreparedStatement stmt;
|
||||
try {
|
||||
stmt = sqlPlatform.getTranslator().translate(connection, operation);//TODO may return an object containing the prep stmt and a string rep of the query
|
||||
} catch (SQLException e) {
|
||||
throw new SQLException("Failed to build query: " + e.getLocalizedMessage(), e);
|
||||
}
|
||||
try {
|
||||
stmt.execute();
|
||||
} catch (SQLException e) {
|
||||
throw new SQLException("Failed to execute query: " + stmt, e);
|
||||
} finally {
|
||||
try {
|
||||
stmt.close();
|
||||
} catch (SQLException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected List<Migration> findMigrations(String packageName) throws MigrationException {//TODO rewrite this function after adding annotations to migrations (@Context, @Name, ...), also get rid of the packageName restristrict and just read the classfiles with asm
|
||||
try (var is = getClass().getClassLoader().getResourceAsStream(packageName.replace(".", "/"));
|
||||
var reader = new BufferedReader(new InputStreamReader(is))) {
|
||||
return reader.lines()
|
||||
.filter(line -> line.endsWith(".class"))
|
||||
.map(line -> line.substring(0, line.length() - ".class".length()))
|
||||
.map(line -> {
|
||||
try {
|
||||
var cls = getClass().getClassLoader().loadClass(packageName + (packageName.isEmpty() ? "" : ".") + line);
|
||||
if (!Migration.class.isAssignableFrom(cls)) {
|
||||
return null;
|
||||
}
|
||||
return (Migration) cls.getDeclaredConstructor().newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
} catch (Exception e) {
|
||||
if (e instanceof RuntimeException re) {
|
||||
e = re;
|
||||
}
|
||||
throw new MigrationException("Error while looking up migrations in package '" + packageName + "'", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void ensureMigrationsTableExists() throws MigrationException {
|
||||
try {
|
||||
if (!migrationsTableExists()) {
|
||||
createMigrationsTable();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new MigrationException("Failed to create migrations log table", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract boolean migrationsTableExists() throws SQLException;
|
||||
|
||||
protected void createMigrationsTable() throws MigrationException {
|
||||
applyMigration(new M00010101000000_MigrationsLogTableMigration(sqlPlatform.getTypeMapper(), options.getMigrationsTableName()));
|
||||
}
|
||||
|
||||
protected abstract List<String> getAppliedMigrations() throws SQLException;//TODO dbset to load later
|
||||
|
||||
protected abstract void insertMigrationLog(Migration m) throws SQLException;//TODO dbset to insert later
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package jef.platform.base.migration;
|
||||
|
||||
import jef.model.migration.operation.MigrationOperation;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public interface MigrationOperationTranslator {
|
||||
PreparedStatement translate(Connection connection, MigrationOperation op) throws SQLException;
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
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() {
|
||||
super(null, null);
|
||||
}
|
||||
|
||||
@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 {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package jef.platform.dummy;
|
||||
|
||||
import jef.platform.SqlPlatform;
|
||||
import jef.platform.base.DatabaseOptions;
|
||||
import jef.platform.base.SqlTypeMapper;
|
||||
import jef.platform.base.migration.MigrationApplier;
|
||||
import jef.platform.base.migration.MigrationOperationTranslator;
|
||||
import jef.platform.dummy.migration.DummyMigrationApplier;
|
||||
import jef.platform.dummy.migration.DummyMigrationOperationTranslator;
|
||||
|
||||
import java.sql.Connection;
|
||||
|
||||
public class DummyPlatform implements SqlPlatform {
|
||||
public static final DummyPlatform INSTANCE = new DummyPlatform();
|
||||
|
||||
@Override
|
||||
public MigrationOperationTranslator getTranslator() {
|
||||
return DummyMigrationOperationTranslator.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlTypeMapper getTypeMapper() {
|
||||
return DummyTypeMapper.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MigrationApplier getMigrationApplier(Connection connection, DatabaseOptions options) {
|
||||
return DummyMigrationApplier.INSTANCE;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package jef.platform.dummy;
|
||||
|
||||
import jef.platform.base.SqlTypeMapper;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class DummyTypeMapper extends SqlTypeMapper {
|
||||
public static final DummyTypeMapper INSTANCE = new DummyTypeMapper();
|
||||
|
||||
@Override
|
||||
public Optional<String> map(String typeName) {
|
||||
return Optional.ofNullable(typeName);
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package jef.platform.dummy.migration;
|
||||
|
||||
import jef.MigrationException;
|
||||
import jef.model.migration.Migration;
|
||||
import jef.platform.base.migration.MigrationApplier;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
public class DummyMigrationApplier extends MigrationApplier {
|
||||
public static final DummyMigrationApplier INSTANCE = new DummyMigrationApplier();
|
||||
|
||||
private DummyMigrationApplier() {
|
||||
super(null, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void migrate() throws MigrationException {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyMigration(Migration m) throws MigrationException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean migrationsTableExists() throws SQLException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createMigrationsTable() throws MigrationException {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getAppliedMigrations() throws SQLException {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void insertMigrationLog(Migration m) throws SQLException {
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package jef.platform.dummy.migration;
|
||||
|
||||
import jef.model.migration.operation.MigrationOperation;
|
||||
import jef.platform.base.migration.MigrationOperationTranslator;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class DummyMigrationOperationTranslator implements MigrationOperationTranslator {
|
||||
public static final DummyMigrationOperationTranslator INSTANCE = new DummyMigrationOperationTranslator();
|
||||
|
||||
@Override
|
||||
public PreparedStatement translate(Connection connection, MigrationOperation op) throws SQLException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package jef.util;
|
||||
|
||||
public class Log {
|
||||
public static void debug(String s) {
|
||||
System.out.println(s);//TODO implement logging
|
||||
}
|
||||
|
||||
private Log() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package jef.util;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public abstract class Util {
|
||||
public static <T> Optional<T> tryGet(ThrowableSupplier<T> s) {
|
||||
try {
|
||||
return Optional.ofNullable(s.get());
|
||||
} catch (Throwable t) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ThrowableSupplier<T> {
|
||||
T get() throws Throwable;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ThrowableFunction<T, R> {
|
||||
R apply(T t) throws Throwable;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ThrowableBiFunction<T, U, R> {
|
||||
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);
|
||||
}
|
||||
|
||||
public interface NullableEquality<T> {
|
||||
default boolean check(T t1, T t2) {
|
||||
if (t1 == t2) {
|
||||
return true;
|
||||
}
|
||||
if (t1 != null) {
|
||||
return compare(t1, t2);
|
||||
} else {
|
||||
return compare(t2, t1);
|
||||
}
|
||||
}
|
||||
|
||||
boolean compare(/*NonNull*/T t1, T t2);
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.model.annotations.Id;
|
||||
import jef.serializable.SerializableObject;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class EntityDefaultConstructorCheckerTest {
|
||||
@Test
|
||||
public void test0ArgConstructor() {
|
||||
var mb = new ModelBuilder();
|
||||
EntityDefaultConstructorChecker.checkEntity(mb.entity(TestClass0Arg.class));
|
||||
assertTrue(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNon0ArgConstructor() {
|
||||
var mb = new ModelBuilder();
|
||||
var ex = assertThrows(ModelException.class, () -> EntityDefaultConstructorChecker.checkEntity(mb.entity(TestClassNon0Arg.class)));
|
||||
assertEquals("Class 'TestClassNon0Arg' does not have a default constructor!", ex.getMessage());
|
||||
}
|
||||
|
||||
@NoArgsConstructor
|
||||
public static class TestClass0Arg extends SerializableObject {
|
||||
@Id
|
||||
public int i = 1;
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
public static class TestClassNon0Arg extends SerializableObject {
|
||||
@Id
|
||||
public int i = 1;
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.DbSet;
|
||||
import jef.model.annotations.Clazz;
|
||||
import jef.model.annotations.Id;
|
||||
import jef.serializable.SerializableObject;
|
||||
import lombok.Getter;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
|
||||
|
||||
class EntityInitializerCollectionAnnotationClassInheritTest {
|
||||
@Test
|
||||
public void test() {
|
||||
assertEquals("Field TestClass2::nested is missing the Clazz annotation",
|
||||
assertThrowsExactly(ModelException.class, () -> ModelBuilder.from(Ctx.class)).getMessage());
|
||||
}
|
||||
|
||||
public static class Ctx extends DbContext {
|
||||
@Clazz(TestClass2.class)
|
||||
private DbSet<TestClass2> objects2;
|
||||
@Clazz(TestClass.class)
|
||||
private DbSet<TestClass> objects;
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class TestClass2 extends SerializableObject {
|
||||
@Id
|
||||
public int i2 = 1;
|
||||
public List<TestClass> nested;
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class TestClass extends SerializableObject {
|
||||
@Id
|
||||
public int i = 1;
|
||||
public Object o = new Object();
|
||||
public double d;
|
||||
public float f;
|
||||
public long l;
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.DbSet;
|
||||
import jef.model.annotations.Clazz;
|
||||
import jef.model.annotations.Id;
|
||||
import jef.serializable.SerializableObject;
|
||||
import lombok.Getter;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
|
||||
|
||||
class EntityInitializerCollectionAnnotationMissingTest {
|
||||
@Test
|
||||
public void test() {
|
||||
assertEquals("Field TestClass2::nested has a class in its Clazz annotation that does not inherit from SerializableObject: int",
|
||||
assertThrowsExactly(ModelException.class, () -> ModelBuilder.from(Ctx.class)).getMessage());
|
||||
}
|
||||
|
||||
public static class Ctx extends DbContext {
|
||||
@Clazz(TestClass2.class)
|
||||
private DbSet<TestClass2> objects2;
|
||||
@Clazz(TestClass.class)
|
||||
private DbSet<TestClass> objects;
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class TestClass2 extends SerializableObject {
|
||||
@Id
|
||||
public int i2 = 1;
|
||||
@Clazz(int.class)
|
||||
public List<TestClass> nested;
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class TestClass extends SerializableObject {
|
||||
@Id
|
||||
public int i = 1;
|
||||
public Object o = new Object();
|
||||
public double d;
|
||||
public float f;
|
||||
public long l;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.DbSet;
|
||||
import jef.model.annotations.Clazz;
|
||||
import jef.model.annotations.Id;
|
||||
import jef.serializable.SerializableObject;
|
||||
import lombok.Getter;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
|
||||
|
||||
class EntityInitializerDbSetAnnotationClassInheritTest {
|
||||
@Test
|
||||
public void test() {
|
||||
assertEquals("DbSet objects1 has a class in its Clazz annotation that does not inherit from SerializableObject: int",
|
||||
assertThrowsExactly(ModelException.class, () -> ModelBuilder.from(Ctx.class)).getMessage());
|
||||
}
|
||||
|
||||
public static class Ctx extends DbContext {
|
||||
@Clazz(int.class)
|
||||
private DbSet<TestClass> objects1;
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class TestClass extends SerializableObject {
|
||||
@Id
|
||||
public int i = 1;
|
||||
public Object o = new Object();
|
||||
public double d;
|
||||
public float f;
|
||||
public long l;
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.DbSet;
|
||||
import jef.model.annotations.Clazz;
|
||||
import jef.model.annotations.Id;
|
||||
import jef.serializable.SerializableObject;
|
||||
import lombok.Getter;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class KeyBuilderPrimaryDropRelatedForeignKeyDropTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
var mb = ModelBuilder.from(Ctx.class);
|
||||
|
||||
assertEquals(1, mb.getEntity(TestClass3.class).getForeignKeys().size());
|
||||
assertEquals(1, mb.getEntity(TestClass2.class).getForeignKeys().size());
|
||||
assertEquals(0, mb.getEntity(TestClass.class).getForeignKeys().size());
|
||||
|
||||
mb.entity(TestClass.class).hasOne(e -> e.i).isPrimaryKey(false);
|
||||
|
||||
assertEquals(1, mb.getEntity(TestClass3.class).getForeignKeys().size());
|
||||
assertEquals(0, mb.getEntity(TestClass2.class).getForeignKeys().size());
|
||||
assertEquals(0, mb.getEntity(TestClass.class).getForeignKeys().size());
|
||||
}
|
||||
|
||||
public static class Ctx extends DbContext {
|
||||
@Clazz(TestClass.class)
|
||||
private DbSet<TestClass> objects1;
|
||||
@Clazz(TestClass2.class)
|
||||
private DbSet<TestClass2> objects2;
|
||||
@Clazz(TestClass3.class)
|
||||
private DbSet<TestClass2> objects3;
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class TestClass3 extends SerializableObject {
|
||||
@Id
|
||||
public int i3 = 1;
|
||||
private TestClass2 nested2;
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class TestClass2 extends SerializableObject {
|
||||
@Id
|
||||
public int i2 = 1;
|
||||
private TestClass nested;
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class TestClass extends SerializableObject {
|
||||
@Id
|
||||
public int i = 1;
|
||||
public Object o = new Object();
|
||||
public double d;
|
||||
public float f;
|
||||
public long l;
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package jef.model.annotations.processors;
|
||||
|
||||
import jef.DbSet;
|
||||
import jef.model.DbContext;
|
||||
import jef.model.ModelBuilder;
|
||||
import jef.model.annotations.Clazz;
|
||||
import jef.model.annotations.ForeignKey;
|
||||
import jef.model.annotations.Id;
|
||||
import jef.serializable.SerializableObject;
|
||||
import lombok.Getter;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class ForeignKeyProcessorExposeViaMethodTest {
|
||||
@Test
|
||||
public void test() {
|
||||
var mb = ModelBuilder.from(Ctx.class);
|
||||
|
||||
assertEquals(2, mb.getEntities().size());
|
||||
assertEquals("objects1", mb.getEntity(TestClass.class).getName());
|
||||
assertEquals("objects2", mb.getEntity(TestClass2.class).getName());
|
||||
assertTrue(mb.getEntity(TestClass.class).getFields().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass.class)));
|
||||
assertTrue(mb.getEntity(TestClass2.class).getFields().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass2.class)));
|
||||
assertTrue(mb.getEntity(TestClass.class).getForeignKeys().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass.class)));
|
||||
assertTrue(mb.getEntity(TestClass2.class).getForeignKeys().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass2.class)));
|
||||
|
||||
//fields ------------------------
|
||||
//TestClass
|
||||
assertEquals(3, mb.getEntity(TestClass.class).getFields().size());
|
||||
assertEquals(1, mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("i") && e.isModelField() && e.isDatabaseField()).count());
|
||||
assertEquals(1, mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("test") && e.isModelField() && !e.isDatabaseField()).count());
|
||||
assertEquals(1, mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("testField") && e.isModelField() && e.isDatabaseField()).count());
|
||||
|
||||
//TestClass2
|
||||
assertEquals(1, mb.getEntity(TestClass2.class).getFields().size());
|
||||
assertEquals(1, mb.getEntity(TestClass2.class).getFields().stream().filter(e -> e.getName().equals("i") && e.isModelField() && e.isDatabaseField()).count());
|
||||
// /fields ------------------------
|
||||
|
||||
//keys ------------------------
|
||||
assertEquals(1, mb.getEntity(TestClass.class).getForeignKeys().size());
|
||||
assertEquals(mb.getEntity(TestClass.class), mb.getEntity(TestClass.class).getForeignKeys().get(0).getEntity());
|
||||
assertEquals(mb.getEntity(TestClass2.class), mb.getEntity(TestClass.class).getForeignKeys().get(0).getReferencedEntity());
|
||||
assertEquals(mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("testField")).toList(),
|
||||
mb.getEntity(TestClass.class).getForeignKeys().get(0).getFields());
|
||||
assertEquals(mb.getEntity(TestClass2.class).getPrimaryKey().getFields(), mb.getEntity(TestClass.class).getForeignKeys().get(0).getReferencedFields());
|
||||
// /keys ------------------------
|
||||
|
||||
//exposing fields
|
||||
assertEquals(mb.getEntity(TestClass.class).getField("test"), mb.getEntity(TestClass.class).getField("testField").getExposingForeignKeyOf());
|
||||
}
|
||||
|
||||
public static class Ctx extends DbContext {
|
||||
@Clazz(TestClass.class)
|
||||
private DbSet<TestClass> objects1;
|
||||
@Clazz(TestClass2.class)
|
||||
private DbSet<TestClass2> objects2;
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class TestClass extends SerializableObject {
|
||||
public int i = 1;
|
||||
private TestClass2 test;
|
||||
@ForeignKey(getterOrField = "getTest")
|
||||
public int testField;
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class TestClass2 extends SerializableObject {
|
||||
@Id
|
||||
public int i = 1;
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package jef.model.annotations.processors;
|
||||
|
||||
import jef.DbSet;
|
||||
import jef.model.DbContext;
|
||||
import jef.model.ModelBuilder;
|
||||
import jef.model.ModelException;
|
||||
import jef.model.annotations.Clazz;
|
||||
import jef.model.annotations.ForeignKey;
|
||||
import jef.model.annotations.Id;
|
||||
import jef.serializable.SerializableObject;
|
||||
import lombok.Getter;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
|
||||
|
||||
class ForeignKeyProcessorInvalidFieldTypeTest {
|
||||
@Test
|
||||
public void test() {
|
||||
assertEquals("The getter/field in TestClass::test is not a SerializableObject (via @ForeignKey in TestClass::testFk)",
|
||||
assertThrowsExactly(ModelException.class, () -> ModelBuilder.from(Ctx.class)).getMessage());
|
||||
}
|
||||
|
||||
public static class Ctx extends DbContext {
|
||||
@Clazz(TestClass.class)
|
||||
private DbSet<TestClass> objects1;
|
||||
@Clazz(TestClass2.class)
|
||||
private DbSet<TestClass2> objects2;
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class TestClass extends SerializableObject {
|
||||
public int i = 1;
|
||||
public Object o = new Object();
|
||||
public double d;
|
||||
public float f;
|
||||
public long l;
|
||||
private TestClass2 test;
|
||||
@ForeignKey(getterOrField = "l")
|
||||
public int testFk;
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class TestClass2 extends SerializableObject {
|
||||
@Id
|
||||
public int i = 1;
|
||||
}
|
||||
}
|
||||
@@ -1,399 +0,0 @@
|
||||
package jef.platform.base.migration;
|
||||
|
||||
import jef.MigrationException;
|
||||
import jef.model.migration.Migration;
|
||||
import jef.model.migration.MigrationBuilder;
|
||||
import jef.model.migration.operation.MigrationOperation;
|
||||
import jef.platform.SqlPlatform;
|
||||
import jef.platform.base.DatabaseOptions;
|
||||
import jef.platform.base.SqlTypeMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.CALLS_REAL_METHODS;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.withSettings;
|
||||
|
||||
class MigrationApplierTest {
|
||||
private String packageName;
|
||||
private DatabaseOptions options;
|
||||
private Connection connection;
|
||||
private SqlPlatform platform;
|
||||
private MigrationApplier applier;
|
||||
private Object[] allMocks;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
//setup
|
||||
packageName = "package";
|
||||
options = mock(DatabaseOptions.class);
|
||||
doReturn(packageName).when(options).getMigrationsPackage();
|
||||
|
||||
connection = mock(Connection.class);
|
||||
platform = mock(SqlPlatform.class);
|
||||
applier = mock(MigrationApplier.class, withSettings().useConstructor(connection, options, platform).defaultAnswer(CALLS_REAL_METHODS)); //spy
|
||||
|
||||
allMocks = List.of(options, connection, platform, applier).toArray();
|
||||
}
|
||||
|
||||
@Test
|
||||
void migrate_success() throws MigrationException, SQLException {
|
||||
interface Migration1 extends Migration {
|
||||
}
|
||||
interface Migration2 extends Migration {
|
||||
}
|
||||
var migration1 = mock(Migration1.class);
|
||||
var migration2 = mock(Migration2.class);
|
||||
doReturn(List.of(migration1, migration2)).when(applier).findMigrations(packageName);
|
||||
|
||||
doNothing().when(applier).ensureMigrationsTableExists();
|
||||
doReturn(List.of(migration1.getClass().getSimpleName())).when(applier).getAppliedMigrations();
|
||||
doNothing().when(applier).applyMigration(any(Migration.class));
|
||||
|
||||
//test
|
||||
applier.migrate();
|
||||
|
||||
//assert
|
||||
verify(applier, times(1)).migrate();
|
||||
verify(options, times(1)).getMigrationsPackage();
|
||||
verify(applier, times(1)).findMigrations(packageName);
|
||||
verify(applier, times(1)).ensureMigrationsTableExists();
|
||||
verify(applier, times(1)).getAppliedMigrations();
|
||||
verify(applier, times(1)).applyMigration(migration2);
|
||||
|
||||
verifyNoMoreInteractions(migration1, migration2);
|
||||
verifyNoMoreInteractions(allMocks);
|
||||
}
|
||||
|
||||
@Test
|
||||
void migrate_findMigrationsThrowsMigrationException() throws MigrationException {
|
||||
//setup
|
||||
var exception = new MigrationException("reason");
|
||||
doThrow(exception).when(applier).findMigrations(packageName);
|
||||
|
||||
//test
|
||||
var ex = assertThrows(MigrationException.class, () -> applier.migrate());
|
||||
|
||||
//assert
|
||||
assertSame(exception, ex);
|
||||
|
||||
verify(applier, times(1)).migrate();
|
||||
verify(options, times(1)).getMigrationsPackage();
|
||||
verify(applier, times(1)).findMigrations(packageName);
|
||||
verifyNoMoreInteractions(allMocks);
|
||||
}
|
||||
|
||||
@Test
|
||||
void migrate_ensureMigrationsTableExistsThrowsMigrationException() throws MigrationException {
|
||||
//setup
|
||||
doReturn(List.of()).when(applier).findMigrations(packageName);
|
||||
var exception = new MigrationException("reason");
|
||||
doThrow(exception).when(applier).ensureMigrationsTableExists();
|
||||
|
||||
//test
|
||||
var ex = assertThrows(MigrationException.class, () -> applier.migrate());
|
||||
|
||||
//assert
|
||||
assertSame(exception, ex);
|
||||
|
||||
verify(applier, times(1)).migrate();
|
||||
verify(options, times(1)).getMigrationsPackage();
|
||||
verify(applier, times(1)).findMigrations(packageName);
|
||||
verify(applier, times(1)).ensureMigrationsTableExists();
|
||||
verifyNoMoreInteractions(allMocks);
|
||||
}
|
||||
|
||||
@Test
|
||||
void migrate_getAppliedMigrationsThrowsSqlException() throws MigrationException, SQLException {
|
||||
//setup
|
||||
doReturn(List.of()).when(applier).findMigrations(packageName);
|
||||
doNothing().when(applier).ensureMigrationsTableExists();
|
||||
var exception = new SQLException("reason");
|
||||
doThrow(exception).when(applier).getAppliedMigrations();
|
||||
|
||||
//test
|
||||
var ex = assertThrows(MigrationException.class, () -> applier.migrate());
|
||||
|
||||
//assert
|
||||
assertEquals("Error while applying migrations: reason", ex.getMessage());
|
||||
assertSame(exception, ex.getCause());
|
||||
|
||||
verify(applier, times(1)).migrate();
|
||||
verify(options, times(1)).getMigrationsPackage();
|
||||
verify(applier, times(1)).findMigrations(packageName);
|
||||
verify(applier, times(1)).ensureMigrationsTableExists();
|
||||
verify(applier, times(1)).getAppliedMigrations();
|
||||
verifyNoMoreInteractions(allMocks);
|
||||
}
|
||||
|
||||
@Test
|
||||
void migrate_applyMigrationThrowsMigrationException() throws MigrationException, SQLException {
|
||||
//setup
|
||||
var migration = mock(Migration.class);
|
||||
doReturn(List.of(migration)).when(applier).findMigrations(packageName);
|
||||
|
||||
doNothing().when(applier).ensureMigrationsTableExists();
|
||||
doReturn(List.of()).when(applier).getAppliedMigrations();
|
||||
|
||||
var exception = new MigrationException("reason");
|
||||
doThrow(exception).when(applier).applyMigration(migration);
|
||||
|
||||
//test
|
||||
var ex = assertThrows(MigrationException.class, () -> applier.migrate());
|
||||
|
||||
//assert
|
||||
assertSame(exception, ex);
|
||||
|
||||
verify(applier, times(1)).migrate();
|
||||
verify(options, times(1)).getMigrationsPackage();
|
||||
verify(applier, times(1)).findMigrations(packageName);
|
||||
verify(applier, times(1)).ensureMigrationsTableExists();
|
||||
verify(applier, times(1)).getAppliedMigrations();
|
||||
verify(applier, times(1)).applyMigration(migration);
|
||||
verifyNoMoreInteractions(migration);
|
||||
verifyNoMoreInteractions(allMocks);
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyMigration_success() throws SQLException, MigrationException {
|
||||
//setup
|
||||
var migration = mock(Migration.class);
|
||||
var operationBuilder = new MigrationOperation.Builder[]{null};
|
||||
var operation = new MigrationOperation[]{null};
|
||||
doAnswer(invok -> {
|
||||
operationBuilder[0] = ((MigrationBuilder) invok.getArguments()[0]).addTable("test", List.of(), null);
|
||||
operation[0] = operationBuilder[0].build();
|
||||
return null;
|
||||
}).when(migration).up(any(MigrationBuilder.class));
|
||||
doNothing().when(applier).applyMigrationOperation(argThat(e -> e.equals(operation[0])));//eq(operation[0]) does not work as var would have to be set already and isn't
|
||||
|
||||
//test
|
||||
applier.applyMigration(migration);
|
||||
|
||||
//assert
|
||||
verify(applier, times(1)).applyMigration(migration);
|
||||
verify(migration, times(1)).up(any(MigrationBuilder.class));
|
||||
verify(connection, times(1)).setAutoCommit(false);
|
||||
verify(applier, times(1)).applyMigrationOperation(operation[0]);
|
||||
verify(applier, times(1)).insertMigrationLog(migration);
|
||||
verify(connection, times(1)).commit();
|
||||
verify(connection, times(1)).setAutoCommit(true);
|
||||
verifyNoMoreInteractions(migration);
|
||||
verifyNoMoreInteractions(allMocks);
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyMigration_applyMigrationOperationThrowsSqlException() throws SQLException, MigrationException {
|
||||
//setup
|
||||
var migration = mock(Migration.class);
|
||||
var operationBuilder = new MigrationOperation.Builder[]{null};
|
||||
var operation = new MigrationOperation[]{null};
|
||||
doAnswer(invok -> {
|
||||
operationBuilder[0] = ((MigrationBuilder) invok.getArguments()[0]).addTable("test", List.of(), null);
|
||||
operation[0] = operationBuilder[0].build();
|
||||
return null;
|
||||
}).when(migration).up(any(MigrationBuilder.class));
|
||||
var sqlException = new SQLException("reason");
|
||||
doThrow(sqlException).when(applier).applyMigrationOperation(argThat(e -> e.equals(operation[0])));//eq(operation[0]) does not work as var would have to be set already and isn't
|
||||
|
||||
//test
|
||||
var thrown = assertThrows(MigrationException.class, () -> applier.applyMigration(migration));
|
||||
|
||||
//assert
|
||||
assertTrue(thrown.getMessage().matches("Failed to apply migration '(.*?)' to the database: reason"));
|
||||
assertSame(sqlException, thrown.getCause());
|
||||
|
||||
verify(applier, times(1)).applyMigration(migration);
|
||||
verify(migration, times(1)).up(any(MigrationBuilder.class));
|
||||
verify(connection, times(1)).setAutoCommit(false);
|
||||
verify(applier, times(1)).applyMigrationOperation(operation[0]);
|
||||
verify(connection, times(1)).rollback();
|
||||
verify(connection, times(1)).setAutoCommit(true);
|
||||
verifyNoMoreInteractions(migration);
|
||||
verifyNoMoreInteractions(allMocks);
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyMigration_insertMigrationLogThrowsSqlException() throws SQLException, MigrationException {
|
||||
//setup
|
||||
var migration = mock(Migration.class);
|
||||
var operationBuilder = new MigrationOperation.Builder[]{null};
|
||||
var operation = new MigrationOperation[]{null};
|
||||
doAnswer(invok -> {
|
||||
operationBuilder[0] = ((MigrationBuilder) invok.getArguments()[0]).addTable("test", List.of(), null);
|
||||
operation[0] = operationBuilder[0].build();
|
||||
return null;
|
||||
}).when(migration).up(any(MigrationBuilder.class));
|
||||
var sqlException = new SQLException("reason");
|
||||
doNothing().when(applier).applyMigrationOperation(argThat(e -> e.equals(operation[0])));//eq(operation[0]) does not work as var would have to be set already and isn't
|
||||
doThrow(sqlException).when(applier).insertMigrationLog(migration);
|
||||
|
||||
//test
|
||||
var thrown = assertThrows(MigrationException.class, () -> applier.applyMigration(migration));
|
||||
|
||||
//assert
|
||||
assertTrue(thrown.getMessage().matches("Failed to apply migration '(.*?)' to the database: reason"));
|
||||
assertSame(sqlException, thrown.getCause());
|
||||
|
||||
verify(applier, times(1)).applyMigration(migration);
|
||||
verify(migration, times(1)).up(any(MigrationBuilder.class));
|
||||
verify(connection, times(1)).setAutoCommit(false);
|
||||
verify(applier, times(1)).applyMigrationOperation(operation[0]);
|
||||
verify(applier, times(1)).insertMigrationLog(migration);
|
||||
verify(connection, times(1)).rollback();
|
||||
verify(connection, times(1)).setAutoCommit(true);
|
||||
verifyNoMoreInteractions(migration);
|
||||
verifyNoMoreInteractions(allMocks);
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyMigrationOperation_success() throws SQLException {
|
||||
//setup
|
||||
var translator = mock(MigrationOperationTranslator.class);
|
||||
doReturn(translator).when(platform).getTranslator();
|
||||
var operation = mock(MigrationOperation.class);
|
||||
var stmt = mock(PreparedStatement.class);
|
||||
doReturn(stmt).when(translator).translate(connection, operation);
|
||||
doReturn("query").when(stmt).toString();
|
||||
|
||||
//test
|
||||
applier.applyMigrationOperation(operation);
|
||||
|
||||
//assert
|
||||
verify(applier, times(1)).applyMigrationOperation(operation);
|
||||
verify(platform, times(1)).getTranslator();
|
||||
verify(translator, times(1)).translate(connection, operation);
|
||||
verify(stmt, times(1)).execute();
|
||||
verify(stmt, times(1)).close();
|
||||
verifyNoMoreInteractions(translator, operation, stmt);
|
||||
verifyNoMoreInteractions(allMocks);
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyMigrationOperation_translateThrowsSQLException() throws SQLException {
|
||||
//setup
|
||||
var translator = mock(MigrationOperationTranslator.class);
|
||||
doReturn(translator).when(platform).getTranslator();
|
||||
var operation = mock(MigrationOperation.class);
|
||||
var stmt = mock(PreparedStatement.class);
|
||||
var sqlException = new SQLException("reason");
|
||||
doThrow(sqlException).when(translator).translate(connection, operation);
|
||||
|
||||
//test
|
||||
var thrown = assertThrows(SQLException.class, () -> applier.applyMigrationOperation(operation));
|
||||
|
||||
//assert
|
||||
assertEquals("Failed to build query: reason", thrown.getMessage());
|
||||
assertSame(sqlException, thrown.getCause());
|
||||
|
||||
verify(applier, times(1)).applyMigrationOperation(operation);
|
||||
verify(platform, times(1)).getTranslator();
|
||||
verify(translator, times(1)).translate(connection, operation);
|
||||
verifyNoMoreInteractions(translator, operation, stmt);
|
||||
verifyNoMoreInteractions(allMocks);
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyMigrationOperation_stmtExecuteThrowsSQLException() throws SQLException {
|
||||
//setup
|
||||
var translator = mock(MigrationOperationTranslator.class);
|
||||
doReturn(translator).when(platform).getTranslator();
|
||||
var operation = mock(MigrationOperation.class);
|
||||
var stmt = mock(PreparedStatement.class);
|
||||
doReturn(stmt).when(translator).translate(connection, operation);
|
||||
doReturn("query").when(stmt).toString();
|
||||
var sqlException = new SQLException("reason");
|
||||
doThrow(sqlException).when(stmt).execute();
|
||||
|
||||
//test
|
||||
var thrown = assertThrows(SQLException.class, () -> applier.applyMigrationOperation(operation));
|
||||
|
||||
//assert
|
||||
assertEquals("Failed to execute query: query", thrown.getMessage());
|
||||
assertSame(sqlException, thrown.getCause());
|
||||
|
||||
verify(applier, times(1)).applyMigrationOperation(operation);
|
||||
verify(platform, times(1)).getTranslator();
|
||||
verify(translator, times(1)).translate(connection, operation);
|
||||
verify(stmt, times(1)).execute();
|
||||
verify(stmt, times(1)).close();
|
||||
verifyNoMoreInteractions(translator, operation, stmt);
|
||||
verifyNoMoreInteractions(allMocks);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findMigrations() {
|
||||
//setup
|
||||
|
||||
|
||||
//test
|
||||
|
||||
|
||||
//assert
|
||||
|
||||
}//TODO after function rewrite
|
||||
|
||||
@Test
|
||||
void createMigrationsTable_success() throws MigrationException {
|
||||
//setup
|
||||
var mapper = mock(SqlTypeMapper.class);
|
||||
doReturn(mapper).when(platform).getTypeMapper();
|
||||
|
||||
var migrationTableName = "migrationtable";
|
||||
doReturn(migrationTableName).when(options).getMigrationsTableName();
|
||||
doNothing().when(applier).applyMigration(new M00010101000000_MigrationsLogTableMigration(mapper, migrationTableName));
|
||||
|
||||
//test
|
||||
applier.createMigrationsTable();
|
||||
|
||||
//assert
|
||||
verify(applier, times(1)).createMigrationsTable();
|
||||
verify(platform, times(1)).getTypeMapper();
|
||||
verify(options, times(1)).getMigrationsTableName();
|
||||
verify(applier, times(1)).applyMigration(new M00010101000000_MigrationsLogTableMigration(mapper, migrationTableName));
|
||||
verifyNoMoreInteractions(allMocks);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createMigrationsTable_applyMigrationThrowsMigrationException() throws MigrationException {
|
||||
//setup
|
||||
var mapper = mock(SqlTypeMapper.class);
|
||||
doReturn(mapper).when(platform).getTypeMapper();
|
||||
|
||||
var migrationTableName = "migrationtable";
|
||||
doReturn(migrationTableName).when(options).getMigrationsTableName();
|
||||
|
||||
var exception = new MigrationException("reason");
|
||||
doThrow(exception).when(applier).applyMigration(new M00010101000000_MigrationsLogTableMigration(mapper, migrationTableName));
|
||||
|
||||
//test
|
||||
var thrown = assertThrows(MigrationException.class, () -> applier.createMigrationsTable());
|
||||
|
||||
//assert
|
||||
assertSame(exception, thrown);
|
||||
|
||||
verify(applier, times(1)).createMigrationsTable();
|
||||
verify(platform, times(1)).getTypeMapper();
|
||||
verify(options, times(1)).getMigrationsTableName();
|
||||
verify(applier, times(1)).applyMigration(new M00010101000000_MigrationsLogTableMigration(mapper, migrationTableName));
|
||||
verifyNoMoreInteractions(allMocks);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package jef.platform.dummy;
|
||||
|
||||
import jef.MigrationException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class DummyDatabaseTest {
|
||||
|
||||
@Test
|
||||
void migrate() throws MigrationException {
|
||||
new DummyDatabase().migrate();
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package jef.platform.dummy;
|
||||
|
||||
import jef.platform.dummy.migration.DummyMigrationApplier;
|
||||
import jef.platform.dummy.migration.DummyMigrationOperationTranslator;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
|
||||
class DummyPlatformTest {
|
||||
|
||||
@Test
|
||||
void getTranslator() {
|
||||
//test
|
||||
var translator = new DummyPlatform().getTranslator();
|
||||
|
||||
//assert
|
||||
assertSame(DummyMigrationOperationTranslator.INSTANCE, translator);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTypeMapper() {
|
||||
assertSame(DummyTypeMapper.INSTANCE, new DummyPlatform().getTypeMapper());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMigrationApplier() {
|
||||
assertSame(DummyMigrationApplier.INSTANCE, DummyPlatform.INSTANCE.getMigrationApplier(null, null));
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package jef.platform.dummy;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class DummyTypeMapperTest {
|
||||
@Test
|
||||
void map() {
|
||||
//setup
|
||||
var type = UUID.randomUUID().toString();
|
||||
|
||||
//test
|
||||
Optional<String> opt = DummyTypeMapper.INSTANCE.map(type);
|
||||
|
||||
//assert
|
||||
assertTrue(opt.isPresent());
|
||||
assertSame(type, opt.orElseThrow());
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package jef.platform.dummy.migration;
|
||||
|
||||
import jef.MigrationException;
|
||||
import jef.model.migration.Migration;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
class DummyMigrationApplierTest {
|
||||
|
||||
@Test
|
||||
void migrate() throws MigrationException {
|
||||
DummyMigrationApplier.INSTANCE.migrate();
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyMigration() throws MigrationException {
|
||||
//setup
|
||||
Migration migration = mock(Migration.class);
|
||||
|
||||
//test
|
||||
DummyMigrationApplier.INSTANCE.applyMigration(migration);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findMigrations() throws MigrationException {
|
||||
var foundMethodOverride = new Throwable();
|
||||
try {
|
||||
DummyMigrationApplier.class.getDeclaredMethod("findMigrations", String.class);//throws NoSuchMethodException, i.e. not overriden
|
||||
throw foundMethodOverride;
|
||||
} catch (Throwable t) {
|
||||
assertNotSame(foundMethodOverride, t);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void migrationsTableExists() throws SQLException {
|
||||
assertFalse(DummyMigrationApplier.INSTANCE.migrationsTableExists());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createMigrationsTable() throws MigrationException {
|
||||
DummyMigrationApplier.INSTANCE.createMigrationsTable();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAppliedMigrations() throws SQLException {
|
||||
assertEquals(0, DummyMigrationApplier.INSTANCE.getAppliedMigrations().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void insertMigrationLog() throws SQLException {
|
||||
//setup
|
||||
Migration migration = mock(Migration.class);
|
||||
|
||||
//test
|
||||
DummyMigrationApplier.INSTANCE.insertMigrationLog(migration);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package jef.platform.dummy.migration;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
class DummyMigrationOperationTranslatorTest {
|
||||
@Test
|
||||
void translate() throws SQLException {
|
||||
assertNull(DummyMigrationOperationTranslator.INSTANCE.translate(null, null));
|
||||
}
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
package jef.query;
|
||||
|
||||
import jef.expressions.AndExpression;
|
||||
import jef.expressions.BinaryExpression;
|
||||
import jef.expressions.ConstantExpression;
|
||||
import jef.expressions.FieldExpression;
|
||||
import jef.expressions.IntermediateFieldExpression;
|
||||
import jef.expressions.LimitExpression;
|
||||
import jef.expressions.NullExpression;
|
||||
import jef.expressions.OrExpression;
|
||||
import jef.expressions.OrderExpression;
|
||||
import jef.expressions.ParameterExpression;
|
||||
import jef.expressions.SelectExpression;
|
||||
import jef.expressions.TableExpression;
|
||||
import jef.expressions.TernaryExpression;
|
||||
import jef.expressions.UnaryExpression;
|
||||
import jef.expressions.WhereExpression;
|
||||
import jef.platform.base.query.QueryBuilder;
|
||||
import jef.serializable.SerializableObject;
|
||||
import lombok.Getter;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
class QueryBuilderTest {
|
||||
@Test
|
||||
void visitAnd() {
|
||||
var v = new QueryBuilder();
|
||||
v.visitAnd(new AndExpression(ConstantExpression.V0, ConstantExpression.V1));
|
||||
Assertions.assertEquals("0 AND 1", v.getQuery());
|
||||
}
|
||||
|
||||
@Test
|
||||
void visitBinary() {
|
||||
var v = new QueryBuilder();
|
||||
v.visitBinary(new BinaryExpression(ConstantExpression.V0, ConstantExpression.V1, BinaryExpression.Operator.EQ));
|
||||
Assertions.assertEquals("0 = 1", v.getQuery());
|
||||
}
|
||||
|
||||
@Test
|
||||
void visitConstant() {
|
||||
var v = new QueryBuilder();
|
||||
v.visitConstant(ConstantExpression.V0);
|
||||
Assertions.assertEquals("0", v.getQuery());
|
||||
}
|
||||
|
||||
@Test
|
||||
void visitField() {
|
||||
var v = new QueryBuilder();
|
||||
v.visitField(new FieldExpression("schema", "table", "name", null));
|
||||
Assertions.assertEquals("`schema`.`table`.`name`", v.getQuery());
|
||||
|
||||
v = new QueryBuilder();
|
||||
v.visitField(new FieldExpression(null, "table", "name", null));
|
||||
Assertions.assertEquals("`table`.`name`", v.getQuery());
|
||||
|
||||
v = new QueryBuilder();
|
||||
v.visitField(new FieldExpression("schema", null, "name", null));
|
||||
Assertions.assertEquals("`name`", v.getQuery());
|
||||
|
||||
v = new QueryBuilder();
|
||||
v.visitField(new FieldExpression(null, null, "name", null));
|
||||
Assertions.assertEquals("`name`", v.getQuery());
|
||||
}
|
||||
|
||||
@Test
|
||||
void visitIntermediateField() {
|
||||
var v = new QueryBuilder();
|
||||
v.visitIntermediateField(new IntermediateFieldExpression("value", null));
|
||||
Assertions.assertEquals("value", v.getQuery());
|
||||
}
|
||||
|
||||
@Test
|
||||
void visitLimit() {
|
||||
var v = new QueryBuilder();
|
||||
v.visitLimit(new LimitExpression(ConstantExpression.V0, 3L, 5L));
|
||||
Assertions.assertEquals("0 OFFSET 3 LIMIT 5", v.getQuery());
|
||||
}
|
||||
|
||||
@Test
|
||||
void visitNull() {
|
||||
var v = new QueryBuilder();
|
||||
v.visitNull(NullExpression.INSTANCE);
|
||||
Assertions.assertEquals("NULL", v.getQuery());
|
||||
}
|
||||
|
||||
@Test
|
||||
void visitOr() {
|
||||
var v = new QueryBuilder();
|
||||
v.visitOr(new OrExpression(ConstantExpression.V0, ConstantExpression.V1));
|
||||
Assertions.assertEquals("0 OR 1", v.getQuery());
|
||||
}
|
||||
|
||||
@Test
|
||||
void visitOrder() {
|
||||
//multiple sorts
|
||||
var v = new QueryBuilder();
|
||||
v.visitOrder(new OrderExpression(ConstantExpression.V0, List.of(
|
||||
new OrderExpression.Sort(new FieldExpression(null, null, "name1", null), OrderExpression.SortDirection.ASCENDING),
|
||||
new OrderExpression.Sort(new FieldExpression(null, null, "name2", null), OrderExpression.SortDirection.DESCENDING))));
|
||||
Assertions.assertEquals("0 ORDER BY `name1` ASC, `name2` DESC", v.getQuery());
|
||||
|
||||
//with schema and table
|
||||
v = new QueryBuilder();
|
||||
v.visitOrder(new OrderExpression(ConstantExpression.V0, List.of(
|
||||
new OrderExpression.Sort(new FieldExpression("schema", "table", "name", null), OrderExpression.SortDirection.ASCENDING))));
|
||||
Assertions.assertEquals("0 ORDER BY `schema`.`table`.`name` ASC", v.getQuery());
|
||||
|
||||
//with table
|
||||
v = new QueryBuilder();
|
||||
v.visitOrder(new OrderExpression(ConstantExpression.V0, List.of(
|
||||
new OrderExpression.Sort(new FieldExpression(null, "table", "name", null), OrderExpression.SortDirection.ASCENDING))));
|
||||
Assertions.assertEquals("0 ORDER BY `table`.`name` ASC", v.getQuery());
|
||||
|
||||
//with schema
|
||||
v = new QueryBuilder();
|
||||
v.visitOrder(new OrderExpression(ConstantExpression.V0, List.of(
|
||||
new OrderExpression.Sort(new FieldExpression("schema", null, "name", null), OrderExpression.SortDirection.ASCENDING))));
|
||||
Assertions.assertEquals("0 ORDER BY `name` ASC", v.getQuery());
|
||||
|
||||
//without schema and table
|
||||
v = new QueryBuilder();
|
||||
v.visitOrder(new OrderExpression(ConstantExpression.V0, List.of(
|
||||
new OrderExpression.Sort(new FieldExpression(null, null, "name", null), OrderExpression.SortDirection.ASCENDING))));
|
||||
Assertions.assertEquals("0 ORDER BY `name` ASC", v.getQuery());
|
||||
}
|
||||
|
||||
@Test
|
||||
void visitParameter() {
|
||||
//isInput
|
||||
var v = new QueryBuilder();
|
||||
v.visitParameter(new ParameterExpression(0, null, true, null));
|
||||
Assertions.assertEquals("param #0", v.getQuery());
|
||||
|
||||
//value == null
|
||||
v = new QueryBuilder();
|
||||
v.visitParameter(new ParameterExpression(0, null, false, null));
|
||||
Assertions.assertEquals("NULL", v.getQuery());
|
||||
|
||||
//value == null
|
||||
v = new QueryBuilder();
|
||||
v.visitParameter(new ParameterExpression(0, Arrays.asList(null, "value"), false, null));
|
||||
Assertions.assertEquals("(NULL, value)", v.getQuery());
|
||||
|
||||
//value == null
|
||||
var v2 = new QueryBuilder();
|
||||
Assertions.assertThrows(UnsupportedOperationException.class, () -> v2.visitParameter(new ParameterExpression(0, "value", false, null)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void visitSelect() {
|
||||
//select from non table expr
|
||||
var v = new QueryBuilder();
|
||||
v.visitSelect(new SelectExpression(List.of(
|
||||
new FieldExpression(null, null, "name", null),
|
||||
new FieldExpression(null, null, "name2", null)
|
||||
), ConstantExpression.V0, "alias"));
|
||||
Assertions.assertEquals("SELECT `name`, `name2` FROM (0) alias", v.getQuery());
|
||||
|
||||
v = new QueryBuilder();
|
||||
v.visitSelect(new SelectExpression(List.of(
|
||||
new FieldExpression(null, null, "name", null),
|
||||
new FieldExpression(null, null, "name2", null)
|
||||
), new TableExpression("table"), "alias"));
|
||||
Assertions.assertEquals("SELECT `name`, `name2` FROM `table` alias", v.getQuery());
|
||||
}
|
||||
|
||||
@Test
|
||||
void visitTable() {
|
||||
var v = new QueryBuilder();
|
||||
v.visitTable(new TableExpression("table"));
|
||||
Assertions.assertEquals("`table`", v.getQuery());
|
||||
}
|
||||
|
||||
@Test
|
||||
void visitTernary() {
|
||||
var v = new QueryBuilder();
|
||||
v.visitTernary(new TernaryExpression(ConstantExpression.V0, ConstantExpression.V1, ConstantExpression.V2));
|
||||
Assertions.assertEquals("IF(0, 1, 2)", v.getQuery());
|
||||
}
|
||||
|
||||
@Test
|
||||
void visitUnary() {
|
||||
var v = new QueryBuilder();
|
||||
v.visitUnary(new UnaryExpression(NullExpression.INSTANCE, UnaryExpression.Operator.NOT));
|
||||
Assertions.assertEquals("NOT NULL", v.getQuery());
|
||||
|
||||
v = new QueryBuilder();
|
||||
v.visitUnary(new UnaryExpression(new BinaryExpression(ConstantExpression.V0, ConstantExpression.V1, BinaryExpression.Operator.EQ), UnaryExpression.Operator.NOT));
|
||||
Assertions.assertEquals("NOT (0 = 1)", v.getQuery());
|
||||
}
|
||||
|
||||
@Test
|
||||
void visitWhere() {
|
||||
var v = new QueryBuilder();
|
||||
v.visitWhere(new WhereExpression(ConstantExpression.V0, ConstantExpression.V1));
|
||||
Assertions.assertEquals("0 WHERE 1", v.getQuery());
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class TestClass extends SerializableObject {
|
||||
public int i = 1;
|
||||
public Object o = new Object();
|
||||
public double d;
|
||||
public float f;
|
||||
public long l;
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package jef.util;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class CheckTest {
|
||||
@Test
|
||||
void notNull_paramNotNull() {
|
||||
//setup
|
||||
var obj = new Object();
|
||||
|
||||
//test
|
||||
var ret = Check.notNull(obj, "obj") ;
|
||||
|
||||
//assert
|
||||
assertEquals(obj, ret);
|
||||
}
|
||||
|
||||
@Test
|
||||
void notNull_paramNull() {
|
||||
//setup
|
||||
var varName = UUID.randomUUID().toString();
|
||||
|
||||
//test & assert
|
||||
var ex = assertThrows(IllegalArgumentException.class, () -> Check.notNull(null, varName));
|
||||
assertEquals(varName + " must be not null", ex.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package jef.util;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class UtilTest {
|
||||
|
||||
@Test
|
||||
void tryGet_NoException() {
|
||||
//setup
|
||||
var object = new Object();
|
||||
|
||||
//test
|
||||
var ret = Util.tryGet(() -> object);
|
||||
|
||||
//assert
|
||||
assertTrue(ret.isPresent());
|
||||
assertEquals(object, ret.orElseThrow());
|
||||
}
|
||||
|
||||
@Test
|
||||
void tryGet_Exception() {
|
||||
//test
|
||||
var ret = Util.tryGet(() -> {
|
||||
throw new Exception();
|
||||
});
|
||||
|
||||
//assert
|
||||
assertFalse(ret.isPresent());
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>jef</artifactId>
|
||||
<groupId>jef</groupId>
|
||||
<version>0.1</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>migration-creator</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>jef</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jef</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,127 +0,0 @@
|
||||
package jef.model.migration.creator;
|
||||
|
||||
import jef.model.DbContext;
|
||||
import jef.model.DbEntity;
|
||||
import jef.model.DbEntityBuilder;
|
||||
import jef.model.DbField;
|
||||
import jef.model.ModelBuilder;
|
||||
import jef.model.annotations.Generated;
|
||||
import jef.model.constraints.ForeignKeyConstraint;
|
||||
import jef.model.constraints.IndexConstraint;
|
||||
import jef.model.constraints.KeyConstraint;
|
||||
import jef.model.constraints.PrimaryKeyConstraint;
|
||||
import jef.model.constraints.UniqueKeyConstraint;
|
||||
import jef.platform.base.SqlTypeMapper;
|
||||
import jef.serializable.SerializableObject;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class ModelBuilderGenerator {
|
||||
private final ModelBuilder mb;
|
||||
private final String className;
|
||||
private final String packageName;
|
||||
private final SqlTypeMapper sqlTypeMapper;
|
||||
|
||||
private final Set<Class<?>> imports = new HashSet<>();
|
||||
@Getter
|
||||
private String java = null;
|
||||
|
||||
public ModelBuilderGenerator generate() {
|
||||
if (java == null) {
|
||||
java = generateModelBuilderJava();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private String generateModelBuilderJava() {
|
||||
var indent = " ";
|
||||
imports.add(DbContext.class);
|
||||
imports.add(ModelBuilder.class);
|
||||
imports.add(DbEntityBuilder.class);
|
||||
var java = ""
|
||||
+ "public class " + className + " extends DbContext {\n"
|
||||
+ " @Override\n"
|
||||
+ " public void onModelCreate(ModelBuilder mb) {\n";
|
||||
for (DbEntity<? extends SerializableObject> entity : mb.getEntities()) {
|
||||
java += indent + "mb.entity(\"" + entity.getTypeName() + "\")\n"
|
||||
+ indent + " .name(\"" + entity.getName() + "\");\n";
|
||||
for (DbField<?> field : entity.getFields()) {
|
||||
if (field.getGenerated() != Generated.Type.NONE) {
|
||||
imports.add(Generated.class);
|
||||
}
|
||||
java += indent + "mb.entity(\"" + entity.getTypeName() + "\")\n"
|
||||
+ indent + " .field(\"" + field.getName() + "\", \"" + field.getTypeName() + "\")"
|
||||
+ "\n" + indent + " .sqlType(" + getSqlType(field) + ")"
|
||||
+ (field.isNotNull() ? "\n" + indent + " .isNotNull()" : "")
|
||||
+ (field.getGenerated() != Generated.Type.NONE ? "\n" + indent + " .generated(Generated.Type." + field.getGenerated() + ")" : "")
|
||||
+ "\n" + indent + " .isDatabaseField(" + field.isDatabaseField() + ")"
|
||||
+ "\n" + indent + " .isModelField(" + field.isModelField() + ");\n";
|
||||
}
|
||||
if (entity.getPrimaryKey() != null) {
|
||||
imports.add(List.class);
|
||||
imports.add(PrimaryKeyConstraint.class);
|
||||
java += indent + "mb.entity(\"" + entity.getTypeName() + "\")\n"
|
||||
+ indent + " .hasOne(" + entity.getPrimaryKey().getFields().stream().map(f -> "\"" + f.getName() + "\"").collect(Collectors.joining(", ")) + ")\n"
|
||||
+ indent + " .isPrimaryKey();\n";
|
||||
}
|
||||
java += "\n";
|
||||
}
|
||||
for (DbEntity<? extends SerializableObject> entity : mb.getEntities()) {
|
||||
if (entity.getForeignKeys().isEmpty() && entity.getForeignKeys().isEmpty() && entity.getForeignKeys().isEmpty() && entity.getForeignKeys().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (ForeignKeyConstraint foreignKey : entity.getForeignKeys()) {
|
||||
imports.add(ForeignKeyConstraint.class);
|
||||
//TODO hasOne/hasMany/withOne/WithMany
|
||||
java += indent + "mb.entity(\"" + entity.getTypeName() + "\")\n"
|
||||
+ indent + " .hasOne(" + foreignKey.getFields().stream().map(f -> "\"" + f.getName() + "\"").collect(Collectors.joining(", " + indent)) + ")\n"
|
||||
+ indent + " .withOne(\"" + foreignKey.getReferencedEntity().getTypeName() + "\", " + foreignKey.getReferencedFields().stream().map(f -> "\"" + f.getName() + "\"").collect(Collectors.joining(", ")) + ")\n"
|
||||
+ indent + " .onUpdate(ForeignKeyConstraint.Action." + foreignKey.getOnUpdate().name() + ")\n"
|
||||
+ indent + " .onDelete(ForeignKeyConstraint.Action." + foreignKey.getOnDelete().name() + ");\n";
|
||||
}
|
||||
for (UniqueKeyConstraint uniqueKey : entity.getUniqueKeys()) {
|
||||
java += indent + "mb.entity(\"" + entity.getTypeName() + "\")\n"
|
||||
+ indent + " .hasOne(" + uniqueKey.getFields().stream().map(f -> "\"" + f.getName() + "\"").collect(Collectors.joining(", ")) + ")\n"
|
||||
+ indent + " .isUnique();\n";
|
||||
}
|
||||
for (KeyConstraint key : entity.getKeys()) {
|
||||
java += indent + "mb.entity(\"" + entity.getTypeName() + "\")\n"
|
||||
+ indent + " .hasOne(" + key.getFields().stream().map(f -> "\"" + f.getName() + "\"").collect(Collectors.joining(", ")) + ")\n"
|
||||
+ indent + " .isKey();\n";
|
||||
}
|
||||
for (IndexConstraint index : entity.getIndexes()) {
|
||||
java += indent + "mb.entity(\"" + entity.getTypeName() + "\")\n"
|
||||
+ indent + " .hasOne(" + index.getFields().stream().map(f -> "\"" + f.getName() + "\"").collect(Collectors.joining(", ")) + ")\n"
|
||||
+ indent + " .isIndex();\n";
|
||||
}
|
||||
}
|
||||
java += " }\n"
|
||||
+ "}\n";
|
||||
|
||||
//imports
|
||||
var normalImports = imports.stream().filter(e -> !e.getName().startsWith("java")).sorted(Comparator.comparing(Class::getName, String.CASE_INSENSITIVE_ORDER)).toList();
|
||||
var javaImports = imports.stream().filter(e -> e.getName().startsWith("java")).sorted(Comparator.comparing(Class::getName, String.CASE_INSENSITIVE_ORDER)).toList();
|
||||
var normalImportsString = normalImports.stream().map(e -> "import " + e.getName().replace("$", ".") + ";").collect(Collectors.joining("\n"));
|
||||
var javaImportsString = javaImports.stream().map(e -> "import " + e.getName().replace("$", ".") + ";").collect(Collectors.joining("\n"));
|
||||
|
||||
//finalize
|
||||
java = (packageName != null ? "package " + packageName + ";\n\n" : "")
|
||||
+ normalImportsString + (normalImportsString.length() > 0 ? "\n\n" : "")
|
||||
+ javaImportsString + (javaImportsString.length() > 0 ? "\n\n" : "")
|
||||
+ java;
|
||||
return java;
|
||||
}
|
||||
|
||||
private String getSqlType(DbField<?> f) {
|
||||
return Optional.ofNullable(f.getSqlType()).or(() -> sqlTypeMapper.map(f.getTypeName())).map(e -> "\"" + e + "\"").orElse(null);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>jef</artifactId>
|
||||
<groupId>jef</groupId>
|
||||
<version>0.1</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>mysql</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>jef</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jef</groupId>
|
||||
<artifactId>migration-creator</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,333 +0,0 @@
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package jef.platform.mysql;
|
||||
|
||||
import jef.platform.SqlPlatform;
|
||||
import jef.platform.base.DatabaseOptions;
|
||||
import jef.platform.base.SqlTypeMapper;
|
||||
import jef.platform.base.migration.MigrationApplier;
|
||||
import jef.platform.mysql.migration.MysqlMigrationApplier;
|
||||
import jef.platform.mysql.migration.MysqlMigrationOperationTranslator;
|
||||
|
||||
import java.sql.Connection;
|
||||
|
||||
public class MysqlPlatform implements SqlPlatform {
|
||||
@Override
|
||||
public MysqlMigrationOperationTranslator getTranslator() {
|
||||
return new MysqlMigrationOperationTranslator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlTypeMapper getTypeMapper() {
|
||||
return new MysqlTypeMapper();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MigrationApplier getMigrationApplier(Connection connection, DatabaseOptions options) {
|
||||
return new MysqlMigrationApplier(connection, options, this);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package jef.platform.mysql;
|
||||
|
||||
import jef.platform.base.SqlTypeMapper;
|
||||
|
||||
public class MysqlTypeMapper extends SqlTypeMapper {
|
||||
|
||||
}
|
||||
@@ -1,445 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,791 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package jef.platform.mysql.migration;
|
||||
|
||||
import jef.model.migration.Migration;
|
||||
import jef.platform.SqlPlatform;
|
||||
import jef.platform.base.DatabaseOptions;
|
||||
import jef.platform.base.migration.MigrationApplier;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class MysqlMigrationApplier extends MigrationApplier {
|
||||
public MysqlMigrationApplier(Connection connection, DatabaseOptions options, SqlPlatform sqlPlatform) {
|
||||
super(connection, options, sqlPlatform);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void insertMigrationLog(Migration m) throws SQLException {
|
||||
try (var stmt = connection.prepareStatement("INSERT INTO `" + options.getMigrationsTableName() + "` (`migration`, `version`) VALUES (?, ?)")) {//TODO configurable log table name
|
||||
stmt.setString(1, m.getClass().getSimpleName());
|
||||
stmt.setString(2, "0.1"); //TODO insert actual library version
|
||||
stmt.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean migrationsTableExists() throws SQLException {
|
||||
try (var stmt = connection.prepareStatement("SHOW TABLES");
|
||||
var res = stmt.executeQuery()) {
|
||||
while (res.next()) {
|
||||
if (res.getString(1).equals(options.getMigrationsTableName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getAppliedMigrations() throws SQLException {
|
||||
var ret = new ArrayList<String>();
|
||||
if (!migrationsTableExists()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
try (var stmt = connection.prepareStatement("SELECT `migration` FROM `" + options.getMigrationsTableName() + "`");
|
||||
var res = stmt.executeQuery()) {
|
||||
while (res.next()) {
|
||||
if (!res.getString(1).equals(options.getMigrationsTableName())) {
|
||||
ret.add(res.getString("migration"));
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
package jef.platform.mysql.migration;
|
||||
|
||||
import jef.model.annotations.Generated;
|
||||
import jef.model.migration.operation.AddFieldOperation;
|
||||
import jef.model.migration.operation.AddForeignKeyOperation;
|
||||
import jef.model.migration.operation.AddIndexOperation;
|
||||
import jef.model.migration.operation.AddKeyOperation;
|
||||
import jef.model.migration.operation.AddPrimaryKeyOperation;
|
||||
import jef.model.migration.operation.AddTableOperation;
|
||||
import jef.model.migration.operation.AddUniqueKeyOperation;
|
||||
import jef.model.migration.operation.DropConstraintOperation;
|
||||
import jef.model.migration.operation.DropFieldOperation;
|
||||
import jef.model.migration.operation.DropTableOperation;
|
||||
import jef.model.migration.operation.MigrationOperation;
|
||||
import jef.model.migration.operation.RenameFieldOperation;
|
||||
import jef.model.migration.operation.RenameTableOperation;
|
||||
import jef.model.migration.operation.UpdateFieldOperation;
|
||||
import jef.platform.base.migration.MigrationOperationTranslator;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MysqlMigrationOperationTranslator implements MigrationOperationTranslator {
|
||||
private static final Map<Class<? extends MigrationOperation>, Mapper<MigrationOperation>> translators;
|
||||
|
||||
static {
|
||||
var map = new HashMap<Class<? extends MigrationOperation>, Mapper<MigrationOperation>>();
|
||||
map.put(AddFieldOperation.class, MysqlMigrationOperationTranslator::translateAddFieldOperation);
|
||||
map.put(AddForeignKeyOperation.class, MysqlMigrationOperationTranslator::translateAddForeignKeyOperation);
|
||||
map.put(AddIndexOperation.class, MysqlMigrationOperationTranslator::translateAddIndexOperation);
|
||||
map.put(AddKeyOperation.class, MysqlMigrationOperationTranslator::translateAddKeyOperation);
|
||||
map.put(AddPrimaryKeyOperation.class, MysqlMigrationOperationTranslator::translateAddPrimaryKeyOperation);
|
||||
map.put(AddTableOperation.class, MysqlMigrationOperationTranslator::translateAddTableOperation);
|
||||
map.put(AddUniqueKeyOperation.class, MysqlMigrationOperationTranslator::translateAddUniqueKeyOperation);
|
||||
map.put(DropConstraintOperation.class, MysqlMigrationOperationTranslator::translateDropConstraintOperation);
|
||||
map.put(DropFieldOperation.class, MysqlMigrationOperationTranslator::translateDropFieldOperation);
|
||||
map.put(DropTableOperation.class, MysqlMigrationOperationTranslator::translateDropTableOperation);
|
||||
map.put(RenameFieldOperation.class, MysqlMigrationOperationTranslator::translateRenameFieldOperation);
|
||||
map.put(RenameTableOperation.class, MysqlMigrationOperationTranslator::translateRenameTableOperation);
|
||||
map.put(UpdateFieldOperation.class, MysqlMigrationOperationTranslator::translateUpdateFieldOperation);
|
||||
translators = Collections.unmodifiableMap(map);
|
||||
}
|
||||
|
||||
public static PreparedStatement translateS(Connection connection, MigrationOperation operation) throws SQLException {
|
||||
return Optional.ofNullable(translators.get(operation.getClass())).orElseThrow().apply(connection, operation);
|
||||
}
|
||||
|
||||
public PreparedStatement translate(Connection connection, MigrationOperation operation) throws SQLException {
|
||||
return translateS(connection, operation);
|
||||
}
|
||||
|
||||
private static PreparedStatement translateAddFieldOperation(Connection connection, MigrationOperation operation) throws SQLException {
|
||||
AddFieldOperation op = (AddFieldOperation) operation;
|
||||
return connection.prepareStatement("ALTER TABLE `" + op.getTable() + "`"
|
||||
+ " ADD COLUMN `" + op.getField() + "` "
|
||||
+ op.getSqlType() + (op.isNotNull() ? " NOT NULL" : "") //TODO add after field specification
|
||||
+ (op.getGenerated() == Generated.Type.IDENTITY ? "AUTO_INCREMENT " : "")
|
||||
// + (op.getGenerated() == Generated.Type.COMPUTED ? "AUTO_INCREMENT " : "")//TODO
|
||||
+ " LAST");
|
||||
}
|
||||
|
||||
private static PreparedStatement translateAddForeignKeyOperation(Connection connection, MigrationOperation operation) throws SQLException {
|
||||
AddForeignKeyOperation op = (AddForeignKeyOperation) operation;
|
||||
return connection.prepareStatement("ALTER TABLE `" + op.getTable() + "`"
|
||||
+ " ADD CONSTRAINT `" + op.getName() + "`"
|
||||
+ " FOREIGN KEY (" + op.getFields().stream().map(e -> "`" + e + "`").collect(Collectors.joining(", ")) + ")"
|
||||
+ " REFERENCES `" + op.getReferencedTable() + "`(" + op.getReferencedFields().stream()
|
||||
.map(e -> "`" + e + "`")
|
||||
.collect(Collectors.joining(", ")) + ")");
|
||||
}
|
||||
|
||||
private static PreparedStatement translateAddIndexOperation(Connection connection, MigrationOperation operation) throws SQLException {
|
||||
AddIndexOperation op = (AddIndexOperation) operation;
|
||||
return connection.prepareStatement("ALTER TABLE `" + op.getTable() + "`"
|
||||
+ " ADD CONSTRAINT `" + op.getName() + "`"
|
||||
+ " INDEX (" + op.getFields().stream().map(e -> "`" + e + "`").collect(Collectors.joining(", ")) + ")");
|
||||
}
|
||||
|
||||
private static PreparedStatement translateAddKeyOperation(Connection connection, MigrationOperation operation) throws SQLException {
|
||||
AddKeyOperation op = (AddKeyOperation) operation;
|
||||
return connection.prepareStatement("ALTER TABLE `" + op.getTable() + "`"
|
||||
+ " ADD CONSTRAINT `" + op.getName() + "`"
|
||||
+ " KEY (" + op.getFields().stream().map(e -> "`" + e + "`").collect(Collectors.joining(", ")) + ")");
|
||||
}
|
||||
|
||||
private static PreparedStatement translateAddPrimaryKeyOperation(Connection connection, MigrationOperation operation) throws SQLException {
|
||||
AddPrimaryKeyOperation op = (AddPrimaryKeyOperation) operation;
|
||||
return connection.prepareStatement("ALTER TABLE `" + op.getTable() + "`"
|
||||
+ " ADD " + (!op.getName().equals("PRIMARY") ? "CONSTRAINT `" + op.getName() + "`" : "")
|
||||
+ " PRIMARY KEY (" + op.getFields().stream().map(e -> "`" + e + "`").collect(Collectors.joining(", ")) + ")");
|
||||
// + " ADD CONSTRAINT `" + op.getName() + "`"
|
||||
// + " PRIMARY KEY (" + op.getFields().stream().map(e -> "`" + e + "`").collect(Collectors.joining(", ")) + ")");
|
||||
}
|
||||
|
||||
private static PreparedStatement translateAddTableOperation(Connection connection, MigrationOperation operation) throws SQLException {
|
||||
AddTableOperation op = (AddTableOperation) operation;
|
||||
return connection.prepareStatement("CREATE TABLE `" + op.getTable() + "` ("
|
||||
+ op.getFields().stream().map(e -> {
|
||||
var f = e.build();
|
||||
return "`" + f.getField() + "` " + f.getSqlType()
|
||||
+ (f.isNotNull() ? " NOT NULL" : "")
|
||||
+ (f.getGenerated() == Generated.Type.IDENTITY ? " AUTO_INCREMENT" : "");
|
||||
// + (op.getGenerated() == Generated.Type.COMPUTED ? "AUTO_INCREMENT " : "")//TODO
|
||||
}).collect(Collectors.joining(", "))
|
||||
+ (op.getPrimaryKey() != null
|
||||
? ", PRIMARY KEY (" + op.getPrimaryKey().build().getFields().stream().map(e -> "`" + e + "`").collect(Collectors.joining(", ")) + ")"
|
||||
: "")
|
||||
+ ")"); //TODO default collocation from database config or operation, field collation, constraints
|
||||
}
|
||||
|
||||
private static PreparedStatement translateAddUniqueKeyOperation(Connection connection, MigrationOperation operation) throws SQLException {
|
||||
AddUniqueKeyOperation op = (AddUniqueKeyOperation) operation;
|
||||
return connection.prepareStatement("ALTER TABLE `" + op.getTable() + "`"
|
||||
+ " ADD CONSTRAINT `" + op.getName() + "`"
|
||||
+ " UNIQUE (" + op.getFields().stream().map(e -> "`" + e + "`").collect(Collectors.joining(", ")) + ")");
|
||||
}
|
||||
|
||||
private static PreparedStatement translateDropConstraintOperation(Connection connection, MigrationOperation operation) throws SQLException {
|
||||
DropConstraintOperation op = (DropConstraintOperation) operation;
|
||||
return connection.prepareStatement("ALTER TABLE `" + op.getTable() + "`"
|
||||
+ " DROP CONSTRAINT `" + op.getName() + "`");
|
||||
}
|
||||
|
||||
private static PreparedStatement translateDropFieldOperation(Connection connection, MigrationOperation operation) throws SQLException {
|
||||
DropFieldOperation op = (DropFieldOperation) operation;
|
||||
return connection.prepareStatement("ALTER TABLE `" + op.getTable() + "`"
|
||||
+ " DROP COLUMN `" + op.getField() + "`");
|
||||
}
|
||||
|
||||
private static PreparedStatement translateDropTableOperation(Connection connection, MigrationOperation operation) throws SQLException {
|
||||
DropTableOperation op = (DropTableOperation) operation;
|
||||
return connection.prepareStatement("DROP TABLE `" + op.getTable() + "`");
|
||||
}
|
||||
|
||||
private static PreparedStatement translateRenameFieldOperation(Connection connection, MigrationOperation operation) throws SQLException {
|
||||
RenameFieldOperation op = (RenameFieldOperation) operation;
|
||||
return connection.prepareStatement("ALTER TABLE `" + op.getTable() + "`"
|
||||
+ " RENAME COLUMN `" + op.getOldName() + "` `" + op.getNewName() + "`");
|
||||
}
|
||||
|
||||
private static PreparedStatement translateRenameTableOperation(Connection connection, MigrationOperation operation) throws SQLException {
|
||||
RenameTableOperation op = (RenameTableOperation) operation;
|
||||
return connection.prepareStatement("RENAME TABLE `" + op.getOldName() + "` `" + op.getNewName() + "`");
|
||||
}
|
||||
|
||||
private static PreparedStatement translateUpdateFieldOperation(Connection connection, MigrationOperation operation) throws SQLException {
|
||||
UpdateFieldOperation op = (UpdateFieldOperation) operation;
|
||||
return connection.prepareStatement("ALTER TABLE `" + op.getTable() + "`"
|
||||
+ " ALTER COLUMN `" + op.getField() + "` `" + op.getNewName() + "`"
|
||||
+ op.getSqlType() + (op.isNotNull() ? " NOT NULL" : ""));
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface Mapper<T extends MigrationOperation> {
|
||||
PreparedStatement apply(Connection connection, T operation) throws SQLException;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package jef.platform.mysql.query;
|
||||
|
||||
import jef.platform.base.query.QueryBuilder;
|
||||
|
||||
public class MysqlQueryBuilder extends QueryBuilder {
|
||||
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
package jef.platform.mysql.migration;
|
||||
|
||||
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.model.migration.creator.MigrationCreator;
|
||||
import jef.platform.base.Database;
|
||||
import jef.platform.base.DatabaseOptions;
|
||||
import jef.platform.mysql.MysqlDatabase;
|
||||
import jef.platform.mysql.MysqlPlatform;
|
||||
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.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.OpenOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.sql.DriverManager;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class MysqlMigrationTest {
|
||||
private static final String GENERATE_MIGRATIONS_FOLDER = "target/test-generated-migrations/";
|
||||
private static final String GENERATE_MIGRATIONS_FOLDER_SRC = GENERATE_MIGRATIONS_FOLDER + "src/";
|
||||
private static final String GENERATE_MIGRATIONS_FOLDER_TARGET = GENERATE_MIGRATIONS_FOLDER + "target/";
|
||||
private static final String PACKAGE_NAME = MysqlMigrationTest.class.getSimpleName();
|
||||
|
||||
@Test
|
||||
public void test() throws Exception {
|
||||
clearMigrationFolders();
|
||||
generateInitialMigration();
|
||||
compileInitialMigration();
|
||||
Class.forName("com.mysql.cj.jdbc.Driver").getDeclaredConstructor().newInstance();
|
||||
var dboptions = new DatabaseOptions("jdbc:mysql://localhost/test", "test", "password", PACKAGE_NAME, 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);
|
||||
ctx.getDatabase().migrate();
|
||||
var result = ctx.getCompanies().filter(e -> e.getName().equals("foobar")).toString();
|
||||
System.out.println(result);
|
||||
}
|
||||
|
||||
private void clearMigrationFolders() {
|
||||
delRecursive(new File(GENERATE_MIGRATIONS_FOLDER_SRC + PACKAGE_NAME));
|
||||
delRecursive(new File(GENERATE_MIGRATIONS_FOLDER_TARGET + PACKAGE_NAME));
|
||||
}
|
||||
|
||||
private void delRecursive(File f) {
|
||||
if (f.isDirectory()) {
|
||||
Arrays.stream(Objects.requireNonNull(f.listFiles()))
|
||||
.filter(e -> !List.of(".", "..").contains(e.getName()))
|
||||
.forEach(this::delRecursive);
|
||||
}
|
||||
f.delete();
|
||||
}
|
||||
|
||||
private void generateInitialMigration() {
|
||||
try {
|
||||
var sqlPlatform = new MysqlPlatform();
|
||||
var from = new ModelBuilder();
|
||||
var to = ModelBuilder.from(Ctx.class);
|
||||
var result = new MigrationCreator(sqlPlatform, from, to, "Initial", PACKAGE_NAME, null).createMigration();
|
||||
|
||||
var dir = Path.of(GENERATE_MIGRATIONS_FOLDER_SRC, PACKAGE_NAME).toFile();
|
||||
dir.mkdirs();
|
||||
|
||||
var fileOptions = new OpenOption[]{StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE};
|
||||
Files.writeString(Path.of(dir.toString(), result.getMigrationClassName() + ".java"), result.getMigration(), fileOptions);
|
||||
Files.writeString(Path.of(dir.toString(), result.getMigrationSnapshotClassName() + ".java"), result.getMigrationSnapshot(), fileOptions);
|
||||
Files.writeString(Path.of(dir.toString(), result.getCurrentSnapshotClassName() + ".java"), result.getCurrentSnapshot(), fileOptions);
|
||||
} catch (AssertionError | RuntimeException e) {
|
||||
throw e;
|
||||
} catch (Throwable t) {
|
||||
throw new RuntimeException("Error while compiling generated class", t);
|
||||
}
|
||||
}
|
||||
|
||||
private void compileInitialMigration() {
|
||||
try {
|
||||
var javaHome = System.getProperty("java.home");
|
||||
var isWindows = System.getProperty("os.name").toLowerCase(Locale.ROOT).equals("win");
|
||||
var javac = new File(javaHome, "bin/javac" + (isWindows ? ".exe" : ""));
|
||||
var params = new ArrayList<>(List.of(
|
||||
javac.getAbsolutePath(),
|
||||
"-cp", "target/classes" + File.pathSeparator + "target/test-classes" + File.pathSeparator + "../core/target/classes",
|
||||
"-encoding", "UTF8",
|
||||
"-g", //debug symbols
|
||||
"-d", GENERATE_MIGRATIONS_FOLDER_TARGET //target
|
||||
));
|
||||
Arrays.stream(new File(GENERATE_MIGRATIONS_FOLDER_SRC + PACKAGE_NAME).listFiles(File::isFile)).map(File::getPath).forEach(params::add);
|
||||
var process = new ProcessBuilder()
|
||||
.command(params.toArray(String[]::new))
|
||||
.inheritIO()
|
||||
.start();
|
||||
process.waitFor(10, TimeUnit.SECONDS);
|
||||
var exitCode = process.exitValue();
|
||||
assertEquals(0, exitCode, "Initial migration compilation failed");
|
||||
} catch (AssertionError | RuntimeException e) {
|
||||
throw e;
|
||||
} catch (Throwable t) {
|
||||
throw new RuntimeException("Error while compiling generated class", t);
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
43
pom.xml
43
pom.xml
@@ -7,13 +7,6 @@
|
||||
<groupId>jef</groupId>
|
||||
<artifactId>jef</artifactId>
|
||||
<version>0.1</version>
|
||||
<packaging>pom</packaging>
|
||||
<modules>
|
||||
<module>core</module>
|
||||
<module>cli</module>
|
||||
<module>mysql</module>
|
||||
<module>migration-creator</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
@@ -101,22 +94,12 @@
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.0</version>
|
||||
</plugin>
|
||||
<!-- <plugin> <!– for inserting values in plugin.yml –>-->
|
||||
<!-- <groupId>org.apache.maven.plugins</groupId>-->
|
||||
<!-- <artifactId>maven-resources-plugin</artifactId>-->
|
||||
<!-- <version>2.7</version>-->
|
||||
<!-- <configuration>-->
|
||||
<!-- <encoding>${project.build.sourceEncoding}</encoding>-->
|
||||
<!-- </configuration>-->
|
||||
<!-- </plugin>-->
|
||||
<plugin> <!-- for tests which generate, compile, and execute migrations -->
|
||||
<plugin> <!-- for inserting values in plugin.yml -->
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.0.0-M7</version>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>2.7</version>
|
||||
<configuration>
|
||||
<additionalClasspathElements>
|
||||
<additionalClasspathElement>${project.basedir}/target/test-generated-migrations/target</additionalClasspathElement>
|
||||
</additionalClasspathElements>
|
||||
<encoding>${project.build.sourceEncoding}</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
@@ -163,18 +146,10 @@
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.30</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>javax.persistence</groupId>-->
|
||||
<!-- <artifactId>javax.persistence-api</artifactId>-->
|
||||
<!-- <version>2.2</version>-->
|
||||
<!-- </dependency>-->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>javax.persistence</groupId>-->
|
||||
<!-- <artifactId>javax.persistence-api</artifactId>-->
|
||||
<!-- <version>2.2</version>-->
|
||||
<!-- </dependency>-->
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -4,24 +4,19 @@ 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(DbContext context, Class<T> clazz, String table) {
|
||||
this.context = context;
|
||||
public DbSet(Class<T> clazz, String table) {
|
||||
this.clazz = clazz;
|
||||
this.table = table;
|
||||
}
|
||||
@@ -37,25 +32,13 @@ public class DbSet<T extends SerializableObject> implements Queryable<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SELECT * FROM `" + table + "`";
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbSet<T> originalSet() {
|
||||
public DBSet<T> clone() {
|
||||
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 String toString() {
|
||||
return "SELECT * FROM `" + table + "`";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -6,15 +6,13 @@ 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.sql.SQLException;
|
||||
import java.io.Serializable;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Spliterator;
|
||||
|
||||
public interface Queryable<T extends SerializableObject> {
|
||||
public interface Queryable<T extends Serializable> {
|
||||
|
||||
//TODO documentation
|
||||
//TODO table alias thing still not ready
|
||||
@@ -22,9 +20,9 @@ public interface Queryable<T extends SerializableObject> {
|
||||
|
||||
Expression getExpression();
|
||||
|
||||
String toString();
|
||||
Queryable<T> clone();
|
||||
|
||||
DbSet<T> originalSet();
|
||||
String toString();
|
||||
|
||||
//stream functions
|
||||
default Iterator<T> iterator() {
|
||||
@@ -135,9 +133,6 @@ public interface Queryable<T extends SerializableObject> {
|
||||
// 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;
|
||||
@@ -172,27 +167,27 @@ public interface Queryable<T extends SerializableObject> {
|
||||
// }
|
||||
//
|
||||
// default long count() {
|
||||
// return 0;
|
||||
// return new CountOp<>(this).execute();
|
||||
// }
|
||||
//
|
||||
// default boolean anyMatch(SerializablePredicate<? super T> SerializablePredicate) {
|
||||
// return false;
|
||||
// default boolean anyMatch(SerializablePredicate<? super T> predicate) {
|
||||
// return filter(predicate).count() > 0;
|
||||
// }
|
||||
//
|
||||
// default boolean allMatch(SerializablePredicate<? super T> SerializablePredicate) {
|
||||
// return false;
|
||||
// default boolean allMatch(SerializablePredicate<? super T> predicate) {
|
||||
// return filter(predicate).count() == clone().count();
|
||||
// }
|
||||
//
|
||||
// default boolean noneMatch(SerializablePredicate<? super T> SerializablePredicate) {
|
||||
// return false;
|
||||
// default boolean noneMatch(SerializablePredicate<? super T> predicate) {
|
||||
// return filter(predicate).count() == 0;
|
||||
// }
|
||||
//
|
||||
|
||||
// default Optional<T> findFirst() {
|
||||
// return Optional.empty();
|
||||
// return limit();
|
||||
// }
|
||||
//
|
||||
// default Optional<T> findAny() {
|
||||
// return Optional.empty();
|
||||
// return findFirst();
|
||||
// }
|
||||
//</editor-fold>
|
||||
}
|
||||
@@ -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 SerializableObject> implements Queryable<T> {
|
||||
public class QueryableProxy<T extends Serializable> implements Queryable<T> {
|
||||
private final Queryable<T> delegate;
|
||||
|
||||
public QueryableProxy(Queryable<T> delegate) {
|
||||
@@ -25,8 +25,8 @@ public class QueryableProxy<T extends SerializableObject> implements Queryable<T
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbSet<T> originalSet() {
|
||||
return delegate.originalSet();
|
||||
public Queryable<T> clone() {
|
||||
return new QueryableProxy<>(delegate.clone());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1,6 +1,6 @@
|
||||
package jef.asm;
|
||||
|
||||
public class AsmParseException extends RuntimeException {
|
||||
public class AsmParseException extends Exception {
|
||||
public AsmParseException() {
|
||||
}
|
||||
|
||||
66
src/main/java/jef/asm/AsmParser.java
Normal file
66
src/main/java/jef/asm/AsmParser.java
Normal file
@@ -0,0 +1,66 @@
|
||||
package jef.asm;
|
||||
|
||||
import jef.expressions.Expression;
|
||||
import jef.serializable.SerializableFunction;
|
||||
import jef.serializable.SerializablePredicate;
|
||||
import lombok.Getter;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.lang.invoke.SerializedLambda;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
@Getter
|
||||
public class AsmParser {
|
||||
private final Serializable lambda;
|
||||
|
||||
public AsmParser(SerializablePredicate<?> predicate) {
|
||||
this.lambda = predicate;
|
||||
}
|
||||
|
||||
public AsmParser(SerializableFunction<?, ?> function) {
|
||||
this.lambda = function;
|
||||
}
|
||||
|
||||
public Expression parse() throws AsmParseException {
|
||||
try {
|
||||
return parseExpression();
|
||||
} catch (Exception e) {
|
||||
throw new AsmParseException("PredicateParser: failed to parse expression: " + e.getLocalizedMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private Expression parseExpression() throws Exception {
|
||||
var cls = lambda.getClass();
|
||||
var loader = cls.getClassLoader();
|
||||
InputStream is;
|
||||
// System.out.println(cls);
|
||||
// System.out.println(cls.getName());
|
||||
if (cls.getName().contains("$$Lambda$")) {
|
||||
// System.out.println(cls.getName().split("\\$\\$")[0].replace(".", "/") + ".class");
|
||||
is = loader.getResourceAsStream(cls.getName().split("\\$\\$")[0].replace(".", "/") + ".class");
|
||||
} else {
|
||||
// System.out.println(cls.getName().replace(".", "/") + ".class");
|
||||
is = loader.getResourceAsStream(cls.getName().replace(".", "/") + ".class");
|
||||
}
|
||||
var x = cls.getDeclaredMethod("writeReplace");
|
||||
// System.out.println(x);
|
||||
x.setAccessible(true);
|
||||
var serlambda = (SerializedLambda) x.invoke(lambda);
|
||||
Object[] args = IntStream.range(0, serlambda.getCapturedArgCount()).mapToObj(serlambda::getCapturedArg).toArray();
|
||||
// System.out.println(serlambda);
|
||||
|
||||
var lambdaname = serlambda.getImplMethodName();
|
||||
// System.out.println(lambdaname);
|
||||
|
||||
var expr = new Expression[1];
|
||||
|
||||
var cr = new ClassReader(is);
|
||||
var visiter = new FilterClassVisitor(Opcodes.ASM9, lambdaname, args, e -> expr[0] = e);
|
||||
cr.accept(visiter, 0);
|
||||
|
||||
return expr[0];
|
||||
}
|
||||
}
|
||||
32
src/main/java/jef/asm/FilterClassVisitor.java
Normal file
32
src/main/java/jef/asm/FilterClassVisitor.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package jef.asm;
|
||||
|
||||
import jef.expressions.Expression;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
class FilterClassVisitor extends ClassVisitor {
|
||||
private final int api;
|
||||
private final String lambdaname;
|
||||
private final Consumer<Expression> queryConsumer;
|
||||
private final Object[] args;
|
||||
|
||||
protected FilterClassVisitor(int api, String lambdaname, Object[] args, Consumer<Expression> queryConsumer) {
|
||||
super(api);
|
||||
this.api = api;
|
||||
this.lambdaname = lambdaname;
|
||||
this.args = args;
|
||||
this.queryConsumer = queryConsumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
|
||||
if (!name.equals(lambdaname)) {
|
||||
return super.visitMethod(access, name, descriptor, signature, exceptions);
|
||||
}
|
||||
|
||||
System.out.println("found method name: " + name);
|
||||
return new FilterMethodVisitor(api, descriptor, args, queryConsumer);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package jef.asm;
|
||||
import jef.expressions.BinaryExpression;
|
||||
import jef.expressions.ConstantExpression;
|
||||
import jef.expressions.Expression;
|
||||
import jef.expressions.FunctionExpression;
|
||||
import jef.expressions.IntermediateFieldExpression;
|
||||
import jef.expressions.NullExpression;
|
||||
import jef.expressions.ParameterExpression;
|
||||
@@ -23,7 +22,6 @@ import org.objectweb.asm.Type;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -31,6 +29,7 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
@@ -38,29 +37,21 @@ class FilterMethodVisitor extends MethodVisitor {
|
||||
private Stack<Expression> varStack = new Stack<>();
|
||||
private final String[] parameterClasses;
|
||||
private final Object[] args;
|
||||
private final Consumer<Expression> exprConsumer;
|
||||
private Expression lambdaExpr;
|
||||
private final Set<Field> accessedMembers = new HashSet<>();
|
||||
|
||||
protected FilterMethodVisitor(int api, String className, String descriptor, Object[] args) {
|
||||
protected FilterMethodVisitor(int api, String descriptor, Object[] args, Consumer<Expression> exprConsumer) {
|
||||
super(api);
|
||||
this.args = args;
|
||||
this.exprConsumer = exprConsumer;
|
||||
|
||||
//parameters
|
||||
var types = Type.getMethodType(descriptor).getArgumentTypes();
|
||||
if (types.length == 0) {// class method //TODO this is dirty, as only works for parameterless class methods
|
||||
this.args = new Object[]{null};
|
||||
parameterClasses = new String[]{className};
|
||||
} else {
|
||||
this.args = args;
|
||||
parameterClasses = new String[types.length];
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
parameterClasses[i] = types[i].getClassName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AsmParseResult getResult() {
|
||||
return new AsmParseResult(lambdaExpr, accessedMembers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) {
|
||||
@@ -76,11 +67,6 @@ class FilterMethodVisitor extends MethodVisitor {
|
||||
var v = varStack.pop();
|
||||
if (v instanceof ParameterExpression p) {
|
||||
if (p.isInput()) {
|
||||
try {
|
||||
accessedMembers.add(Class.forName(owner.replace("/", ".")).getDeclaredField(name));
|
||||
} catch (NoSuchFieldException | ClassNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
varStack.push(new IntermediateFieldExpression(name, descriptor));
|
||||
} else {
|
||||
throw new RuntimeException("field insn: unsupported GETFIELD expression");
|
||||
@@ -115,22 +101,8 @@ class FilterMethodVisitor extends MethodVisitor {
|
||||
var element = varStack.pop();
|
||||
var collection = varStack.pop();
|
||||
varStack.push(new BinaryExpression(element, collection, BinaryExpression.Operator.IN));
|
||||
} else if (name.equals("equals")
|
||||
&& owner.equals("java/lang/String")) {
|
||||
var value = varStack.pop();
|
||||
var variable = varStack.pop();
|
||||
varStack.push(new BinaryExpression(variable, value, BinaryExpression.Operator.EQ));
|
||||
} else if (name.equals("equalsIgnoreCase")
|
||||
&& owner.equals("java/lang/String")) {
|
||||
var value = varStack.pop();
|
||||
var variable = varStack.pop();
|
||||
varStack.push(new BinaryExpression(new FunctionExpression(FunctionExpression.TOLOWER, List.of(variable)),
|
||||
new FunctionExpression(FunctionExpression.TOLOWER, List.of(value)),
|
||||
BinaryExpression.Operator.EQ));
|
||||
} else if (descriptor.startsWith("()")) {
|
||||
var method = Class.forName(owner.replace("/", ".")).getDeclaredMethod(name);
|
||||
var res = new OptimizedAsmParser(method).parse();
|
||||
var field = res.getAccessedFields().stream().findFirst();
|
||||
} else if (name.startsWith("get") && name.length() > 3 && descriptor.startsWith("()")) { //hacky getter support, TODO replace this with proper getter eval later
|
||||
var field = findField(owner, name);
|
||||
if (field.isPresent()) {
|
||||
var v = varStack.pop();
|
||||
if (v instanceof ParameterExpression p) {
|
||||
@@ -149,9 +121,6 @@ class FilterMethodVisitor extends MethodVisitor {
|
||||
throw new RuntimeException("method insn: unsupported function " + name + " in " + owner);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (e instanceof RuntimeException re) {
|
||||
throw re;
|
||||
}
|
||||
throw new RuntimeException("method insn: ", e);
|
||||
}
|
||||
} else {
|
||||
@@ -417,6 +386,7 @@ class FilterMethodVisitor extends MethodVisitor {
|
||||
System.out.println("end");
|
||||
|
||||
super.visitEnd();
|
||||
exprConsumer.accept(lambdaExpr);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1,16 +1,23 @@
|
||||
package jef.asm;
|
||||
|
||||
import jef.expressions.Expression;
|
||||
import jef.expressions.modifier.ExpressionOptimizerBottomUp;
|
||||
import jef.expressions.modifier.IConst0Fixer;
|
||||
import jef.expressions.modifier.TableAliasInjector;
|
||||
import jef.expressions.modifier.TernaryRewriter;
|
||||
import jef.serializable.SerializableFunction;
|
||||
import jef.serializable.SerializablePredicate;
|
||||
import lombok.Getter;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.lang.invoke.SerializedLambda;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
@Getter
|
||||
public class OptimizedAsmParser extends AsmParser {
|
||||
public class OptimizedAsmParser extends AsmParser{
|
||||
|
||||
public OptimizedAsmParser(SerializablePredicate<?> predicate) {
|
||||
super(predicate);
|
||||
@@ -20,13 +27,8 @@ public class OptimizedAsmParser extends AsmParser {
|
||||
super(function);
|
||||
}
|
||||
|
||||
public OptimizedAsmParser(Method method) {
|
||||
super(method);
|
||||
}
|
||||
|
||||
public AsmParseResult parse() throws AsmParseException {
|
||||
var result = super.parse();
|
||||
var expr = result.getExpression();
|
||||
public Expression parse() throws AsmParseException {
|
||||
var expr = super.parse();
|
||||
|
||||
System.out.println(expr);
|
||||
expr = new TernaryRewriter().modify(expr);
|
||||
@@ -35,6 +37,6 @@ public class OptimizedAsmParser extends AsmParser {
|
||||
System.out.println(expr);
|
||||
expr = new ExpressionOptimizerBottomUp().modify(expr);
|
||||
|
||||
return new AsmParseResult(expr, result.getAccessedFields());
|
||||
return expr;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@@ -25,7 +25,6 @@ public class BinaryExpression implements Expression {
|
||||
case EQ, NE, IS -> Priority.EQUALITY;
|
||||
case LT, LE, GT, GE -> Priority.RELATIONAL;
|
||||
case IN -> Priority.RELATIONAL; //or equality?
|
||||
//TODO +-, */, %, ...
|
||||
default -> throw new IllegalStateException();
|
||||
};
|
||||
}
|
||||
34
src/main/java/jef/expressions/ConstantExpression.java
Normal file
34
src/main/java/jef/expressions/ConstantExpression.java
Normal file
@@ -0,0 +1,34 @@
|
||||
package jef.expressions;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
public class ConstantExpression implements Expression {
|
||||
public static final jef.expressions.ConstantExpression V0 = new jef.expressions.ConstantExpression(0);
|
||||
public static final jef.expressions.ConstantExpression V1 = new jef.expressions.ConstantExpression(1);
|
||||
public static final jef.expressions.ConstantExpression V2 = new jef.expressions.ConstantExpression(2);
|
||||
public static final jef.expressions.ConstantExpression V3 = new jef.expressions.ConstantExpression(3);
|
||||
public static final jef.expressions.ConstantExpression V4 = new jef.expressions.ConstantExpression(4);
|
||||
public static final jef.expressions.ConstantExpression V5 = new jef.expressions.ConstantExpression(5);
|
||||
|
||||
protected final Object value;
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.CONSTANT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Priority getPriority() {
|
||||
return Priority.CONSTANT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@ public interface Expression {
|
||||
BINARY,
|
||||
CONSTANT,
|
||||
FIELD,
|
||||
FUNCTION,
|
||||
INTERMEDIATE_FIELD,
|
||||
LIMIT,
|
||||
NULL,
|
||||
@@ -43,7 +42,7 @@ public interface Expression {
|
||||
BIT_SHIFT(10), // <<, >>
|
||||
ADD_SUB(13), // +, -
|
||||
MUL_DIV_MOD(14), // *, /, %
|
||||
UNARY_PRE(15), // +, -, !, ~, --i, ++i, function calls
|
||||
UNARY_PRE(15), // +, -, !, ~, --i, ++i
|
||||
UNARY_POST(16), // i++, i--
|
||||
CONSTANT(17), // constant values
|
||||
;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user