From aeab02ffe0539ed780aad38850f4653dea976314 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Sat, 14 Nov 2020 15:09:55 +0100 Subject: [PATCH 1/9] fix: Return null from Clazz.getRole if name is null --- src/main/java/org/fulib/classmodel/Clazz.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/org/fulib/classmodel/Clazz.java b/src/main/java/org/fulib/classmodel/Clazz.java index c3bdb5fb..711f362d 100644 --- a/src/main/java/org/fulib/classmodel/Clazz.java +++ b/src/main/java/org/fulib/classmodel/Clazz.java @@ -529,6 +529,12 @@ public Clazz withoutAttributes(Collection value) public AssocRole getRole(String name) { + if (name == null) + { + // searching for an anonymous role by name is nonsensical, + // especially since multiple of them can exist. + return null; + } for (AssocRole role : this.getRoles()) { if (Objects.equals(role.getName(), name)) From d9d99f670554b47a32f577c3bbad71c1ccf1fff5 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Sat, 14 Nov 2020 15:10:48 +0100 Subject: [PATCH 2/9] fix: Treat empty or omitted link target as unidirectional association --- .../org/fulib/builder/ReflectiveClassBuilder.java | 11 +++++++++-- src/main/java/org/fulib/builder/reflect/Link.java | 3 ++- .../fulib/builder/ReflectiveClassBuilderTest.java | 15 +++++++++++++-- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/fulib/builder/ReflectiveClassBuilder.java b/src/main/java/org/fulib/builder/ReflectiveClassBuilder.java index 2fd2409e..7d004c4a 100644 --- a/src/main/java/org/fulib/builder/ReflectiveClassBuilder.java +++ b/src/main/java/org/fulib/builder/ReflectiveClassBuilder.java @@ -179,11 +179,18 @@ private static void loadAssoc(Field field, Link link, Clazz clazz, ClassModelMan final String name = field.getName(); final CollectionType collectionType = getCollectionType(field.getType()); - final String otherName = link.value(); + String otherName = link.value(); + if (otherName.isEmpty()) + { + otherName = null; + } final Class other = getOther(field, collectionType); - validateLinkTarget(field.getDeclaringClass(), name, otherName, other); + if (otherName != null) + { + validateLinkTarget(field.getDeclaringClass(), name, otherName, other); + } final String otherClazzName = other.getSimpleName(); final Clazz otherClazz = manager.haveClass(otherClazzName); diff --git a/src/main/java/org/fulib/builder/reflect/Link.java b/src/main/java/org/fulib/builder/reflect/Link.java index 8782eea9..3a8071a5 100644 --- a/src/main/java/org/fulib/builder/reflect/Link.java +++ b/src/main/java/org/fulib/builder/reflect/Link.java @@ -16,6 +16,7 @@ { /** * @return the name of the attribute in the other class. + * An omitted or empty value makes the association unidirectional. */ - String value(); + String value() default ""; } diff --git a/src/test/java/org/fulib/builder/ReflectiveClassBuilderTest.java b/src/test/java/org/fulib/builder/ReflectiveClassBuilderTest.java index 27d16076..2abba70a 100644 --- a/src/test/java/org/fulib/builder/ReflectiveClassBuilderTest.java +++ b/src/test/java/org/fulib/builder/ReflectiveClassBuilderTest.java @@ -61,7 +61,11 @@ class University @Link("uni") List students; + @Link() Person president; + + @Link() + List employees; } @Test @@ -136,8 +140,15 @@ public void test() assertThat(uni.getCardinality(), is(Type.ONE)); assertThat(uni.getCollectionType(), nullValue()); - final Attribute president = university.getAttribute("president"); - assertThat(president.getType(), equalTo("Person")); + final AssocRole president = university.getRole("president"); + assertThat(president.getCardinality(), is(Type.ONE)); + assertThat(president.getOther().getName(), nullValue()); + assertThat(president.getOther().getClazz(), is(person)); + + final AssocRole employees = university.getRole("employees"); + assertThat(employees.getCardinality(), is(Type.MANY)); + assertThat(employees.getOther().getName(), nullValue()); + assertThat(employees.getOther().getClazz(), is(person)); } class StringList extends ArrayList From af70a1ed4d009736fc8f270d049c1b844e9bd881 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Sat, 14 Nov 2020 15:30:04 +0100 Subject: [PATCH 3/9] fix: Improve POJO templates for unidirectional associations - Setters now simply write to the backing field. - The without method no longer uses an empty if statement. --- .../org/fulib/templates/associations.pojo.stg | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/resources/org/fulib/templates/associations.pojo.stg b/src/main/resources/org/fulib/templates/associations.pojo.stg index a6fa5984..954d402e 100644 --- a/src/main/resources/org/fulib/templates/associations.pojo.stg +++ b/src/main/resources/org/fulib/templates/associations.pojo.stg @@ -75,25 +75,25 @@ setMethod(role, other) ::= << public set( value) { + if (this. == value) { return this; } final oldValue = this.; - if (this. != null) { this. = null; oldValue.; } - this. = value; - if (value != null) { value.; } + + this. = value; return this; } @@ -158,12 +158,17 @@ withoutItem(role, other) ::= << public without( value) { + if (this. != null && this..remove(value)) { - value.; - } + + if (this. != null) + { + this..remove(value); + } + return this; } >> From 17e9def6a013e696ebbbfbd677efe8fd6fe265f1 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Sat, 14 Nov 2020 15:32:36 +0100 Subject: [PATCH 4/9] fix: Don't import collection classes for unidirectional reverse roles --- src/main/java/org/fulib/util/Generator4ClassFile.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/fulib/util/Generator4ClassFile.java b/src/main/java/org/fulib/util/Generator4ClassFile.java index af378fab..2f4045e0 100644 --- a/src/main/java/org/fulib/util/Generator4ClassFile.java +++ b/src/main/java/org/fulib/util/Generator4ClassFile.java @@ -141,7 +141,7 @@ private void addDefaultImports(Clazz clazz, Set qualifiedNames) { for (final AssocRole role : clazz.getRoles()) { - if (role.isToMany()) + if (role.getName() != null && role.isToMany()) { this.addCollectionTypeImports(role.getCollectionType(), qualifiedNames); } From a1a049a398495596c91767837a825b6f5ed31d67 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Sat, 14 Nov 2020 15:37:33 +0100 Subject: [PATCH 5/9] fix: Handle null name in AssocRole.getId --- src/main/java/org/fulib/classmodel/AssocRole.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/fulib/classmodel/AssocRole.java b/src/main/java/org/fulib/classmodel/AssocRole.java index b1bc343c..560bbc4a 100644 --- a/src/main/java/org/fulib/classmodel/AssocRole.java +++ b/src/main/java/org/fulib/classmodel/AssocRole.java @@ -132,7 +132,8 @@ public AssocRole setOther(AssocRole value) public String getId() { final Clazz clazz = this.getClazz(); - return (clazz != null ? clazz.getName() : "_") + "_" + this.getName(); + final String name = this.getName(); + return (clazz != null ? clazz.getName() : "_") + "_" + (name != null ? name : this.getOther().getId()); } public String getName() From fed4c388a53204b89cf22f07427d0abff8bd49d2 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Sat, 14 Nov 2020 15:37:47 +0100 Subject: [PATCH 6/9] docs: Example for unidirectional associations in ClassModelDefinition.md --- doc/ClassModelDefinition.md | 90 +++++++++++++++++- .../src/gen/java/org/fulib/docs/GenModel.java | 6 ++ test/src/main/java/org/fulib/docs/Person.java | 2 + .../main/java/org/fulib/docs/University.java | 80 ++++++++++++++++ .../main/java/org/fulib/docs/classDiagram.png | Bin 11106 -> 13414 bytes .../main/java/org/fulib/docs/classModel.yaml | 81 ++++++++++++---- 6 files changed, 239 insertions(+), 20 deletions(-) diff --git a/doc/ClassModelDefinition.md b/doc/ClassModelDefinition.md index c2466ba1..498eb27e 100644 --- a/doc/ClassModelDefinition.md +++ b/doc/ClassModelDefinition.md @@ -184,13 +184,19 @@ class University { @Link("uni") List students; + + @Link + Person president; + + @Link + List employees; } ``` -The `@Link` annotation is intended for *bidirectional* associations. +The `@Link` annotation is primarily intended for *bidirectional* associations. The generated code will ensure referential integrity when setting a student's university or when adding or removing students to a university. -*Unidirectional* associations behave no different from attributes, so there is no special annotation for them. +In case you want a *unidirectional* association, you can simply omit the annotation argument, as shown with `president` and `employees` in the `University` example. ```java @@ -238,7 +244,11 @@ public class Student public class University { public static final String PROPERTY_STUDENTS = "students"; + public static final String PROPERTY_PRESIDENT = "president"; + public static final String PROPERTY_EMPLOYEES = "employees"; private List students; + private Person president; + private List employees; public List getStudents() { @@ -304,9 +314,85 @@ public class University return this; } + public Person getPresident() + { + return this.president; + } + + public University setPresident(Person value) + { + this.president = value; + return this; + } + + public List getEmployees() + { + return this.employees != null ? Collections.unmodifiableList(this.employees) : Collections.emptyList(); + } + + public University withEmployees(Person value) + { + if (this.employees == null) + { + this.employees = new ArrayList<>(); + } + if (!this.employees.contains(value)) + { + this.employees.add(value); + } + return this; + } + + public University withEmployees(Person... value) + { + for (final Person item : value) + { + this.withEmployees(item); + } + return this; + } + + public University withEmployees(Collection value) + { + for (final Person item : value) + { + this.withEmployees(item); + } + return this; + } + + public University withoutEmployees(Person value) + { + if (this.employees != null) + { + this.employees.remove(value); + } + return this; + } + + public University withoutEmployees(Person... value) + { + for (final Person item : value) + { + this.withoutEmployees(item); + } + return this; + } + + public University withoutEmployees(Collection value) + { + for (final Person item : value) + { + this.withoutEmployees(item); + } + return this; + } + public void removeYou() { this.withoutStudents(new ArrayList<>(this.getStudents())); + this.setPresident(null); + this.withoutEmployees(new ArrayList<>(this.getEmployees())); } } ``` diff --git a/test/src/gen/java/org/fulib/docs/GenModel.java b/test/src/gen/java/org/fulib/docs/GenModel.java index 79999623..11efedb5 100644 --- a/test/src/gen/java/org/fulib/docs/GenModel.java +++ b/test/src/gen/java/org/fulib/docs/GenModel.java @@ -42,6 +42,12 @@ class University { @Link("uni") List students; + + @Link + Person president; + + @Link + List employees; } // end_code_fragment: diff --git a/test/src/main/java/org/fulib/docs/Person.java b/test/src/main/java/org/fulib/docs/Person.java index 6d93f4d4..76940f58 100644 --- a/test/src/main/java/org/fulib/docs/Person.java +++ b/test/src/main/java/org/fulib/docs/Person.java @@ -2,6 +2,8 @@ import java.util.Objects; import java.beans.PropertyChangeSupport; import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.List; // start_code_fragment: docs.Person public class Person diff --git a/test/src/main/java/org/fulib/docs/University.java b/test/src/main/java/org/fulib/docs/University.java index 8fda3687..138cf569 100644 --- a/test/src/main/java/org/fulib/docs/University.java +++ b/test/src/main/java/org/fulib/docs/University.java @@ -8,7 +8,11 @@ public class University { public static final String PROPERTY_STUDENTS = "students"; + public static final String PROPERTY_PRESIDENT = "president"; + public static final String PROPERTY_EMPLOYEES = "employees"; private List students; + private Person president; + private List employees; public List getStudents() { @@ -74,9 +78,85 @@ public University withoutStudents(Collection value) return this; } + public Person getPresident() + { + return this.president; + } + + public University setPresident(Person value) + { + this.president = value; + return this; + } + + public List getEmployees() + { + return this.employees != null ? Collections.unmodifiableList(this.employees) : Collections.emptyList(); + } + + public University withEmployees(Person value) + { + if (this.employees == null) + { + this.employees = new ArrayList<>(); + } + if (!this.employees.contains(value)) + { + this.employees.add(value); + } + return this; + } + + public University withEmployees(Person... value) + { + for (final Person item : value) + { + this.withEmployees(item); + } + return this; + } + + public University withEmployees(Collection value) + { + for (final Person item : value) + { + this.withEmployees(item); + } + return this; + } + + public University withoutEmployees(Person value) + { + if (this.employees != null) + { + this.employees.remove(value); + } + return this; + } + + public University withoutEmployees(Person... value) + { + for (final Person item : value) + { + this.withoutEmployees(item); + } + return this; + } + + public University withoutEmployees(Collection value) + { + for (final Person item : value) + { + this.withoutEmployees(item); + } + return this; + } + public void removeYou() { this.withoutStudents(new ArrayList<>(this.getStudents())); + this.setPresident(null); + this.withoutEmployees(new ArrayList<>(this.getEmployees())); } } // end_code_fragment: diff --git a/test/src/main/java/org/fulib/docs/classDiagram.png b/test/src/main/java/org/fulib/docs/classDiagram.png index 720d488dec92b65a84237e669e6c411bd05f4783..30ae2e68f3ec15f938383c07ba48e70262b85201 100644 GIT binary patch literal 13414 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 Oo|c-vYPIr<@c#uFH{jgFs+3@cTD8 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 `=M%I6@#X%>p7 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 diff --git a/test/src/main/java/org/fulib/docs/classModel.yaml b/test/src/main/java/org/fulib/docs/classModel.yaml index 6955f865..b8e67630 100644 --- a/test/src/main/java/org/fulib/docs/classModel.yaml +++ b/test/src/main/java/org/fulib/docs/classModel.yaml @@ -1,5 +1,5 @@ - c: ClassModel - classes: example university student page person + classes: example university student person page defaultCollectionType: c1 defaultPropertyStyle: POJO defaultRoleType: "java.util.ArrayList<%s>" @@ -19,7 +19,7 @@ modified: false name: University propertyStyle: POJO - roles: university_students + roles: university_students university_president university_employees - student: Clazz model: c @@ -28,18 +28,19 @@ propertyStyle: POJO roles: student_uni -- page: Clazz - attributes: page_lines +- person: Clazz + attributes: person_name person_age model: c modified: false - name: Page + name: Person propertyStyle: POJO + roles: person_University_president person_University_employees -- person: Clazz - attributes: person_name person_age +- page: Clazz + attributes: page_lines model: c modified: false - name: Person + name: Page propertyStyle: POJO - c1: CollectionType @@ -133,6 +134,28 @@ propertyStyle: POJO roleType: "java.util.ArrayList<%s>" +- university_president: AssocRole + aggregation: false + cardinality: 1 + clazz: university + id: University_president + modified: false + name: president + other: person_University_president + propertyStyle: POJO + +- university_employees: AssocRole + aggregation: false + cardinality: 42 + clazz: university + collectionType: c1 + id: University_employees + modified: false + name: employees + other: person_University_employees + propertyStyle: POJO + roleType: "java.util.ArrayList<%s>" + - student_uni: AssocRole aggregation: false cardinality: 1 @@ -143,16 +166,6 @@ other: university_students propertyStyle: POJO -- page_lines: Attribute - clazz: page - collectionType: c1 - id: Page_lines - modified: false - name: lines - propertyStyle: POJO - type: String - typeSignature: String - - person_name: Attribute clazz: person id: Person_name @@ -171,6 +184,38 @@ type: int typeSignature: int +- person_University_president: AssocRole + aggregation: false + cardinality: 0 + clazz: person + collectionType: c1 + id: Person_University_president + modified: false + other: university_president + propertyStyle: POJO + roleType: "java.util.ArrayList<%s>" + +- person_University_employees: AssocRole + aggregation: false + cardinality: 0 + clazz: person + collectionType: c1 + id: Person_University_employees + modified: false + other: university_employees + propertyStyle: POJO + roleType: "java.util.ArrayList<%s>" + +- page_lines: Attribute + clazz: page + collectionType: c1 + id: Page_lines + modified: false + name: lines + propertyStyle: POJO + type: String + typeSignature: String + - c3: CollectionType implClass: class it.unimi.dsi.fastutil.ints.IntArrayList implTemplate: it.unimi.dsi.fastutil.ints.IntArrayList From 0a8ac13ea850529798cd2c03f67bfd74a7bcfcd5 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Sat, 14 Nov 2020 15:49:22 +0100 Subject: [PATCH 7/9] fix: Handle unidirectional associations in ClassModelManager - Documented the behaviour of calling `associate` with tgtRole == null. - `associate` now throws an NPE if srcRole == null. --- .../java/org/fulib/builder/ClassModelManager.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/fulib/builder/ClassModelManager.java b/src/main/java/org/fulib/builder/ClassModelManager.java index 0c2ba3f3..e80f8e5e 100644 --- a/src/main/java/org/fulib/builder/ClassModelManager.java +++ b/src/main/java/org/fulib/builder/ClassModelManager.java @@ -664,7 +664,7 @@ public AssocRole associate(Clazz srcClass, String srcRole, int srcSize, Clazz tg * @param srcSize * the cardinality in the source class * @param tgtRole - * the role name in the target class + * the role name in the target class, or {@code null} to make the association unidirectional * @param tgtSize * the cardinality in the target class * @@ -691,7 +691,7 @@ public AssocRole haveRole(Clazz srcClass, String srcRole, Clazz tgtClass, int sr * @param tgtClass * the target class * @param tgtRole - * the role name in the target class + * the role name in the target class, or {@code null} to make the association unidirectional * @param tgtSize * the cardinality in the target class * @@ -716,7 +716,7 @@ public AssocRole haveRole(Clazz srcClass, String srcRole, int srcSize, Clazz tgt * @param tgtClass * the target class * @param tgtRole - * the role name in the target class + * the role name in the target class, or {@code null} to make the association unidirectional * @param tgtSize * the cardinality in the target class * @@ -726,6 +726,11 @@ public AssocRole haveRole(Clazz srcClass, String srcRole, int srcSize, Clazz tgt */ public AssocRole associate(Clazz srcClass, String srcRole, int srcSize, Clazz tgtClass, String tgtRole, int tgtSize) { + if (srcRole == null) + { + throw new NullPointerException("srcRole must not be null"); + } + final AtomicBoolean modified = new AtomicBoolean(false); final AssocRole role = this.haveRole(srcClass, srcRole, srcSize, modified); From 6933eefd66e70e757fc63cb26507758bf0a2a92c Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Sat, 14 Nov 2020 16:22:18 +0100 Subject: [PATCH 8/9] test: Add unidirectional association in AssociationTest --- .../org/fulib/generator/AssociationTest.java | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/test/java/org/fulib/generator/AssociationTest.java b/src/test/java/org/fulib/generator/AssociationTest.java index 65053993..ba8070bd 100644 --- a/src/test/java/org/fulib/generator/AssociationTest.java +++ b/src/test/java/org/fulib/generator/AssociationTest.java @@ -6,17 +6,18 @@ import org.fulib.builder.ClassModelBuilder; import org.fulib.builder.Type; import org.fulib.classmodel.ClassModel; +import org.fulib.classmodel.CollectionType; import org.junit.jupiter.api.Test; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; import java.io.File; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Paths; -import java.util.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; @@ -79,24 +80,28 @@ protected void configureModel(ClassModelBuilder mb) protected final void buildModel(ClassModelBuilder mb) { - ClassBuilder universitiy = mb.buildClass("University").buildAttribute("name", Type.STRING); - // end_code_fragment: - + ClassBuilder university = mb.buildClass("University").buildAttribute("name", Type.STRING); ClassBuilder studi = mb.buildClass("Student").buildAttribute("name", Type.STRING, "\"Karli\""); - - universitiy.buildAssociation(studi, "students", Type.MANY, "uni", Type.ONE); - ClassBuilder room = mb.buildClass("Room").buildAttribute("no", Type.STRING); + ClassBuilder assignment = mb.buildClass("Assignment").buildAttribute("topic", Type.STRING); - universitiy.buildAssociation(room, "rooms", Type.MANY, "uni", Type.ONE) - .setSourceRoleCollection(LinkedHashSet.class).setAggregation(); + // n to 1 + university.buildAssociation(studi, "students", Type.MANY, "uni", Type.ONE); - studi.buildAssociation(room, "condo", Type.ONE, "owner", Type.ONE); + // n to 1 - custom collection type + university + .buildAssociation(room, "rooms", Type.MANY, "uni", Type.ONE) + .setSourceRoleCollection(CollectionType.LinkedHashSet) + .setAggregation(); + // 1 to 1 + studi.buildAssociation(room, "condo", Type.ONE, "owner", Type.ONE); + // n to n studi.buildAssociation(room, "in", Type.MANY, "students", Type.MANY); - ClassBuilder assignment = mb.buildClass("Assignment").buildAttribute("topic", Type.STRING); - studi.buildAssociation(assignment, "done", Type.MANY, "students", Type.MANY); + // unidirectional - the assignment does not need to know who did it + studi.buildAssociation(assignment, "workingOn", Type.ONE, null, 0); + studi.buildAssociation(assignment, "done", Type.MANY, null, 0); } protected void runDataTests(ClassLoader classLoader, String packageName) throws Exception From 8fb0e022c9277cb302059fa05ba80e82b03cd349 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Sat, 14 Nov 2020 16:23:14 +0100 Subject: [PATCH 9/9] fix: Correct JavaFX templates for unidirectional associations --- .../resources/org/fulib/templates/associations.javafx.stg | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/resources/org/fulib/templates/associations.javafx.stg b/src/main/resources/org/fulib/templates/associations.javafx.stg index c701e736..a0a3408e 100644 --- a/src/main/resources/org/fulib/templates/associations.javafx.stg +++ b/src/main/resources/org/fulib/templates/associations.javafx.stg @@ -91,12 +91,16 @@ initMethod(role, other) ::= << { for (final value : change.getRemoved()) { + value.; + this.firePropertyChange(PROPERTY_, value, null); } for (final value : change.getAddedSubList()) { + value.; + this.firePropertyChange(PROPERTY_, null, value); } } @@ -105,6 +109,7 @@ initMethod(role, other) ::= << final import(javafx.beans.property.ObjectProperty)\<\> result = new import(javafx.beans.property.SimpleObjectProperty)\<>(); result.addListener((observable, oldValue, newValue) -> { + if (oldValue != null) { oldValue.; @@ -113,6 +118,7 @@ initMethod(role, other) ::= << { newValue.; } + this.firePropertyChange(PROPERTY_, oldValue, newValue); }); return result;