51 Commits

Author SHA1 Message Date
apucher
c0f7b919f7 rename null classes to dummy classes 2022-12-22 10:20:17 +01:00
wea_ondara
d0d51c1013 rename nul folder to dummy folders because windows is shit 2022-12-22 10:12:25 +01:00
wea_ondara
e1203eb5f4 cleanup 2022-11-27 08:31:19 +01:00
wea_ondara
7407d673fb print diff of modelbuilder diff check test function to stderr 2022-11-27 08:21:21 +01:00
wea_ondara
506725b96d fix sqlType missing in hashCode of DbField, also add it to moldelbuilder diff check test function 2022-11-27 08:20:00 +01:00
wea_ondara
ddce1c83dc improve object comparision of DbEntity and DbField 2022-11-27 08:16:48 +01:00
wea_ondara
195f3d0148 added database value generation with identity generation 2022-11-27 08:10:52 +01:00
wea_ondara
3e581a9e4a create primary keys on directly on table creation instead of a seperate operation 2022-11-27 07:58:29 +01:00
wea_ondara
1f90bc551b fix migration generation where sqltype was not properly set; also fix tests for that 2022-11-27 06:41:38 +01:00
wea_ondara
03850b78dd added todos 2022-11-27 06:35:04 +01:00
wea_ondara
d4efbfff46 fix mysql migration log insert 2022-11-26 07:33:27 +01:00
wea_ondara
2910b2c282 create migrations table via built-in migration 2022-11-26 06:06:51 +01:00
wea_ondara
913df99732 refactor migration applier, added tests 2022-11-26 05:35:59 +01:00
wea_ondara
88366c937a add equals, hashcode and to toString to migration operations 2022-11-26 05:33:23 +01:00
wea_ondara
591ac504fe fully impl nullplatform 2022-11-24 15:42:37 +01:00
wea_ondara
58a8211513 fix import 2022-11-24 14:56:01 +01:00
wea_ondara
cd30f58500 added to to binary expression 2022-11-24 14:55:16 +01:00
wea_ondara
922da9556c move query builders to proper locations 2022-11-24 14:54:56 +01:00
wea_ondara
103beeaafb adjust/create tests for query builder 2022-11-24 14:53:21 +01:00
wea_ondara
a1e3377599 add null checks to asm constructors 2022-11-24 14:17:26 +01:00
wea_ondara
038ebe4aff cleanup log 2022-11-24 14:16:43 +01:00
wea_ondara
237be45dc3 added tests for util class 2022-11-24 14:15:52 +01:00
wea_ondara
65570d0029 added tests for check class 2022-11-24 14:15:35 +01:00
wea_ondara
f5cb7c93c5 move class to proper package 2022-11-23 17:33:24 +01:00
wea_ondara
8b9659e867 cleanup 2022-11-23 17:30:13 +01:00
wea_ondara
c11e1b09f1 fix dep in pom 2022-11-23 17:23:30 +01:00
wea_ondara
6f0743889b refactor modelbuilder 2022-11-23 17:23:09 +01:00
wea_ondara
3c85ef2c84 refactor migration creator 2022-11-23 17:22:08 +01:00
wea_ondara
f0d3948338 add migration molue ref to main pom 2022-11-23 17:17:50 +01:00
wea_ondara
94b408bd79 cleanup code generation 2022-11-23 17:17:20 +01:00
wea_ondara
802288e6ab move remaining mysql class to mysql module 2022-11-23 17:15:53 +01:00
wea_ondara
99b49083ec added SqlPlatform class with getters to platform specific implementations 2022-11-23 17:13:39 +01:00
wea_ondara
1b8d9f94d8 move migration creator to seperate module 2022-11-23 16:31:25 +01:00
wea_ondara
481280ed88 modularize project 2022-11-23 14:21:11 +01:00
wea_ondara
915fc4a87b added command line to create migrations 2022-11-23 13:50:54 +01:00
wea_ondara
17d71eb2f3 cleanup 2022-10-01 16:32:00 +02:00
wea_ondara
27198a1e78 added string support for lambda expressions 2022-09-18 14:34:53 +02:00
wea_ondara
6870435eea add method reference support to asm parser 2022-09-12 18:49:41 +02:00
wea_ondara
4e75ce3b2e use proper javac for migration tests 2022-09-10 13:19:12 +02:00
wea_ondara
4e90b7cc90 properly parse getters via asm parser 2022-09-10 13:00:32 +02:00
wea_ondara
de17b8985a fix issues with foreign key exposure and add some tests for it 2022-09-10 11:19:06 +02:00
wea_ondara
967d099d81 fix asm parser for methods 2022-09-10 11:12:21 +02:00
wea_ondara
284bf647b4 resolve getters 2022-09-08 19:24:55 +02:00
wea_ondara
2f0fa26c6d cleanup 2022-09-08 18:36:14 +02:00
wea_ondara
6cdf74f351 more tests 2022-09-08 17:20:16 +02:00
wea_ondara
703e3b1367 rename test 2022-09-08 17:12:42 +02:00
wea_ondara
cdd650c1de more tests 2022-09-08 17:12:03 +02:00
wea_ondara
445d3f4a9c rename test 2022-09-08 17:09:16 +02:00
wea_ondara
dff7dd4229 drop referencing foreign keys when primary key of entity builder is dropped 2022-09-08 17:05:50 +02:00
wea_ondara
ad29897cd8 added option to modelbuilder from context initialization 2022-09-08 16:54:32 +02:00
wea_ondara
6a1585f438 added migrations 2022-09-08 15:58:55 +02:00
229 changed files with 4849 additions and 1329 deletions

View File

@@ -8,13 +8,15 @@
- references to platform specific impls - references to platform specific impls
## Asm ## Asm
- method references
- equals function for primitive and string + String::equalsIgnoreCase
- equals function for registered primitive conversion types - equals function for registered primitive conversion types
- IConst0Fixer: IConst0/1 on its own => false/true - IConst0Fixer: IConst0/1 on its own => false/true
- IConst0Fixer: IConst0/1 false/true depending on compared field type
- actually parse getter - actually parse getter
- resolve Predicate functions (not, and, or) - 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 - 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 ## ModelBuilder
- add registrable conversion types e.g. UUID, Date, Timestamp, Calendar, LocalTime, Instant,... - add registrable conversion types e.g. UUID, Date, Timestamp, Calendar, LocalTime, Instant,...
@@ -22,6 +24,9 @@
- field length - field length
- field precision - field precision
- sql type - sql type
- ignore() entity/field for db
- object inlining
- entity() with callback
## Annotations ## Annotations
- db type - db type

45
cli/pom.xml Normal file
View File

@@ -0,0 +1,45 @@
<?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>

View File

@@ -0,0 +1,27 @@
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);
}
}

View File

@@ -0,0 +1,279 @@
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) {
// }
// }
// }
}

View File

@@ -15,4 +15,9 @@ public abstract class Util {
public interface ThrowableSupplier<T> { public interface ThrowableSupplier<T> {
T get() throws Throwable; T get() throws Throwable;
} }
@FunctionalInterface
public interface ThrowableFunction<T, R> {
R apply(T t) throws Throwable;
}
} }

36
core/pom.xml Normal file
View File

@@ -0,0 +1,36 @@
<?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>

View File

@@ -0,0 +1,18 @@
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);
}
}

View File

@@ -1,6 +1,6 @@
package jef.asm; package jef.asm;
public class AsmParseException extends Exception { public class AsmParseException extends RuntimeException {
public AsmParseException() { public AsmParseException() {
} }

View File

@@ -0,0 +1,15 @@
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;
}

View File

@@ -0,0 +1,81 @@
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();
}
}

View File

@@ -0,0 +1,54 @@
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);
}
}

View File

@@ -3,6 +3,7 @@ package jef.asm;
import jef.expressions.BinaryExpression; import jef.expressions.BinaryExpression;
import jef.expressions.ConstantExpression; import jef.expressions.ConstantExpression;
import jef.expressions.Expression; import jef.expressions.Expression;
import jef.expressions.FunctionExpression;
import jef.expressions.IntermediateFieldExpression; import jef.expressions.IntermediateFieldExpression;
import jef.expressions.NullExpression; import jef.expressions.NullExpression;
import jef.expressions.ParameterExpression; import jef.expressions.ParameterExpression;
@@ -22,6 +23,7 @@ import org.objectweb.asm.Type;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@@ -29,7 +31,6 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.Stack; import java.util.Stack;
import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
@@ -37,21 +38,29 @@ class FilterMethodVisitor extends MethodVisitor {
private Stack<Expression> varStack = new Stack<>(); private Stack<Expression> varStack = new Stack<>();
private final String[] parameterClasses; private final String[] parameterClasses;
private final Object[] args; private final Object[] args;
private final Consumer<Expression> exprConsumer;
private Expression lambdaExpr; private Expression lambdaExpr;
private final Set<Field> accessedMembers = new HashSet<>();
protected FilterMethodVisitor(int api, String descriptor, Object[] args, Consumer<Expression> exprConsumer) { protected FilterMethodVisitor(int api, String className, String descriptor, Object[] args) {
super(api); super(api);
this.args = args;
this.exprConsumer = exprConsumer;
//parameters //parameters
var types = Type.getMethodType(descriptor).getArgumentTypes(); 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]; parameterClasses = new String[types.length];
for (int i = 0; i < types.length; i++) { for (int i = 0; i < types.length; i++) {
parameterClasses[i] = types[i].getClassName(); parameterClasses[i] = types[i].getClassName();
} }
} }
}
public AsmParseResult getResult() {
return new AsmParseResult(lambdaExpr, accessedMembers);
}
@Override @Override
public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) { public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) {
@@ -67,6 +76,11 @@ class FilterMethodVisitor extends MethodVisitor {
var v = varStack.pop(); var v = varStack.pop();
if (v instanceof ParameterExpression p) { if (v instanceof ParameterExpression p) {
if (p.isInput()) { 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)); varStack.push(new IntermediateFieldExpression(name, descriptor));
} else { } else {
throw new RuntimeException("field insn: unsupported GETFIELD expression"); throw new RuntimeException("field insn: unsupported GETFIELD expression");
@@ -101,8 +115,22 @@ class FilterMethodVisitor extends MethodVisitor {
var element = varStack.pop(); var element = varStack.pop();
var collection = varStack.pop(); var collection = varStack.pop();
varStack.push(new BinaryExpression(element, collection, BinaryExpression.Operator.IN)); varStack.push(new BinaryExpression(element, collection, BinaryExpression.Operator.IN));
} else if (name.startsWith("get") && name.length() > 3 && descriptor.startsWith("()")) { //hacky getter support, TODO replace this with proper getter eval later } else if (name.equals("equals")
var field = findField(owner, name); && 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();
if (field.isPresent()) { if (field.isPresent()) {
var v = varStack.pop(); var v = varStack.pop();
if (v instanceof ParameterExpression p) { if (v instanceof ParameterExpression p) {
@@ -121,6 +149,9 @@ class FilterMethodVisitor extends MethodVisitor {
throw new RuntimeException("method insn: unsupported function " + name + " in " + owner); throw new RuntimeException("method insn: unsupported function " + name + " in " + owner);
} }
} catch (Exception e) { } catch (Exception e) {
if (e instanceof RuntimeException re) {
throw re;
}
throw new RuntimeException("method insn: ", e); throw new RuntimeException("method insn: ", e);
} }
} else { } else {
@@ -386,7 +417,6 @@ class FilterMethodVisitor extends MethodVisitor {
System.out.println("end"); System.out.println("end");
super.visitEnd(); super.visitEnd();
exprConsumer.accept(lambdaExpr);
} }
@Override @Override

View File

@@ -1,23 +1,16 @@
package jef.asm; package jef.asm;
import jef.expressions.Expression;
import jef.expressions.modifier.ExpressionOptimizerBottomUp; import jef.expressions.modifier.ExpressionOptimizerBottomUp;
import jef.expressions.modifier.IConst0Fixer; import jef.expressions.modifier.IConst0Fixer;
import jef.expressions.modifier.TableAliasInjector;
import jef.expressions.modifier.TernaryRewriter; import jef.expressions.modifier.TernaryRewriter;
import jef.serializable.SerializableFunction; import jef.serializable.SerializableFunction;
import jef.serializable.SerializablePredicate; import jef.serializable.SerializablePredicate;
import lombok.Getter; import lombok.Getter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import java.io.InputStream; import java.lang.reflect.Method;
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.util.stream.IntStream;
@Getter @Getter
public class OptimizedAsmParser extends AsmParser{ public class OptimizedAsmParser extends AsmParser {
public OptimizedAsmParser(SerializablePredicate<?> predicate) { public OptimizedAsmParser(SerializablePredicate<?> predicate) {
super(predicate); super(predicate);
@@ -27,8 +20,13 @@ public class OptimizedAsmParser extends AsmParser{
super(function); super(function);
} }
public Expression parse() throws AsmParseException { public OptimizedAsmParser(Method method) {
var expr = super.parse(); super(method);
}
public AsmParseResult parse() throws AsmParseException {
var result = super.parse();
var expr = result.getExpression();
System.out.println(expr); System.out.println(expr);
expr = new TernaryRewriter().modify(expr); expr = new TernaryRewriter().modify(expr);
@@ -37,6 +35,6 @@ public class OptimizedAsmParser extends AsmParser{
System.out.println(expr); System.out.println(expr);
expr = new ExpressionOptimizerBottomUp().modify(expr); expr = new ExpressionOptimizerBottomUp().modify(expr);
return expr; return new AsmParseResult(expr, result.getAccessedFields());
} }
} }

View File

@@ -5,7 +5,6 @@ import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Getter @Getter

View File

@@ -25,6 +25,7 @@ public class BinaryExpression implements Expression {
case EQ, NE, IS -> Priority.EQUALITY; case EQ, NE, IS -> Priority.EQUALITY;
case LT, LE, GT, GE -> Priority.RELATIONAL; case LT, LE, GT, GE -> Priority.RELATIONAL;
case IN -> Priority.RELATIONAL; //or equality? case IN -> Priority.RELATIONAL; //or equality?
//TODO +-, */, %, ...
default -> throw new IllegalStateException(); default -> throw new IllegalStateException();
}; };
} }

View File

@@ -0,0 +1,37 @@
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();
}
}

View File

@@ -13,6 +13,7 @@ public interface Expression {
BINARY, BINARY,
CONSTANT, CONSTANT,
FIELD, FIELD,
FUNCTION,
INTERMEDIATE_FIELD, INTERMEDIATE_FIELD,
LIMIT, LIMIT,
NULL, NULL,
@@ -42,7 +43,7 @@ public interface Expression {
BIT_SHIFT(10), // <<, >> BIT_SHIFT(10), // <<, >>
ADD_SUB(13), // +, - ADD_SUB(13), // +, -
MUL_DIV_MOD(14), // *, /, % MUL_DIV_MOD(14), // *, /, %
UNARY_PRE(15), // +, -, !, ~, --i, ++i UNARY_PRE(15), // +, -, !, ~, --i, ++i, function calls
UNARY_POST(16), // i++, i-- UNARY_POST(16), // i++, i--
CONSTANT(17), // constant values CONSTANT(17), // constant values
; ;

View File

@@ -0,0 +1,32 @@
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(", ")) + ")";
}
}

View File

@@ -4,7 +4,6 @@ import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import java.util.Collection; import java.util.Collection;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Getter @Getter
@@ -33,7 +32,7 @@ public class ParameterExpression extends ConstantExpression implements Expressio
} else if (this.value == null) { } else if (this.value == null) {
return "null"; return "null";
} else if (this.value instanceof Collection) { } else if (this.value instanceof Collection) {
return "(" + ((Collection<?>) this.value).stream().map(e -> e == null ? "NULL" : Objects.toString(e)).collect(Collectors.joining(", ")) + ")"; return "(" + ((Collection<?>) this.value).stream().map(e -> e == null ? "NULL" : (e instanceof String ? "\"" + e + "\"" : e.toString())).collect(Collectors.joining(", ")) + ")";
} }
return value.toString(); return value.toString();
} }

View File

@@ -25,6 +25,6 @@ public class TableExpression extends ConstantExpression {
@Override @Override
public String toString() { public String toString() {
return "`" + super.toString() + "`"; return "`" + name + "`";
} }
} }

View File

@@ -5,6 +5,7 @@ import jef.expressions.BinaryExpression;
import jef.expressions.ConstantExpression; import jef.expressions.ConstantExpression;
import jef.expressions.Expression; import jef.expressions.Expression;
import jef.expressions.FieldExpression; import jef.expressions.FieldExpression;
import jef.expressions.FunctionExpression;
import jef.expressions.IntermediateFieldExpression; import jef.expressions.IntermediateFieldExpression;
import jef.expressions.LimitExpression; import jef.expressions.LimitExpression;
import jef.expressions.NullExpression; import jef.expressions.NullExpression;
@@ -28,6 +29,7 @@ public abstract class ExpressionModifier {
case BINARY -> modifyBinary((BinaryExpression) expr); case BINARY -> modifyBinary((BinaryExpression) expr);
case CONSTANT -> modifyConstant((ConstantExpression) expr); case CONSTANT -> modifyConstant((ConstantExpression) expr);
case FIELD -> modifyField((FieldExpression) expr); case FIELD -> modifyField((FieldExpression) expr);
case FUNCTION -> modifyFunction((FunctionExpression) expr);
case INTERMEDIATE_FIELD -> modifyIntermediateField((IntermediateFieldExpression) expr); case INTERMEDIATE_FIELD -> modifyIntermediateField((IntermediateFieldExpression) expr);
case LIMIT -> modifyLimit((LimitExpression) expr); case LIMIT -> modifyLimit((LimitExpression) expr);
case NULL -> modifyNull((NullExpression) expr); case NULL -> modifyNull((NullExpression) expr);
@@ -63,6 +65,10 @@ public abstract class ExpressionModifier {
return expr; return expr;
} }
public Expression modifyFunction(FunctionExpression expr) {
return new FunctionExpression(expr.getFunction(), expr.getParameters().stream().map(this::modify).toList());
}
public Expression modifyIntermediateField(IntermediateFieldExpression expr) { public Expression modifyIntermediateField(IntermediateFieldExpression expr) {
return expr; return expr;
} }

View File

@@ -9,13 +9,13 @@ public class DatabaseSelectAllExpression implements Expression, SelectableExpres
public static final DatabaseSelectAllExpression INSTANCE = new DatabaseSelectAllExpression(); public static final DatabaseSelectAllExpression INSTANCE = new DatabaseSelectAllExpression();
@Override @Override
public Expression.Type getType() { public Type getType() {
return Expression.Type.CONSTANT; return Type.CONSTANT;
} }
@Override @Override
public Expression.Priority getPriority() { public Priority getPriority() {
return Expression.Priority.UNDEFINED; return Priority.UNDEFINED;
} }
@Override @Override

View File

@@ -4,6 +4,7 @@ import jef.expressions.AndExpression;
import jef.expressions.BinaryExpression; import jef.expressions.BinaryExpression;
import jef.expressions.ConstantExpression; import jef.expressions.ConstantExpression;
import jef.expressions.FieldExpression; import jef.expressions.FieldExpression;
import jef.expressions.FunctionExpression;
import jef.expressions.IntermediateFieldExpression; import jef.expressions.IntermediateFieldExpression;
import jef.expressions.LimitExpression; import jef.expressions.LimitExpression;
import jef.expressions.NullExpression; import jef.expressions.NullExpression;
@@ -56,6 +57,11 @@ public class DebugExpressionVisitor extends ExpressionVisitor {
System.out.println(i() + expr.toString()); System.out.println(i() + expr.toString());
} }
@Override
public void visitFunction(FunctionExpression expr) {
System.out.println(i() + expr.toString());
}
@Override @Override
public void visitIntermediateField(IntermediateFieldExpression expr) { public void visitIntermediateField(IntermediateFieldExpression expr) {
System.out.println(i() + expr.toString()); System.out.println(i() + expr.toString());

View File

@@ -5,6 +5,7 @@ import jef.expressions.BinaryExpression;
import jef.expressions.ConstantExpression; import jef.expressions.ConstantExpression;
import jef.expressions.Expression; import jef.expressions.Expression;
import jef.expressions.FieldExpression; import jef.expressions.FieldExpression;
import jef.expressions.FunctionExpression;
import jef.expressions.IntermediateFieldExpression; import jef.expressions.IntermediateFieldExpression;
import jef.expressions.LimitExpression; import jef.expressions.LimitExpression;
import jef.expressions.NullExpression; import jef.expressions.NullExpression;
@@ -24,6 +25,7 @@ public abstract class ExpressionVisitor {
case BINARY -> visitBinary((BinaryExpression) expr); case BINARY -> visitBinary((BinaryExpression) expr);
case CONSTANT -> visitConstant((ConstantExpression) expr); case CONSTANT -> visitConstant((ConstantExpression) expr);
case FIELD -> visitField((FieldExpression) expr); case FIELD -> visitField((FieldExpression) expr);
case FUNCTION -> visitFunction((FunctionExpression) expr);
case INTERMEDIATE_FIELD -> visitIntermediateField((IntermediateFieldExpression) expr); case INTERMEDIATE_FIELD -> visitIntermediateField((IntermediateFieldExpression) expr);
case LIMIT -> visitLimit((LimitExpression) expr); case LIMIT -> visitLimit((LimitExpression) expr);
case NULL -> visitNull((NullExpression) expr); case NULL -> visitNull((NullExpression) expr);
@@ -56,6 +58,9 @@ public abstract class ExpressionVisitor {
public void visitField(FieldExpression expr) { public void visitField(FieldExpression expr) {
} }
public void visitFunction(FunctionExpression expr) {
}
public void visitIntermediateField(IntermediateFieldExpression expr) { public void visitIntermediateField(IntermediateFieldExpression expr) {
} }

View File

@@ -1,7 +1,11 @@
package jef.model; package jef.model;
import jef.DbSet;
import jef.model.annotations.Clazz;
import jef.model.constraints.ForeignKeyConstraint; import jef.model.constraints.ForeignKeyConstraint;
import jef.platform.base.Database;
import jef.serializable.SerializableObject; import jef.serializable.SerializableObject;
import lombok.Getter;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
@@ -9,8 +13,39 @@ import java.util.HashMap;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Getter
public abstract class DbContext { public abstract class DbContext {
private static final String ILLEGAL_CHARACTERS = "\"'`,.";//TODO also validate this in modelbuilder/dbentity/dbfield private static final String ILLEGAL_CHARACTERS = "\"'`,.";
private final Database database;
private final DbContextOptions options;
public DbContext() {
database = null;
options = null;
}
public DbContext(Database database, DbContextOptions options) {
this.database = database;
this.options = options;
initInitializeDbSets();
}
private void initInitializeDbSets() {
for (Field f : getClass().getDeclaredFields()) {
if (f.getType() != DbSet.class) {
continue;
}
f.setAccessible(true);
Clazz anno = f.getAnnotation(Clazz.class);
try {
f.set(this, new DbSet(anno.value(), f.getName()));//TODO use table name from modelbuilder
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
public void onModelCreate(ModelBuilder mb) { public void onModelCreate(ModelBuilder mb) {
} }
@@ -44,13 +79,13 @@ public abstract class DbContext {
//check for duplicate field names //check for duplicate field names
for (DbEntity<? extends SerializableObject> entity : mb.getEntities()) { for (DbEntity<? extends SerializableObject> entity : mb.getEntities()) {
var fieldNameMap = new HashMap<String, DbEntity<?>>(); var fieldNameMap = new HashMap<String, DbField<?>>();
for (DbField<?> field : entity.getFields()) { for (DbField<?> field : entity.getFields()) {
var name = field.getName().toLowerCase(); var name = field.getName().toLowerCase();
if (fieldNameMap.containsKey(name)) { if (fieldNameMap.containsKey(name)) {
errors.add("Duplicate DbField in entity " + entity.getTypeName() + " with name " + field.getName() + ": " + field.getName() + " and " + fieldNameMap.get(name).getName()); errors.add("Duplicate DbField in entity " + entity.getTypeName() + " with name " + field.getName() + ": " + field.getName() + " and " + fieldNameMap.get(name).getName());
} else { } else {
fieldNameMap.put(name, entity); fieldNameMap.put(name, field);
} }
} }
} }

View File

@@ -0,0 +1,15 @@
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;
}

View File

@@ -24,10 +24,10 @@ import java.util.List;
import java.util.Objects; import java.util.Objects;
@Getter @Getter
@Setter @Setter(value = AccessLevel.PACKAGE)
public class DbEntity<T extends SerializableObject> { public class DbEntity<T extends SerializableObject> {
private final String typeName; private String typeName;
@Setter(value = AccessLevel.PACKAGE) // @Setter
private Class<T> type; private Class<T> type;
private final List<DbField<?>> fields; private final List<DbField<?>> fields;
private String name; private String name;
@@ -39,7 +39,6 @@ public class DbEntity<T extends SerializableObject> {
//only used for migrations //only used for migrations
DbEntity(String typeName) { DbEntity(String typeName) {
this.type = (Class) Util.tryGet(() -> Class.forName(typeName)).orElse(null);
this.typeName = typeName; this.typeName = typeName;
this.fields = new ArrayList<>(); this.fields = new ArrayList<>();
var split = typeName.split("\\."); var split = typeName.split("\\.");
@@ -55,7 +54,7 @@ public class DbEntity<T extends SerializableObject> {
} }
DbEntity(Class<T> type, String name, List<DbField<?>> fields) { DbEntity(Class<T> type, String name, List<DbField<?>> fields) {
this.type = Check.notNull(type, "type"); this.type = type;
this.typeName = type.getName(); this.typeName = type.getName();
this.fields = Check.notNull(fields, "fields"); this.fields = Check.notNull(fields, "fields");
this.name = Check.notNull(name, "name"); this.name = Check.notNull(name, "name");
@@ -107,7 +106,7 @@ public class DbEntity<T extends SerializableObject> {
var prop = getField(getter); var prop = getField(getter);
if (prop == null) { if (prop == null) {
var name = extractFieldName(getter); var name = extractFieldName(getter);
var field = ReflectionUtil.getFieldsRecursive(type).stream().filter(f -> f.getName().equals(name)).findFirst().orElse(null); var field = ReflectionUtil.getFieldsRecursive((Class<SerializableObject>) Class.forName(typeName)).stream().filter(f -> f.getName().equals(name)).findFirst().orElse(null);
if (field == null) { if (field == null) {
throw new RuntimeException("Field not found: " + name); throw new RuntimeException("Field not found: " + name);
} }
@@ -181,6 +180,7 @@ public class DbEntity<T extends SerializableObject> {
/** /**
* Does not validate foreign key integrity * Does not validate foreign key integrity
*
* @param primaryKey * @param primaryKey
*/ */
public void setPrimaryKey(PrimaryKeyConstraint primaryKey) { public void setPrimaryKey(PrimaryKeyConstraint primaryKey) {
@@ -211,9 +211,17 @@ public class DbEntity<T extends SerializableObject> {
} }
} }
private <R> String extractFieldName(SerializableFunction<T, R> getter) { void addField(DbField<?> field) {
this.fields.add(field);
}
DbEntityBuilder<T> builder(ModelBuilder mb) {
return new DbEntityBuilder<>(mb, this);
}
<R> String extractFieldName(SerializableFunction<T, R> getter) {
try { try {
var expr = new AsmParser(getter).parse(); var expr = new AsmParser(getter).parse().getExpression();
if (expr.getType() != Expression.Type.INTERMEDIATE_FIELD) { if (expr.getType() != Expression.Type.INTERMEDIATE_FIELD) {
throw new RuntimeException(expr.getClass().getSimpleName() + " is not a field expression"); throw new RuntimeException(expr.getClass().getSimpleName() + " is not a field expression");
} }
@@ -226,11 +234,15 @@ public class DbEntity<T extends SerializableObject> {
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
return equals(o, DbField::equalsExceptTypeAndFieldAndSqlType);
}
public boolean equals(Object o, Util.Equality<DbField<?>> fieldEquality) {
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
DbEntity<?> dbEntity = (DbEntity<?>) o; DbEntity<?> dbEntity = (DbEntity<?>) o;
return typeName.equals(dbEntity.typeName) return typeName.equals(dbEntity.typeName)
&& fields.equals(dbEntity.fields) && compareFields(fields, dbEntity.fields, fieldEquality)
&& name.equals(dbEntity.name) && name.equals(dbEntity.name)
&& Objects.equals(primaryKey, dbEntity.primaryKey) && Objects.equals(primaryKey, dbEntity.primaryKey)
&& foreignKeys.equals(dbEntity.foreignKeys) && foreignKeys.equals(dbEntity.foreignKeys)
@@ -239,15 +251,27 @@ public class DbEntity<T extends SerializableObject> {
&& indexes.equals(dbEntity.indexes); && indexes.equals(dbEntity.indexes);
} }
private boolean compareFields(List<DbField<?>> thisFields, List<DbField<?>> otherFields, Util.Equality<DbField<?>> fieldEquality) {
if (thisFields.size() != otherFields.size()) {
return false;
}
for (int i = 0; i < thisFields.size(); i++) {
if (!fieldEquality.check(thisFields.get(i), otherFields.get(i))) {
return false;
}
}
return true;
}
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(typeName, type, fields, name, primaryKey, foreignKeys, uniqueKeys, keys, indexes); return Objects.hash(typeName, fields, name, primaryKey, foreignKeys, uniqueKeys, keys, indexes);
} }
@Override @Override
public String toString() { public String toString() {
return "DbEntity{" + return "DbEntity{" +
"type=" + type + "typeName=" + typeName +
", name='" + name + '\'' + ", name='" + name + '\'' +
'}'; '}';
} }

View File

@@ -0,0 +1,121 @@
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();
}
}

View File

@@ -1,34 +1,37 @@
package jef.model; package jef.model;
import jef.model.constraints.IndexConstraint; import jef.model.annotations.Generated;
import jef.model.constraints.KeyConstraint;
import jef.model.constraints.UniqueKeyConstraint;
import jef.serializable.SerializableObject; import jef.serializable.SerializableObject;
import jef.util.Check; import jef.util.Check;
import jef.util.Util;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.Objects; import java.util.Objects;
@Getter @Getter
@Setter @Setter
public class DbField<T> { public class DbField<T> {
public static final Util.NullableEquality<DbField<?>> EQUALITY_EXACT = DbField::equals;
public static final Util.NullableEquality<DbField<?>> EQUALITY_WITHOUT_TYPE_FIELD_NAME = DbField::equalsExceptTypeAndFieldAndName;
public static final Util.NullableEquality<DbField<?>> EQUALITY_WITHOUT_TYPE_FIELD_SQLTYPE = DbField::equalsExceptTypeAndFieldAndSqlType;
private final DbEntity<? extends SerializableObject> entity; private final DbEntity<? extends SerializableObject> entity;
private final String typeName; private final String typeName;
@Setter(value = AccessLevel.PACKAGE) @Setter(value = AccessLevel.PACKAGE)
private Class<T> type; private Class<T> type;
@Setter(value = AccessLevel.PACKAGE) @Setter(value = AccessLevel.PACKAGE)
private Field field; private Field field;
private boolean isModelField; private boolean isModelField;//TODO get rid of this field
private boolean isDatabaseField; private boolean isDatabaseField;//TODO get rid of this field
private DbField<?> foreignKeyModelLink; private DbField<?> exposingForeignKeyOf;
private String name; private String name;
private boolean notNull = false; private boolean notNull = false;
private String sqlType;
private Generated.Type generated = Generated.Type.NONE;
DbField(DbEntity<? extends SerializableObject> entity, String name, String typeName) { DbField(DbEntity<? extends SerializableObject> entity, String name, String typeName) {
this.entity = Check.notNull(entity, "entity"); this.entity = Check.notNull(entity, "entity");
@@ -41,7 +44,7 @@ public class DbField<T> {
} }
DbField(DbEntity<? extends SerializableObject> entity, Class<T> type, Field field) { DbField(DbEntity<? extends SerializableObject> entity, Class<T> type, Field field) {
this(entity, type, field, field.getName()); this(entity, type, field, Check.notNull(field, "field").getName());
} }
DbField(DbEntity<? extends SerializableObject> entity, Class<T> type, Field field, String name) { DbField(DbEntity<? extends SerializableObject> entity, Class<T> type, Field field, String name) {
@@ -62,71 +65,33 @@ public class DbField<T> {
return entity.getUniqueKeys().stream().anyMatch(u -> u.getFields().size() == 1 && u.getFields().get(0) == this); return entity.getUniqueKeys().stream().anyMatch(u -> u.getFields().size() == 1 && u.getFields().get(0) == this);
} }
public void setUnique(boolean unique) {
var constr = entity.getUniqueKeys().stream().filter(u -> u.getFields().size() == 1 && u.getFields().get(0) == this).findFirst();
if (!constr.isPresent() && unique) {
entity.addUniqueKey(new UniqueKeyConstraint(entity, new ArrayList<>(List.of(this))));
} else if (constr.isPresent() && !unique) {
entity.dropUniqueKey(constr.get());
} //else do nothing
}
public boolean isIndex() { public boolean isIndex() {
return entity.getIndexes().stream().anyMatch(u -> u.getFields().size() == 1 && u.getFields().get(0) == this); return entity.getIndexes().stream().anyMatch(u -> u.getFields().size() == 1 && u.getFields().get(0) == this);
} }
public void setIndex(boolean indexed) {
var constr = entity.getIndexes().stream().filter(u -> u.getFields().size() == 1 && u.getFields().get(0) == this).findFirst();
if (!constr.isPresent() && indexed) {
entity.addIndex(new IndexConstraint(entity, new ArrayList<>(List.of(this))));
} else if (constr.isPresent() && !indexed) {
entity.dropIndex(constr.get());
} //else do nothing
}
public boolean isKey() { public boolean isKey() {
return entity.getKeys().stream().anyMatch(u -> u.getFields().size() == 1 && u.getFields().get(0) == this); return entity.getKeys().stream().anyMatch(u -> u.getFields().size() == 1 && u.getFields().get(0) == this);
} }
public void setKey(boolean keyed) { public void setGenerated(Generated.Type generated) {
var constr = entity.getKeys().stream().filter(u -> u.getFields().size() == 1 && u.getFields().get(0) == this).findFirst(); Check.notNull(generated, "generated");
if (!constr.isPresent() && keyed) { this.generated = generated;
entity.addKey(new KeyConstraint(entity, new ArrayList<>(List.of(this))));
} else if (constr.isPresent() && !keyed) {
entity.dropKey(constr.get());
} //else do nothing
}
public DbField<T> setModelField(boolean modelField) {
isModelField = modelField;
return this;
}
public DbField<T> setDatabaseField(boolean databaseField) {
isDatabaseField = databaseField;
return this;
}
public DbField<T> setNotNull(boolean notNull) {
this.notNull = notNull;
return this;
} }
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (!equalsCommon(o)) { return equalsCommon(o, false, false, true, true);
return false;
}
DbField<?> dbField = (DbField<?>) o;
return name.equals(dbField.name);
// && Objects.equals(field == null ? null : field.getName(), dbField.field == null ? null : dbField.field.getName());
} }
public boolean equalsExceptName(Object o) { public boolean equalsExceptTypeAndFieldAndName(Object o) {
return equalsCommon(o); return equalsCommon(o, false, false, false, true);
} }
private boolean equalsCommon(Object o) { public boolean equalsExceptTypeAndFieldAndSqlType(Object o) {
return equalsCommon(o, false, false, true, false);
}
private boolean equalsCommon(Object o, boolean compareType, boolean compareField, boolean compareName, boolean compareSqlType) {
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
DbField<?> dbField = (DbField<?>) o; DbField<?> dbField = (DbField<?>) o;
@@ -135,16 +100,21 @@ public class DbField<T> {
&& notNull == dbField.notNull && notNull == dbField.notNull
&& entity.getName().equals(dbField.entity.getName()) && entity.getName().equals(dbField.entity.getName())
&& typeName.equals(dbField.typeName) && typeName.equals(dbField.typeName)
&& (!compareType || type == dbField.type)
&& (!compareField || field == dbField.field)
&& (!compareName || name.equals(dbField.name))
&& (!compareSqlType || Objects.equals(sqlType, dbField.sqlType))
&& generated == dbField.generated
// && Objects.equals(type, dbField.type) // && Objects.equals(type, dbField.type)
&& Objects.equals(foreignKeyModelLink == null ? null : foreignKeyModelLink.getName(), && Objects.equals(exposingForeignKeyOf == null ? null : exposingForeignKeyOf.getName(),
dbField.foreignKeyModelLink == null ? null : dbField.foreignKeyModelLink.getName()); dbField.exposingForeignKeyOf == null ? null : dbField.exposingForeignKeyOf.getName());
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(entity.getName(), typeName, type, field == null ? null : field.getName(), isModelField, isDatabaseField, return Objects.hash(entity.getName(), typeName, type, field == null ? null : field.getName(), isModelField, isDatabaseField,
foreignKeyModelLink == null ? null : foreignKeyModelLink.getName(), exposingForeignKeyOf == null ? null : exposingForeignKeyOf.getName(),
name, notNull); name, notNull, sqlType, generated);
} }
@Override @Override

View File

@@ -0,0 +1,95 @@
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;
}
}

View File

@@ -0,0 +1,23 @@
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!");
}
}
}

View File

@@ -0,0 +1,83 @@
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();
}
}
}
}
}

View File

@@ -0,0 +1,20 @@
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;
}
}

View File

@@ -0,0 +1,56 @@
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);
});
}
}
}
}

View File

@@ -0,0 +1,196 @@
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;
}
}

View File

@@ -0,0 +1,186 @@
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);
}
}
}

View File

@@ -1,11 +1,6 @@
package jef.model; package jef.model;
import jef.model.annotations.processors.AnnotationProcessor; import jef.model.annotations.processors.AnnotationProcessor;
import jef.model.annotations.processors.ForeignKeyProcessor;
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 jef.model.constraints.ForeignKeyConstraint; import jef.model.constraints.ForeignKeyConstraint;
import jef.model.constraints.IndexConstraint; import jef.model.constraints.IndexConstraint;
import jef.model.constraints.KeyConstraint; import jef.model.constraints.KeyConstraint;
@@ -14,49 +9,64 @@ import jef.model.constraints.UniqueKeyConstraint;
import jef.serializable.SerializableObject; import jef.serializable.SerializableObject;
import jef.util.Check; import jef.util.Check;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors;
public class ModelBuilder { public class ModelBuilder {
private static final List<AnnotationProcessor> annotationProcessors = new ArrayList<>();
static {//TODO move this to a passable config class
annotationProcessors.add(NotNullProcessor.INSTANCE);
annotationProcessors.add(UniqueProcessor.INSTANCE);
annotationProcessors.add(IndexProcessor.INSTANCE);
annotationProcessors.add(KeyProcessor.INSTANCE);
annotationProcessors.add(ForeignKeyProcessor.INSTANCE);
}
/** /**
* Initializes a ModelBuilder and configures the entities found in the context Class according to annotations. * Initializes a ModelBuilder and configures the entities found in the context Class according to annotations.
* *
* @param context the context to use for initialization * @param context the context to use for initialization
* @return an initialized ModelBuilder * @return an initialized ModelBuilder
*/ */
public static ModelBuilder from(Class<? extends DbContext> context) { //TODO pass optional config here public static ModelBuilder from(Class<? extends DbContext> context) {
return from(context, new ModelBuilderOptions());
}
/**
* Initializes a ModelBuilder and configures the entities found in the context Class according to annotations.
*
* @param context the context to use for initialization
* @param options the options to user during initialization
* @return an initialized ModelBuilder
*/
public static ModelBuilder from(Class<? extends DbContext> context, ModelBuilderOptions options) {
try { try {
return from0(context); return from0(context, options);
} catch (Exception e) { } catch (Throwable e) {
throw e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e); throw e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e);
} }
} }
private static ModelBuilder from0(Class<? extends DbContext> context) throws Exception { private static ModelBuilder from0(Class<? extends DbContext> context, ModelBuilderOptions options) {
var mb = new ModelBuilder(new ArrayList<>()); var mb = new ModelBuilder();
EntityInitializer.initEntities(mb, context); EntityInitializer.initEntities(mb, context);
EntityDefaultConstructorChecker.checkEntities(mb);
PrimaryKeyInitializer.initPrimaryKeys(mb); PrimaryKeyInitializer.initPrimaryKeys(mb);
ForeignKeyExposeInitializer.initForeignKeyExposures(mb);
ForeignKeyInitializer.initForeignKeys(mb); ForeignKeyInitializer.initForeignKeys(mb);
for (AnnotationProcessor processor : annotationProcessors) { for (AnnotationProcessor processor : options.getAnnotationProcessors()) {
processor.apply(mb); processor.apply(mb);
} }
var instance = context.getDeclaredConstructor().newInstance();//TODO find constructor, //TODO optionally with config try {
var instance = context.getDeclaredConstructor().newInstance();
instance.onModelCreate(mb); instance.onModelCreate(mb);
instance.onModelValidate(mb); instance.onModelValidate(mb);
} catch (InvocationTargetException e) {
throw new RuntimeException("Error while instantiating DbContext '" + context.getName() + "'", e);
} catch (InstantiationException e) {
throw new RuntimeException("Failed to instantiate DbContext '" + context.getName() + "'", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Default constructor of DbContext '" + context.getName() + "' must be publicly accessible", e);
} catch (NoSuchMethodException e) {
throw new RuntimeException("DbContext '" + context.getName() + "' does not have a default constructor", e);
}
return mb; return mb;
} }
@@ -86,6 +96,10 @@ public class ModelBuilder {
return Collections.unmodifiableList(entities); return Collections.unmodifiableList(entities);
} }
public List<DbEntityBuilder<? extends SerializableObject>> entities() {
return entities.stream().map(e -> new DbEntityBuilder<>(this, e)).collect(Collectors.toUnmodifiableList());
}
/** /**
* Returns the database model for the requested class or null if not present. * Returns the database model for the requested class or null if not present.
* *
@@ -117,9 +131,14 @@ public class ModelBuilder {
* @param <T> the type of model class * @param <T> the type of model class
* @return the DbEntity for the requested class or the newly created empty DbEntity if none existed. * @return the DbEntity for the requested class or the newly created empty DbEntity if none existed.
*/ */
public <T extends SerializableObject> DbEntity<T> getOrCreateEntity(Class<T> clazz) { public <T extends SerializableObject> DbEntityBuilder<T> entity(Class<T> clazz) {
Check.notNull(clazz, "clazz"); Check.notNull(clazz, "clazz");
return getOrCreateEntity(clazz.getName()); var entity = (DbEntity<T>) getEntity(clazz);
if (entity == null) {
entity = new DbEntity<>(clazz);
entities.add(entity);
}
return new DbEntityBuilder<>(this, entity);
} }
/** /**
@@ -129,14 +148,14 @@ public class ModelBuilder {
* @param <T> the type of model class * @param <T> the type of model class
* @return the DbEntity for the requested class or the newly created empty DbEntity if none existed. * @return the DbEntity for the requested class or the newly created empty DbEntity if none existed.
*/ */
public <T extends SerializableObject> DbEntity<T> getOrCreateEntity(String typeName) { public <T extends SerializableObject> DbEntityBuilder<T> entity(String typeName) {
Check.notNull(typeName, "typeName"); Check.notNull(typeName, "typeName");
var entity = (DbEntity<T>) getEntity(typeName); var entity = (DbEntity<T>) getEntity(typeName);
if (entity == null) { if (entity == null) {
entity = new DbEntity<>(typeName); entity = new DbEntity<>(typeName);
entities.add(entity); entities.add(entity);
} }
return entity; return new DbEntityBuilder<>(this, entity);
} }
/** /**
@@ -168,14 +187,16 @@ public class ModelBuilder {
var old = this.entities.get(i); var old = this.entities.get(i);
var entity = (DbEntity<?>) new DbEntity(old.getTypeName()); var entity = (DbEntity<?>) new DbEntity(old.getTypeName());
entity.setName(old.getName()); entity.setName(old.getName());
entity.setType((Class) old.getType()); entity.setTypeName(old.getTypeName());
//add fields //add fields
old.getFields().stream().map(e -> { old.getFields().stream().map(e -> {
var nf = new DbField(entity, e.getName(), e.getTypeName()); var nf = new DbField(entity, e.getName(), e.getTypeName());
nf.setField(e.getField()); nf.setField(e.getField());
nf.setType(e.getType()); nf.setType(e.getType());
nf.setSqlType(e.getSqlType());
nf.setNotNull(e.isNotNull()); nf.setNotNull(e.isNotNull());
nf.setGenerated(e.getGenerated());
nf.setModelField(e.isModelField()); nf.setModelField(e.isModelField());
nf.setDatabaseField(e.isDatabaseField()); nf.setDatabaseField(e.isDatabaseField());
return nf; return nf;
@@ -183,9 +204,9 @@ public class ModelBuilder {
.forEach(entity::addIfAbsent); .forEach(entity::addIfAbsent);
//apply exposed foreign keys //apply exposed foreign keys
old.getFields().stream().filter(e -> e.getForeignKeyModelLink() != null).forEach(e -> { old.getFields().stream().filter(e -> e.getExposingForeignKeyOf() != null).forEach(e -> {
var nf = entity.getFields().stream().filter(f -> f.getName().equals(e.getName())).findFirst().get(); //get(): should always be there var nf = entity.getFields().stream().filter(f -> f.getName().equals(e.getName())).findFirst().get(); //get(): should always be there
nf.setForeignKeyModelLink(old.getFields().stream().filter(f -> f.getName().equals(e.getForeignKeyModelLink().getName())).findFirst().get()); //get(): should always be there nf.setExposingForeignKeyOf(old.getFields().stream().filter(f -> f.getName().equals(e.getExposingForeignKeyOf().getName())).findFirst().get()); //get(): should always be there
}); });
entity.setName(old.getName()); entity.setName(old.getName());

View File

@@ -0,0 +1,37 @@
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
}
}

View File

@@ -1,23 +1,23 @@
package jef.model; package jef.model;
import jef.model.annotations.Generated;
import jef.model.annotations.Id; import jef.model.annotations.Id;
import jef.model.annotations.Transient; import jef.model.annotations.Transient;
import jef.model.constraints.PrimaryKeyConstraint;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
class PrimaryKeyInitializer { class PrimaryKeyInitializer {
static void initPrimaryKeys(ModelBuilder mb) { static void initPrimaryKeys(ModelBuilder mb) {
for (int i = 0; i < mb.getEntities().size(); i++) { var entities = mb.entities();
var entity = mb.getEntities().get(i); for (int i = 0; i < entities.size(); i++) {
var entity = entities.get(i);
initPrimaryKeys(mb, entity); initPrimaryKeys(mb, entity);
} }
} }
static void initPrimaryKeys(ModelBuilder mb, DbEntity<?> entity) { static void initPrimaryKeys(ModelBuilder mb, DbEntityBuilder<?> entity) {
var fields = ReflectionUtil.getFieldsRecursive(entity.getType()); var fields = ReflectionUtil.getFieldsRecursive(entity.type().orElseThrow());
var idFields = new ArrayList<Field>(); var idFields = new ArrayList<Field>();
//search for fields with @Id annotation //search for fields with @Id annotation
@@ -56,8 +56,12 @@ class PrimaryKeyInitializer {
} }
if (!idFields.isEmpty()) { if (!idFields.isEmpty()) {
var dbfields = idFields.stream().map(entity::getField).toList(); var dbfields = idFields.stream().map(e -> entity.field(e).getField().getName()).toList();
entity.setPrimaryKey(new PrimaryKeyConstraint(entity, (List) dbfields)); entity.hasOne(dbfields.toArray(String[]::new)).isPrimaryKey(true);
}
if (idFields.size() == 1) {//auto enable identity generation for primary key
entity.field(idFields.get(0)).generated(Generated.Type.IDENTITY);
} }
} }
} }

View File

@@ -0,0 +1,21 @@
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
;
}
}

View File

@@ -1,7 +1,6 @@
package jef.model.annotations.processors; package jef.model.annotations.processors;
import jef.model.DbEntity; import jef.model.DbEntityBuilder;
import jef.model.DbField;
import jef.model.ModelBuilder; import jef.model.ModelBuilder;
import jef.model.ModelException; import jef.model.ModelException;
import jef.model.annotations.ForeignKey; import jef.model.annotations.ForeignKey;
@@ -22,61 +21,62 @@ public class ForeignKeyProcessor implements AnnotationProcessor {
@Override @Override
public void apply(ModelBuilder mb) { public void apply(ModelBuilder mb) {
for (DbEntity<? extends SerializableObject> entity : mb.getEntities()) { for (var entity : mb.entities()) {
//annotation on fields //annotation on fields
for (DbField<?> field : entity.getFields()) { for (var field : entity.fields()) {
if (field.isModelField() && field.getField().getAnnotationsByType(ForeignKey.class).length > 0) { if (field.getField().isModelField() && field.getField().getField().getAnnotationsByType(ForeignKey.class).length > 0) {
var anno = field.getField().getAnnotationsByType(ForeignKey.class)[0]; var anno = field.getField().getField().getAnnotationsByType(ForeignKey.class)[0];
// var dbfield = entity.getField(field.getField()); // var dbfield = entity.getField(field.getField());
// if (dbfield == null) { // if (dbfield == null) {
// throw new ModelException("DbField with field " + field.getName() + " not found in " + entity.getType().getSimpleName()); // throw new ModelException("DbField with field " + field.getName() + " not found in " + entity.getType().getSimpleName());
// } // }
var exposeForeignKey = anno.entity() == SerializableObject.class; var exposeForeignKey = anno.entity() == SerializableObject.class;
var refEntity = exposeForeignKey ? entity : mb.getEntity(anno.entity()); var refEntity = exposeForeignKey ? entity.getEntity() : mb.getEntity(anno.entity());
if (!exposeForeignKey && refEntity == null) { if (!exposeForeignKey && refEntity == null) {
throw new ModelException("Could not find referenced entity " + anno.entity().getSimpleName() + " (via @" + ForeignKey.class.getSimpleName() throw new ModelException("Could not find referenced entity " + anno.entity().getSimpleName() + " (via @" + ForeignKey.class.getSimpleName()
+ " in " + entity.getType().getSimpleName() + "::" + field.getField().getName() + ")"); + " in " + entity.className() + "::" + field.getField().getName() + ")");
} }
var refEntityBuilder = mb.entity(refEntity.getTypeName());
Field refClassField; Field refClassField;
try { try {
refClassField = getField(refEntity, anno.getterOrField()); refClassField = getField(refEntityBuilder, anno.getterOrField());
} catch (ModelException e) { } catch (ModelException e) {
throw new ModelException(e.getMessage() + " (via @" + ForeignKey.class.getSimpleName() throw new ModelException(e.getMessage() + " (via @" + ForeignKey.class.getSimpleName()
+ " in " + entity.getType().getSimpleName() + "::" + field.getField().getName() + ")"); + " in " + entity.className() + "::" + field.getField().getName() + ")");
} }
var refField = refEntity.getField(refClassField); var refField = refEntity.getField(refClassField);
if (exposeForeignKey) { if (exposeForeignKey) {
field.setForeignKeyModelLink(refField); // field.getField().setExposingForeignKeyOf(refField);
refField.setForeignKeyModelLink(field); refField.setExposingForeignKeyOf(field.getField());
continue; continue;
} }
//check for primary key //check for primary key
if (refEntity.getPrimaryKey() == null) { if (refEntity.getPrimaryKey() == null) {
throw new ModelException("Entity " + refEntity.getType().getSimpleName() + " does not have a primary key and can " throw new ModelException("Entity " + refEntityBuilder.className() + " does not have a primary key and can "
+ "therefore not be referenced (via @" + ForeignKey.class.getSimpleName() + "therefore not be referenced (via @" + ForeignKey.class.getSimpleName()
+ " in " + entity.getType().getSimpleName() + "::" + field.getField().getName() + ")"); + " in " + entity.className() + "::" + field.getField().getName() + ")");
} }
//check if references field is the primary key of the refeneced entity //check if references field is the primary key of the refeneced entity
if (!refEntity.getPrimaryKey().getFields().equals(List.of(refField))) { if (!refEntity.getPrimaryKey().getFields().equals(List.of(refField))) {
throw new ModelException(refEntity.getType().getSimpleName() + "::" + refField.getField().getName() throw new ModelException(refEntityBuilder.className() + "::" + refField.getField().getName()
+ " is not equal to the primary key of entity " + refEntity.getType().getSimpleName() + " is not equal to the primary key of entity " + refEntityBuilder.className()
+ " (" + refEntity.getPrimaryKey().getFields().stream().map(e -> e.getField().getName()).collect(Collectors.joining(", ")) + ")" + " (" + refEntity.getPrimaryKey().getFields().stream().map(e -> e.getField().getName()).collect(Collectors.joining(", ")) + ")"
+ " and can therefore not be referenced (via @" + ForeignKey.class.getSimpleName() + " and can therefore not be referenced (via @" + ForeignKey.class.getSimpleName()
+ " in " + entity.getType().getSimpleName() + "::" + field.getField().getName() + ")"); + " in " + entity.className() + "::" + field.getField().getName() + ")");
} }
entity.addForeignKey(new ForeignKeyConstraint(entity, List.of(field), refEntity, List.of(refField), anno.onUpdate(), anno.onDelete())); entity.getEntity().addForeignKey(new ForeignKeyConstraint(entity.getEntity(), List.of(field.getField()), refEntity, List.of(refField), anno.onUpdate(), anno.onDelete()));
} }
} }
} }
} }
private Field getField(DbEntity<? extends SerializableObject> entity, String fieldOrGetter) { private Field getField(DbEntityBuilder<? extends SerializableObject> entity, String fieldOrGetter) {
Method getter = null; Method getter = null;
try { try {
getter = entity.getType().getDeclaredMethod(fieldOrGetter); getter = entity.type().get().getDeclaredMethod(fieldOrGetter);
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
} }
@@ -84,15 +84,15 @@ public class ForeignKeyProcessor implements AnnotationProcessor {
try { try {
if (getter != null && getter.getName().length() > 3 && getter.getName().startsWith("get")) { if (getter != null && getter.getName().length() > 3 && getter.getName().startsWith("get")) {
var name = getter.getName().substring(3, 4).toLowerCase(Locale.ROOT) + getter.getName().substring(4); //HACK var name = getter.getName().substring(3, 4).toLowerCase(Locale.ROOT) + getter.getName().substring(4); //HACK
field = entity.getType().getDeclaredField(name); field = entity.type().get().getDeclaredField(name);
} else { } else {
field = entity.getType().getDeclaredField(fieldOrGetter); field = entity.type().get().getDeclaredField(fieldOrGetter);
} }
} catch (NoSuchFieldException e) { } catch (NoSuchFieldException e) {
} }
if (field == null) { if (field == null) {
throw new ModelException("Cannot find getter/field " + entity.getType().getSimpleName() + "::" + fieldOrGetter); throw new ModelException("Cannot find getter/field " + entity.className() + "::" + fieldOrGetter);
} }
return field; return field;
} }

View File

@@ -0,0 +1,27 @@
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());
}
}
}
}

View File

@@ -1,7 +1,7 @@
package jef.model.annotations.processors; package jef.model.annotations.processors;
import jef.model.DbEntity; import jef.model.DbEntityBuilder;
import jef.model.DbField; import jef.model.DbFieldBuilder;
import jef.model.annotations.Index; import jef.model.annotations.Index;
import jef.model.constraints.IndexConstraint; import jef.model.constraints.IndexConstraint;
import jef.serializable.SerializableObject; import jef.serializable.SerializableObject;
@@ -17,8 +17,8 @@ public class IndexProcessor extends KeyProcessorBase<IndexConstraint, Index> {
} }
@Override @Override
protected IndexConstraint initConstraint(DbEntity<? extends SerializableObject> entity, List<DbField<?>> fields) { protected IndexConstraint initConstraint(DbEntityBuilder<? extends SerializableObject> entity, List<DbFieldBuilder<?>> fields) {
return new IndexConstraint(entity, fields); return new IndexConstraint(entity.getEntity(), (List)fields.stream().map(DbFieldBuilder::getField).toList());
} }
@Override @Override

View File

@@ -1,7 +1,7 @@
package jef.model.annotations.processors; package jef.model.annotations.processors;
import jef.model.DbEntity; import jef.model.DbEntityBuilder;
import jef.model.DbField; import jef.model.DbFieldBuilder;
import jef.model.annotations.Key; import jef.model.annotations.Key;
import jef.model.constraints.KeyConstraint; import jef.model.constraints.KeyConstraint;
import jef.serializable.SerializableObject; import jef.serializable.SerializableObject;
@@ -17,8 +17,8 @@ public class KeyProcessor extends KeyProcessorBase<KeyConstraint, Key> {
} }
@Override @Override
protected KeyConstraint initConstraint(DbEntity<? extends SerializableObject> entity, List<DbField<?>> fields) { protected KeyConstraint initConstraint(DbEntityBuilder<? extends SerializableObject> entity, List<DbFieldBuilder<?>> fields) {
return new KeyConstraint(entity, fields); return new KeyConstraint(entity.getEntity(), (List)fields.stream().map(DbFieldBuilder::getField).toList());
} }
@Override @Override

View File

@@ -1,7 +1,8 @@
package jef.model.annotations.processors; package jef.model.annotations.processors;
import jef.model.DbEntity; import jef.asm.OptimizedAsmParser;
import jef.model.DbField; import jef.model.DbEntityBuilder;
import jef.model.DbFieldBuilder;
import jef.model.ModelBuilder; import jef.model.ModelBuilder;
import jef.model.ModelException; import jef.model.ModelException;
import jef.serializable.SerializableObject; import jef.serializable.SerializableObject;
@@ -12,7 +13,6 @@ import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Locale;
abstract class KeyProcessorBase<K, T extends Annotation> implements AnnotationProcessor { abstract class KeyProcessorBase<K, T extends Annotation> implements AnnotationProcessor {
private final Class<T> annotationClass; private final Class<T> annotationClass;
@@ -21,7 +21,7 @@ abstract class KeyProcessorBase<K, T extends Annotation> implements AnnotationPr
this.annotationClass = annotationClass; this.annotationClass = annotationClass;
} }
protected abstract K initConstraint(DbEntity<? extends SerializableObject> entity, List<DbField<?>> fields); protected abstract K initConstraint(DbEntityBuilder<? extends SerializableObject> entity, List<DbFieldBuilder<?>> fields);
protected abstract void addConstraint(K constr); protected abstract void addConstraint(K constr);
@@ -29,16 +29,16 @@ abstract class KeyProcessorBase<K, T extends Annotation> implements AnnotationPr
@Override @Override
public void apply(ModelBuilder mb) { public void apply(ModelBuilder mb) {
for (DbEntity<? extends SerializableObject> entity : mb.getEntities()) { for (DbEntityBuilder<? extends SerializableObject> entity : mb.entities()) {
//class annotation //class annotation
var classAnno = entity.getType().getAnnotationsByType(annotationClass); var classAnno = entity.type().get().getAnnotationsByType(annotationClass);
for (T t : classAnno) { for (T t : classAnno) {
var classFields = Arrays.stream(getGettersOrFields(t)) var classFields = Arrays.stream(getGettersOrFields(t))
.map(name -> getField(entity, name)) .map(name -> getField(entity, name))
.map(field -> { .map(field -> {
var dbfield = (DbField<?>) entity.getField(field); var dbfield = (DbFieldBuilder<?>) entity.field(field);
if (dbfield == null) { if (dbfield == null) {
throw new ModelException("DbField with field " + field.getName() + " not found in " + entity.getType().getSimpleName()); throw new ModelException("DbField with field " + field.getName() + " not found in " + entity.className());
} }
return dbfield; return dbfield;
}).toList(); }).toList();
@@ -46,11 +46,11 @@ abstract class KeyProcessorBase<K, T extends Annotation> implements AnnotationPr
} }
//annotation on fields //annotation on fields
for (DbField<?> field : entity.getFields()) { for (DbFieldBuilder<?> field : entity.fields()) {
if (field.getField() != null && field.getField().getAnnotationsByType(annotationClass).length > 0) { if (field.getField().getField() != null && field.getField().getField().getAnnotationsByType(annotationClass).length > 0) {
var dbfield = entity.getField(field.getField()); var dbfield = entity.field(field.getField().getField());
if (dbfield == null) { if (dbfield == null) {
throw new ModelException("DbField with field " + field.getName() + " not found in " + entity.getType().getSimpleName()); throw new ModelException("DbField with field " + field.getField().getName() + " not found in " + entity.className());
} }
addConstraint(initConstraint(entity, List.of(dbfield))); addConstraint(initConstraint(entity, List.of(dbfield)));
} }
@@ -58,26 +58,27 @@ abstract class KeyProcessorBase<K, T extends Annotation> implements AnnotationPr
} }
} }
private Field getField(DbEntity<? extends SerializableObject> entity, String fieldOrGetter) { private Field getField(DbEntityBuilder<? extends SerializableObject> entity, String fieldOrGetter) {
Method getter = null; Method getter = null;
try { try {
getter = entity.getType().getDeclaredMethod(fieldOrGetter); getter = entity.type().orElseThrow().getDeclaredMethod(fieldOrGetter);
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException ignored) {
} }
Field field = null; Field field = null;
try { try {
if (getter != null && getter.getName().length() > 3 && getter.getName().startsWith("get")) { if (getter != null) {
var name = getter.getName().substring(3, 4).toLowerCase(Locale.ROOT) + getter.getName().substring(4); //HACK var res = new OptimizedAsmParser(getter ).parse();
field = entity.getType().getDeclaredField(name); var name = res.getAccessedFields().stream().findFirst().orElseThrow().getName();
field = entity.type().orElseThrow().getDeclaredField(name);
} else { } else {
field = entity.getType().getDeclaredField(fieldOrGetter); field = entity.type().orElseThrow().getDeclaredField(fieldOrGetter);
} }
} catch (NoSuchFieldException e) { } catch (NoSuchFieldException ignored) {
} }
if (field == null) { if (field == null) {
throw new ModelException("Cannot find getter/field '" + fieldOrGetter + "' in " + entity.getType().getSimpleName()); throw new ModelException("Cannot find getter/field '" + fieldOrGetter + "' in " + entity.className());
} }
return field; return field;
} }

View File

@@ -1,7 +1,7 @@
package jef.model.annotations.processors; package jef.model.annotations.processors;
import jef.model.DbEntity; import jef.model.DbEntityBuilder;
import jef.model.DbField; import jef.model.DbFieldBuilder;
import jef.model.annotations.Unique; import jef.model.annotations.Unique;
import jef.model.constraints.UniqueKeyConstraint; import jef.model.constraints.UniqueKeyConstraint;
import jef.serializable.SerializableObject; import jef.serializable.SerializableObject;
@@ -17,8 +17,8 @@ public class UniqueProcessor extends KeyProcessorBase<UniqueKeyConstraint, Uniqu
} }
@Override @Override
protected UniqueKeyConstraint initConstraint(DbEntity<? extends SerializableObject> entity, List<DbField<?>> fields) { protected UniqueKeyConstraint initConstraint(DbEntityBuilder<? extends SerializableObject> entity, List<DbFieldBuilder<?>> fields) {
return new UniqueKeyConstraint(entity, fields); return new UniqueKeyConstraint(entity.getEntity(), (List)fields.stream().map(DbFieldBuilder::getField).toList());
} }
@Override @Override

View File

@@ -2,25 +2,28 @@ package jef.model.constraints;
import jef.model.DbEntity; import jef.model.DbEntity;
import jef.model.DbField; import jef.model.DbField;
import jef.util.Check;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Getter @Getter
@Setter
public class ForeignKeyConstraint extends ConstraintBase { public class ForeignKeyConstraint extends ConstraintBase {
private final DbEntity<?> referencedEntity; private final DbEntity<?> referencedEntity;
private final List<DbField<?>> referencedFields; private final List<DbField<?>> referencedFields;
private final Action onUpdate; private Action onUpdate;
private final Action onDelete; private Action onDelete;
public ForeignKeyConstraint(DbEntity<?> entity, List<DbField<?>> fields, DbEntity<?> referencedEntity, List<DbField<?>> referencedFields, Action onUpdate, Action onDelete) { public ForeignKeyConstraint(DbEntity<?> entity, List<DbField<?>> fields, DbEntity<?> referencedEntity, List<DbField<?>> referencedFields, Action onUpdate, Action onDelete) {
super(entity, fields); super(entity, fields);
this.referencedEntity = referencedEntity; this.referencedEntity = Check.notNull(referencedEntity, "referencedEntity");
this.referencedFields = referencedFields; this.referencedFields = Check.notNull(referencedFields, "referencedFields");
this.onUpdate = onUpdate; this.onUpdate = Check.notNull(onUpdate, "onUpdate");
this.onDelete = onDelete; this.onDelete = Check.notNull(onDelete, "onDelete");
} }
@Override @Override

View File

@@ -15,15 +15,17 @@ import jef.model.migration.operation.MigrationOperation;
import jef.model.migration.operation.RenameFieldOperation; import jef.model.migration.operation.RenameFieldOperation;
import jef.model.migration.operation.RenameTableOperation; import jef.model.migration.operation.RenameTableOperation;
import jef.model.migration.operation.UpdateFieldOperation; import jef.model.migration.operation.UpdateFieldOperation;
import lombok.Getter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@Getter
public class MigrationBuilder { public class MigrationBuilder {
private final List<MigrationOperation.Builder<?>> operations = new ArrayList<>(); private final List<MigrationOperation.Builder<?>> operations = new ArrayList<>();
public AddTableOperation.Builder addTable(String table, List<AddFieldOperation.Builder> fields) { public AddTableOperation.Builder addTable(String table, List<AddFieldOperation.Builder> fields, AddPrimaryKeyOperation.Builder primaryKey) {
var op = new AddTableOperation.Builder(table, fields); var op = new AddTableOperation.Builder(table, fields, primaryKey);
operations.add(op); operations.add(op);
return op; return op;
} }

View File

@@ -1,23 +1,31 @@
package jef.model.migration.operation; package jef.model.migration.operation;
import jef.model.annotations.Generated;
import jef.util.Check;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.ToString;
import java.util.List;
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
@EqualsAndHashCode
@ToString
public class AddFieldOperation implements MigrationOperation { public class AddFieldOperation implements MigrationOperation {
protected final String table; protected final String table;
protected final String field; protected final String field;
protected final String sqlType; protected final String sqlType;
protected final boolean notNull; protected final boolean notNull;
protected final Generated.Type generated;
@EqualsAndHashCode
@ToString
public static class Builder implements MigrationOperation.Builder<AddFieldOperation> { public static class Builder implements MigrationOperation.Builder<AddFieldOperation> {
protected final String table; protected final String table;
protected final String field; protected final String field;
protected String sqlType; protected String sqlType;
protected boolean notNull; protected boolean notNull;
protected Generated.Type generated;
public Builder(String table, String field) { public Builder(String table, String field) {
this.table = table; this.table = table;
@@ -34,8 +42,14 @@ public class AddFieldOperation implements MigrationOperation {
return this; return this;
} }
public Builder generated(Generated.Type generated) {
Check.notNull(generated, "generated");
this.generated = generated;
return this;
}
public AddFieldOperation build() { public AddFieldOperation build() {
return new AddFieldOperation(table, field, sqlType, notNull); return new AddFieldOperation(table, field, sqlType, notNull, generated);
} }
} }
} }

View File

@@ -2,13 +2,16 @@ package jef.model.migration.operation;
import jef.model.constraints.ForeignKeyConstraint; import jef.model.constraints.ForeignKeyConstraint;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.ToString;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
@EqualsAndHashCode
@ToString
public class AddForeignKeyOperation implements MigrationOperation { public class AddForeignKeyOperation implements MigrationOperation {
private final String name; private final String name;
private final String table; private final String table;
@@ -18,6 +21,8 @@ public class AddForeignKeyOperation implements MigrationOperation {
private final ForeignKeyConstraint.Action onUpdate; private final ForeignKeyConstraint.Action onUpdate;
private final ForeignKeyConstraint.Action onDelete; private final ForeignKeyConstraint.Action onDelete;
@EqualsAndHashCode
@ToString
public static class Builder implements MigrationOperation.Builder<AddForeignKeyOperation> { public static class Builder implements MigrationOperation.Builder<AddForeignKeyOperation> {
private final String name; private final String name;
private final String table; private final String table;

View File

@@ -1,16 +1,21 @@
package jef.model.migration.operation; package jef.model.migration.operation;
import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.ToString;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
@Getter @Getter
@EqualsAndHashCode(callSuper = true)
@ToString
public class AddIndexOperation extends AddKeyOperationBase { public class AddIndexOperation extends AddKeyOperationBase {
public AddIndexOperation(String name, String table, List<String> fields) { public AddIndexOperation(String name, String table, List<String> fields) {
super(name, table, fields); super(name, table, fields);
} }
@EqualsAndHashCode(callSuper = true)
@ToString
public static class Builder extends AddKeyOperationBase.Builder<AddIndexOperation> { public static class Builder extends AddKeyOperationBase.Builder<AddIndexOperation> {
public Builder(String name, String table, List<String> fields) { public Builder(String name, String table, List<String> fields) {
super(name, table, fields); super(name, table, fields);

View File

@@ -1,16 +1,21 @@
package jef.model.migration.operation; package jef.model.migration.operation;
import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.ToString;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
@Getter @Getter
@EqualsAndHashCode(callSuper = true)
@ToString
public class AddKeyOperation extends AddKeyOperationBase { public class AddKeyOperation extends AddKeyOperationBase {
public AddKeyOperation(String name, String table, List<String> fields) { public AddKeyOperation(String name, String table, List<String> fields) {
super(name, table, fields); super(name, table, fields);
} }
@EqualsAndHashCode(callSuper = true)
@ToString
public static class Builder extends AddKeyOperationBase.Builder<AddKeyOperation> { public static class Builder extends AddKeyOperationBase.Builder<AddKeyOperation> {
public Builder(String name, String table, List<String> fields) { public Builder(String name, String table, List<String> fields) {
super(name, table, fields); super(name, table, fields);

View File

@@ -1,17 +1,23 @@
package jef.model.migration.operation; package jef.model.migration.operation;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.ToString;
import java.util.List; import java.util.List;
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
@EqualsAndHashCode
@ToString
public abstract class AddKeyOperationBase implements MigrationOperation { public abstract class AddKeyOperationBase implements MigrationOperation {
protected final String name; protected final String name;
protected final String table; protected final String table;
protected final List<String> fields; protected final List<String> fields;
@EqualsAndHashCode
@ToString
public abstract static class Builder<T extends AddKeyOperationBase> implements MigrationOperation.Builder<T> { public abstract static class Builder<T extends AddKeyOperationBase> implements MigrationOperation.Builder<T> {
protected final String name; protected final String name;
protected final String table; protected final String table;

View File

@@ -1,16 +1,21 @@
package jef.model.migration.operation; package jef.model.migration.operation;
import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.ToString;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
@Getter @Getter
@EqualsAndHashCode(callSuper = true)
@ToString
public class AddPrimaryKeyOperation extends AddKeyOperationBase { public class AddPrimaryKeyOperation extends AddKeyOperationBase {
public AddPrimaryKeyOperation(String name, String table, List<String> fields) { public AddPrimaryKeyOperation(String name, String table, List<String> fields) {
super(name, table, fields); super(name, table, fields);
} }
@EqualsAndHashCode(callSuper = true)
@ToString
public static class Builder extends AddKeyOperationBase.Builder<AddPrimaryKeyOperation> { public static class Builder extends AddKeyOperationBase.Builder<AddPrimaryKeyOperation> {
public Builder(String name, String table, List<String> fields) { public Builder(String name, String table, List<String> fields) {
super(name, table, fields); super(name, table, fields);

View File

@@ -1,28 +1,36 @@
package jef.model.migration.operation; package jef.model.migration.operation;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.ToString;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
@EqualsAndHashCode
@ToString
public class AddTableOperation implements MigrationOperation { public class AddTableOperation implements MigrationOperation {
private final String table; private final String table;
private final List<AddFieldOperation.Builder> fields; private final List<AddFieldOperation.Builder> fields;
private final AddPrimaryKeyOperation.Builder primaryKey;
@EqualsAndHashCode
@ToString
public static class Builder implements MigrationOperation.Builder<AddTableOperation> { public static class Builder implements MigrationOperation.Builder<AddTableOperation> {
private final String table; private final String table;
private final List<AddFieldOperation.Builder> fields; private final List<AddFieldOperation.Builder> fields;
private final AddPrimaryKeyOperation.Builder primaryKey;
public Builder(String table, List<AddFieldOperation.Builder> fields) { public Builder(String table, List<AddFieldOperation.Builder> fields, AddPrimaryKeyOperation.Builder primaryKey) {
this.table = table; this.table = table;
this.fields = fields; this.fields = fields;
this.primaryKey = primaryKey;
} }
public AddTableOperation build() { public AddTableOperation build() {
return new AddTableOperation(table, fields); return new AddTableOperation(table, fields, primaryKey);
} }
} }
} }

View File

@@ -1,16 +1,21 @@
package jef.model.migration.operation; package jef.model.migration.operation;
import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.ToString;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
@Getter @Getter
@EqualsAndHashCode(callSuper = true)
@ToString
public class AddUniqueKeyOperation extends AddKeyOperationBase { public class AddUniqueKeyOperation extends AddKeyOperationBase {
public AddUniqueKeyOperation(String name, String table, List<String> fields) { public AddUniqueKeyOperation(String name, String table, List<String> fields) {
super(name, table, fields); super(name, table, fields);
} }
@EqualsAndHashCode(callSuper = true)
@ToString
public static class Builder extends AddKeyOperationBase.Builder<AddUniqueKeyOperation> { public static class Builder extends AddKeyOperationBase.Builder<AddUniqueKeyOperation> {
public Builder(String name, String table, List<String> fields) { public Builder(String name, String table, List<String> fields) {
super(name, table, fields); super(name, table, fields);

View File

@@ -1,16 +1,20 @@
package jef.model.migration.operation; package jef.model.migration.operation;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.ToString;
import java.util.List;
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
@EqualsAndHashCode
@ToString
public class DropConstraintOperation implements MigrationOperation { public class DropConstraintOperation implements MigrationOperation {
private final String name; private final String name;
private final String table; private final String table;
@EqualsAndHashCode
@ToString
public static class Builder implements MigrationOperation.Builder<DropConstraintOperation> { public static class Builder implements MigrationOperation.Builder<DropConstraintOperation> {
private final String name; private final String name;
private final String table; private final String table;

View File

@@ -1,16 +1,20 @@
package jef.model.migration.operation; package jef.model.migration.operation;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.ToString;
import java.util.List;
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
@EqualsAndHashCode
@ToString
public class DropFieldOperation implements MigrationOperation { public class DropFieldOperation implements MigrationOperation {
private final String table; private final String table;
private final String field; private final String field;
@EqualsAndHashCode
@ToString
public static class Builder implements MigrationOperation.Builder<DropFieldOperation> { public static class Builder implements MigrationOperation.Builder<DropFieldOperation> {
private final String table; private final String table;
private final String field; private final String field;

View File

@@ -1,15 +1,19 @@
package jef.model.migration.operation; package jef.model.migration.operation;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.ToString;
import java.util.List;
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
@EqualsAndHashCode
@ToString
public class DropTableOperation implements MigrationOperation { public class DropTableOperation implements MigrationOperation {
private final String table; private final String table;
@EqualsAndHashCode
@ToString
public static class Builder implements MigrationOperation.Builder<DropTableOperation> { public static class Builder implements MigrationOperation.Builder<DropTableOperation> {
private final String table; private final String table;

Some files were not shown because too many files have changed in this diff Show More