diff --git a/pom.xml b/pom.xml
index ba7a7a9..5aaef21 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
jef
jef
- 1.0
+ 0.1
UTF-8
@@ -133,5 +133,10 @@
${lombok.version}
compile
+
+ javax.persistence
+ javax.persistence-api
+ 2.2
+
\ No newline at end of file
diff --git a/src/main/java/jef/model/DbContext.java b/src/main/java/jef/model/DbContext.java
new file mode 100644
index 0000000..530f6b9
--- /dev/null
+++ b/src/main/java/jef/model/DbContext.java
@@ -0,0 +1,4 @@
+package jef.model;
+
+public abstract class DbContext {
+}
diff --git a/src/main/java/jef/model/DbEntity.java b/src/main/java/jef/model/DbEntity.java
new file mode 100644
index 0000000..56000ec
--- /dev/null
+++ b/src/main/java/jef/model/DbEntity.java
@@ -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 {
+ private final Class entityClazz;
+ private final List> fields;
+ private String name;
+
+ public DbEntity(Class entityClazz) {
+ this(entityClazz, new ArrayList<>());
+ }
+
+ public DbEntity(Class entityClazz, List> fields) {
+ this.entityClazz = entityClazz;
+ this.fields = fields;
+ this.name = entityClazz.getSimpleName();
+ }
+
+ public List> 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 the type of property class
+ * @return the database model for the requested property or null if not present.
+ */
+ public DbField getField(SerializableFunction getter) {
+ var name = extractFieldName(getter);
+ var prop = (DbField) this.fields.stream().filter(p -> p.getField().getName().equals(name)).findFirst().orElse(null);
+ return prop;
+ }
+
+ public DbField getOrCreateField(SerializableFunction 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) field.getType(), field);
+ fields.add(prop);
+ }
+ return prop;
+ } catch (Exception e) {
+ throw new RuntimeException("Invalid expression", e);
+ }
+ }
+
+ public DbField getOrCreateField(Field f) {
+ try {
+ var prop = (DbField) fields.stream().filter(e -> e.getField() == f).findFirst().orElse(null);
+ if (prop == null) {
+ prop = new DbField<>((Class) f.getType(), f);
+ fields.add(prop);
+ }
+ return prop;
+ } catch (Exception e) {
+ throw new RuntimeException("Invalid expression", e);
+ }
+ }
+
+ private String extractFieldName(SerializableFunction 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);
+ }
+ }
+}
diff --git a/src/main/java/jef/model/DbField.java b/src/main/java/jef/model/DbField.java
new file mode 100644
index 0000000..45802e4
--- /dev/null
+++ b/src/main/java/jef/model/DbField.java
@@ -0,0 +1,21 @@
+package jef.model;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.lang.reflect.Field;
+
+@Getter
+@Setter
+public class DbField {
+ private final Class propertyClazz;
+ private final Field field;
+ private String name;
+ private boolean notNull = false;
+
+ public DbField(Class propertyClazz, Field field) {
+ this.propertyClazz = propertyClazz;
+ this.field = field;
+ this.name = field.getName();
+ }
+}
diff --git a/src/main/java/jef/model/ModelBuilder.java b/src/main/java/jef/model/ModelBuilder.java
new file mode 100644
index 0000000..4ead7c8
--- /dev/null
+++ b/src/main/java/jef/model/ModelBuilder.java
@@ -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 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, 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> entities;
+
+ public ModelBuilder(List> entities) {
+ this.entities = entities;
+ }
+
+ public List> 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 the type of model class
+ * @return the database model for the requested class or null if not present.
+ */
+ public DbEntity getEntity(Class clazz) {
+ return (DbEntity) 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 the type of model class
+ * @return the database model for the requested class or the newly created one if none existed.
+ */
+ public DbEntity getOrCreateEntity(Class clazz) {
+ var entity = getEntity(clazz);
+ if (entity == null) {
+ entity = new DbEntity<>(clazz);
+ entities.add(entity);
+ }
+ return entity;
+ }
+}
diff --git a/src/main/java/jef/model/ModelException.java b/src/main/java/jef/model/ModelException.java
new file mode 100644
index 0000000..9e01c8c
--- /dev/null
+++ b/src/main/java/jef/model/ModelException.java
@@ -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);
+ }
+}
diff --git a/src/main/java/jef/model/ReflectionUtil.java b/src/main/java/jef/model/ReflectionUtil.java
new file mode 100644
index 0000000..d27bc1f
--- /dev/null
+++ b/src/main/java/jef/model/ReflectionUtil.java
@@ -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 getFieldsRecursive(Class extends SerializableObject> clazz) {
+ var fields = new HashMap();
+ 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();
+ }
+}
diff --git a/src/main/java/jef/model/annotations/Clazz.java b/src/main/java/jef/model/annotations/Clazz.java
new file mode 100644
index 0000000..1590ec7
--- /dev/null
+++ b/src/main/java/jef/model/annotations/Clazz.java
@@ -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();
+}
diff --git a/src/main/java/jef/model/annotations/NotNull.java b/src/main/java/jef/model/annotations/NotNull.java
new file mode 100644
index 0000000..0d444f6
--- /dev/null
+++ b/src/main/java/jef/model/annotations/NotNull.java
@@ -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 {
+}
diff --git a/src/main/java/jef/model/annotations/processors/AnnotationProcessor.java b/src/main/java/jef/model/annotations/processors/AnnotationProcessor.java
new file mode 100644
index 0000000..b2f1c41
--- /dev/null
+++ b/src/main/java/jef/model/annotations/processors/AnnotationProcessor.java
@@ -0,0 +1,8 @@
+package jef.model.annotations.processors;
+
+import jef.model.ModelBuilder;
+
+public interface AnnotationProcessor {
+
+ void apply(ModelBuilder mb);
+}
diff --git a/src/main/java/jef/model/annotations/processors/NotNullProcessor.java b/src/main/java/jef/model/annotations/processors/NotNullProcessor.java
new file mode 100644
index 0000000..3cc9994
--- /dev/null
+++ b/src/main/java/jef/model/annotations/processors/NotNullProcessor.java
@@ -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);
+ }
+ }
+ }
+ }
+}
diff --git a/src/test/java/jef/model/DbContextTest.java b/src/test/java/jef/model/DbContextTest.java
new file mode 100644
index 0000000..98a519f
--- /dev/null
+++ b/src/test/java/jef/model/DbContextTest.java
@@ -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 objects1;
+ }
+
+ public static class CtxMissingAnnotation extends DbContext {
+
+ private DbSet 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;
+ }
+}
\ No newline at end of file