From 7ac7799d5760a2607209929611d8d9752b0f1fe8 Mon Sep 17 00:00:00 2001 From: wea_ondara Date: Thu, 21 Jul 2022 21:45:26 +0200 Subject: [PATCH] added constraints and constraint annotations, except foreign key annotation --- pom.xml | 10 +- src/main/java/jef/model/DbEntity.java | 65 +++++-- src/main/java/jef/model/DbField.java | 60 +++++- src/main/java/jef/model/ModelBuilder.java | 183 +++++++++++++----- src/main/java/jef/model/ReflectionUtil.java | 9 + .../java/jef/model/annotations/Clazz.java | 2 + src/main/java/jef/model/annotations/Id.java | 13 ++ .../java/jef/model/annotations/Index.java | 14 ++ src/main/java/jef/model/annotations/Key.java | 14 ++ .../java/jef/model/annotations/NotNull.java | 2 + .../java/jef/model/annotations/Transient.java | 13 ++ .../java/jef/model/annotations/Unique.java | 14 ++ .../processors/IndexProcessor.java | 33 ++++ .../annotations/processors/KeyProcessor.java | 33 ++++ .../processors/KeyProcessorBase.java | 84 ++++++++ .../processors/NotNullProcessor.java | 2 +- .../processors/UniqueProcessor.java | 33 ++++ .../jef/model/constraints/Constraint.java | 29 +++ .../constraints/ForeignKeyConstraint.java | 23 +++ .../model/constraints/IndexConstraint.java | 21 ++ .../jef/model/constraints/KeyConstraint.java | 21 ++ .../constraints/PrimaryKeyConstraint.java | 20 ++ .../model/constraints/UniqueConstraint.java | 21 ++ src/test/java/jef/model/DbContextTest.java | 125 +++++++++--- .../processors/IndexProcessorTest.java | 82 ++++++++ .../processors/KeyProcessorTest.java | 82 ++++++++ .../processors/NotNullProcessorTest.java | 51 +++++ .../processors/UniqueProcessorTest.java | 82 ++++++++ 28 files changed, 1055 insertions(+), 86 deletions(-) create mode 100644 src/main/java/jef/model/annotations/Id.java create mode 100644 src/main/java/jef/model/annotations/Index.java create mode 100644 src/main/java/jef/model/annotations/Key.java create mode 100644 src/main/java/jef/model/annotations/Transient.java create mode 100644 src/main/java/jef/model/annotations/Unique.java create mode 100644 src/main/java/jef/model/annotations/processors/IndexProcessor.java create mode 100644 src/main/java/jef/model/annotations/processors/KeyProcessor.java create mode 100644 src/main/java/jef/model/annotations/processors/KeyProcessorBase.java create mode 100644 src/main/java/jef/model/annotations/processors/UniqueProcessor.java create mode 100644 src/main/java/jef/model/constraints/Constraint.java create mode 100644 src/main/java/jef/model/constraints/ForeignKeyConstraint.java create mode 100644 src/main/java/jef/model/constraints/IndexConstraint.java create mode 100644 src/main/java/jef/model/constraints/KeyConstraint.java create mode 100644 src/main/java/jef/model/constraints/PrimaryKeyConstraint.java create mode 100644 src/main/java/jef/model/constraints/UniqueConstraint.java create mode 100644 src/test/java/jef/model/annotations/processors/IndexProcessorTest.java create mode 100644 src/test/java/jef/model/annotations/processors/KeyProcessorTest.java create mode 100644 src/test/java/jef/model/annotations/processors/NotNullProcessorTest.java create mode 100644 src/test/java/jef/model/annotations/processors/UniqueProcessorTest.java 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 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 entity, Class type, Field field) { + this.entity = entity; + this.type = type; this.field = field; this.name = field.getName(); } + + public DbField(DbEntity 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 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 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)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>(); -// for (Field ctxfield : context.getClass().getDeclaredFields()) { -// if (!DbSet.class.isAssignableFrom(ctxfield.getClass())) { -// continue; -// } -// ctxfield.setAccessible(true); -// var dbset = (DbSet) 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 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) clazzAnnotation.clazz(); + initEntity(mb, dbsetClazz); + } + } + + private static void initEntity(ModelBuilder mb, Class 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) clazzAnnotation.clazz()); + if (refEntity == null) { + initEntity(mb, (Class) clazzAnnotation.clazz()); + refEntity = mb.getEntity((Class) 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 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 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 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 entity, List> fields); + + protected abstract void addConstraint(K constr); + + protected abstract String[] getGettersOrFields(Annotation annotation); + + @Override + public void apply(ModelBuilder mb) { + for (DbEntity 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 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 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 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 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