diff --git a/pom.xml b/pom.xml
index 5aaef21..7fd4a5a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -133,10 +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/DbEntity.java b/src/main/java/jef/model/DbEntity.java
index 56000ec..ae041a1 100644
--- a/src/main/java/jef/model/DbEntity.java
+++ b/src/main/java/jef/model/DbEntity.java
@@ -4,6 +4,11 @@ import jef.asm.AsmParseException;
import jef.asm.AsmParser;
import jef.expressions.Expression;
import jef.expressions.IntermediateFieldExpression;
+import jef.model.constraints.ForeignKeyConstraint;
+import jef.model.constraints.IndexConstraint;
+import jef.model.constraints.KeyConstraint;
+import jef.model.constraints.PrimaryKeyConstraint;
+import jef.model.constraints.UniqueConstraint;
import jef.serializable.SerializableFunction;
import jef.serializable.SerializableObject;
import lombok.Getter;
@@ -17,18 +22,23 @@ import java.util.List;
@Getter
@Setter
public class DbEntity {
- private final Class entityClazz;
+ private final Class type;
private final List> fields;
private String name;
+ private PrimaryKeyConstraint primaryKey;
+ private final List foreignKeys = new ArrayList<>();
+ private final List uniqueKeys = new ArrayList<>();
+ private final List keys = new ArrayList<>();
+ private final List indexes = new ArrayList<>();
- public DbEntity(Class entityClazz) {
- this(entityClazz, new ArrayList<>());
+ public DbEntity(Class type) {
+ this(type, new ArrayList<>());
}
- public DbEntity(Class entityClazz, List> fields) {
- this.entityClazz = entityClazz;
+ public DbEntity(Class type, List> fields) {
+ this.type = type;
this.fields = fields;
- this.name = entityClazz.getSimpleName();
+ this.name = type.getSimpleName();
}
public List> getFields() {
@@ -48,16 +58,20 @@ public class DbEntity {
return prop;
}
+ public DbField getField(Field field) {
+ return (DbField) fields.stream().filter(e -> e.getField().equals(field)).findFirst().orElse(null);
+ }
+
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);
+ var field = ReflectionUtil.getFieldsRecursive(type).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);
+ prop = new DbField<>(this, (Class) field.getType(), field);
fields.add(prop);
}
return prop;
@@ -66,11 +80,24 @@ public class DbEntity {
}
}
- public DbField getOrCreateField(Field f) {
+ public DbField getOrCreateField(Field field) {
try {
- var prop = (DbField) fields.stream().filter(e -> e.getField() == f).findFirst().orElse(null);
+ var prop = (DbField) fields.stream().filter(e -> e.getField() == field).findFirst().orElse(null);
if (prop == null) {
- prop = new DbField<>((Class) f.getType(), f);
+ prop = new DbField<>(this, (Class) field.getType(), field);
+ fields.add(prop);
+ }
+ return prop;
+ } catch (Exception e) {
+ throw new RuntimeException("Invalid expression", e);
+ }
+ }
+
+ public DbField getOrAddField(DbField field) {
+ try {
+ var prop = (DbField) fields.stream().filter(e -> e.getName().equals(field.getName())).findFirst().orElse(null);
+ if (prop == null) {
+ prop = field;
fields.add(prop);
}
return prop;
@@ -91,4 +118,20 @@ public class DbEntity {
throw new RuntimeException("Invalid expression", e);
}
}
+
+ public void addForeignKey(ForeignKeyConstraint foreignKey) {
+ foreignKeys.add(foreignKey);
+ }
+
+ public void addUniqueContstraint(UniqueConstraint uniqueConstraint) {
+ uniqueKeys.add(uniqueConstraint);
+ }
+
+ public void addIndex(IndexConstraint index) {
+ indexes.add(index);
+ }
+
+ public void addKey(KeyConstraint key) {
+ keys.add(key);
+ }
}
diff --git a/src/main/java/jef/model/DbField.java b/src/main/java/jef/model/DbField.java
index 45802e4..3e59f88 100644
--- a/src/main/java/jef/model/DbField.java
+++ b/src/main/java/jef/model/DbField.java
@@ -1,21 +1,75 @@
package jef.model;
+import jef.model.constraints.IndexConstraint;
+import jef.model.constraints.KeyConstraint;
+import jef.model.constraints.UniqueConstraint;
+import jef.serializable.SerializableObject;
import lombok.Getter;
import lombok.Setter;
import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
@Getter
@Setter
public class DbField {
- private final Class propertyClazz;
+ private final DbEntity extends SerializableObject> entity;
+ private final Class type;
private final Field field;
private String name;
private boolean notNull = false;
- public DbField(Class propertyClazz, Field field) {
- this.propertyClazz = propertyClazz;
+ public DbField(DbEntity extends SerializableObject> entity, Class type, Field field) {
+ this.entity = entity;
+ this.type = type;
this.field = field;
this.name = field.getName();
}
+
+ public DbField(DbEntity extends SerializableObject> entity, Class type, Field field, String name) {
+ this.entity = entity;
+ this.type = type;
+ this.field = field;
+ this.name = name;
+ }
+
+ public boolean isUnique() {
+ 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.getUniqueKeys().add(new UniqueConstraint(entity, new ArrayList<>(List.of(this))));
+ } else if (constr.isPresent() && !unique) {
+ entity.getUniqueKeys().remove(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.getIndexes().add(new IndexConstraint(entity, new ArrayList<>(List.of(this))));
+ } else if (constr.isPresent() && !indexed) {
+ entity.getIndexes().remove(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.getKeys().add(new KeyConstraint(entity, new ArrayList<>(List.of(this))));
+ } else if (constr.isPresent() && !keyed) {
+ entity.getKeys().remove(constr.get());
+ } //else do nothing
+ }
}
diff --git a/src/main/java/jef/model/ModelBuilder.java b/src/main/java/jef/model/ModelBuilder.java
index 4ead7c8..6c24415 100644
--- a/src/main/java/jef/model/ModelBuilder.java
+++ b/src/main/java/jef/model/ModelBuilder.java
@@ -2,26 +2,35 @@ package jef.model;
import jef.DbSet;
import jef.model.annotations.Clazz;
+import jef.model.annotations.Id;
+import jef.model.annotations.Transient;
import jef.model.annotations.processors.AnnotationProcessor;
+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.PrimaryKeyConstraint;
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;
+import java.util.Locale;
public class ModelBuilder {
private static final List annotationProcessors = new ArrayList<>();
static {
annotationProcessors.add(NotNullProcessor.INSTANCE);
+ annotationProcessors.add(UniqueProcessor.INSTANCE);
+ annotationProcessors.add(IndexProcessor.INSTANCE);
+ annotationProcessors.add(KeyProcessor.INSTANCE);
}
-
- public static ModelBuilder from(DbContext context) {
+ public static ModelBuilder from(Class extends DbContext> context) {
try {
return from0(context);
} catch (Exception e) {
@@ -29,54 +38,140 @@ public class ModelBuilder {
}
}
- private static ModelBuilder from0(DbContext context) throws Exception {
+ private static ModelBuilder from0(Class extends 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);
+ initEntities(mb, context);
+ addPrimaryKeys(mb);
+ addForeignKeys(mb);
- 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 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");
+ }
+ var dbsetClazz = (Class extends SerializableObject>) clazzAnnotation.clazz();
+ initEntity(mb, dbsetClazz);
+ }
+ }
+
+ private static void initEntity(ModelBuilder mb, Class extends SerializableObject> clazz) {
+ var entity = mb.getOrCreateEntity(clazz);
+
+ var fields = ReflectionUtil.getFieldsRecursive(clazz);
+ for (var f : fields) {
+ if (f.getAnnotationsByType(Transient.class).length > 0) {
+ continue;
+ }
+ if (Collection.class.isAssignableFrom(f.getType())) {
+ } else {
+ var dbField = entity.getOrCreateField(f);
+ if (f.getType().isPrimitive()) {
+ dbField.setNotNull(true);
+ }
+ }
+ }
+ }
+
+ private static void addPrimaryKeys(ModelBuilder mb) {
+ for (int i = 0; i < mb.getEntities().size(); i++) {
+ var entity = mb.getEntities().get(i);
+ addPrimaryKeys(mb, entity);
+ }
+ }
+
+ private static void addPrimaryKeys(ModelBuilder mb, DbEntity> entity) {
+ var fields = ReflectionUtil.getFieldsRecursive(entity.getType());
+ var idFields = new ArrayList();
+
+ //search for fields wuth @Id annotation
+ for (var f : fields) {
+ if (f.getAnnotationsByType(Transient.class).length > 0) {
+ continue;
+ }
+ if (!f.getType().isPrimitive()) {
+ continue;
+ }
+ if (f.getAnnotationsByType(Id.class).length == 0) {
+ continue;
+ }
+
+ //fields in same class check
+ if (idFields.size() == 0 || idFields.get(0).getDeclaringClass() == f.getDeclaringClass()) {
+ idFields.add(f);
+ } else {
+ break;
+ }
+ }
+
+ if (idFields.isEmpty()) {
+ for (var f : fields) {
+ if (f.getAnnotationsByType(Transient.class).length > 0) {
+ continue;
+ }
+ if (!f.getType().isPrimitive()) {
+ continue;
+ }
+ if (f.getName().equals("id")) {
+ idFields.add(f);
+ break;
+ }
+ }
+ }
+
+ if (!idFields.isEmpty()) {
+ var dbfields = idFields.stream().map(entity::getField).toList();
+ entity.setPrimaryKey(new PrimaryKeyConstraint(entity, (List) dbfields));
+ }
+ }
+
+ private static void addForeignKeys(ModelBuilder mb) {
+ for (int i = 0; i < mb.getEntities().size(); i++) {
+ var entity = mb.getEntities().get(i);
+ addForeignKeys(mb, entity);
+ }
+ }
+
+ private static void addForeignKeys(ModelBuilder mb, DbEntity> entity) {
+ var fields = ReflectionUtil.getFieldsRecursive(entity.getType());
+ for (var f : fields) {
+ if (f.getAnnotationsByType(Transient.class).length > 0) {
+ continue;
+ }
+ if (Collection.class.isAssignableFrom(f.getType())) {
+ Clazz clazzAnnotation = f.getAnnotation(Clazz.class);
+ if (clazzAnnotation == null) {
+ throw new ModelException("Collection " + entity.getType().getSimpleName() + "." + f.getName() + " is missing the " + Clazz.class.getSimpleName() + " annotation");
+ }
+ var refEntity = mb.getEntity((Class extends SerializableObject>) clazzAnnotation.clazz());
+ if (refEntity == null) {
+ initEntity(mb, (Class extends SerializableObject>) clazzAnnotation.clazz());
+ refEntity = mb.getEntity((Class extends SerializableObject>) clazzAnnotation.clazz());
+ addPrimaryKeys(mb, refEntity);
+ }
+ var primary = refEntity.getPrimaryKey();
+ if (primary == null) {
+ throw new ModelException("Entity " + refEntity.getType().getSimpleName() + " is missing a primary key and therefore cannot be referenced by " + entity.getType().getSimpleName());
+ }
+ var refFields = primary.getFields().stream()
+ .map(e -> new DbField<>(entity, e.getType(), null, f.getName() + e.getName().substring(0, 1).toUpperCase(Locale.ROOT) + e.getName().substring(1)))
+ .map(entity::getOrAddField)
+ .toList();
+ entity.addForeignKey(new ForeignKeyConstraint(entity, (List) refFields, refEntity, primary.getFields()));
+ }
+ }
+ }
+
+
private final List> entities;
public ModelBuilder(List> entities) {
@@ -95,7 +190,7 @@ public class ModelBuilder {
* @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);
+ return (DbEntity) entities.stream().filter(e -> e.getType() == clazz).findFirst().orElse(null);
}
/**
diff --git a/src/main/java/jef/model/ReflectionUtil.java b/src/main/java/jef/model/ReflectionUtil.java
index d27bc1f..aeb4c2e 100644
--- a/src/main/java/jef/model/ReflectionUtil.java
+++ b/src/main/java/jef/model/ReflectionUtil.java
@@ -7,6 +7,15 @@ import java.util.Collection;
import java.util.HashMap;
class ReflectionUtil {
+ /**
+ * Returns a list of all declared fields of the given class.
+ * This also includes fields from super classes up until SerializableObject (exclusive).
+ * The fields within the same are not ordered in any particular way.
+ * The overall ordering is super class fields last, i.e., [...fields of the passed class, ...super class fields, ...super-super class fields, ...]
+ *
+ * @param clazz the class the go the declared fields from
+ * @return a list of all declared fields of the given class, including super class fields
+ */
static Collection getFieldsRecursive(Class extends SerializableObject> clazz) {
var fields = new HashMap();
do {
diff --git a/src/main/java/jef/model/annotations/Clazz.java b/src/main/java/jef/model/annotations/Clazz.java
index 1590ec7..7d3f261 100644
--- a/src/main/java/jef/model/annotations/Clazz.java
+++ b/src/main/java/jef/model/annotations/Clazz.java
@@ -1,10 +1,12 @@
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 Clazz {
diff --git a/src/main/java/jef/model/annotations/Id.java b/src/main/java/jef/model/annotations/Id.java
new file mode 100644
index 0000000..11914f1
--- /dev/null
+++ b/src/main/java/jef/model/annotations/Id.java
@@ -0,0 +1,13 @@
+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 Id {
+}
diff --git a/src/main/java/jef/model/annotations/Index.java b/src/main/java/jef/model/annotations/Index.java
new file mode 100644
index 0000000..84f4f90
--- /dev/null
+++ b/src/main/java/jef/model/annotations/Index.java
@@ -0,0 +1,14 @@
+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, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Index {
+ String[] gettersOrFields() default {};
+}
diff --git a/src/main/java/jef/model/annotations/Key.java b/src/main/java/jef/model/annotations/Key.java
new file mode 100644
index 0000000..432eecf
--- /dev/null
+++ b/src/main/java/jef/model/annotations/Key.java
@@ -0,0 +1,14 @@
+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, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Key {
+ String[] gettersOrFields() default {};
+}
diff --git a/src/main/java/jef/model/annotations/NotNull.java b/src/main/java/jef/model/annotations/NotNull.java
index 0d444f6..ae856b3 100644
--- a/src/main/java/jef/model/annotations/NotNull.java
+++ b/src/main/java/jef/model/annotations/NotNull.java
@@ -1,10 +1,12 @@
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 NotNull {
diff --git a/src/main/java/jef/model/annotations/Transient.java b/src/main/java/jef/model/annotations/Transient.java
new file mode 100644
index 0000000..708bf2d
--- /dev/null
+++ b/src/main/java/jef/model/annotations/Transient.java
@@ -0,0 +1,13 @@
+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 Transient {
+}
diff --git a/src/main/java/jef/model/annotations/Unique.java b/src/main/java/jef/model/annotations/Unique.java
new file mode 100644
index 0000000..90392db
--- /dev/null
+++ b/src/main/java/jef/model/annotations/Unique.java
@@ -0,0 +1,14 @@
+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, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Unique {
+ String[] gettersOrFields() default {};
+}
diff --git a/src/main/java/jef/model/annotations/processors/IndexProcessor.java b/src/main/java/jef/model/annotations/processors/IndexProcessor.java
new file mode 100644
index 0000000..a6ae043
--- /dev/null
+++ b/src/main/java/jef/model/annotations/processors/IndexProcessor.java
@@ -0,0 +1,33 @@
+package jef.model.annotations.processors;
+
+import jef.model.DbEntity;
+import jef.model.DbField;
+import jef.model.annotations.Index;
+import jef.model.constraints.IndexConstraint;
+import jef.serializable.SerializableObject;
+
+import java.lang.annotation.Annotation;
+import java.util.List;
+
+public class IndexProcessor extends KeyProcessorBase {
+ public static final IndexProcessor INSTANCE = new IndexProcessor();
+
+ private IndexProcessor() {
+ super(Index.class);
+ }
+
+ @Override
+ protected IndexConstraint initConstraint(DbEntity extends SerializableObject> entity, List> fields) {
+ return new IndexConstraint(entity, fields);
+ }
+
+ @Override
+ protected void addConstraint(IndexConstraint constr) {
+ constr.getEntity().addIndex(constr);
+ }
+
+ @Override
+ protected String[] getGettersOrFields(Annotation annotation) {
+ return ((Index) annotation).gettersOrFields();
+ }
+}
diff --git a/src/main/java/jef/model/annotations/processors/KeyProcessor.java b/src/main/java/jef/model/annotations/processors/KeyProcessor.java
new file mode 100644
index 0000000..f207535
--- /dev/null
+++ b/src/main/java/jef/model/annotations/processors/KeyProcessor.java
@@ -0,0 +1,33 @@
+package jef.model.annotations.processors;
+
+import jef.model.DbEntity;
+import jef.model.DbField;
+import jef.model.annotations.Key;
+import jef.model.constraints.KeyConstraint;
+import jef.serializable.SerializableObject;
+
+import java.lang.annotation.Annotation;
+import java.util.List;
+
+public class KeyProcessor extends KeyProcessorBase {
+ public static final KeyProcessor INSTANCE = new KeyProcessor();
+
+ private KeyProcessor() {
+ super(Key.class);
+ }
+
+ @Override
+ protected KeyConstraint initConstraint(DbEntity extends SerializableObject> entity, List> fields) {
+ return new KeyConstraint(entity, fields);
+ }
+
+ @Override
+ protected void addConstraint(KeyConstraint constr) {
+ constr.getEntity().addKey(constr);
+ }
+
+ @Override
+ protected String[] getGettersOrFields(Annotation annotation) {
+ return ((Key) annotation).gettersOrFields();
+ }
+}
diff --git a/src/main/java/jef/model/annotations/processors/KeyProcessorBase.java b/src/main/java/jef/model/annotations/processors/KeyProcessorBase.java
new file mode 100644
index 0000000..34195af
--- /dev/null
+++ b/src/main/java/jef/model/annotations/processors/KeyProcessorBase.java
@@ -0,0 +1,84 @@
+package jef.model.annotations.processors;
+
+import jef.model.DbEntity;
+import jef.model.DbField;
+import jef.model.ModelBuilder;
+import jef.model.ModelException;
+import jef.serializable.SerializableObject;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+public abstract class KeyProcessorBase implements AnnotationProcessor {
+ private final Class annotationClass;
+
+ protected KeyProcessorBase(Class annotationClass) {
+ this.annotationClass = annotationClass;
+ }
+
+ protected abstract K initConstraint(DbEntity extends SerializableObject> entity, List> fields);
+
+ protected abstract void addConstraint(K constr);
+
+ protected abstract String[] getGettersOrFields(Annotation annotation);
+
+ @Override
+ public void apply(ModelBuilder mb) {
+ for (DbEntity extends SerializableObject> entity : mb.getEntities()) {
+ //class annotation
+ var classAnno = entity.getType().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);
+ if (dbfield == null) {
+ throw new ModelException("DbField with field " + field.getName() + " not found in " + entity.getType().getSimpleName());
+ }
+ return dbfield;
+ }).toList();
+ addConstraint(initConstraint(entity, new ArrayList<>(classFields)));
+ }
+
+ //annotation on fields
+ for (DbField> field : entity.getFields()) {
+ if (field.getField() != null && field.getField().getAnnotationsByType(annotationClass).length > 0) {
+ var dbfield = entity.getField(field.getField());
+ if (dbfield == null) {
+ throw new ModelException("DbField with field " + field.getName() + " not found in " + entity.getType().getSimpleName());
+ }
+ addConstraint(initConstraint(entity, List.of(dbfield)));
+ }
+ }
+ }
+ }
+
+ private Field getField(DbEntity extends SerializableObject> entity, String fieldOrGetter) {
+ Method getter = null;
+ try {
+ getter = entity.getType().getDeclaredMethod(fieldOrGetter);
+ } catch (NoSuchMethodException e) {
+ }
+
+ 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);
+ } else {
+ field = entity.getType().getDeclaredField(fieldOrGetter);
+ }
+ } catch (NoSuchFieldException e) {
+ }
+
+ if (field == null) {
+ throw new ModelException("Cannot find getter/field '" + fieldOrGetter + "' in " + entity.getType().getSimpleName());
+ }
+ return field;
+ }
+}
diff --git a/src/main/java/jef/model/annotations/processors/NotNullProcessor.java b/src/main/java/jef/model/annotations/processors/NotNullProcessor.java
index 3cc9994..1a29d3d 100644
--- a/src/main/java/jef/model/annotations/processors/NotNullProcessor.java
+++ b/src/main/java/jef/model/annotations/processors/NotNullProcessor.java
@@ -16,7 +16,7 @@ public class NotNullProcessor implements AnnotationProcessor {
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) {
+ if (field.getField() != null && field.getField().getAnnotationsByType(NotNull.class).length > 0) {
field.setNotNull(true);
}
}
diff --git a/src/main/java/jef/model/annotations/processors/UniqueProcessor.java b/src/main/java/jef/model/annotations/processors/UniqueProcessor.java
new file mode 100644
index 0000000..cb76b93
--- /dev/null
+++ b/src/main/java/jef/model/annotations/processors/UniqueProcessor.java
@@ -0,0 +1,33 @@
+package jef.model.annotations.processors;
+
+import jef.model.DbEntity;
+import jef.model.DbField;
+import jef.model.annotations.Unique;
+import jef.model.constraints.UniqueConstraint;
+import jef.serializable.SerializableObject;
+
+import java.lang.annotation.Annotation;
+import java.util.List;
+
+public class UniqueProcessor extends KeyProcessorBase {
+ public static final UniqueProcessor INSTANCE = new UniqueProcessor();
+
+ private UniqueProcessor() {
+ super(Unique.class);
+ }
+
+ @Override
+ protected UniqueConstraint initConstraint(DbEntity extends SerializableObject> entity, List> fields) {
+ return new UniqueConstraint(entity, fields);
+ }
+
+ @Override
+ protected void addConstraint(UniqueConstraint constr) {
+ constr.getEntity().addUniqueContstraint(constr);
+ }
+
+ @Override
+ protected String[] getGettersOrFields(Annotation annotation) {
+ return ((Unique) annotation).gettersOrFields();
+ }
+}
diff --git a/src/main/java/jef/model/constraints/Constraint.java b/src/main/java/jef/model/constraints/Constraint.java
new file mode 100644
index 0000000..401dd1f
--- /dev/null
+++ b/src/main/java/jef/model/constraints/Constraint.java
@@ -0,0 +1,29 @@
+package jef.model.constraints;
+
+import jef.model.DbEntity;
+import jef.model.DbField;
+
+import java.util.List;
+
+public interface Constraint {
+ /**
+ * Returns the DbEntity containing the constraint.
+ *
+ * @return he DbEntity containing the constraint
+ */
+ DbEntity> getEntity();
+
+ /**
+ * Returns the name of the constraint.
+ *
+ * @return the name of the constraint
+ */
+ String getName();
+
+ /**
+ * Returns the fields of {@code getEntity()} involved in this constraint.
+ *
+ * @return the fields of {@code getEntity()} involved in this constraint
+ */
+ List> getFields();
+}
diff --git a/src/main/java/jef/model/constraints/ForeignKeyConstraint.java b/src/main/java/jef/model/constraints/ForeignKeyConstraint.java
new file mode 100644
index 0000000..761b970
--- /dev/null
+++ b/src/main/java/jef/model/constraints/ForeignKeyConstraint.java
@@ -0,0 +1,23 @@
+package jef.model.constraints;
+
+import jef.model.DbEntity;
+import jef.model.DbField;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Getter
+@AllArgsConstructor
+public class ForeignKeyConstraint implements Constraint {
+ private final DbEntity> entity;
+ private final List> fields;
+ private final DbEntity> referencedEntity;
+ private final List> referencedFields;
+
+ @Override
+ public String getName() {
+ return "FK_" + entity.getName() + "_" + referencedEntity.getName() + "_" + fields.stream().map(DbField::getName).collect(Collectors.joining("_"));
+ }
+}
diff --git a/src/main/java/jef/model/constraints/IndexConstraint.java b/src/main/java/jef/model/constraints/IndexConstraint.java
new file mode 100644
index 0000000..e9f7803
--- /dev/null
+++ b/src/main/java/jef/model/constraints/IndexConstraint.java
@@ -0,0 +1,21 @@
+package jef.model.constraints;
+
+import jef.model.DbEntity;
+import jef.model.DbField;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Getter
+@AllArgsConstructor
+public class IndexConstraint implements Constraint {
+ private final DbEntity> entity;
+ private final List> fields;
+
+ @Override
+ public String getName() {
+ return "I_" + entity.getName() + "_" + fields.stream().map(DbField::getName).collect(Collectors.joining("_"));
+ }
+}
diff --git a/src/main/java/jef/model/constraints/KeyConstraint.java b/src/main/java/jef/model/constraints/KeyConstraint.java
new file mode 100644
index 0000000..fc173a2
--- /dev/null
+++ b/src/main/java/jef/model/constraints/KeyConstraint.java
@@ -0,0 +1,21 @@
+package jef.model.constraints;
+
+import jef.model.DbEntity;
+import jef.model.DbField;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Getter
+@AllArgsConstructor
+public class KeyConstraint implements Constraint {
+ private final DbEntity> entity;
+ private final List> fields;
+
+ @Override
+ public String getName() {
+ return "K_" + entity.getName() + "_" + fields.stream().map(DbField::getName).collect(Collectors.joining("_"));
+ }
+}
diff --git a/src/main/java/jef/model/constraints/PrimaryKeyConstraint.java b/src/main/java/jef/model/constraints/PrimaryKeyConstraint.java
new file mode 100644
index 0000000..81e7fb6
--- /dev/null
+++ b/src/main/java/jef/model/constraints/PrimaryKeyConstraint.java
@@ -0,0 +1,20 @@
+package jef.model.constraints;
+
+import jef.model.DbEntity;
+import jef.model.DbField;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.List;
+
+@Getter
+@AllArgsConstructor
+public class PrimaryKeyConstraint implements Constraint {
+ private final DbEntity> entity;
+ private final List> fields;
+
+ @Override
+ public String getName() {
+ return "PRIMARY";
+ }
+}
diff --git a/src/main/java/jef/model/constraints/UniqueConstraint.java b/src/main/java/jef/model/constraints/UniqueConstraint.java
new file mode 100644
index 0000000..40c0e88
--- /dev/null
+++ b/src/main/java/jef/model/constraints/UniqueConstraint.java
@@ -0,0 +1,21 @@
+package jef.model.constraints;
+
+import jef.model.DbEntity;
+import jef.model.DbField;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Getter
+@AllArgsConstructor
+public class UniqueConstraint implements Constraint {
+ private final DbEntity> entity;
+ private final List> fields;
+
+ @Override
+ public String getName() {
+ return "U_" + entity.getName() + "_" + fields.stream().map(DbField::getName).collect(Collectors.joining("_"));
+ }
+}
diff --git a/src/test/java/jef/model/DbContextTest.java b/src/test/java/jef/model/DbContextTest.java
index 98a519f..4c9c742 100644
--- a/src/test/java/jef/model/DbContextTest.java
+++ b/src/test/java/jef/model/DbContextTest.java
@@ -2,10 +2,13 @@ package jef.model;
import jef.DbSet;
import jef.model.annotations.Clazz;
+import jef.model.annotations.Id;
import jef.serializable.SerializableObject;
import lombok.Getter;
import org.junit.jupiter.api.Test;
+import java.util.List;
+
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -14,30 +17,25 @@ 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());
+ public void testSimple() {
+ var mb = ModelBuilder.from(Ctx.class);
+ assertEquals(1, mb.getEntities().size());
+ assertEquals(TestClass.class.getSimpleName(), mb.getEntities().get(0).getName());
- //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());
- }
- }
+ assertEquals(5, mb.getEntities().get(0).getFields().size());
+ assertEquals(1, mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("i")).count());
+ assertEquals(1, mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("d")).count());
+ assertEquals(1, mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("f")).count());
+ assertEquals(1, mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("l")).count());
+ assertEquals(1, mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("o")).count());
- @Test
- public void testMissingAnnotation() {
- assertThrows(ModelException.class, () -> ModelBuilder.from(new CtxMissingAnnotation()));
+ //intrinsic non null
+ assertEquals(5, mb.getEntities().get(0).getFields().size());
+ assertTrue(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("i")).findFirst().get().isNotNull());
+ assertTrue(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("d")).findFirst().get().isNotNull());
+ assertTrue(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("f")).findFirst().get().isNotNull());
+ assertTrue(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("l")).findFirst().get().isNotNull());
+ assertFalse(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("o")).findFirst().get().isNotNull());
}
public static class Ctx extends DbContext {
@@ -46,6 +44,11 @@ class DbContextTest {
private DbSet objects1;
}
+ @Test
+ public void testMissingAnnotation() {
+ assertThrows(ModelException.class, () -> ModelBuilder.from(CtxMissingAnnotation.class));
+ }
+
public static class CtxMissingAnnotation extends DbContext {
private DbSet objects1;
@@ -53,10 +56,88 @@ class DbContextTest {
@Getter
public static class TestClass extends SerializableObject {
+ @Id
public int i = 1;
public Object o = new Object();
public double d;
public float f;
public long l;
}
+
+ //test nested
+ @Test
+ public void testNestedObjectsSimple() {
+ var mb = ModelBuilder.from(CtxNestedSimple.class);
+
+ assertEquals(2, mb.getEntities().size());
+ assertEquals(TestClass2.class.getSimpleName(), mb.getEntities().get(0).getName());
+ assertEquals(TestClass.class.getSimpleName(), mb.getEntities().get(1).getName());
+
+ //TestClass2
+ assertEquals(2, mb.getEntities().get(0).getFields().size());
+ assertEquals(1, mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("i")).count());
+ assertEquals(1, mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("nestedI")).count());
+
+ //TestClass
+ assertEquals(5, mb.getEntities().get(1).getFields().size());
+ assertEquals(1, mb.getEntities().get(1).getFields().stream().filter(e -> e.getName().equals("i")).count());
+ assertEquals(1, mb.getEntities().get(1).getFields().stream().filter(e -> e.getName().equals("d")).count());
+ assertEquals(1, mb.getEntities().get(1).getFields().stream().filter(e -> e.getName().equals("f")).count());
+ assertEquals(1, mb.getEntities().get(1).getFields().stream().filter(e -> e.getName().equals("l")).count());
+ assertEquals(1, mb.getEntities().get(1).getFields().stream().filter(e -> e.getName().equals("o")).count());
+ }
+
+ public static class CtxNestedSimple extends DbContext {
+ @Clazz(clazz = TestClass2.class)
+ private DbSet objects1;
+ }
+
+ @Getter
+ public static class TestClass2 extends SerializableObject {
+ @Id
+ public int i = 1;
+ @Clazz(clazz = TestClass.class)
+ public List nested;
+ }
+
+ //test nested
+ @Test
+ public void testNestedObjects2Layer() {
+ var mb = ModelBuilder.from(CtxNested2Layer.class);
+
+ assertEquals(3, mb.getEntities().size());
+ assertEquals(TestClass3.class.getSimpleName(), mb.getEntities().get(0).getName());
+ assertEquals(TestClass2.class.getSimpleName(), mb.getEntities().get(1).getName());
+ assertEquals(TestClass.class.getSimpleName(), mb.getEntities().get(2).getName());
+
+ //TestClass2
+ assertEquals(2, mb.getEntities().get(0).getFields().size());
+ assertEquals(1, mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("id")).count());
+ assertEquals(1, mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("nested2I")).count());
+
+ //TestClass2
+ assertEquals(2, mb.getEntities().get(1).getFields().size());
+ assertEquals(1, mb.getEntities().get(1).getFields().stream().filter(e -> e.getName().equals("i")).count());
+ assertEquals(1, mb.getEntities().get(1).getFields().stream().filter(e -> e.getName().equals("nestedI")).count());
+
+ //TestClass
+ assertEquals(5, mb.getEntities().get(2).getFields().size());
+ assertEquals(1, mb.getEntities().get(2).getFields().stream().filter(e -> e.getName().equals("i")).count());
+ assertEquals(1, mb.getEntities().get(2).getFields().stream().filter(e -> e.getName().equals("d")).count());
+ assertEquals(1, mb.getEntities().get(2).getFields().stream().filter(e -> e.getName().equals("f")).count());
+ assertEquals(1, mb.getEntities().get(2).getFields().stream().filter(e -> e.getName().equals("l")).count());
+ assertEquals(1, mb.getEntities().get(2).getFields().stream().filter(e -> e.getName().equals("o")).count());
+ }
+
+ public static class CtxNested2Layer extends DbContext {
+ @Clazz(clazz = TestClass3.class)
+ private DbSet objects1;
+ }
+
+ @Getter
+ public static class TestClass3 extends SerializableObject {
+ public int id = 1;
+ @Clazz(clazz = TestClass2.class)
+ public List nested2;
+ }
}
\ No newline at end of file
diff --git a/src/test/java/jef/model/annotations/processors/IndexProcessorTest.java b/src/test/java/jef/model/annotations/processors/IndexProcessorTest.java
new file mode 100644
index 0000000..05ec609
--- /dev/null
+++ b/src/test/java/jef/model/annotations/processors/IndexProcessorTest.java
@@ -0,0 +1,82 @@
+package jef.model.annotations.processors;
+
+import jef.DbSet;
+import jef.model.DbContext;
+import jef.model.ModelBuilder;
+import jef.model.annotations.Clazz;
+import jef.model.annotations.Index;
+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.assertTrue;
+
+class IndexProcessorTest {
+ @Test
+ public void testSimple() {
+ var mb = ModelBuilder.from(Ctx.class);
+
+ assertEquals(1, mb.getEntities().size());
+ assertEquals(TestClass.class.getSimpleName(), mb.getEntities().get(0).getName());
+
+ assertFalse(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("i")).findFirst().get().isIndex());
+ assertFalse(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("d")).findFirst().get().isIndex());
+ assertFalse(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("f")).findFirst().get().isIndex());
+ assertTrue(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("l")).findFirst().get().isIndex());
+ assertFalse(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("o")).findFirst().get().isIndex());
+ }
+
+ @Test
+ public void testIndexOnClass() {
+ var mb = ModelBuilder.from(CtxIndexOnClass.class);
+
+ assertEquals(1, mb.getEntities().size());
+ assertEquals(TestClassIndexOnClass.class.getSimpleName(), mb.getEntities().get(0).getName());
+
+ assertFalse(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("i")).findFirst().get().isIndex());
+ assertTrue(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("d")).findFirst().get().isIndex());
+ assertFalse(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("f")).findFirst().get().isIndex());
+ assertFalse(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("l")).findFirst().get().isIndex());
+ assertFalse(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("o")).findFirst().get().isIndex());
+
+ assertTrue(mb.getEntities().get(0).getIndexes().stream()
+ .anyMatch(e -> e.getFields().size() == 2
+ && e.getFields().stream().anyMatch(f -> f.getField().getName().equals("i"))
+ && e.getFields().stream().anyMatch(f -> f.getField().getName().equals("l"))));
+ }
+
+ public static class Ctx extends DbContext {
+
+ @Clazz(clazz = TestClass.class)
+ 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;
+ @Index
+ public long l;
+ }
+
+ public static class CtxIndexOnClass extends DbContext {
+
+ @Clazz(clazz = TestClassIndexOnClass.class)
+ private DbSet objects1;
+ }
+
+ @Getter
+ @Index(gettersOrFields = {"l", "getI"})
+ public static class TestClassIndexOnClass extends SerializableObject {
+ public int i = 1;
+ public Object o = new Object();
+ @Index
+ public double d;
+ public float f;
+ public long l;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/jef/model/annotations/processors/KeyProcessorTest.java b/src/test/java/jef/model/annotations/processors/KeyProcessorTest.java
new file mode 100644
index 0000000..58828c8
--- /dev/null
+++ b/src/test/java/jef/model/annotations/processors/KeyProcessorTest.java
@@ -0,0 +1,82 @@
+package jef.model.annotations.processors;
+
+import jef.DbSet;
+import jef.model.DbContext;
+import jef.model.ModelBuilder;
+import jef.model.annotations.Clazz;
+import jef.model.annotations.Key;
+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.assertTrue;
+
+class KeyProcessorTest {
+ @Test
+ public void testSimple() {
+ var mb = ModelBuilder.from(Ctx.class);
+
+ assertEquals(1, mb.getEntities().size());
+ assertEquals(TestClass.class.getSimpleName(), mb.getEntities().get(0).getName());
+
+ assertFalse(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("i")).findFirst().get().isKey());
+ assertFalse(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("d")).findFirst().get().isKey());
+ assertFalse(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("f")).findFirst().get().isKey());
+ assertTrue(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("l")).findFirst().get().isKey());
+ assertFalse(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("o")).findFirst().get().isKey());
+ }
+
+ @Test
+ public void testKeyOnClass() {
+ var mb = ModelBuilder.from(CtxKeyOnClass.class);
+
+ assertEquals(1, mb.getEntities().size());
+ assertEquals(TestClassKeyOnClass.class.getSimpleName(), mb.getEntities().get(0).getName());
+
+ assertFalse(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("i")).findFirst().get().isKey());
+ assertTrue(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("d")).findFirst().get().isKey());
+ assertFalse(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("f")).findFirst().get().isKey());
+ assertFalse(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("l")).findFirst().get().isKey());
+ assertFalse(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("o")).findFirst().get().isKey());
+
+ assertTrue(mb.getEntities().get(0).getKeys().stream()
+ .anyMatch(e -> e.getFields().size() == 2
+ && e.getFields().stream().anyMatch(f -> f.getField().getName().equals("i"))
+ && e.getFields().stream().anyMatch(f -> f.getField().getName().equals("l"))));
+ }
+
+ public static class Ctx extends DbContext {
+
+ @Clazz(clazz = TestClass.class)
+ 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;
+ @Key
+ public long l;
+ }
+
+ public static class CtxKeyOnClass extends DbContext {
+
+ @Clazz(clazz = TestClassKeyOnClass.class)
+ private DbSet objects1;
+ }
+
+ @Getter
+ @Key(gettersOrFields = {"l", "getI"})
+ public static class TestClassKeyOnClass extends SerializableObject {
+ public int i = 1;
+ public Object o = new Object();
+ @Key
+ public double d;
+ public float f;
+ public long l;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/jef/model/annotations/processors/NotNullProcessorTest.java b/src/test/java/jef/model/annotations/processors/NotNullProcessorTest.java
new file mode 100644
index 0000000..b911c21
--- /dev/null
+++ b/src/test/java/jef/model/annotations/processors/NotNullProcessorTest.java
@@ -0,0 +1,51 @@
+package jef.model.annotations.processors;
+
+import jef.DbSet;
+import jef.model.DbContext;
+import jef.model.ModelBuilder;
+import jef.model.annotations.Clazz;
+import jef.model.annotations.NotNull;
+import jef.model.annotations.Unique;
+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.assertTrue;
+
+class NotNullProcessorTest {
+
+ @Test
+ public void test() {
+ var mb = ModelBuilder.from(Ctx.class);
+
+ assertEquals(1, mb.getEntities().size());
+ assertEquals(TestClass.class.getSimpleName(), mb.getEntities().get(0).getName());
+
+ assertTrue(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("i")).findFirst().get().isNotNull());
+ assertTrue(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("d")).findFirst().get().isNotNull());
+ assertTrue(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("f")).findFirst().get().isNotNull());
+ assertTrue(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("l")).findFirst().get().isNotNull());
+ assertFalse(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("o")).findFirst().get().isNotNull());
+ assertTrue(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("s")).findFirst().get().isNotNull());
+ }
+
+
+ public static class Ctx extends DbContext {
+
+ @Clazz(clazz = TestClass.class)
+ 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;
+ @NotNull
+ public String s;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/jef/model/annotations/processors/UniqueProcessorTest.java b/src/test/java/jef/model/annotations/processors/UniqueProcessorTest.java
new file mode 100644
index 0000000..9ee017e
--- /dev/null
+++ b/src/test/java/jef/model/annotations/processors/UniqueProcessorTest.java
@@ -0,0 +1,82 @@
+package jef.model.annotations.processors;
+
+import jef.DbSet;
+import jef.model.DbContext;
+import jef.model.ModelBuilder;
+import jef.model.annotations.Clazz;
+import jef.model.annotations.Unique;
+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.assertTrue;
+
+class UniqueProcessorTest {
+ @Test
+ public void testSimple() {
+ var mb = ModelBuilder.from(Ctx.class);
+
+ assertEquals(1, mb.getEntities().size());
+ assertEquals(TestClass.class.getSimpleName(), mb.getEntities().get(0).getName());
+
+ assertFalse(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("i")).findFirst().get().isUnique());
+ assertFalse(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("d")).findFirst().get().isUnique());
+ assertFalse(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("f")).findFirst().get().isUnique());
+ assertTrue(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("l")).findFirst().get().isUnique());
+ assertFalse(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("o")).findFirst().get().isUnique());
+ }
+
+ @Test
+ public void testUniqueOnClass() {
+ var mb = ModelBuilder.from(CtxUniqueOnClass.class);
+
+ assertEquals(1, mb.getEntities().size());
+ assertEquals(TestClassUniqueOnClass.class.getSimpleName(), mb.getEntities().get(0).getName());
+
+ assertFalse(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("i")).findFirst().get().isUnique());
+ assertTrue(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("d")).findFirst().get().isUnique());
+ assertFalse(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("f")).findFirst().get().isUnique());
+ assertFalse(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("l")).findFirst().get().isUnique());
+ assertFalse(mb.getEntities().get(0).getFields().stream().filter(e -> e.getName().equals("o")).findFirst().get().isUnique());
+
+ assertTrue(mb.getEntities().get(0).getUniqueKeys().stream()
+ .anyMatch(e -> e.getFields().size() == 2
+ && e.getFields().stream().anyMatch(f -> f.getField().getName().equals("i"))
+ && e.getFields().stream().anyMatch(f -> f.getField().getName().equals("l"))));
+ }
+
+ public static class Ctx extends DbContext {
+
+ @Clazz(clazz = TestClass.class)
+ 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;
+ @Unique
+ public long l;
+ }
+
+ public static class CtxUniqueOnClass extends DbContext {
+
+ @Clazz(clazz = TestClassUniqueOnClass.class)
+ private DbSet objects1;
+ }
+
+ @Getter
+ @Unique(gettersOrFields = {"l", "getI"})
+ public static class TestClassUniqueOnClass extends SerializableObject {
+ public int i = 1;
+ public Object o = new Object();
+ @Unique
+ public double d;
+ public float f;
+ public long l;
+ }
+}
\ No newline at end of file