added modelbuilder
This commit is contained in:
4
src/main/java/jef/model/DbContext.java
Normal file
4
src/main/java/jef/model/DbContext.java
Normal file
@@ -0,0 +1,4 @@
|
||||
package jef.model;
|
||||
|
||||
public abstract class DbContext {
|
||||
}
|
||||
94
src/main/java/jef/model/DbEntity.java
Normal file
94
src/main/java/jef/model/DbEntity.java
Normal file
@@ -0,0 +1,94 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.asm.AsmParseException;
|
||||
import jef.asm.AsmParser;
|
||||
import jef.expressions.Expression;
|
||||
import jef.expressions.IntermediateFieldExpression;
|
||||
import jef.serializable.SerializableFunction;
|
||||
import jef.serializable.SerializableObject;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class DbEntity<T extends SerializableObject> {
|
||||
private final Class<T> entityClazz;
|
||||
private final List<DbField<?>> fields;
|
||||
private String name;
|
||||
|
||||
public DbEntity(Class<T> entityClazz) {
|
||||
this(entityClazz, new ArrayList<>());
|
||||
}
|
||||
|
||||
public DbEntity(Class<T> entityClazz, List<DbField<?>> fields) {
|
||||
this.entityClazz = entityClazz;
|
||||
this.fields = fields;
|
||||
this.name = entityClazz.getSimpleName();
|
||||
}
|
||||
|
||||
public List<DbField<?>> getFields() {
|
||||
return Collections.unmodifiableList(fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the database model for the requested property or null if not present.
|
||||
*
|
||||
* @param getter the expression selecting the property
|
||||
* @param <R> the type of property class
|
||||
* @return the database model for the requested property or null if not present.
|
||||
*/
|
||||
public <R> DbField<R> getField(SerializableFunction<T, R> getter) {
|
||||
var name = extractFieldName(getter);
|
||||
var prop = (DbField<R>) this.fields.stream().filter(p -> p.getField().getName().equals(name)).findFirst().orElse(null);
|
||||
return prop;
|
||||
}
|
||||
|
||||
public <R> DbField<R> getOrCreateField(SerializableFunction<T, R> getter) {
|
||||
try {
|
||||
var prop = getField(getter);
|
||||
if (prop == null) {
|
||||
var name = extractFieldName(getter);
|
||||
var field = ReflectionUtil.getFieldsRecursive(entityClazz).stream().filter(f -> f.getName().equals(name)).findFirst().orElse(null);
|
||||
if (field == null) {
|
||||
throw new RuntimeException("Field not found: " + name);
|
||||
}
|
||||
prop = new DbField<>((Class<R>) field.getType(), field);
|
||||
fields.add(prop);
|
||||
}
|
||||
return prop;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Invalid expression", e);
|
||||
}
|
||||
}
|
||||
|
||||
public <R> DbField<R> getOrCreateField(Field f) {
|
||||
try {
|
||||
var prop = (DbField<R>) fields.stream().filter(e -> e.getField() == f).findFirst().orElse(null);
|
||||
if (prop == null) {
|
||||
prop = new DbField<>((Class<R>) f.getType(), f);
|
||||
fields.add(prop);
|
||||
}
|
||||
return prop;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Invalid expression", e);
|
||||
}
|
||||
}
|
||||
|
||||
private <R> String extractFieldName(SerializableFunction<T, R> getter) {
|
||||
try {
|
||||
var expr = new AsmParser(getter).parse();
|
||||
if (expr.getType() != Expression.Type.INTERMEDIATE_FIELD) {
|
||||
throw new RuntimeException(expr.getClass().getSimpleName() + " is not a field expression");
|
||||
}
|
||||
var name = ((IntermediateFieldExpression) expr).getName();
|
||||
return name;
|
||||
} catch (AsmParseException e) {
|
||||
throw new RuntimeException("Invalid expression", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/main/java/jef/model/DbField.java
Normal file
21
src/main/java/jef/model/DbField.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package jef.model;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class DbField<T> {
|
||||
private final Class<T> propertyClazz;
|
||||
private final Field field;
|
||||
private String name;
|
||||
private boolean notNull = false;
|
||||
|
||||
public DbField(Class<T> propertyClazz, Field field) {
|
||||
this.propertyClazz = propertyClazz;
|
||||
this.field = field;
|
||||
this.name = field.getName();
|
||||
}
|
||||
}
|
||||
116
src/main/java/jef/model/ModelBuilder.java
Normal file
116
src/main/java/jef/model/ModelBuilder.java
Normal file
@@ -0,0 +1,116 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.DbSet;
|
||||
import jef.model.annotations.Clazz;
|
||||
import jef.model.annotations.processors.AnnotationProcessor;
|
||||
import jef.model.annotations.processors.NotNullProcessor;
|
||||
import jef.serializable.SerializableObject;
|
||||
|
||||
import javax.persistence.Transient;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class ModelBuilder {
|
||||
private static final List<AnnotationProcessor> annotationProcessors = new ArrayList<>();
|
||||
|
||||
static {
|
||||
annotationProcessors.add(NotNullProcessor.INSTANCE);
|
||||
}
|
||||
|
||||
|
||||
public static ModelBuilder from(DbContext context) {
|
||||
try {
|
||||
return from0(context);
|
||||
} catch (Exception e) {
|
||||
throw e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static ModelBuilder from0(DbContext context) throws Exception {
|
||||
var mb = new ModelBuilder(new ArrayList<>());
|
||||
for (Field ctxfield : context.getClass().getDeclaredFields()) {
|
||||
System.out.println(ctxfield);
|
||||
if (!DbSet.class.isAssignableFrom(ctxfield.getType())) {
|
||||
continue;
|
||||
}
|
||||
Clazz clazzAnnontation = ctxfield.getAnnotation(Clazz.class);
|
||||
if (clazzAnnontation == null) {
|
||||
throw new ModelException("DbSet " + ctxfield.getName() + " is missing the Clazz annotation");
|
||||
}
|
||||
var dbsetClazz = (Class<? extends SerializableObject>)clazzAnnontation.clazz();
|
||||
var entity = mb.getOrCreateEntity(dbsetClazz);
|
||||
|
||||
var fields = ReflectionUtil.getFieldsRecursive(dbsetClazz);
|
||||
for (var f : fields) {
|
||||
if (f.getAnnotationsByType(Transient.class).length > 0) {
|
||||
continue;
|
||||
}
|
||||
if (Collection.class.isAssignableFrom(f.getType())) {
|
||||
throw new UnsupportedOperationException();
|
||||
} else {
|
||||
var dbField = entity.getOrCreateField(f);
|
||||
if (f.getType().isPrimitive()) {
|
||||
dbField.setNotNull(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (AnnotationProcessor processor : annotationProcessors) {
|
||||
processor.apply(mb);
|
||||
}
|
||||
return mb;
|
||||
// var entities = new HashMap<Class<? extends SerializableObject>, DbEntity<? extends SerializableObject>>();
|
||||
// for (Field ctxfield : context.getClass().getDeclaredFields()) {
|
||||
// if (!DbSet.class.isAssignableFrom(ctxfield.getClass())) {
|
||||
// continue;
|
||||
// }
|
||||
// ctxfield.setAccessible(true);
|
||||
// var dbset = (DbSet<? extends SerializableObject>) ctxfield.get(context);
|
||||
// var dbsetClazz = dbset.getClazz();
|
||||
// var fields = getFieldsRecursive(dbsetClazz);
|
||||
// entities.put(dbsetClazz, createEntity(dbsetClazz, fields));
|
||||
// }
|
||||
//
|
||||
// return new ModelBuilder(new ArrayList<>(entities.values()));
|
||||
}
|
||||
|
||||
private final List<DbEntity<? extends SerializableObject>> entities;
|
||||
|
||||
public ModelBuilder(List<DbEntity<? extends SerializableObject>> entities) {
|
||||
this.entities = entities;
|
||||
}
|
||||
|
||||
public List<DbEntity<? extends SerializableObject>> getEntities() {
|
||||
return Collections.unmodifiableList(entities);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the database model for the requested class or null if not present.
|
||||
*
|
||||
* @param clazz the class of the model class
|
||||
* @param <T> the type of model class
|
||||
* @return the database model for the requested class or null if not present.
|
||||
*/
|
||||
public <T extends SerializableObject> DbEntity<T> getEntity(Class<T> clazz) {
|
||||
return (DbEntity<T>) entities.stream().filter(e -> e.getEntityClazz() == clazz).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the database model for the requested class or creates a new one if none exists.
|
||||
*
|
||||
* @param clazz the class of the model class
|
||||
* @param <T> the type of model class
|
||||
* @return the database model for the requested class or the newly created one if none existed.
|
||||
*/
|
||||
public <T extends SerializableObject> DbEntity<T> getOrCreateEntity(Class<T> clazz) {
|
||||
var entity = getEntity(clazz);
|
||||
if (entity == null) {
|
||||
entity = new DbEntity<>(clazz);
|
||||
entities.add(entity);
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
15
src/main/java/jef/model/ModelException.java
Normal file
15
src/main/java/jef/model/ModelException.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package jef.model;
|
||||
|
||||
public class ModelException extends RuntimeException {
|
||||
public ModelException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ModelException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public ModelException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
22
src/main/java/jef/model/ReflectionUtil.java
Normal file
22
src/main/java/jef/model/ReflectionUtil.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.serializable.SerializableObject;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
|
||||
class ReflectionUtil {
|
||||
static Collection<Field> getFieldsRecursive(Class<? extends SerializableObject> clazz) {
|
||||
var fields = new HashMap<String, Field>();
|
||||
do {
|
||||
for (Field f : clazz.getDeclaredFields()) {
|
||||
if (!fields.containsKey(f.getName())) {
|
||||
fields.put(f.getName(), f);
|
||||
}
|
||||
}
|
||||
clazz = (Class<? extends SerializableObject>) clazz.getSuperclass();
|
||||
} while (clazz != SerializableObject.class);
|
||||
return fields.values();
|
||||
}
|
||||
}
|
||||
12
src/main/java/jef/model/annotations/Clazz.java
Normal file
12
src/main/java/jef/model/annotations/Clazz.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package jef.model.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Clazz {
|
||||
Class<?> clazz();
|
||||
}
|
||||
11
src/main/java/jef/model/annotations/NotNull.java
Normal file
11
src/main/java/jef/model/annotations/NotNull.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package jef.model.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface NotNull {
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package jef.model.annotations.processors;
|
||||
|
||||
import jef.model.ModelBuilder;
|
||||
|
||||
public interface AnnotationProcessor {
|
||||
|
||||
void apply(ModelBuilder mb);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package jef.model.annotations.processors;
|
||||
|
||||
import jef.model.DbEntity;
|
||||
import jef.model.DbField;
|
||||
import jef.model.ModelBuilder;
|
||||
import jef.model.annotations.NotNull;
|
||||
import jef.serializable.SerializableObject;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class NotNullProcessor implements AnnotationProcessor {
|
||||
public static final NotNullProcessor INSTANCE = new NotNullProcessor();
|
||||
|
||||
@Override
|
||||
public void apply(ModelBuilder mb) {
|
||||
for (DbEntity<? extends SerializableObject> entity : mb.getEntities()) {
|
||||
for (DbField<?> field : entity.getFields()) {
|
||||
if (field.getField().getAnnotationsByType(NotNull.class).length > 0) {
|
||||
field.setNotNull(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
62
src/test/java/jef/model/DbContextTest.java
Normal file
62
src/test/java/jef/model/DbContextTest.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.DbSet;
|
||||
import jef.model.annotations.Clazz;
|
||||
import jef.serializable.SerializableObject;
|
||||
import lombok.Getter;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class DbContextTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
var mb = ModelBuilder.from(new Ctx());
|
||||
for (DbEntity<? extends SerializableObject> entity : mb.getEntities()) {
|
||||
assertEquals("TestClass", entity.getName());
|
||||
assertEquals(5, entity.getFields().size());
|
||||
assertEquals(1, entity.getFields().stream().filter(e -> e.getName().equals("i")).count());
|
||||
assertEquals(1, entity.getFields().stream().filter(e -> e.getName().equals("d")).count());
|
||||
assertEquals(1, entity.getFields().stream().filter(e -> e.getName().equals("f")).count());
|
||||
assertEquals(1, entity.getFields().stream().filter(e -> e.getName().equals("l")).count());
|
||||
assertEquals(1, entity.getFields().stream().filter(e -> e.getName().equals("o")).count());
|
||||
|
||||
//intrinsic non null
|
||||
assertEquals(5, entity.getFields().size());
|
||||
assertTrue(entity.getFields().stream().filter(e -> e.getName().equals("i")).findFirst().get().isNotNull());
|
||||
assertTrue(entity.getFields().stream().filter(e -> e.getName().equals("d")).findFirst().get().isNotNull());
|
||||
assertTrue(entity.getFields().stream().filter(e -> e.getName().equals("f")).findFirst().get().isNotNull());
|
||||
assertTrue(entity.getFields().stream().filter(e -> e.getName().equals("l")).findFirst().get().isNotNull());
|
||||
assertFalse(entity.getFields().stream().filter(e -> e.getName().equals("o")).findFirst().get().isNotNull());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMissingAnnotation() {
|
||||
assertThrows(ModelException.class, () -> ModelBuilder.from(new CtxMissingAnnotation()));
|
||||
}
|
||||
|
||||
public static class Ctx extends DbContext {
|
||||
|
||||
@Clazz(clazz = TestClass.class)
|
||||
private DbSet<TestClass> objects1;
|
||||
}
|
||||
|
||||
public static class CtxMissingAnnotation extends DbContext {
|
||||
|
||||
private DbSet<TestClass> objects1;
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class TestClass extends SerializableObject {
|
||||
public int i = 1;
|
||||
public Object o = new Object();
|
||||
public double d;
|
||||
public float f;
|
||||
public long l;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user