Compare commits
53 Commits
include
...
wip-byteco
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74a8f56ab5 | ||
|
|
b805eed622 | ||
|
|
c0f7b919f7 | ||
|
|
d0d51c1013 | ||
|
|
e1203eb5f4 | ||
|
|
7407d673fb | ||
|
|
506725b96d | ||
|
|
ddce1c83dc | ||
|
|
195f3d0148 | ||
|
|
3e581a9e4a | ||
|
|
1f90bc551b | ||
|
|
03850b78dd | ||
|
|
d4efbfff46 | ||
|
|
2910b2c282 | ||
|
|
913df99732 | ||
|
|
88366c937a | ||
|
|
591ac504fe | ||
|
|
58a8211513 | ||
|
|
cd30f58500 | ||
|
|
922da9556c | ||
|
|
103beeaafb | ||
|
|
a1e3377599 | ||
|
|
038ebe4aff | ||
|
|
237be45dc3 | ||
|
|
65570d0029 | ||
|
|
f5cb7c93c5 | ||
|
|
8b9659e867 | ||
|
|
c11e1b09f1 | ||
|
|
6f0743889b | ||
|
|
3c85ef2c84 | ||
|
|
f0d3948338 | ||
|
|
94b408bd79 | ||
|
|
802288e6ab | ||
|
|
99b49083ec | ||
|
|
1b8d9f94d8 | ||
|
|
481280ed88 | ||
|
|
915fc4a87b | ||
|
|
17d71eb2f3 | ||
|
|
27198a1e78 | ||
|
|
6870435eea | ||
|
|
4e75ce3b2e | ||
|
|
4e90b7cc90 | ||
|
|
de17b8985a | ||
|
|
967d099d81 | ||
|
|
284bf647b4 | ||
|
|
2f0fa26c6d | ||
|
|
6cdf74f351 | ||
|
|
703e3b1367 | ||
|
|
cdd650c1de | ||
|
|
445d3f4a9c | ||
|
|
dff7dd4229 | ||
|
|
ad29897cd8 | ||
|
|
6a1585f438 |
@@ -8,13 +8,15 @@
|
||||
- references to platform specific impls
|
||||
|
||||
## Asm
|
||||
- method references
|
||||
- equals function for primitive and string + String::equalsIgnoreCase
|
||||
- equals function for registered primitive conversion types
|
||||
- IConst0Fixer: IConst0/1 on its own => false/true
|
||||
- IConst0Fixer: IConst0/1 false/true depending on compared field type
|
||||
- actually parse getter
|
||||
- resolve Predicate functions (not, and, or)
|
||||
- don't bind external vars in lambdas, instead make information (parameter and intercepted values) available in a context object
|
||||
- expose more stuff as result (e.g. accessed members/functions, ...)
|
||||
- cleanup exception handling
|
||||
- cleanup if else trees
|
||||
|
||||
## ModelBuilder
|
||||
- add registrable conversion types e.g. UUID, Date, Timestamp, Calendar, LocalTime, Instant,...
|
||||
@@ -22,6 +24,9 @@
|
||||
- field length
|
||||
- field precision
|
||||
- sql type
|
||||
- ignore() entity/field for db
|
||||
- object inlining
|
||||
- entity() with callback
|
||||
|
||||
## Annotations
|
||||
- db type
|
||||
|
||||
45
cli/pom.xml
Normal file
45
cli/pom.xml
Normal 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>
|
||||
27
cli/src/main/java/jef/main/Main.java
Normal file
27
cli/src/main/java/jef/main/Main.java
Normal 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);
|
||||
}
|
||||
}
|
||||
279
cli/src/main/java/jef/main/MigrationCommandHandler.java
Normal file
279
cli/src/main/java/jef/main/MigrationCommandHandler.java
Normal 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) {
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -15,4 +15,9 @@ public abstract class Util {
|
||||
public interface ThrowableSupplier<T> {
|
||||
T get() throws Throwable;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ThrowableFunction<T, R> {
|
||||
R apply(T t) throws Throwable;
|
||||
}
|
||||
}
|
||||
36
core/pom.xml
Normal file
36
core/pom.xml
Normal 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>
|
||||
@@ -4,19 +4,24 @@ import jef.expressions.Expression;
|
||||
import jef.expressions.SelectExpression;
|
||||
import jef.expressions.TableExpression;
|
||||
import jef.expressions.selectable.DatabaseSelectAllExpression;
|
||||
import jef.model.DbContext;
|
||||
import jef.serializable.SerializableObject;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Spliterator;
|
||||
|
||||
public class DbSet<T extends SerializableObject> implements Queryable<T> {
|
||||
@Getter
|
||||
private final DbContext context;
|
||||
@Getter
|
||||
private final Class<T> clazz;
|
||||
private final String table;
|
||||
|
||||
public DbSet(Class<T> clazz, String table) {
|
||||
public DbSet(DbContext context, Class<T> clazz, String table) {
|
||||
this.context = context;
|
||||
this.clazz = clazz;
|
||||
this.table = table;
|
||||
}
|
||||
@@ -36,6 +41,23 @@ public class DbSet<T extends SerializableObject> implements Queryable<T> {
|
||||
return "SELECT * FROM `" + table + "`";
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbSet<T> originalSet() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public void add(T entity) throws SQLException {
|
||||
context.getDatabase().add(context, clazz, entity);
|
||||
}
|
||||
|
||||
public void update(T entity) throws SQLException {
|
||||
context.getDatabase().update(context, clazz, entity);
|
||||
}
|
||||
|
||||
public void delete(T entity) throws SQLException {
|
||||
context.getDatabase().delete(context, clazz, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return null;
|
||||
18
core/src/main/java/jef/MigrationException.java
Normal file
18
core/src/main/java/jef/MigrationException.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,15 @@ import jef.operations.FilterOp;
|
||||
import jef.operations.LimitOp;
|
||||
import jef.operations.SortOp;
|
||||
import jef.serializable.SerializableFunction;
|
||||
import jef.serializable.SerializableObject;
|
||||
import jef.serializable.SerializablePredicate;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Spliterator;
|
||||
|
||||
public interface Queryable<T extends Serializable> {
|
||||
public interface Queryable<T extends SerializableObject> {
|
||||
|
||||
//TODO documentation
|
||||
//TODO table alias thing still not ready
|
||||
@@ -22,6 +24,8 @@ public interface Queryable<T extends Serializable> {
|
||||
|
||||
String toString();
|
||||
|
||||
DbSet<T> originalSet();
|
||||
|
||||
//stream functions
|
||||
default Iterator<T> iterator() {
|
||||
return null;
|
||||
@@ -131,6 +135,9 @@ public interface Queryable<T extends Serializable> {
|
||||
// default Object[] toArray() {
|
||||
// return new Object[0];
|
||||
// }
|
||||
default List<T> toList() throws SQLException {
|
||||
return originalSet().getContext().getDatabase().queryExpression(originalSet().getContext(), originalSet().getClazz(), getExpression());
|
||||
}
|
||||
//
|
||||
// default <A> A[] toArray(IntFunction<A[]> intFunction) {
|
||||
// return null;
|
||||
@@ -1,13 +1,13 @@
|
||||
package jef;
|
||||
|
||||
import jef.expressions.Expression;
|
||||
import jef.serializable.SerializableObject;
|
||||
import jef.serializable.SerializablePredicate;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Iterator;
|
||||
import java.util.Spliterator;
|
||||
|
||||
public class QueryableProxy<T extends Serializable> implements Queryable<T> {
|
||||
public class QueryableProxy<T extends SerializableObject> implements Queryable<T> {
|
||||
private final Queryable<T> delegate;
|
||||
|
||||
public QueryableProxy(Queryable<T> delegate) {
|
||||
@@ -24,6 +24,11 @@ public class QueryableProxy<T extends Serializable> implements Queryable<T> {
|
||||
return delegate.getExpression();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbSet<T> originalSet() {
|
||||
return delegate.originalSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return delegate.iterator();
|
||||
@@ -1,6 +1,6 @@
|
||||
package jef.asm;
|
||||
|
||||
public class AsmParseException extends Exception {
|
||||
public class AsmParseException extends RuntimeException {
|
||||
public AsmParseException() {
|
||||
}
|
||||
|
||||
15
core/src/main/java/jef/asm/AsmParseResult.java
Normal file
15
core/src/main/java/jef/asm/AsmParseResult.java
Normal 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;
|
||||
}
|
||||
81
core/src/main/java/jef/asm/AsmParser.java
Normal file
81
core/src/main/java/jef/asm/AsmParser.java
Normal 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();
|
||||
}
|
||||
}
|
||||
54
core/src/main/java/jef/asm/FilterClassVisitor.java
Normal file
54
core/src/main/java/jef/asm/FilterClassVisitor.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package jef.asm;
|
||||
import jef.expressions.BinaryExpression;
|
||||
import jef.expressions.ConstantExpression;
|
||||
import jef.expressions.Expression;
|
||||
import jef.expressions.FunctionExpression;
|
||||
import jef.expressions.IntermediateFieldExpression;
|
||||
import jef.expressions.NullExpression;
|
||||
import jef.expressions.ParameterExpression;
|
||||
@@ -22,6 +23,7 @@ import org.objectweb.asm.Type;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -29,7 +31,6 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
@@ -37,22 +38,30 @@ class FilterMethodVisitor extends MethodVisitor {
|
||||
private Stack<Expression> varStack = new Stack<>();
|
||||
private final String[] parameterClasses;
|
||||
private final Object[] args;
|
||||
private final Consumer<Expression> exprConsumer;
|
||||
private Expression lambdaExpr;
|
||||
private final Set<Field> accessedMembers = new HashSet<>();
|
||||
|
||||
protected FilterMethodVisitor(int api, String descriptor, Object[] args, Consumer<Expression> exprConsumer) {
|
||||
protected FilterMethodVisitor(int api, String className, String descriptor, Object[] args) {
|
||||
super(api);
|
||||
this.args = args;
|
||||
this.exprConsumer = exprConsumer;
|
||||
|
||||
//parameters
|
||||
var types = Type.getMethodType(descriptor).getArgumentTypes();
|
||||
parameterClasses = new String[types.length];
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
parameterClasses[i] = types[i].getClassName();
|
||||
if (types.length == 0) {// class method //TODO this is dirty, as only works for parameterless class methods
|
||||
this.args = new Object[]{null};
|
||||
parameterClasses = new String[]{className};
|
||||
} else {
|
||||
this.args = args;
|
||||
parameterClasses = new String[types.length];
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
parameterClasses[i] = types[i].getClassName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AsmParseResult getResult() {
|
||||
return new AsmParseResult(lambdaExpr, accessedMembers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) {
|
||||
System.out.println("local var: " + name);
|
||||
@@ -67,6 +76,11 @@ class FilterMethodVisitor extends MethodVisitor {
|
||||
var v = varStack.pop();
|
||||
if (v instanceof ParameterExpression p) {
|
||||
if (p.isInput()) {
|
||||
try {
|
||||
accessedMembers.add(Class.forName(owner.replace("/", ".")).getDeclaredField(name));
|
||||
} catch (NoSuchFieldException | ClassNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
varStack.push(new IntermediateFieldExpression(name, descriptor));
|
||||
} else {
|
||||
throw new RuntimeException("field insn: unsupported GETFIELD expression");
|
||||
@@ -96,13 +110,27 @@ class FilterMethodVisitor extends MethodVisitor {
|
||||
} else if (opcode == Opcodes.INVOKEINTERFACE || opcode == Opcodes.INVOKEVIRTUAL) {
|
||||
try {
|
||||
if (name.equals("contains")
|
||||
&& owner.startsWith("java/util/")
|
||||
&& Collection.class.isAssignableFrom(Class.forName(owner.replace("/", ".")))) {
|
||||
&& owner.startsWith("java/util/")
|
||||
&& Collection.class.isAssignableFrom(Class.forName(owner.replace("/", ".")))) {
|
||||
var element = varStack.pop();
|
||||
var collection = varStack.pop();
|
||||
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
|
||||
var field = findField(owner, name);
|
||||
} else if (name.equals("equals")
|
||||
&& owner.equals("java/lang/String")) {
|
||||
var value = varStack.pop();
|
||||
var variable = varStack.pop();
|
||||
varStack.push(new BinaryExpression(variable, value, BinaryExpression.Operator.EQ));
|
||||
} else if (name.equals("equalsIgnoreCase")
|
||||
&& owner.equals("java/lang/String")) {
|
||||
var value = varStack.pop();
|
||||
var variable = varStack.pop();
|
||||
varStack.push(new BinaryExpression(new FunctionExpression(FunctionExpression.TOLOWER, List.of(variable)),
|
||||
new FunctionExpression(FunctionExpression.TOLOWER, List.of(value)),
|
||||
BinaryExpression.Operator.EQ));
|
||||
} else if (descriptor.startsWith("()")) {
|
||||
var method = Class.forName(owner.replace("/", ".")).getDeclaredMethod(name);
|
||||
var res = new OptimizedAsmParser(method).parse();
|
||||
var field = res.getAccessedFields().stream().findFirst();
|
||||
if (field.isPresent()) {
|
||||
var v = varStack.pop();
|
||||
if (v instanceof ParameterExpression p) {
|
||||
@@ -121,6 +149,9 @@ class FilterMethodVisitor extends MethodVisitor {
|
||||
throw new RuntimeException("method insn: unsupported function " + name + " in " + owner);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (e instanceof RuntimeException re) {
|
||||
throw re;
|
||||
}
|
||||
throw new RuntimeException("method insn: ", e);
|
||||
}
|
||||
} else {
|
||||
@@ -386,7 +417,6 @@ class FilterMethodVisitor extends MethodVisitor {
|
||||
System.out.println("end");
|
||||
|
||||
super.visitEnd();
|
||||
exprConsumer.accept(lambdaExpr);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1,23 +1,16 @@
|
||||
package jef.asm;
|
||||
|
||||
import jef.expressions.Expression;
|
||||
import jef.expressions.modifier.ExpressionOptimizerBottomUp;
|
||||
import jef.expressions.modifier.IConst0Fixer;
|
||||
import jef.expressions.modifier.TableAliasInjector;
|
||||
import jef.expressions.modifier.TernaryRewriter;
|
||||
import jef.serializable.SerializableFunction;
|
||||
import jef.serializable.SerializablePredicate;
|
||||
import lombok.Getter;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.lang.invoke.SerializedLambda;
|
||||
import java.util.stream.IntStream;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
@Getter
|
||||
public class OptimizedAsmParser extends AsmParser{
|
||||
public class OptimizedAsmParser extends AsmParser {
|
||||
|
||||
public OptimizedAsmParser(SerializablePredicate<?> predicate) {
|
||||
super(predicate);
|
||||
@@ -27,8 +20,13 @@ public class OptimizedAsmParser extends AsmParser{
|
||||
super(function);
|
||||
}
|
||||
|
||||
public Expression parse() throws AsmParseException {
|
||||
var expr = super.parse();
|
||||
public OptimizedAsmParser(Method method) {
|
||||
super(method);
|
||||
}
|
||||
|
||||
public AsmParseResult parse() throws AsmParseException {
|
||||
var result = super.parse();
|
||||
var expr = result.getExpression();
|
||||
|
||||
System.out.println(expr);
|
||||
expr = new TernaryRewriter().modify(expr);
|
||||
@@ -37,6 +35,6 @@ public class OptimizedAsmParser extends AsmParser{
|
||||
System.out.println(expr);
|
||||
expr = new ExpressionOptimizerBottomUp().modify(expr);
|
||||
|
||||
return expr;
|
||||
return new AsmParseResult(expr, result.getAccessedFields());
|
||||
}
|
||||
}
|
||||
26
core/src/main/java/jef/asm/access/ClassAnalyzer.java
Normal file
26
core/src/main/java/jef/asm/access/ClassAnalyzer.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package jef.asm.access;
|
||||
|
||||
import jef.asm.access.model.ClassDescription;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ClassAnalyzer {
|
||||
public ClassDescription analyze(Class clazz) {
|
||||
return analyzeClass(clazz);
|
||||
}
|
||||
|
||||
private ClassDescription analyzeClass(Class clazz) {
|
||||
try (var is = clazz.getClassLoader().getResourceAsStream(clazz.getName().replace(".", "/") + ".class")) {
|
||||
var result = new ClassDescription[1];
|
||||
var byteCode = is.readAllBytes();
|
||||
var reader = new ClassReader(byteCode);
|
||||
var visitor = new ClassAnalyzerVisitor(Opcodes.ASM9, description -> result[0] = description);
|
||||
reader.accept(visitor, 0);
|
||||
return result[0];
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
69
core/src/main/java/jef/asm/access/ClassAnalyzerVisitor.java
Normal file
69
core/src/main/java/jef/asm/access/ClassAnalyzerVisitor.java
Normal file
@@ -0,0 +1,69 @@
|
||||
package jef.asm.access;
|
||||
|
||||
import jef.asm.access.model.ClassDescription;
|
||||
import jef.asm.access.model.ConstructorDescription;
|
||||
import jef.asm.access.model.FieldDescription;
|
||||
import jef.asm.access.model.GetterDescription;
|
||||
import jef.asm.access.model.SetterDescription;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.FieldVisitor;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
class ClassAnalyzerVisitor extends ClassVisitor {
|
||||
private final Consumer<ClassDescription> callback;
|
||||
|
||||
private String className;
|
||||
private String superClassName;
|
||||
private final List<FieldDescription> declaredFields = new ArrayList<>();
|
||||
private final List<ConstructorDescription> declaredConstructor = new ArrayList<>();
|
||||
private final List<GetterDescription> declaredGetters = new ArrayList<>();
|
||||
private final List<SetterDescription> declaredSetters = new ArrayList<>();
|
||||
|
||||
protected ClassAnalyzerVisitor(int api, Consumer<ClassDescription> callback) {
|
||||
super(api);
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
protected ClassAnalyzerVisitor(int api, ClassVisitor classVisitor, Consumer<ClassDescription> callback) {
|
||||
super(api, classVisitor);
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||
className = name.replace("/", ".");
|
||||
superClassName = superName.replace("/", ".");
|
||||
super.visit(version, access, name, signature, superName, interfaces);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
|
||||
declaredFields.add(new FieldDescription(className, name));
|
||||
return super.visitField(access, name, descriptor, signature, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
|
||||
var type = Type.getMethodType(descriptor);
|
||||
var returnClassName = type.getReturnType().getClassName();
|
||||
var parameterClassNames = Arrays.stream(type.getArgumentTypes()).map(Type::getClassName).toArray(String[]::new);
|
||||
return new MethodAnalyzerVisitor(Opcodes.ASM9, className, name, access, returnClassName, parameterClassNames, entityAccess -> {
|
||||
declaredConstructor.addAll(entityAccess.getDeclaredConstructors());
|
||||
declaredGetters.addAll(entityAccess.getDeclaredGetters());
|
||||
declaredSetters.addAll(entityAccess.getDeclaredSetters());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
super.visitEnd();
|
||||
callback.accept(new ClassDescription(className, superClassName, declaredFields, declaredConstructor, declaredGetters, declaredSetters));
|
||||
}
|
||||
}
|
||||
330
core/src/main/java/jef/asm/access/MethodAnalyzerVisitor.java
Normal file
330
core/src/main/java/jef/asm/access/MethodAnalyzerVisitor.java
Normal file
@@ -0,0 +1,330 @@
|
||||
package jef.asm.access;
|
||||
|
||||
import jef.asm.access.model.ClassDescription;
|
||||
import jef.asm.access.model.ConstructorDescription;
|
||||
import jef.asm.access.model.ConstructorParameterDescription;
|
||||
import jef.asm.access.model.GetterDescription;
|
||||
import jef.asm.access.model.SetterDescription;
|
||||
import jef.asm.access.model.SuperConstructorParameterDescription;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.objectweb.asm.Handle;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Getter
|
||||
public class MethodAnalyzerVisitor extends MethodVisitor {//TODO verify param count
|
||||
private final Consumer<ClassDescription> callback;
|
||||
private final String declaringClassName;
|
||||
private final String methodName;
|
||||
private final int access;
|
||||
private final String returnClassName;
|
||||
private final String[] parameterClassNames;
|
||||
|
||||
private List<ConstructorParameterDescription> constructorParameterDescriptions = new ArrayList<>();
|
||||
private List<SuperConstructorParameterDescription> superConstructorParameterDescriptions = new ArrayList<>();
|
||||
private Stack<Var> varStack = new Stack<>();
|
||||
private String fieldDeclaringClassName = null;
|
||||
private String fieldName = null;
|
||||
|
||||
private ConstructorSteps constructorStep = ConstructorSteps.INIT;
|
||||
private GetterSteps getterStep = GetterSteps.INIT;
|
||||
private SetterSteps setterStep = SetterSteps.INIT;
|
||||
|
||||
enum ConstructorSteps {
|
||||
INIT,
|
||||
CODE,
|
||||
PRE_SUPER_INIT_ALOAD_0,
|
||||
PRE_SUPER_INIT_VAR_LOAD,
|
||||
SUPER_INIT,
|
||||
ALOAD_0,
|
||||
VAR_LOAD,
|
||||
PUT_FIELD,
|
||||
RETURN,
|
||||
INVALID
|
||||
}
|
||||
|
||||
enum GetterSteps {
|
||||
INIT,
|
||||
CODE,
|
||||
ALOAD_0,
|
||||
GET_FIELD,
|
||||
RETURN,
|
||||
INVALID
|
||||
}
|
||||
|
||||
enum SetterSteps {
|
||||
INIT,
|
||||
CODE,
|
||||
ALOAD_0,
|
||||
VAR_LOAD,
|
||||
PUT_FIELD,
|
||||
RETURN,
|
||||
INVALID
|
||||
}
|
||||
|
||||
protected MethodAnalyzerVisitor(int api, String declaringClassName, String methodName, int access, String returnClassName, String[] parameterClassNames, Consumer<ClassDescription> callback) {
|
||||
super(api);
|
||||
this.declaringClassName = declaringClassName;
|
||||
this.methodName = methodName;
|
||||
this.access = access;
|
||||
this.returnClassName = returnClassName;
|
||||
if (hasThis()) {
|
||||
this.parameterClassNames = new String[parameterClassNames.length + 1];
|
||||
System.arraycopy(parameterClassNames, 0, this.parameterClassNames, 1, parameterClassNames.length);
|
||||
this.parameterClassNames[0] = declaringClassName;
|
||||
} else {
|
||||
this.parameterClassNames = parameterClassNames;
|
||||
}
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitCode() {
|
||||
constructorStep = constructorStep == ConstructorSteps.INIT ? ConstructorSteps.CODE : ConstructorSteps.INVALID;
|
||||
getterStep = getterStep == GetterSteps.INIT ? GetterSteps.CODE : GetterSteps.INVALID;
|
||||
setterStep = setterStep == SetterSteps.INIT ? SetterSteps.CODE : SetterSteps.INVALID;
|
||||
super.visitCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitFrame(int type, int numLocal, Object[] local, int numStack, Object[] stack) {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
getterStep = GetterSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
super.visitFrame(type, numLocal, local, numStack, stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInsn(int opcode) {
|
||||
if (Set.of(Opcodes.IRETURN, Opcodes.LRETURN, Opcodes.FRETURN, Opcodes.DRETURN, Opcodes.ARETURN).contains(opcode)) {
|
||||
getterStep = getterStep == GetterSteps.GET_FIELD ? GetterSteps.RETURN : GetterSteps.INVALID;
|
||||
} else {
|
||||
getterStep = GetterSteps.INVALID;
|
||||
}
|
||||
if (opcode == Opcodes.RETURN) {
|
||||
constructorStep = constructorStep == ConstructorSteps.PUT_FIELD ? ConstructorSteps.RETURN : ConstructorSteps.INVALID;
|
||||
setterStep = setterStep == SetterSteps.PUT_FIELD ? SetterSteps.RETURN : SetterSteps.INVALID;
|
||||
} else {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
}
|
||||
super.visitInsn(opcode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitIntInsn(int opcode, int operand) {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
getterStep = GetterSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
super.visitIntInsn(opcode, operand);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitVarInsn(int opcode, int varIndex) {
|
||||
if (Set.of(Opcodes.ILOAD, Opcodes.LLOAD, Opcodes.FLOAD, Opcodes.DLOAD, Opcodes.ALOAD).contains(opcode)) {
|
||||
System.out.println(methodName + ": load_" + varIndex + ": " + (varIndex < parameterClassNames.length ? parameterClassNames[varIndex] : null));
|
||||
if (varIndex == 5) {
|
||||
int x = 0;
|
||||
}
|
||||
varStack.push(new Var(varIndex < parameterClassNames.length ? parameterClassNames[varIndex] : null, varIndex));
|
||||
}
|
||||
if (opcode == Opcodes.ALOAD && varIndex == 0) {
|
||||
if (constructorStep == ConstructorSteps.CODE) {
|
||||
constructorStep = ConstructorSteps.PRE_SUPER_INIT_ALOAD_0;
|
||||
} else if (constructorStep == ConstructorSteps.SUPER_INIT || constructorStep == ConstructorSteps.PUT_FIELD) {
|
||||
constructorStep = ConstructorSteps.ALOAD_0;
|
||||
} else {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
}
|
||||
} else if (Set.of(Opcodes.ILOAD, Opcodes.LLOAD, Opcodes.FLOAD, Opcodes.DLOAD, Opcodes.ALOAD).contains(opcode)) {
|
||||
if (constructorStep == ConstructorSteps.PRE_SUPER_INIT_ALOAD_0) {
|
||||
constructorStep = ConstructorSteps.PRE_SUPER_INIT_VAR_LOAD;
|
||||
} else if (constructorStep == ConstructorSteps.PRE_SUPER_INIT_VAR_LOAD) {
|
||||
constructorStep = ConstructorSteps.PRE_SUPER_INIT_VAR_LOAD;
|
||||
} else if (constructorStep == ConstructorSteps.ALOAD_0) {
|
||||
constructorStep = ConstructorSteps.VAR_LOAD;
|
||||
} else {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
}
|
||||
} else {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
}
|
||||
if (opcode == Opcodes.ALOAD && varIndex == 0) {
|
||||
getterStep = getterStep == GetterSteps.CODE ? GetterSteps.ALOAD_0 : GetterSteps.INVALID;
|
||||
} else {
|
||||
getterStep = GetterSteps.INVALID;
|
||||
}
|
||||
if (opcode == Opcodes.ALOAD && varIndex == 0) {
|
||||
setterStep = setterStep == SetterSteps.CODE ? SetterSteps.ALOAD_0 : SetterSteps.INVALID;
|
||||
} else if (Set.of(Opcodes.ILOAD, Opcodes.LLOAD, Opcodes.FLOAD, Opcodes.DLOAD, Opcodes.ALOAD).contains(opcode)) {
|
||||
setterStep = setterStep == SetterSteps.ALOAD_0 ? SetterSteps.VAR_LOAD : SetterSteps.INVALID;
|
||||
} else {
|
||||
setterStep = SetterSteps.INVALID;
|
||||
}
|
||||
super.visitVarInsn(opcode, varIndex);
|
||||
}
|
||||
|
||||
private boolean hasThis() {
|
||||
return (access & Opcodes.ACC_STATIC) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTypeInsn(int opcode, String type) {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
getterStep = GetterSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
super.visitTypeInsn(opcode, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
|
||||
Var lastLoadedVar = null;
|
||||
if (opcode == Opcodes.PUTFIELD) {
|
||||
lastLoadedVar = varStack.pop();
|
||||
varStack.pop();
|
||||
constructorStep = constructorStep == ConstructorSteps.VAR_LOAD ? ConstructorSteps.PUT_FIELD : ConstructorSteps.INVALID;
|
||||
getterStep = GetterSteps.INVALID;
|
||||
setterStep = setterStep == SetterSteps.VAR_LOAD ? SetterSteps.PUT_FIELD : SetterSteps.INVALID;
|
||||
} else if (opcode == Opcodes.GETFIELD) {
|
||||
lastLoadedVar = varStack.pop();
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
getterStep = getterStep == GetterSteps.ALOAD_0 ? GetterSteps.GET_FIELD : GetterSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
} else {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
getterStep = GetterSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
}
|
||||
this.fieldDeclaringClassName = owner.replace("/", ".");
|
||||
this.fieldName = name;
|
||||
this.constructorParameterDescriptions.add(new ConstructorParameterDescription(lastLoadedVar.getIndex() - 1, lastLoadedVar.getClassName(), this.fieldDeclaringClassName, this.fieldName)); //lastLoadedVar - 1 because arg 0 of an instance function is always this
|
||||
super.visitFieldInsn(opcode, owner, name, descriptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
|
||||
var args = new ArrayList<Var>();
|
||||
for (var i = 0; i < Type.getArgumentTypes(descriptor).length; i++) {
|
||||
args.add(0, varStack.pop());
|
||||
}
|
||||
varStack.pop(); // also pop ALOAD0
|
||||
if (opcode == Opcodes.INVOKESPECIAL && name.equals("<init>") //TODO does it have to be called '<init>' or is this just convention
|
||||
&& Set.of(ConstructorSteps.PRE_SUPER_INIT_ALOAD_0, ConstructorSteps.PRE_SUPER_INIT_VAR_LOAD).contains(constructorStep)) {
|
||||
for (int i = 0; i < args.size(); i++) {
|
||||
superConstructorParameterDescriptions.add(new SuperConstructorParameterDescription(args.get(i).getIndex(), i));
|
||||
}
|
||||
constructorStep = ConstructorSteps.SUPER_INIT;
|
||||
} else {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
}
|
||||
getterStep = GetterSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
getterStep = GetterSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitJumpInsn(int opcode, Label label) {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
getterStep = GetterSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
super.visitJumpInsn(opcode, label);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLabel(Label label) {
|
||||
super.visitLabel(label);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLdcInsn(Object value) {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
getterStep = GetterSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
super.visitLdcInsn(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitIincInsn(int varIndex, int increment) {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
getterStep = GetterSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
super.visitIincInsn(varIndex, increment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
getterStep = GetterSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
super.visitTableSwitchInsn(min, max, dflt, labels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
getterStep = GetterSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
super.visitLookupSwitchInsn(dflt, keys, labels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMultiANewArrayInsn(String descriptor, int numDimensions) {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
getterStep = GetterSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
super.visitMultiANewArrayInsn(descriptor, numDimensions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
|
||||
constructorStep = ConstructorSteps.INVALID;
|
||||
getterStep = GetterSteps.INVALID;
|
||||
setterStep = SetterSteps.INVALID;
|
||||
super.visitTryCatchBlock(start, end, handler, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) {
|
||||
super.visitLocalVariable(name, descriptor, signature, start, end, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMaxs(int maxStack, int maxLocals) {
|
||||
super.visitMaxs(maxStack, maxLocals);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
super.visitEnd();
|
||||
List<ConstructorDescription> constructors = constructorStep == ConstructorSteps.RETURN ? List.of(new ConstructorDescription(constructorParameterDescriptions, superConstructorParameterDescriptions)) : List.of();
|
||||
List<GetterDescription> getters = getterStep == GetterSteps.RETURN ? List.of(new GetterDescription(declaringClassName, methodName, returnClassName, Arrays.stream(parameterClassNames).skip(hasThis() ? 1 : 0).toArray(String[]::new), fieldDeclaringClassName, fieldName)) : List.of();
|
||||
List<SetterDescription> setters = setterStep == SetterSteps.RETURN ? List.of(new SetterDescription(declaringClassName, methodName, returnClassName, Arrays.stream(parameterClassNames).skip(hasThis() ? 1 : 0).toArray(String[]::new), fieldDeclaringClassName, fieldName)) : List.of();
|
||||
var access = new ClassDescription(null, null, null, constructors, getters, setters);
|
||||
callback.accept(access);
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
private static class Var {
|
||||
private final String className;
|
||||
private final int index;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package jef.asm.access.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public class ClassDescription {
|
||||
private final String className;
|
||||
private final String superClassName;
|
||||
private final List<FieldDescription> declaredFields;
|
||||
private final List<ConstructorDescription> declaredConstructors;
|
||||
private final List<GetterDescription> declaredGetters;
|
||||
private final List<SetterDescription> declaredSetters;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ClassDescription{" +
|
||||
"className='" + className + '\'' +
|
||||
", superClassName='" + superClassName + '\'' +
|
||||
", declaredFields=" + declaredFields +
|
||||
", declaredConstructors=" + declaredConstructors +
|
||||
", declaredGetters=" + declaredGetters +
|
||||
", declaredSetters=" + declaredSetters +
|
||||
'}';
|
||||
}
|
||||
|
||||
public String toStringFriendly() {
|
||||
return "ClassAccessDescription{\n"
|
||||
+ " className: " + className + '\n'
|
||||
+ " superClassName: " + superClassName + '\n'
|
||||
+ " declaredFields: " + declaredFields + '\n'
|
||||
+ " declaredConstructors:\n" + declaredConstructors.stream().map(e -> " " + e.toString() + '\n').collect(Collectors.joining())
|
||||
+ " declaredGetters:\n" + declaredGetters.stream().map(e -> " " + e.toString() + '\n').collect(Collectors.joining())
|
||||
+ " declaredSetters:\n" + declaredSetters.stream().map(e -> " " + e.toString()+ '\n').collect(Collectors.joining())
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package jef.asm.access.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@ToString
|
||||
public class ConstructorDescription {
|
||||
private final List<ConstructorParameterDescription> constructorParameterDescriptions;
|
||||
private final List<SuperConstructorParameterDescription> superConstructorParameterDescriptions;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package jef.asm.access.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@ToString
|
||||
public class ConstructorParameterDescription {
|
||||
private final int parameterIndex;
|
||||
private final String parameterClassName;
|
||||
private final String declaringClassName;
|
||||
private final String fieldName;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package jef.asm.access.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@ToString
|
||||
public class FieldDescription {
|
||||
private final String fieldDeclaringClassName;
|
||||
private final String fieldName;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package jef.asm.access.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@ToString
|
||||
public class GetterDescription {
|
||||
private final String methodDeclaringClassName;
|
||||
private final String methodName;
|
||||
private final String methodReturnClassName;
|
||||
private final String[] methodParameterClassNames;
|
||||
private final String fieldDeclaringClassName;
|
||||
private final String fieldName;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package jef.asm.access.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@ToString
|
||||
public class SetterDescription {
|
||||
private final String methodDeclaringClassName;
|
||||
private final String methodName;
|
||||
private final String methodReturnClassName;
|
||||
private final String[] methodParameterClassNames;
|
||||
private final String fieldDeclaringClassName;
|
||||
private final String fieldName;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package jef.asm.access.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@ToString
|
||||
public class SuperConstructorParameterDescription {
|
||||
private final int constructorParameterIndex;
|
||||
private final int superConstructorParameterIndex;
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@@ -25,6 +25,7 @@ public class BinaryExpression implements Expression {
|
||||
case EQ, NE, IS -> Priority.EQUALITY;
|
||||
case LT, LE, GT, GE -> Priority.RELATIONAL;
|
||||
case IN -> Priority.RELATIONAL; //or equality?
|
||||
//TODO +-, */, %, ...
|
||||
default -> throw new IllegalStateException();
|
||||
};
|
||||
}
|
||||
37
core/src/main/java/jef/expressions/ConstantExpression.java
Normal file
37
core/src/main/java/jef/expressions/ConstantExpression.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ public interface Expression {
|
||||
BINARY,
|
||||
CONSTANT,
|
||||
FIELD,
|
||||
FUNCTION,
|
||||
INTERMEDIATE_FIELD,
|
||||
LIMIT,
|
||||
NULL,
|
||||
@@ -42,7 +43,7 @@ public interface Expression {
|
||||
BIT_SHIFT(10), // <<, >>
|
||||
ADD_SUB(13), // +, -
|
||||
MUL_DIV_MOD(14), // *, /, %
|
||||
UNARY_PRE(15), // +, -, !, ~, --i, ++i
|
||||
UNARY_PRE(15), // +, -, !, ~, --i, ++i, function calls
|
||||
UNARY_POST(16), // i++, i--
|
||||
CONSTANT(17), // constant values
|
||||
;
|
||||
32
core/src/main/java/jef/expressions/FunctionExpression.java
Normal file
32
core/src/main/java/jef/expressions/FunctionExpression.java
Normal 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(", ")) + ")";
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@@ -33,7 +32,7 @@ public class ParameterExpression extends ConstantExpression implements Expressio
|
||||
} else if (this.value == null) {
|
||||
return "null";
|
||||
} 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();
|
||||
}
|
||||
@@ -25,6 +25,6 @@ public class TableExpression extends ConstantExpression {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "`" + super.toString() + "`";
|
||||
return "`" + name + "`";
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import jef.expressions.BinaryExpression;
|
||||
import jef.expressions.ConstantExpression;
|
||||
import jef.expressions.Expression;
|
||||
import jef.expressions.FieldExpression;
|
||||
import jef.expressions.FunctionExpression;
|
||||
import jef.expressions.IntermediateFieldExpression;
|
||||
import jef.expressions.LimitExpression;
|
||||
import jef.expressions.NullExpression;
|
||||
@@ -28,6 +29,7 @@ public abstract class ExpressionModifier {
|
||||
case BINARY -> modifyBinary((BinaryExpression) expr);
|
||||
case CONSTANT -> modifyConstant((ConstantExpression) expr);
|
||||
case FIELD -> modifyField((FieldExpression) expr);
|
||||
case FUNCTION -> modifyFunction((FunctionExpression) expr);
|
||||
case INTERMEDIATE_FIELD -> modifyIntermediateField((IntermediateFieldExpression) expr);
|
||||
case LIMIT -> modifyLimit((LimitExpression) expr);
|
||||
case NULL -> modifyNull((NullExpression) expr);
|
||||
@@ -63,6 +65,10 @@ public abstract class ExpressionModifier {
|
||||
return expr;
|
||||
}
|
||||
|
||||
public Expression modifyFunction(FunctionExpression expr) {
|
||||
return new FunctionExpression(expr.getFunction(), expr.getParameters().stream().map(this::modify).toList());
|
||||
}
|
||||
|
||||
public Expression modifyIntermediateField(IntermediateFieldExpression expr) {
|
||||
return expr;
|
||||
}
|
||||
@@ -51,15 +51,15 @@ public class ExpressionOptimizerBottomUp extends ExpressionModifier {
|
||||
//<- SELECT * FROM (SELECT * FROM (SELECT * FROM table) WHERE 1) LIMIT 1
|
||||
//-> SELECT * FROM (SELECT * FROM table) WHERE 1 LIMIT 1
|
||||
if (inner instanceof SelectExpression s
|
||||
&& s.getFields().equals(List.of(DatabaseSelectAllExpression.INSTANCE))
|
||||
&& s.getFrom() instanceof WhereExpression w) {
|
||||
&& s.getFields().equals(List.of(DatabaseSelectAllExpression.INSTANCE))
|
||||
&& s.getFrom() instanceof WhereExpression w) {
|
||||
return new LimitExpression(w, expr.getStart(), expr.getCount());
|
||||
}
|
||||
//<- SELECT * FROM (SELECT * FROM (SELECT * FROM table) ORDER BY x ASC 1) LIMIT 1
|
||||
//-> SELECT * FROM (SELECT * FROM table) ORDER BY x ASC LIMIT 1
|
||||
else if (inner instanceof SelectExpression s
|
||||
&& s.getFields().equals(List.of(DatabaseSelectAllExpression.INSTANCE))
|
||||
&& s.getFrom() instanceof OrderExpression o) {
|
||||
&& s.getFields().equals(List.of(DatabaseSelectAllExpression.INSTANCE))
|
||||
&& s.getFrom() instanceof OrderExpression o) {
|
||||
return new LimitExpression(o, expr.getStart(), expr.getCount());
|
||||
}
|
||||
//<- SELECT * FROM (SELECT * FROM (SELECT * FROM table) LIMIT 3) LIMIT 1
|
||||
@@ -103,8 +103,8 @@ public class ExpressionOptimizerBottomUp extends ExpressionModifier {
|
||||
//<- order(select(where(...)))
|
||||
//-> order(where(...))
|
||||
if (inner instanceof SelectExpression s
|
||||
&& s.getFields().equals(List.of(DatabaseSelectAllExpression.INSTANCE))
|
||||
&& s.getFrom() instanceof WhereExpression w) {
|
||||
&& s.getFields().equals(List.of(DatabaseSelectAllExpression.INSTANCE))
|
||||
&& s.getFrom() instanceof WhereExpression w) {
|
||||
return new OrderExpression(w, expr.getSorts());
|
||||
}
|
||||
//<- order1(order2(...))
|
||||
@@ -119,7 +119,7 @@ public class ExpressionOptimizerBottomUp extends ExpressionModifier {
|
||||
public Expression modifySelect(SelectExpression expr) {
|
||||
var from = modify(expr.getFrom());
|
||||
if (from instanceof SelectExpression s
|
||||
&& s.getFields().equals(expr.getFields())) {
|
||||
&& s.getFields().equals(expr.getFields())) {
|
||||
return s;
|
||||
}
|
||||
return super.modifySelect(expr);
|
||||
@@ -141,18 +141,18 @@ public class ExpressionOptimizerBottomUp extends ExpressionModifier {
|
||||
public Expression modifyUnary(UnaryExpression expr) {
|
||||
var inner = modify(expr.getExpr());
|
||||
if (inner instanceof UnaryExpression u
|
||||
&& u.getOperator() == UnaryExpression.Operator.NOT
|
||||
&& expr.getOperator() == UnaryExpression.Operator.NOT) {
|
||||
&& u.getOperator() == UnaryExpression.Operator.NOT
|
||||
&& expr.getOperator() == UnaryExpression.Operator.NOT) {
|
||||
//!!x -> x
|
||||
return modify(u.getExpr());
|
||||
} else if (inner instanceof BinaryExpression b
|
||||
&& expr.getOperator() == UnaryExpression.Operator.NOT
|
||||
&& b.getOperator().isInvertible()) {
|
||||
&& expr.getOperator() == UnaryExpression.Operator.NOT
|
||||
&& b.getOperator().isInvertible()) {
|
||||
//!(a < b) -> a >= b
|
||||
return new BinaryExpression(b.getLeft(), b.getRight(), b.getOperator().invert());
|
||||
} else if (inner instanceof BinaryExpression b
|
||||
&& expr.getOperator() == UnaryExpression.Operator.NOT
|
||||
&& b.getOperator() == BinaryExpression.Operator.IS) {
|
||||
&& expr.getOperator() == UnaryExpression.Operator.NOT
|
||||
&& b.getOperator() == BinaryExpression.Operator.IS) {
|
||||
//!(a < b) -> a >= b
|
||||
return new BinaryExpression(b.getLeft(), new UnaryExpression(b.getRight(), UnaryExpression.Operator.NOT), b.getOperator());
|
||||
} else {
|
||||
@@ -173,8 +173,8 @@ public class ExpressionOptimizerBottomUp extends ExpressionModifier {
|
||||
//<- where(cond1, select(where(cond2, ...)))
|
||||
//-> where(and(cond1, cond2) ...))
|
||||
else if (queryable instanceof SelectExpression s
|
||||
&& s.getFields().equals(List.of(DatabaseSelectAllExpression.INSTANCE))
|
||||
&& s.getFrom() instanceof WhereExpression w) {
|
||||
&& s.getFields().equals(List.of(DatabaseSelectAllExpression.INSTANCE))
|
||||
&& s.getFrom() instanceof WhereExpression w) {
|
||||
return new WhereExpression(w.getQueryable(), new AndExpression(w.getWhere(), where));
|
||||
}
|
||||
return super.modifyWhere(expr);
|
||||
@@ -9,13 +9,13 @@ public class DatabaseSelectAllExpression implements Expression, SelectableExpres
|
||||
public static final DatabaseSelectAllExpression INSTANCE = new DatabaseSelectAllExpression();
|
||||
|
||||
@Override
|
||||
public Expression.Type getType() {
|
||||
return Expression.Type.CONSTANT;
|
||||
public Type getType() {
|
||||
return Type.CONSTANT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression.Priority getPriority() {
|
||||
return Expression.Priority.UNDEFINED;
|
||||
public Priority getPriority() {
|
||||
return Priority.UNDEFINED;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -4,6 +4,7 @@ import jef.expressions.AndExpression;
|
||||
import jef.expressions.BinaryExpression;
|
||||
import jef.expressions.ConstantExpression;
|
||||
import jef.expressions.FieldExpression;
|
||||
import jef.expressions.FunctionExpression;
|
||||
import jef.expressions.IntermediateFieldExpression;
|
||||
import jef.expressions.LimitExpression;
|
||||
import jef.expressions.NullExpression;
|
||||
@@ -56,6 +57,11 @@ public class DebugExpressionVisitor extends ExpressionVisitor {
|
||||
System.out.println(i() + expr.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitFunction(FunctionExpression expr) {
|
||||
System.out.println(i() + expr.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitIntermediateField(IntermediateFieldExpression expr) {
|
||||
System.out.println(i() + expr.toString());
|
||||
@@ -5,6 +5,7 @@ import jef.expressions.BinaryExpression;
|
||||
import jef.expressions.ConstantExpression;
|
||||
import jef.expressions.Expression;
|
||||
import jef.expressions.FieldExpression;
|
||||
import jef.expressions.FunctionExpression;
|
||||
import jef.expressions.IntermediateFieldExpression;
|
||||
import jef.expressions.LimitExpression;
|
||||
import jef.expressions.NullExpression;
|
||||
@@ -24,6 +25,7 @@ public abstract class ExpressionVisitor {
|
||||
case BINARY -> visitBinary((BinaryExpression) expr);
|
||||
case CONSTANT -> visitConstant((ConstantExpression) expr);
|
||||
case FIELD -> visitField((FieldExpression) expr);
|
||||
case FUNCTION -> visitFunction((FunctionExpression) expr);
|
||||
case INTERMEDIATE_FIELD -> visitIntermediateField((IntermediateFieldExpression) expr);
|
||||
case LIMIT -> visitLimit((LimitExpression) expr);
|
||||
case NULL -> visitNull((NullExpression) expr);
|
||||
@@ -56,6 +58,9 @@ public abstract class ExpressionVisitor {
|
||||
public void visitField(FieldExpression expr) {
|
||||
}
|
||||
|
||||
public void visitFunction(FunctionExpression expr) {
|
||||
}
|
||||
|
||||
public void visitIntermediateField(IntermediateFieldExpression expr) {
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.DbSet;
|
||||
import jef.model.annotations.Clazz;
|
||||
import jef.model.constraints.ForeignKeyConstraint;
|
||||
import jef.platform.base.Database;
|
||||
import jef.serializable.SerializableObject;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
@@ -9,8 +13,39 @@ import java.util.HashMap;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
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(this, anno.value(), f.getName()));//TODO use table name from modelbuilder
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onModelCreate(ModelBuilder mb) {
|
||||
}
|
||||
@@ -44,13 +79,13 @@ public abstract class DbContext {
|
||||
|
||||
//check for duplicate field names
|
||||
for (DbEntity<? extends SerializableObject> entity : mb.getEntities()) {
|
||||
var fieldNameMap = new HashMap<String, DbEntity<?>>();
|
||||
var fieldNameMap = new HashMap<String, DbField<?>>();
|
||||
for (DbField<?> field : entity.getFields()) {
|
||||
var name = field.getName().toLowerCase();
|
||||
if (fieldNameMap.containsKey(name)) {
|
||||
errors.add("Duplicate DbField in entity " + entity.getTypeName() + " with name " + field.getName() + ": " + field.getName() + " and " + fieldNameMap.get(name).getName());
|
||||
} else {
|
||||
fieldNameMap.put(name, entity);
|
||||
fieldNameMap.put(name, field);
|
||||
}
|
||||
}
|
||||
}
|
||||
15
core/src/main/java/jef/model/DbContextOptions.java
Normal file
15
core/src/main/java/jef/model/DbContextOptions.java
Normal 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;
|
||||
}
|
||||
@@ -24,10 +24,10 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Setter(value = AccessLevel.PACKAGE)
|
||||
public class DbEntity<T extends SerializableObject> {
|
||||
private final String typeName;
|
||||
@Setter(value = AccessLevel.PACKAGE)
|
||||
private String typeName;
|
||||
// @Setter
|
||||
private Class<T> type;
|
||||
private final List<DbField<?>> fields;
|
||||
private String name;
|
||||
@@ -39,7 +39,6 @@ public class DbEntity<T extends SerializableObject> {
|
||||
|
||||
//only used for migrations
|
||||
DbEntity(String typeName) {
|
||||
this.type = (Class) Util.tryGet(() -> Class.forName(typeName)).orElse(null);
|
||||
this.typeName = typeName;
|
||||
this.fields = new ArrayList<>();
|
||||
var split = typeName.split("\\.");
|
||||
@@ -55,7 +54,7 @@ public class DbEntity<T extends SerializableObject> {
|
||||
}
|
||||
|
||||
DbEntity(Class<T> type, String name, List<DbField<?>> fields) {
|
||||
this.type = Check.notNull(type, "type");
|
||||
this.type = type;
|
||||
this.typeName = type.getName();
|
||||
this.fields = Check.notNull(fields, "fields");
|
||||
this.name = Check.notNull(name, "name");
|
||||
@@ -107,7 +106,7 @@ public class DbEntity<T extends SerializableObject> {
|
||||
var prop = getField(getter);
|
||||
if (prop == null) {
|
||||
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) {
|
||||
throw new RuntimeException("Field not found: " + name);
|
||||
}
|
||||
@@ -181,6 +180,7 @@ public class DbEntity<T extends SerializableObject> {
|
||||
|
||||
/**
|
||||
* Does not validate foreign key integrity
|
||||
*
|
||||
* @param 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 {
|
||||
var expr = new AsmParser(getter).parse();
|
||||
var expr = new AsmParser(getter).parse().getExpression();
|
||||
if (expr.getType() != Expression.Type.INTERMEDIATE_FIELD) {
|
||||
throw new RuntimeException(expr.getClass().getSimpleName() + " is not a field expression");
|
||||
}
|
||||
@@ -226,11 +234,15 @@ public class DbEntity<T extends SerializableObject> {
|
||||
|
||||
@Override
|
||||
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 (o == null || getClass() != o.getClass()) return false;
|
||||
DbEntity<?> dbEntity = (DbEntity<?>) o;
|
||||
return typeName.equals(dbEntity.typeName)
|
||||
&& fields.equals(dbEntity.fields)
|
||||
&& compareFields(fields, dbEntity.fields, fieldEquality)
|
||||
&& name.equals(dbEntity.name)
|
||||
&& Objects.equals(primaryKey, dbEntity.primaryKey)
|
||||
&& foreignKeys.equals(dbEntity.foreignKeys)
|
||||
@@ -239,15 +251,27 @@ public class DbEntity<T extends SerializableObject> {
|
||||
&& 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
|
||||
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
|
||||
public String toString() {
|
||||
return "DbEntity{" +
|
||||
"type=" + type +
|
||||
"typeName=" + typeName +
|
||||
", name='" + name + '\'' +
|
||||
'}';
|
||||
}
|
||||
121
core/src/main/java/jef/model/DbEntityBuilder.java
Normal file
121
core/src/main/java/jef/model/DbEntityBuilder.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,37 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.model.constraints.IndexConstraint;
|
||||
import jef.model.constraints.KeyConstraint;
|
||||
import jef.model.constraints.UniqueKeyConstraint;
|
||||
import jef.model.annotations.Generated;
|
||||
import jef.serializable.SerializableObject;
|
||||
import jef.util.Check;
|
||||
import jef.util.Util;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
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 String typeName;
|
||||
@Setter(value = AccessLevel.PACKAGE)
|
||||
private Class<T> type;
|
||||
@Setter(value = AccessLevel.PACKAGE)
|
||||
private Field field;
|
||||
private boolean isModelField;
|
||||
private boolean isDatabaseField;
|
||||
private DbField<?> foreignKeyModelLink;
|
||||
private boolean isModelField;//TODO get rid of this field
|
||||
private boolean isDatabaseField;//TODO get rid of this field
|
||||
private DbField<?> exposingForeignKeyOf;
|
||||
private String name;
|
||||
private boolean notNull = false;
|
||||
private String sqlType;
|
||||
private Generated.Type generated = Generated.Type.NONE;
|
||||
|
||||
DbField(DbEntity<? extends SerializableObject> entity, String name, String typeName) {
|
||||
this.entity = Check.notNull(entity, "entity");
|
||||
@@ -41,7 +44,7 @@ public class DbField<T> {
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -62,71 +65,33 @@ public class DbField<T> {
|
||||
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() {
|
||||
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() {
|
||||
return entity.getKeys().stream().anyMatch(u -> u.getFields().size() == 1 && u.getFields().get(0) == this);
|
||||
}
|
||||
|
||||
public void setKey(boolean keyed) {
|
||||
var constr = entity.getKeys().stream().filter(u -> u.getFields().size() == 1 && u.getFields().get(0) == this).findFirst();
|
||||
if (!constr.isPresent() && keyed) {
|
||||
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;
|
||||
public void setGenerated(Generated.Type generated) {
|
||||
Check.notNull(generated, "generated");
|
||||
this.generated = generated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!equalsCommon(o)) {
|
||||
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());
|
||||
return equalsCommon(o, false, false, true, true);
|
||||
}
|
||||
|
||||
public boolean equalsExceptName(Object o) {
|
||||
return equalsCommon(o);
|
||||
public boolean equalsExceptTypeAndFieldAndName(Object 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 (o == null || getClass() != o.getClass()) return false;
|
||||
DbField<?> dbField = (DbField<?>) o;
|
||||
@@ -135,16 +100,21 @@ public class DbField<T> {
|
||||
&& notNull == dbField.notNull
|
||||
&& entity.getName().equals(dbField.entity.getName())
|
||||
&& 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(foreignKeyModelLink == null ? null : foreignKeyModelLink.getName(),
|
||||
dbField.foreignKeyModelLink == null ? null : dbField.foreignKeyModelLink.getName());
|
||||
&& Objects.equals(exposingForeignKeyOf == null ? null : exposingForeignKeyOf.getName(),
|
||||
dbField.exposingForeignKeyOf == null ? null : dbField.exposingForeignKeyOf.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(entity.getName(), typeName, type, field == null ? null : field.getName(), isModelField, isDatabaseField,
|
||||
foreignKeyModelLink == null ? null : foreignKeyModelLink.getName(),
|
||||
name, notNull);
|
||||
exposingForeignKeyOf == null ? null : exposingForeignKeyOf.getName(),
|
||||
name, notNull, sqlType, generated);
|
||||
}
|
||||
|
||||
@Override
|
||||
95
core/src/main/java/jef/model/DbFieldBuilder.java
Normal file
95
core/src/main/java/jef/model/DbFieldBuilder.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
83
core/src/main/java/jef/model/EntityInitializer.java
Normal file
83
core/src/main/java/jef/model/EntityInitializer.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
core/src/main/java/jef/model/ForeignKeyBuilder.java
Normal file
20
core/src/main/java/jef/model/ForeignKeyBuilder.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
196
core/src/main/java/jef/model/ForeignKeyInitializer.java
Normal file
196
core/src/main/java/jef/model/ForeignKeyInitializer.java
Normal 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;
|
||||
}
|
||||
}
|
||||
186
core/src/main/java/jef/model/KeyBuilder.java
Normal file
186
core/src/main/java/jef/model/KeyBuilder.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,6 @@
|
||||
package jef.model;
|
||||
|
||||
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.IndexConstraint;
|
||||
import jef.model.constraints.KeyConstraint;
|
||||
@@ -14,49 +9,64 @@ import jef.model.constraints.UniqueKeyConstraint;
|
||||
import jef.serializable.SerializableObject;
|
||||
import jef.util.Check;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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.
|
||||
*
|
||||
* @param context the context to use for initialization
|
||||
* @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 {
|
||||
return from0(context);
|
||||
} catch (Exception e) {
|
||||
return from0(context, options);
|
||||
} catch (Throwable e) {
|
||||
throw e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static ModelBuilder from0(Class<? extends DbContext> context) throws Exception {
|
||||
var mb = new ModelBuilder(new ArrayList<>());
|
||||
private static ModelBuilder from0(Class<? extends DbContext> context, ModelBuilderOptions options) {
|
||||
var mb = new ModelBuilder();
|
||||
EntityInitializer.initEntities(mb, context);
|
||||
EntityDefaultConstructorChecker.checkEntities(mb);
|
||||
PrimaryKeyInitializer.initPrimaryKeys(mb);
|
||||
ForeignKeyExposeInitializer.initForeignKeyExposures(mb);
|
||||
ForeignKeyInitializer.initForeignKeys(mb);
|
||||
|
||||
for (AnnotationProcessor processor : annotationProcessors) {
|
||||
for (AnnotationProcessor processor : options.getAnnotationProcessors()) {
|
||||
processor.apply(mb);
|
||||
}
|
||||
|
||||
var instance = context.getDeclaredConstructor().newInstance();//TODO find constructor, //TODO optionally with config
|
||||
instance.onModelCreate(mb);
|
||||
instance.onModelValidate(mb);
|
||||
try {
|
||||
var instance = context.getDeclaredConstructor().newInstance();
|
||||
instance.onModelCreate(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;
|
||||
}
|
||||
@@ -86,6 +96,10 @@ public class ModelBuilder {
|
||||
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.
|
||||
*
|
||||
@@ -117,9 +131,14 @@ public class ModelBuilder {
|
||||
* @param <T> the type of model class
|
||||
* @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");
|
||||
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
|
||||
* @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");
|
||||
var entity = (DbEntity<T>) getEntity(typeName);
|
||||
if (entity == null) {
|
||||
entity = new DbEntity<>(typeName);
|
||||
entities.add(entity);
|
||||
}
|
||||
return entity;
|
||||
return new DbEntityBuilder<>(this, entity);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,14 +187,16 @@ public class ModelBuilder {
|
||||
var old = this.entities.get(i);
|
||||
var entity = (DbEntity<?>) new DbEntity(old.getTypeName());
|
||||
entity.setName(old.getName());
|
||||
entity.setType((Class) old.getType());
|
||||
entity.setTypeName(old.getTypeName());
|
||||
|
||||
//add fields
|
||||
old.getFields().stream().map(e -> {
|
||||
var nf = new DbField(entity, e.getName(), e.getTypeName());
|
||||
nf.setField(e.getField());
|
||||
nf.setType(e.getType());
|
||||
nf.setSqlType(e.getSqlType());
|
||||
nf.setNotNull(e.isNotNull());
|
||||
nf.setGenerated(e.getGenerated());
|
||||
nf.setModelField(e.isModelField());
|
||||
nf.setDatabaseField(e.isDatabaseField());
|
||||
return nf;
|
||||
@@ -183,9 +204,9 @@ public class ModelBuilder {
|
||||
.forEach(entity::addIfAbsent);
|
||||
|
||||
//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
|
||||
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());
|
||||
37
core/src/main/java/jef/model/ModelBuilderOptions.java
Normal file
37
core/src/main/java/jef/model/ModelBuilderOptions.java
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,23 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.model.annotations.Generated;
|
||||
import jef.model.annotations.Id;
|
||||
import jef.model.annotations.Transient;
|
||||
import jef.model.constraints.PrimaryKeyConstraint;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class PrimaryKeyInitializer {
|
||||
static void initPrimaryKeys(ModelBuilder mb) {
|
||||
for (int i = 0; i < mb.getEntities().size(); i++) {
|
||||
var entity = mb.getEntities().get(i);
|
||||
var entities = mb.entities();
|
||||
for (int i = 0; i < entities.size(); i++) {
|
||||
var entity = entities.get(i);
|
||||
initPrimaryKeys(mb, entity);
|
||||
}
|
||||
}
|
||||
|
||||
static void initPrimaryKeys(ModelBuilder mb, DbEntity<?> entity) {
|
||||
var fields = ReflectionUtil.getFieldsRecursive(entity.getType());
|
||||
static void initPrimaryKeys(ModelBuilder mb, DbEntityBuilder<?> entity) {
|
||||
var fields = ReflectionUtil.getFieldsRecursive(entity.type().orElseThrow());
|
||||
var idFields = new ArrayList<Field>();
|
||||
|
||||
//search for fields with @Id annotation
|
||||
@@ -56,8 +56,12 @@ class PrimaryKeyInitializer {
|
||||
}
|
||||
|
||||
if (!idFields.isEmpty()) {
|
||||
var dbfields = idFields.stream().map(entity::getField).toList();
|
||||
entity.setPrimaryKey(new PrimaryKeyConstraint(entity, (List) dbfields));
|
||||
var dbfields = idFields.stream().map(e -> entity.field(e).getField().getName()).toList();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
core/src/main/java/jef/model/annotations/Generated.java
Normal file
21
core/src/main/java/jef/model/annotations/Generated.java
Normal 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
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package jef.model.annotations.processors;
|
||||
|
||||
import jef.model.DbEntity;
|
||||
import jef.model.DbField;
|
||||
import jef.model.DbEntityBuilder;
|
||||
import jef.model.ModelBuilder;
|
||||
import jef.model.ModelException;
|
||||
import jef.model.annotations.ForeignKey;
|
||||
@@ -22,61 +21,62 @@ public class ForeignKeyProcessor implements AnnotationProcessor {
|
||||
|
||||
@Override
|
||||
public void apply(ModelBuilder mb) {
|
||||
for (DbEntity<? extends SerializableObject> entity : mb.getEntities()) {
|
||||
for (var entity : mb.entities()) {
|
||||
//annotation on fields
|
||||
for (DbField<?> field : entity.getFields()) {
|
||||
if (field.isModelField() && field.getField().getAnnotationsByType(ForeignKey.class).length > 0) {
|
||||
var anno = field.getField().getAnnotationsByType(ForeignKey.class)[0];
|
||||
for (var field : entity.fields()) {
|
||||
if (field.getField().isModelField() && field.getField().getField().getAnnotationsByType(ForeignKey.class).length > 0) {
|
||||
var anno = field.getField().getField().getAnnotationsByType(ForeignKey.class)[0];
|
||||
// var dbfield = entity.getField(field.getField());
|
||||
// if (dbfield == null) {
|
||||
// throw new ModelException("DbField with field " + field.getName() + " not found in " + entity.getType().getSimpleName());
|
||||
// }
|
||||
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) {
|
||||
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;
|
||||
try {
|
||||
refClassField = getField(refEntity, anno.getterOrField());
|
||||
refClassField = getField(refEntityBuilder, anno.getterOrField());
|
||||
} catch (ModelException e) {
|
||||
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);
|
||||
|
||||
if (exposeForeignKey) {
|
||||
field.setForeignKeyModelLink(refField);
|
||||
refField.setForeignKeyModelLink(field);
|
||||
// field.getField().setExposingForeignKeyOf(refField);
|
||||
refField.setExposingForeignKeyOf(field.getField());
|
||||
continue;
|
||||
}
|
||||
|
||||
//check for primary key
|
||||
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()
|
||||
+ " 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
|
||||
if (!refEntity.getPrimaryKey().getFields().equals(List.of(refField))) {
|
||||
throw new ModelException(refEntity.getType().getSimpleName() + "::" + refField.getField().getName()
|
||||
+ " is not equal to the primary key of entity " + refEntity.getType().getSimpleName()
|
||||
throw new ModelException(refEntityBuilder.className() + "::" + refField.getField().getName()
|
||||
+ " is not equal to the primary key of entity " + refEntityBuilder.className()
|
||||
+ " (" + refEntity.getPrimaryKey().getFields().stream().map(e -> e.getField().getName()).collect(Collectors.joining(", ")) + ")"
|
||||
+ " 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;
|
||||
try {
|
||||
getter = entity.getType().getDeclaredMethod(fieldOrGetter);
|
||||
getter = entity.type().get().getDeclaredMethod(fieldOrGetter);
|
||||
} catch (NoSuchMethodException e) {
|
||||
}
|
||||
|
||||
@@ -84,15 +84,15 @@ public class ForeignKeyProcessor implements AnnotationProcessor {
|
||||
try {
|
||||
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
|
||||
field = entity.getType().getDeclaredField(name);
|
||||
field = entity.type().get().getDeclaredField(name);
|
||||
} else {
|
||||
field = entity.getType().getDeclaredField(fieldOrGetter);
|
||||
field = entity.type().get().getDeclaredField(fieldOrGetter);
|
||||
}
|
||||
} catch (NoSuchFieldException e) {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package jef.model.annotations.processors;
|
||||
|
||||
import jef.model.DbEntity;
|
||||
import jef.model.DbField;
|
||||
import jef.model.DbEntityBuilder;
|
||||
import jef.model.DbFieldBuilder;
|
||||
import jef.model.annotations.Index;
|
||||
import jef.model.constraints.IndexConstraint;
|
||||
import jef.serializable.SerializableObject;
|
||||
@@ -17,8 +17,8 @@ public class IndexProcessor extends KeyProcessorBase<IndexConstraint, Index> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IndexConstraint initConstraint(DbEntity<? extends SerializableObject> entity, List<DbField<?>> fields) {
|
||||
return new IndexConstraint(entity, fields);
|
||||
protected IndexConstraint initConstraint(DbEntityBuilder<? extends SerializableObject> entity, List<DbFieldBuilder<?>> fields) {
|
||||
return new IndexConstraint(entity.getEntity(), (List)fields.stream().map(DbFieldBuilder::getField).toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1,7 +1,7 @@
|
||||
package jef.model.annotations.processors;
|
||||
|
||||
import jef.model.DbEntity;
|
||||
import jef.model.DbField;
|
||||
import jef.model.DbEntityBuilder;
|
||||
import jef.model.DbFieldBuilder;
|
||||
import jef.model.annotations.Key;
|
||||
import jef.model.constraints.KeyConstraint;
|
||||
import jef.serializable.SerializableObject;
|
||||
@@ -17,8 +17,8 @@ public class KeyProcessor extends KeyProcessorBase<KeyConstraint, Key> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected KeyConstraint initConstraint(DbEntity<? extends SerializableObject> entity, List<DbField<?>> fields) {
|
||||
return new KeyConstraint(entity, fields);
|
||||
protected KeyConstraint initConstraint(DbEntityBuilder<? extends SerializableObject> entity, List<DbFieldBuilder<?>> fields) {
|
||||
return new KeyConstraint(entity.getEntity(), (List)fields.stream().map(DbFieldBuilder::getField).toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1,7 +1,8 @@
|
||||
package jef.model.annotations.processors;
|
||||
|
||||
import jef.model.DbEntity;
|
||||
import jef.model.DbField;
|
||||
import jef.asm.OptimizedAsmParser;
|
||||
import jef.model.DbEntityBuilder;
|
||||
import jef.model.DbFieldBuilder;
|
||||
import jef.model.ModelBuilder;
|
||||
import jef.model.ModelException;
|
||||
import jef.serializable.SerializableObject;
|
||||
@@ -12,7 +13,6 @@ import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
abstract class KeyProcessorBase<K, T extends Annotation> implements AnnotationProcessor {
|
||||
private final Class<T> annotationClass;
|
||||
@@ -21,7 +21,7 @@ abstract class KeyProcessorBase<K, T extends Annotation> implements AnnotationPr
|
||||
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);
|
||||
|
||||
@@ -29,16 +29,16 @@ abstract class KeyProcessorBase<K, T extends Annotation> implements AnnotationPr
|
||||
|
||||
@Override
|
||||
public void apply(ModelBuilder mb) {
|
||||
for (DbEntity<? extends SerializableObject> entity : mb.getEntities()) {
|
||||
for (DbEntityBuilder<? extends SerializableObject> entity : mb.entities()) {
|
||||
//class annotation
|
||||
var classAnno = entity.getType().getAnnotationsByType(annotationClass);
|
||||
var classAnno = entity.type().get().getAnnotationsByType(annotationClass);
|
||||
for (T t : classAnno) {
|
||||
var classFields = Arrays.stream(getGettersOrFields(t))
|
||||
.map(name -> getField(entity, name))
|
||||
.map(field -> {
|
||||
var dbfield = (DbField<?>) entity.getField(field);
|
||||
var dbfield = (DbFieldBuilder<?>) entity.field(field);
|
||||
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;
|
||||
}).toList();
|
||||
@@ -46,11 +46,11 @@ abstract class KeyProcessorBase<K, T extends Annotation> implements AnnotationPr
|
||||
}
|
||||
|
||||
//annotation on fields
|
||||
for (DbField<?> field : entity.getFields()) {
|
||||
if (field.getField() != null && field.getField().getAnnotationsByType(annotationClass).length > 0) {
|
||||
var dbfield = entity.getField(field.getField());
|
||||
for (DbFieldBuilder<?> field : entity.fields()) {
|
||||
if (field.getField().getField() != null && field.getField().getField().getAnnotationsByType(annotationClass).length > 0) {
|
||||
var dbfield = entity.field(field.getField().getField());
|
||||
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)));
|
||||
}
|
||||
@@ -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;
|
||||
try {
|
||||
getter = entity.getType().getDeclaredMethod(fieldOrGetter);
|
||||
} catch (NoSuchMethodException e) {
|
||||
getter = entity.type().orElseThrow().getDeclaredMethod(fieldOrGetter);
|
||||
} catch (NoSuchMethodException ignored) {
|
||||
}
|
||||
|
||||
Field field = null;
|
||||
try {
|
||||
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
|
||||
field = entity.getType().getDeclaredField(name);
|
||||
if (getter != null) {
|
||||
var res = new OptimizedAsmParser(getter ).parse();
|
||||
var name = res.getAccessedFields().stream().findFirst().orElseThrow().getName();
|
||||
field = entity.type().orElseThrow().getDeclaredField(name);
|
||||
} else {
|
||||
field = entity.getType().getDeclaredField(fieldOrGetter);
|
||||
field = entity.type().orElseThrow().getDeclaredField(fieldOrGetter);
|
||||
}
|
||||
} catch (NoSuchFieldException e) {
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package jef.model.annotations.processors;
|
||||
|
||||
import jef.model.DbEntity;
|
||||
import jef.model.DbField;
|
||||
import jef.model.DbEntityBuilder;
|
||||
import jef.model.DbFieldBuilder;
|
||||
import jef.model.annotations.Unique;
|
||||
import jef.model.constraints.UniqueKeyConstraint;
|
||||
import jef.serializable.SerializableObject;
|
||||
@@ -17,8 +17,8 @@ public class UniqueProcessor extends KeyProcessorBase<UniqueKeyConstraint, Uniqu
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UniqueKeyConstraint initConstraint(DbEntity<? extends SerializableObject> entity, List<DbField<?>> fields) {
|
||||
return new UniqueKeyConstraint(entity, fields);
|
||||
protected UniqueKeyConstraint initConstraint(DbEntityBuilder<? extends SerializableObject> entity, List<DbFieldBuilder<?>> fields) {
|
||||
return new UniqueKeyConstraint(entity.getEntity(), (List)fields.stream().map(DbFieldBuilder::getField).toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2,25 +2,28 @@ package jef.model.constraints;
|
||||
|
||||
import jef.model.DbEntity;
|
||||
import jef.model.DbField;
|
||||
import jef.util.Check;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class ForeignKeyConstraint extends ConstraintBase {
|
||||
private final DbEntity<?> referencedEntity;
|
||||
private final List<DbField<?>> referencedFields;
|
||||
private final Action onUpdate;
|
||||
private final Action onDelete;
|
||||
private Action onUpdate;
|
||||
private Action onDelete;
|
||||
|
||||
public ForeignKeyConstraint(DbEntity<?> entity, List<DbField<?>> fields, DbEntity<?> referencedEntity, List<DbField<?>> referencedFields, Action onUpdate, Action onDelete) {
|
||||
super(entity, fields);
|
||||
this.referencedEntity = referencedEntity;
|
||||
this.referencedFields = referencedFields;
|
||||
this.onUpdate = onUpdate;
|
||||
this.onDelete = onDelete;
|
||||
this.referencedEntity = Check.notNull(referencedEntity, "referencedEntity");
|
||||
this.referencedFields = Check.notNull(referencedFields, "referencedFields");
|
||||
this.onUpdate = Check.notNull(onUpdate, "onUpdate");
|
||||
this.onDelete = Check.notNull(onDelete, "onDelete");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -15,15 +15,17 @@ import jef.model.migration.operation.MigrationOperation;
|
||||
import jef.model.migration.operation.RenameFieldOperation;
|
||||
import jef.model.migration.operation.RenameTableOperation;
|
||||
import jef.model.migration.operation.UpdateFieldOperation;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
public class MigrationBuilder {
|
||||
private final List<MigrationOperation.Builder<?>> operations = new ArrayList<>();
|
||||
|
||||
public AddTableOperation.Builder addTable(String table, List<AddFieldOperation.Builder> fields) {
|
||||
var op = new AddTableOperation.Builder(table, fields);
|
||||
public AddTableOperation.Builder addTable(String table, List<AddFieldOperation.Builder> fields, AddPrimaryKeyOperation.Builder primaryKey) {
|
||||
var op = new AddTableOperation.Builder(table, fields, primaryKey);
|
||||
operations.add(op);
|
||||
return op;
|
||||
}
|
||||
@@ -1,23 +1,31 @@
|
||||
package jef.model.migration.operation;
|
||||
|
||||
import jef.model.annotations.Generated;
|
||||
import jef.util.Check;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.ToString;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
public class AddFieldOperation implements MigrationOperation {
|
||||
protected final String table;
|
||||
protected final String field;
|
||||
protected final String sqlType;
|
||||
protected final boolean notNull;
|
||||
protected final Generated.Type generated;
|
||||
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
public static class Builder implements MigrationOperation.Builder<AddFieldOperation> {
|
||||
protected final String table;
|
||||
protected final String field;
|
||||
protected String sqlType;
|
||||
protected boolean notNull;
|
||||
protected Generated.Type generated;
|
||||
|
||||
public Builder(String table, String field) {
|
||||
this.table = table;
|
||||
@@ -34,8 +42,14 @@ public class AddFieldOperation implements MigrationOperation {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder generated(Generated.Type generated) {
|
||||
Check.notNull(generated, "generated");
|
||||
this.generated = generated;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AddFieldOperation build() {
|
||||
return new AddFieldOperation(table, field, sqlType, notNull);
|
||||
return new AddFieldOperation(table, field, sqlType, notNull, generated);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,16 @@ package jef.model.migration.operation;
|
||||
|
||||
import jef.model.constraints.ForeignKeyConstraint;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
public class AddForeignKeyOperation implements MigrationOperation {
|
||||
private final String name;
|
||||
private final String table;
|
||||
@@ -18,6 +21,8 @@ public class AddForeignKeyOperation implements MigrationOperation {
|
||||
private final ForeignKeyConstraint.Action onUpdate;
|
||||
private final ForeignKeyConstraint.Action onDelete;
|
||||
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
public static class Builder implements MigrationOperation.Builder<AddForeignKeyOperation> {
|
||||
private final String name;
|
||||
private final String table;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user