diff --git a/cdlang/src/main/java/de/monticore/cdgen/cocos/CDAssociationReferenceUnique.java b/cdlang/src/main/java/de/monticore/cdgen/cocos/CDAssociationReferenceUnique.java new file mode 100644 index 000000000..c1d4af6ec --- /dev/null +++ b/cdlang/src/main/java/de/monticore/cdgen/cocos/CDAssociationReferenceUnique.java @@ -0,0 +1,102 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.cdgen.cocos; + +import de.monticore.cdassociation._ast.ASTCDAssocSide; +import de.monticore.cdassociation._ast.ASTCDAssociation; +import de.monticore.cdbasis._ast.ASTCDDefinition; +import de.monticore.cdbasis._ast.ASTCDType; +import org.apache.commons.lang3.StringUtils; + +import java.util.*; + +/** + * Checks that generated association references are unique. + */ +public class CDAssociationReferenceUnique extends CDAssociationUniqueInHierarchy { + + @Override + public void check(ASTCDDefinition node) { + List alreadyChecked = new ArrayList<>(); + + // we check for each pair of associations + for (ASTCDAssociation assoc2 : node.getCDAssociationsList()) { + for (ASTCDAssociation assoc1 : alreadyChecked) { + for (AssociationReference ref1 : getAssociationReferences(assoc1)) { + for (AssociationReference ref2 : getAssociationReferences(assoc2)) { + if (ref1.name.equals(ref2.name)) { + checkRef(node, ref1.sourceType, ref2.sourceType, assoc2); + } + } + } + } + + alreadyChecked.add(assoc2); + } + } + + @Override + protected void checkRef(ASTCDDefinition node, ASTCDType type1, ASTCDType type2, + ASTCDAssociation assoc1) { + if (type1 == null || type2 == null) { + return; + } + super.checkRef(node, type1, type2, assoc1); + } + + protected List getAssociationReferences(ASTCDAssociation assoc) { + List references = new ArrayList<>(); + + boolean navigableLeft = assoc.getCDAssocDir().isDefinitiveNavigableLeft(); + boolean navigableRight = assoc.getCDAssocDir().isDefinitiveNavigableRight(); + boolean undirected = !navigableLeft && !navigableRight; + + if (navigableRight || undirected) { + references.add(new AssociationReference(findTypeByFullName(assoc, assoc.getLeftQualifiedName() + .getQName()), deriveReferenceName(assoc, AssocSide.RIGHT))); + } + if (navigableLeft || undirected) { + references.add(new AssociationReference(findTypeByFullName(assoc, assoc + .getRightQualifiedName().getQName()), deriveReferenceName(assoc, AssocSide.LEFT))); + } + + return references; + } + + protected String deriveReferenceName(ASTCDAssociation assoc, AssocSide side) { + ASTCDAssocSide assocSide; + if (side.equals(AssocSide.LEFT)) { + assocSide = assoc.getLeft(); + } + else { + assocSide = assoc.getRight(); + } + if (assoc.isPresentName()) { + return StringUtils.uncapitalize(assoc.getName()); + } + else if (assocSide.isPresentCDRole()) { + return assocSide.getCDRole().getName(); + } + else { + return StringUtils.uncapitalize(assocSide.getMCQualifiedType().getMCQualifiedName() + .getBaseName()); + } + } + + protected enum AssocSide { + LEFT, RIGHT; + } + + protected static class AssociationReference { + + protected final ASTCDType sourceType; + + protected final String name; + + protected AssociationReference(ASTCDType sourceType, String name) { + this.sourceType = sourceType; + this.name = name; + } + + } + +} diff --git a/cdlang/src/test/java/de/monticore/cdgen/cocos/CDAssociationReferenceUniqueTest.java b/cdlang/src/test/java/de/monticore/cdgen/cocos/CDAssociationReferenceUniqueTest.java new file mode 100644 index 000000000..06ee230d0 --- /dev/null +++ b/cdlang/src/test/java/de/monticore/cdgen/cocos/CDAssociationReferenceUniqueTest.java @@ -0,0 +1,69 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.cdgen.cocos; + +import de.monticore.cd4code._cocos.CD4CodeCoCoChecker; +import de.monticore.cd4code.cocos.AbstractJavaGenCoCoTest; +import java.io.IOException; +import org.junit.jupiter.api.Test; + +public class CDAssociationReferenceUniqueTest extends AbstractJavaGenCoCoTest { + + @Override + protected CD4CodeCoCoChecker createChecker() { + CD4CodeCoCoChecker checker = new CD4CodeCoCoChecker(); + checker.addCoCo(new CDAssociationReferenceUnique()); + return checker; + } + + private static final String ERROR_CODE = "0xCDCE1"; + + @Test + public void testUniqueNames() throws IOException { + String model = "classdiagram UniqueAssocs {" + " class A; class B;" + " association A -> B;" + + " association A (l) -> (r) B;" + "}"; + runTest(model, false); + } + + @Test + public void testDuplicatesWithExplicitRoles() throws IOException { + String model = "classdiagram DuplicateAssocs {" + " class A; class B;" + + " association A -> B;" + " association A -> (b) B;" + "}"; + runTestForErrorCode(model, ERROR_CODE); + } + + @Test + public void testDuplicatesWithImplicitRoles() throws IOException { + String model = "classdiagram DuplicateAssocs {" + " class A; class B;" + + " association A -> B;" + " association A -> B;" + "}"; + runTestForErrorCode(model, ERROR_CODE); + } + + @Test + public void testUniqueExplicitRole() throws IOException { + String model = "classdiagram UniqueAssocs {" + " class A; class B;" + " association A -> B;" + + " association A -> (other) B;" + "}"; + runTest(model, false); + } + + @Test + public void testDuplicatesInReverse() throws IOException { + String model = "classdiagram DuplicateAssocs {" + " class A; class B;" + + " association A -> B;" + " association B <- A;" + "}"; + runTestForErrorCode(model, ERROR_CODE); + } + + @Test + public void testUniqueAssocName() throws IOException { + String model = "classdiagram UniqueAssocs {" + " class A; class B;" + + " association assoc1 A -> B;" + " association assoc2 A -> B;" + "}"; + runTest(model, false); + } + + @Test + public void testUniqueAssocNameWithSameRole() throws IOException { + String model = "classdiagram UniqueAssocs {" + " class A; class B;" + + " association assoc1 A -> (b) B;" + " association assoc2 A -> (b) B;" + "}"; + runTest(model, false); + } + +}