From f11120e0a34e6bfb034ed520d80c5e61b8840dbc Mon Sep 17 00:00:00 2001 From: Leonie Dahle Date: Fri, 4 Sep 2026 14:31:13 +0200 Subject: [PATCH 1/8] add CDAssociationModifierCoCo to prevent visibility conflicts in CDAssociationModifierCoCo.java --- .../cocos/CDAssociationCoCos.java | 9 +++-- .../cocos/ebnf/CDAssociationModifierCoCo.java | 40 +++++++++++++++++++ 2 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 cdlang/src/main/java/de/monticore/cdassociation/cocos/ebnf/CDAssociationModifierCoCo.java diff --git a/cdlang/src/main/java/de/monticore/cdassociation/cocos/CDAssociationCoCos.java b/cdlang/src/main/java/de/monticore/cdassociation/cocos/CDAssociationCoCos.java index 1d93b5339..455c9c2d5 100644 --- a/cdlang/src/main/java/de/monticore/cdassociation/cocos/CDAssociationCoCos.java +++ b/cdlang/src/main/java/de/monticore/cdassociation/cocos/CDAssociationCoCos.java @@ -6,19 +6,19 @@ import de.monticore.cdassociation.cocos.ebnf.*; public class CDAssociationCoCos extends CoCoParent { - + @Override public CDAssociationCoCoChecker createNewChecker() { return new CDAssociationCoCoChecker(); } - + @Override protected void addCheckerForAllCoCos(CDAssociationCoCoChecker checker) { addCheckerForEbnfCoCos(checker); addCheckerForMcgCoCos(checker); addCheckerForMcg2EbnfCoCos(checker); } - + @Override protected void addEbnfCoCos(CDAssociationCoCoChecker checker) { // CDAssociation @@ -28,6 +28,7 @@ protected void addEbnfCoCos(CDAssociationCoCoChecker checker) { checker.addCoCo(new CDAssociationOrderedCardinalityGreaterOne()); checker.addCoCo(new CDAssociationRoleNameLowerCase()); checker.addCoCo(new CDAssociationRoleNameNoConflictWithLocalAttribute()); + checker.addCoCo(new CDAssociationModifierCoCo()); } - + } diff --git a/cdlang/src/main/java/de/monticore/cdassociation/cocos/ebnf/CDAssociationModifierCoCo.java b/cdlang/src/main/java/de/monticore/cdassociation/cocos/ebnf/CDAssociationModifierCoCo.java new file mode 100644 index 000000000..6b5c3c1f5 --- /dev/null +++ b/cdlang/src/main/java/de/monticore/cdassociation/cocos/ebnf/CDAssociationModifierCoCo.java @@ -0,0 +1,40 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.cdassociation.cocos.ebnf; + +import de.monticore.cdassociation._ast.ASTCDAssociation; +import de.monticore.cdassociation._cocos.CDAssociationASTCDAssociationCoCo; +import de.monticore.umlmodifier._ast.ASTModifier; +import de.se_rwth.commons.logging.Log; + +public class CDAssociationModifierCoCo implements CDAssociationASTCDAssociationCoCo { + + @Override + public void check(ASTCDAssociation a) { + + //----------------------------------check the visibility modifiers----------------------------------------------------------- + //main modifier + boolean hasMainVisibility=false; + if(a.getModifier()!=null) { + ASTModifier mainModifier = a.getModifier(); + hasMainVisibility= mainModifier.isPublic()||mainModifier.isProtected()||mainModifier.isPrivate(); + } + + //left modifier + boolean hasLeftVisibility=false; + if(a.getLeft().getModifier()!=null) { + ASTModifier leftModifier = a.getLeft().getModifier(); + hasLeftVisibility=leftModifier.isPublic()||leftModifier.isProtected()||leftModifier.isPrivate(); + } + + //right modifier + boolean hasRightVisibility=false; + if(a.getRight().getModifier()!=null) { + ASTModifier rightModifier = a.getRight().getModifier(); + hasRightVisibility=rightModifier.isPublic()||rightModifier.isProtected()||rightModifier.isPrivate(); + } + + if(hasMainVisibility && (hasLeftVisibility || hasRightVisibility)) { + Log.error("Association cannot combine a main visibility modifier with individual role visibility modifiers.", a.get_SourcePositionStart()); + } + } +} From d5edace4829e4e3246b620db6009ab8ab68e938c Mon Sep 17 00:00:00 2001 From: Leonie Dahle Date: Fri, 4 Sep 2026 14:32:59 +0200 Subject: [PATCH 2/8] in progress: support additional modifiers for Associations in CDAssociationSymbolTableCompleter.java --- .../CDAssociationSymbolTableCompleter.java | 57 ++++++++++--------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/cdlang/src/main/java/de/monticore/cdassociation/_symboltable/CDAssociationSymbolTableCompleter.java b/cdlang/src/main/java/de/monticore/cdassociation/_symboltable/CDAssociationSymbolTableCompleter.java index df5f1d757..a35dabb54 100644 --- a/cdlang/src/main/java/de/monticore/cdassociation/_symboltable/CDAssociationSymbolTableCompleter.java +++ b/cdlang/src/main/java/de/monticore/cdassociation/_symboltable/CDAssociationSymbolTableCompleter.java @@ -21,19 +21,19 @@ public class CDAssociationSymbolTableCompleter implements CDAssociationVisitor2, CDAssociationHandler { - + protected CDAssociationTraverser traverser; - + protected ISynthesize typeSynthesizer; - + public CDAssociationSymbolTableCompleter(ISynthesize typeSynthesizer) { this.typeSynthesizer = typeSynthesizer; } - + public CDAssociationSymbolTableCompleter() { this(new FullSynthesizeFromMCBasicTypes()); } - + @Override public void handle(ASTCDAssociation node) { if (node.getLeft().isPresentSymbol()) { @@ -44,31 +44,31 @@ public void handle(ASTCDAssociation node) { } endVisit(node); } - + public void initialize_CDRole(CDRoleSymbol symbol, ASTCDAssociation ast, boolean isLeft) { final ASTCDAssocSide side = isLeft ? ast.getLeft() : ast.getRight(); - + symbol.setAssocSide(side); final Optional typeResult = getSymTypeExpression(ast, side); if (!typeResult.isPresent()) { return; } symbol.setType(typeResult.get()); - + setupModifiers(side.getModifier(), ast.getModifier(), symbol); - + symbol.setIsDefinitiveNavigable(isLeft ? ast.getCDAssocDir().isDefinitiveNavigableLeft() : ast .getCDAssocDir().isDefinitiveNavigableRight()); - + if (side.isPresentCDCardinality()) { symbol.setCardinality(side.getCDCardinality()); } - + handleQualifier(symbol, side); symbol.setIsOrdered(side.isPresentCDOrdered()); symbol.setIsLeft(isLeft); } - + protected Optional getSymTypeExpression(ASTCDAssociation ast, ASTCDAssocSide side) { final TypeCheckResult typeResult = getTypeSynthesizer().synthesizeType(side @@ -79,12 +79,12 @@ protected Optional getSymTypeExpression(ASTCDAssociation ast, .getMCQualifiedType().get_SourcePositionStart()); return Optional.empty(); } - + // check if the type can be resolved - + return Optional.of(typeResult.getResult()); } - + protected void handleQualifier(CDRoleSymbol symbol, ASTCDAssocSide side) { if (side.isPresentCDQualifier()) { if (side.getCDQualifier().isPresentByType()) { @@ -109,17 +109,17 @@ else if (side.getCDQualifier().isPresentByAttributeName()) { } } } - + @Override public void endVisit(ASTCDAssociation node) { final ASTCDAssocLeftSide l = node.getLeft(); final ASTCDAssocRightSide r = node.getRight(); - + final TypeCheckResult rType = getTypeSynthesizer().synthesizeType(r.getMCQualifiedType() .getMCQualifiedName()); final TypeCheckResult lType = getTypeSynthesizer().synthesizeType(l.getMCQualifiedType() .getMCQualifiedName()); - + if (l.isPresentSymbol()) { if (rType.isPresentResult() && !rType.getResult().isObscureType() && rType.getResult() .hasTypeInfo()) { @@ -143,7 +143,7 @@ public void endVisit(ASTCDAssociation node) { } } } - + public void setupModifiers(ASTModifier assocSideModifier, ASTModifier assocModifier, CDRoleSymbol roleSymbol) { roleSymbol.setIsPublic(assocSideModifier.isPublic() || assocModifier.isPublic()); @@ -152,31 +152,34 @@ public void setupModifiers(ASTModifier assocSideModifier, ASTModifier assocModif roleSymbol.setIsStatic(assocSideModifier.isStatic() || assocModifier.isStatic()); roleSymbol.setIsFinal(assocSideModifier.isFinal() || assocModifier.isFinal()); roleSymbol.setIsDerived(assocSideModifier.isDerived() || assocModifier.isDerived()); + //TODO: add isOrdered()-getter + //roleSymbol.setIsOrdered(assocSideModifier.isOrdered() ||assocModifier.isOrdered()); + roleSymbol.setIsReadOnly(assocSideModifier.isReadonly()||assocModifier.isReadonly()); } - + public static void addRoleToTheirType(CDRoleSymbol symbol, TypeSymbol otherType) { // move the RoleSymbol to their Type final ICDAssociationScope spannedScope = (ICDAssociationScope) otherType.getSpannedScope(); - + // remove the role from its current scope(s) symbol.getEnclosingScope().remove(symbol); - + if (!spannedScope.getCDRoleSymbols().containsKey(symbol.getName())) { // add the symbol to the type; add to all relevant lists spannedScope.add(symbol); } } - + public ISynthesize getTypeSynthesizer() { return typeSynthesizer; } - + public void setTypeSynthesizer(ISynthesize typeSynthesizer) { this.typeSynthesizer = typeSynthesizer; } - + @Override public CDAssociationTraverser getTraverser() { return traverser; } - + @Override public void setTraverser(CDAssociationTraverser traverser) { this.traverser = traverser; } - + } From 027b75a5ecc579b0a4fcadc145cf239ab797f4ba Mon Sep 17 00:00:00 2001 From: Leonie Dahle Date: Wed, 9 Sep 2026 13:47:13 +0200 Subject: [PATCH 3/8] forbid static modifier for associations in CDAssociationModifierCoCo.java --- .../CDAssociationSymbolTableCompleter.java | 8 +++----- .../cocos/ebnf/CDAssociationModifierCoCo.java | 13 ++++++++++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/cdlang/src/main/java/de/monticore/cdassociation/_symboltable/CDAssociationSymbolTableCompleter.java b/cdlang/src/main/java/de/monticore/cdassociation/_symboltable/CDAssociationSymbolTableCompleter.java index a35dabb54..dc877fabc 100644 --- a/cdlang/src/main/java/de/monticore/cdassociation/_symboltable/CDAssociationSymbolTableCompleter.java +++ b/cdlang/src/main/java/de/monticore/cdassociation/_symboltable/CDAssociationSymbolTableCompleter.java @@ -146,14 +146,12 @@ public void endVisit(ASTCDAssociation node) { public void setupModifiers(ASTModifier assocSideModifier, ASTModifier assocModifier, CDRoleSymbol roleSymbol) { - roleSymbol.setIsPublic(assocSideModifier.isPublic() || assocModifier.isPublic()); - roleSymbol.setIsPrivate(assocSideModifier.isPrivate() || assocModifier.isPrivate()); - roleSymbol.setIsProtected(assocSideModifier.isProtected() || assocModifier.isProtected()); + roleSymbol.setIsPublic(assocSideModifier.isPublic()); + roleSymbol.setIsPrivate(assocSideModifier.isPrivate() ); + roleSymbol.setIsProtected(assocSideModifier.isProtected()); roleSymbol.setIsStatic(assocSideModifier.isStatic() || assocModifier.isStatic()); roleSymbol.setIsFinal(assocSideModifier.isFinal() || assocModifier.isFinal()); roleSymbol.setIsDerived(assocSideModifier.isDerived() || assocModifier.isDerived()); - //TODO: add isOrdered()-getter - //roleSymbol.setIsOrdered(assocSideModifier.isOrdered() ||assocModifier.isOrdered()); roleSymbol.setIsReadOnly(assocSideModifier.isReadonly()||assocModifier.isReadonly()); } diff --git a/cdlang/src/main/java/de/monticore/cdassociation/cocos/ebnf/CDAssociationModifierCoCo.java b/cdlang/src/main/java/de/monticore/cdassociation/cocos/ebnf/CDAssociationModifierCoCo.java index 6b5c3c1f5..1c5dfff62 100644 --- a/cdlang/src/main/java/de/monticore/cdassociation/cocos/ebnf/CDAssociationModifierCoCo.java +++ b/cdlang/src/main/java/de/monticore/cdassociation/cocos/ebnf/CDAssociationModifierCoCo.java @@ -34,7 +34,18 @@ public void check(ASTCDAssociation a) { } if(hasMainVisibility && (hasLeftVisibility || hasRightVisibility)) { - Log.error("Association cannot combine a main visibility modifier with individual role visibility modifiers.", a.get_SourcePositionStart()); + Log.warn("Association cannot combine a main visibility modifier with individual role visibility modifiers.", a.get_SourcePositionStart()); + } + + //check whether the association or symbolRole use the static modifier + boolean mainStatic = a.getModifier() != null && a.getModifier().isStatic(); + boolean leftStatic = a.getLeft().getModifier() != null && a.getLeft().getModifier().isStatic(); + boolean rightStatic = a.getRight().getModifier() != null && a.getRight().getModifier().isStatic(); + + if(mainStatic || leftStatic || rightStatic) { + String assocName = a.isPresentName() ? a.getName() : "unnamed"; + Log.error(String.format("0xCDA61: The association '%s' must not use static modifiers.", assocName), + a.get_SourcePositionStart()); } } } From 2338d07387d1ccec9040ea5dc8bdf41301963ded Mon Sep 17 00:00:00 2001 From: Leonie Dahle Date: Wed, 9 Sep 2026 13:47:13 +0200 Subject: [PATCH 4/8] forbid static modifier for associations in CDAssociationModifierCoCo.java --- .../CDAssociationSymbolTableCompleter.java | 64 +++++++++---------- .../cocos/ebnf/CDAssociationModifierCoCo.java | 50 ++++++++++----- 2 files changed, 65 insertions(+), 49 deletions(-) diff --git a/cdlang/src/main/java/de/monticore/cdassociation/_symboltable/CDAssociationSymbolTableCompleter.java b/cdlang/src/main/java/de/monticore/cdassociation/_symboltable/CDAssociationSymbolTableCompleter.java index a35dabb54..ec1e7b6fd 100644 --- a/cdlang/src/main/java/de/monticore/cdassociation/_symboltable/CDAssociationSymbolTableCompleter.java +++ b/cdlang/src/main/java/de/monticore/cdassociation/_symboltable/CDAssociationSymbolTableCompleter.java @@ -21,19 +21,19 @@ public class CDAssociationSymbolTableCompleter implements CDAssociationVisitor2, CDAssociationHandler { - + protected CDAssociationTraverser traverser; - + protected ISynthesize typeSynthesizer; - + public CDAssociationSymbolTableCompleter(ISynthesize typeSynthesizer) { this.typeSynthesizer = typeSynthesizer; } - + public CDAssociationSymbolTableCompleter() { this(new FullSynthesizeFromMCBasicTypes()); } - + @Override public void handle(ASTCDAssociation node) { if (node.getLeft().isPresentSymbol()) { @@ -44,31 +44,31 @@ public void handle(ASTCDAssociation node) { } endVisit(node); } - + public void initialize_CDRole(CDRoleSymbol symbol, ASTCDAssociation ast, boolean isLeft) { final ASTCDAssocSide side = isLeft ? ast.getLeft() : ast.getRight(); - + symbol.setAssocSide(side); final Optional typeResult = getSymTypeExpression(ast, side); if (!typeResult.isPresent()) { return; } symbol.setType(typeResult.get()); - + setupModifiers(side.getModifier(), ast.getModifier(), symbol); - + symbol.setIsDefinitiveNavigable(isLeft ? ast.getCDAssocDir().isDefinitiveNavigableLeft() : ast .getCDAssocDir().isDefinitiveNavigableRight()); - + if (side.isPresentCDCardinality()) { symbol.setCardinality(side.getCDCardinality()); } - + handleQualifier(symbol, side); symbol.setIsOrdered(side.isPresentCDOrdered()); symbol.setIsLeft(isLeft); } - + protected Optional getSymTypeExpression(ASTCDAssociation ast, ASTCDAssocSide side) { final TypeCheckResult typeResult = getTypeSynthesizer().synthesizeType(side @@ -79,12 +79,12 @@ protected Optional getSymTypeExpression(ASTCDAssociation ast, .getMCQualifiedType().get_SourcePositionStart()); return Optional.empty(); } - + // check if the type can be resolved - + return Optional.of(typeResult.getResult()); } - + protected void handleQualifier(CDRoleSymbol symbol, ASTCDAssocSide side) { if (side.isPresentCDQualifier()) { if (side.getCDQualifier().isPresentByType()) { @@ -109,17 +109,17 @@ else if (side.getCDQualifier().isPresentByAttributeName()) { } } } - + @Override public void endVisit(ASTCDAssociation node) { final ASTCDAssocLeftSide l = node.getLeft(); final ASTCDAssocRightSide r = node.getRight(); - + final TypeCheckResult rType = getTypeSynthesizer().synthesizeType(r.getMCQualifiedType() .getMCQualifiedName()); final TypeCheckResult lType = getTypeSynthesizer().synthesizeType(l.getMCQualifiedType() .getMCQualifiedName()); - + if (l.isPresentSymbol()) { if (rType.isPresentResult() && !rType.getResult().isObscureType() && rType.getResult() .hasTypeInfo()) { @@ -143,43 +143,41 @@ public void endVisit(ASTCDAssociation node) { } } } - + public void setupModifiers(ASTModifier assocSideModifier, ASTModifier assocModifier, CDRoleSymbol roleSymbol) { - roleSymbol.setIsPublic(assocSideModifier.isPublic() || assocModifier.isPublic()); - roleSymbol.setIsPrivate(assocSideModifier.isPrivate() || assocModifier.isPrivate()); - roleSymbol.setIsProtected(assocSideModifier.isProtected() || assocModifier.isProtected()); + roleSymbol.setIsPublic(assocSideModifier.isPublic()); + roleSymbol.setIsPrivate(assocSideModifier.isPrivate()); + roleSymbol.setIsProtected(assocSideModifier.isProtected()); roleSymbol.setIsStatic(assocSideModifier.isStatic() || assocModifier.isStatic()); roleSymbol.setIsFinal(assocSideModifier.isFinal() || assocModifier.isFinal()); roleSymbol.setIsDerived(assocSideModifier.isDerived() || assocModifier.isDerived()); - //TODO: add isOrdered()-getter - //roleSymbol.setIsOrdered(assocSideModifier.isOrdered() ||assocModifier.isOrdered()); - roleSymbol.setIsReadOnly(assocSideModifier.isReadonly()||assocModifier.isReadonly()); + roleSymbol.setIsReadOnly(assocSideModifier.isReadonly() || assocModifier.isReadonly()); } - + public static void addRoleToTheirType(CDRoleSymbol symbol, TypeSymbol otherType) { // move the RoleSymbol to their Type final ICDAssociationScope spannedScope = (ICDAssociationScope) otherType.getSpannedScope(); - + // remove the role from its current scope(s) symbol.getEnclosingScope().remove(symbol); - + if (!spannedScope.getCDRoleSymbols().containsKey(symbol.getName())) { // add the symbol to the type; add to all relevant lists spannedScope.add(symbol); } } - + public ISynthesize getTypeSynthesizer() { return typeSynthesizer; } - + public void setTypeSynthesizer(ISynthesize typeSynthesizer) { this.typeSynthesizer = typeSynthesizer; } - + @Override public CDAssociationTraverser getTraverser() { return traverser; } - + @Override public void setTraverser(CDAssociationTraverser traverser) { this.traverser = traverser; } - + } diff --git a/cdlang/src/main/java/de/monticore/cdassociation/cocos/ebnf/CDAssociationModifierCoCo.java b/cdlang/src/main/java/de/monticore/cdassociation/cocos/ebnf/CDAssociationModifierCoCo.java index 6b5c3c1f5..240b7a7fc 100644 --- a/cdlang/src/main/java/de/monticore/cdassociation/cocos/ebnf/CDAssociationModifierCoCo.java +++ b/cdlang/src/main/java/de/monticore/cdassociation/cocos/ebnf/CDAssociationModifierCoCo.java @@ -7,34 +7,52 @@ import de.se_rwth.commons.logging.Log; public class CDAssociationModifierCoCo implements CDAssociationASTCDAssociationCoCo { - + @Override public void check(ASTCDAssociation a) { - + //----------------------------------check the visibility modifiers----------------------------------------------------------- //main modifier - boolean hasMainVisibility=false; - if(a.getModifier()!=null) { + boolean hasMainVisibility = false; + if (a.getModifier() != null) { ASTModifier mainModifier = a.getModifier(); - hasMainVisibility= mainModifier.isPublic()||mainModifier.isProtected()||mainModifier.isPrivate(); + hasMainVisibility = mainModifier.isPublic() || mainModifier.isProtected() || mainModifier + .isPrivate(); } - + //left modifier - boolean hasLeftVisibility=false; - if(a.getLeft().getModifier()!=null) { + boolean hasLeftVisibility = false; + if (a.getLeft().getModifier() != null) { ASTModifier leftModifier = a.getLeft().getModifier(); - hasLeftVisibility=leftModifier.isPublic()||leftModifier.isProtected()||leftModifier.isPrivate(); + hasLeftVisibility = leftModifier.isPublic() || leftModifier.isProtected() || leftModifier + .isPrivate(); } - + //right modifier - boolean hasRightVisibility=false; - if(a.getRight().getModifier()!=null) { + boolean hasRightVisibility = false; + if (a.getRight().getModifier() != null) { ASTModifier rightModifier = a.getRight().getModifier(); - hasRightVisibility=rightModifier.isPublic()||rightModifier.isProtected()||rightModifier.isPrivate(); + hasRightVisibility = rightModifier.isPublic() || rightModifier.isProtected() || rightModifier + .isPrivate(); } - - if(hasMainVisibility && (hasLeftVisibility || hasRightVisibility)) { - Log.error("Association cannot combine a main visibility modifier with individual role visibility modifiers.", a.get_SourcePositionStart()); + + if (hasMainVisibility && (hasLeftVisibility || hasRightVisibility)) { + Log.warn( + "Association cannot combine a main visibility modifier with individual role visibility modifiers.", + a.get_SourcePositionStart()); + } + + //check whether the association or symbolRole use the static modifier + boolean mainStatic = a.getModifier() != null && a.getModifier().isStatic(); + boolean leftStatic = a.getLeft().getModifier() != null && a.getLeft().getModifier().isStatic(); + boolean rightStatic = a.getRight().getModifier() != null && a.getRight().getModifier() + .isStatic(); + + if (mainStatic || leftStatic || rightStatic) { + String assocName = a.isPresentName() ? a.getName() : "unnamed"; + Log.error(String.format("0xCDA61: The association '%s' must not use static modifiers.", + assocName), a.get_SourcePositionStart()); } } + } From e0b1e2642da9dcb1a6df9d2a12e5ce090b8599d7 Mon Sep 17 00:00:00 2001 From: Leonie Dahle Date: Mon, 21 Sep 2026 12:24:01 +0200 Subject: [PATCH 5/8] still in progress: Adding a CoCo to forbid using java classes on either side of an association + tests (solving issue #123) files: CDAssociationCoCos.java, CDAssociationJavaClassTypeCoCo.java, CDAssociationLeftAndRightJavaClassInvalidTest.java, CDAssociationLeftAndRightJavaClassInvalid.cd --- .../cocos/CDAssociationCoCos.java | 9 ++-- .../ebnf/CDAssociationJavaClassTypeCoCo.java | 45 +++++++++++++++++++ ...ationLeftAndRightJavaClassInvalidTest.java | 35 +++++++++++++++ ...AssociationLeftAndRightJavaClassInvalid.cd | 16 +++++++ 4 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 cdlang/src/main/java/de/monticore/cdassociation/cocos/ebnf/CDAssociationJavaClassTypeCoCo.java create mode 100644 cdlang/src/test/java/de/monticore/testcdassociation/cocos/CDAssociationLeftAndRightJavaClassInvalidTest.java create mode 100644 cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationLeftAndRightJavaClassInvalid.cd diff --git a/cdlang/src/main/java/de/monticore/cdassociation/cocos/CDAssociationCoCos.java b/cdlang/src/main/java/de/monticore/cdassociation/cocos/CDAssociationCoCos.java index 455c9c2d5..5e58ea795 100644 --- a/cdlang/src/main/java/de/monticore/cdassociation/cocos/CDAssociationCoCos.java +++ b/cdlang/src/main/java/de/monticore/cdassociation/cocos/CDAssociationCoCos.java @@ -6,19 +6,19 @@ import de.monticore.cdassociation.cocos.ebnf.*; public class CDAssociationCoCos extends CoCoParent { - + @Override public CDAssociationCoCoChecker createNewChecker() { return new CDAssociationCoCoChecker(); } - + @Override protected void addCheckerForAllCoCos(CDAssociationCoCoChecker checker) { addCheckerForEbnfCoCos(checker); addCheckerForMcgCoCos(checker); addCheckerForMcg2EbnfCoCos(checker); } - + @Override protected void addEbnfCoCos(CDAssociationCoCoChecker checker) { // CDAssociation @@ -29,6 +29,7 @@ protected void addEbnfCoCos(CDAssociationCoCoChecker checker) { checker.addCoCo(new CDAssociationRoleNameLowerCase()); checker.addCoCo(new CDAssociationRoleNameNoConflictWithLocalAttribute()); checker.addCoCo(new CDAssociationModifierCoCo()); + checker.addCoCo(new CDAssociationJavaClassTypeCoCo()); } - + } diff --git a/cdlang/src/main/java/de/monticore/cdassociation/cocos/ebnf/CDAssociationJavaClassTypeCoCo.java b/cdlang/src/main/java/de/monticore/cdassociation/cocos/ebnf/CDAssociationJavaClassTypeCoCo.java new file mode 100644 index 000000000..c9ca16f02 --- /dev/null +++ b/cdlang/src/main/java/de/monticore/cdassociation/cocos/ebnf/CDAssociationJavaClassTypeCoCo.java @@ -0,0 +1,45 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.cdassociation.cocos.ebnf; + +import de.monticore.cdassociation._ast.ASTCDAssociation; +import de.monticore.cdassociation._cocos.CDAssociationASTCDAssociationCoCo; +import de.monticore.cdassociation._symboltable.ICDAssociationScope; +import de.monticore.cdbasis._symboltable.CDTypeSymbol; +import de.monticore.symbols.basicsymbols._symboltable.TypeSymbol; +import de.monticore.types.mcbasictypes._ast.ASTMCQualifiedType; +import de.se_rwth.commons.logging.Log; + +public class CDAssociationJavaClassTypeCoCo implements CDAssociationASTCDAssociationCoCo { + + @Override + public void check(ASTCDAssociation node) { + check(node, node.getLeft().getMCQualifiedType(), "left"); + check(node, node.getRight().getMCQualifiedType(), "right"); + } + + protected void check(ASTCDAssociation a, ASTMCQualifiedType qualifiedType, String sideName) { + + if (qualifiedType == null || a == null) { + return; + } + + ICDAssociationScope scope = a.getEnclosingScope(); + String qName = qualifiedType.getMCQualifiedName().getQName(); + + var optTypeSymbol = scope.resolveType(qName); + + if (optTypeSymbol.isPresent()) { + TypeSymbol typeSymbol = optTypeSymbol.get(); + + if (!(typeSymbol instanceof CDTypeSymbol)) { + String assocName = a.isPresentName() ? a.getName() : "unnamed"; + + Log.error(String.format( + "0xCDA64: Java class '%s' cannot be used on the %s side of association '%s'.", qName, + sideName, assocName), a.get_SourcePositionStart()); + + } + } + } + +} diff --git a/cdlang/src/test/java/de/monticore/testcdassociation/cocos/CDAssociationLeftAndRightJavaClassInvalidTest.java b/cdlang/src/test/java/de/monticore/testcdassociation/cocos/CDAssociationLeftAndRightJavaClassInvalidTest.java new file mode 100644 index 000000000..b7fc7a729 --- /dev/null +++ b/cdlang/src/test/java/de/monticore/testcdassociation/cocos/CDAssociationLeftAndRightJavaClassInvalidTest.java @@ -0,0 +1,35 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.testcdassociation.cocos; + +import de.monticore.cdassociation.cocos.ebnf.CDAssociationJavaClassTypeCoCo; +import de.monticore.cdbasis._ast.ASTCDCompilationUnit; +import de.monticore.testcdassociation.CDAssociationTestBasis; +import de.se_rwth.commons.logging.Log; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +public class CDAssociationLeftAndRightJavaClassInvalidTest extends CDAssociationTestBasis { + + @Test + public void testInvalid() throws IOException { + + coCoChecker.addCoCo(new CDAssociationJavaClassTypeCoCo()); + final Optional optAST = p.parse(getFilePath( + "cdassociation/cocos/CDAssociationLeftAndRightJavaClassInvalid.cd")); + assertTrue(optAST.isPresent()); + + final ASTCDCompilationUnit ast = optAST.get(); + Log.getFindings().clear(); + createSymTab(ast); + completeSymTab(ast); + coCoChecker.checkAll(ast); + assertTrue(Log.getFindings().get(0).getMsg().startsWith("0xCDA64")); + assertEquals(4, Log.getFindings().size()); + Log.clearFindings(); + } + +} diff --git a/cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationLeftAndRightJavaClassInvalid.cd b/cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationLeftAndRightJavaClassInvalid.cd new file mode 100644 index 000000000..6b50498c7 --- /dev/null +++ b/cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationLeftAndRightJavaClassInvalid.cd @@ -0,0 +1,16 @@ +/* (c) https://github.com/MontiCore/monticore */ + +classdiagram CDAssociationLeftAndRightJavaClassInvalid{ + + class A{ + } + + //left side is an external java class + association leftJava java.lang.String -> A; + + // right side is an external java class + association rightJava A -> java.lang.Integer; + + // both sides are external java classes + association bothJava java.lang.Boolean -> java.lang.Long; +} From 3cd3c22a0986160ba8c5f0dd008b7ed447016c25 Mon Sep 17 00:00:00 2001 From: Leonie Dahle Date: Thu, 24 Sep 2026 12:47:16 +0200 Subject: [PATCH 6/8] Add positive test in CDAssociationLeftAndRightJavaClassInvalidTest.java --- ...ationLeftAndRightJavaClassInvalidTest.java | 15 ++++++++++++ ...CDAssociationLeftAndRightJavaClassValid.cd | 23 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationLeftAndRightJavaClassValid.cd diff --git a/cdlang/src/test/java/de/monticore/testcdassociation/cocos/CDAssociationLeftAndRightJavaClassInvalidTest.java b/cdlang/src/test/java/de/monticore/testcdassociation/cocos/CDAssociationLeftAndRightJavaClassInvalidTest.java index b7fc7a729..5f190f517 100644 --- a/cdlang/src/test/java/de/monticore/testcdassociation/cocos/CDAssociationLeftAndRightJavaClassInvalidTest.java +++ b/cdlang/src/test/java/de/monticore/testcdassociation/cocos/CDAssociationLeftAndRightJavaClassInvalidTest.java @@ -14,6 +14,21 @@ public class CDAssociationLeftAndRightJavaClassInvalidTest extends CDAssociationTestBasis { + @Test + public void testValid() throws IOException { + coCoChecker.addCoCo(new CDAssociationJavaClassTypeCoCo()); + final Optional optAST = p.parse(getFilePath( + "cdassociation/cocos/CDAssociationLeftAndRightJavaClassValid.cd")); + assertTrue(optAST.isPresent()); + + final ASTCDCompilationUnit ast = optAST.get(); + Log.getFindings().clear(); + createSymTab(ast); + completeSymTab(ast); + coCoChecker.checkAll(ast); + assertTrue(Log.getFindings().isEmpty()); + } + @Test public void testInvalid() throws IOException { diff --git a/cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationLeftAndRightJavaClassValid.cd b/cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationLeftAndRightJavaClassValid.cd new file mode 100644 index 000000000..ee2dc6096 --- /dev/null +++ b/cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationLeftAndRightJavaClassValid.cd @@ -0,0 +1,23 @@ +/* (c) https://github.com/MontiCore/monticore */ + +classdiagram CDAssociationLeftAndRightJavaClassInvalid{ + + public class A{ + java.lang.String attributeA1; + java.lang.Integer attributeA2; + } + + private abstract class B{ + readonly int attributeB1; + final java.lang.String attributeB2; + } + + protected static class C { + } + + association abstract B -> A; + association [0..2] A -> B; + + association A -> C; + +} From 234283a7b46c98da60c3628804b65d8d06d16e44 Mon Sep 17 00:00:00 2001 From: Leonie Dahle Date: Fri, 16 Oct 2026 18:48:55 +0200 Subject: [PATCH 7/8] Update CDAssociationModifierCoCo.java: Forbid local and abstract modifier for the association and the roleSymbols. Also add test in CDAssociationModifierCoCoTest.java --- .../cocos/ebnf/CDAssociationModifierCoCo.java | 33 +++++++- .../cocos/CDAssociationModifierCoCoTest.java | 76 +++++++++++++++++++ .../cocos/CDAssociationModifierCoCoInvalid.cd | 26 +++++++ ...ssociationModifierCoCoVisibilityInvalid.cd | 22 ++++++ ...DAssociationModifierCoCoVisibilityValid.cd | 19 +++++ 5 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 cdlang/src/test/java/de/monticore/testcdassociation/cocos/CDAssociationModifierCoCoTest.java create mode 100644 cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationModifierCoCoInvalid.cd create mode 100644 cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationModifierCoCoVisibilityInvalid.cd create mode 100644 cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationModifierCoCoVisibilityValid.cd diff --git a/cdlang/src/main/java/de/monticore/cdassociation/cocos/ebnf/CDAssociationModifierCoCo.java b/cdlang/src/main/java/de/monticore/cdassociation/cocos/ebnf/CDAssociationModifierCoCo.java index 240b7a7fc..52558dc28 100644 --- a/cdlang/src/main/java/de/monticore/cdassociation/cocos/ebnf/CDAssociationModifierCoCo.java +++ b/cdlang/src/main/java/de/monticore/cdassociation/cocos/ebnf/CDAssociationModifierCoCo.java @@ -42,6 +42,9 @@ public void check(ASTCDAssociation a) { a.get_SourcePositionStart()); } + //----------------------------------check the rest modifiers----------------------------------------------------------- + String assocName = a.isPresentName() ? a.getName() : "unnamed"; + //check whether the association or symbolRole use the static modifier boolean mainStatic = a.getModifier() != null && a.getModifier().isStatic(); boolean leftStatic = a.getLeft().getModifier() != null && a.getLeft().getModifier().isStatic(); @@ -49,10 +52,36 @@ public void check(ASTCDAssociation a) { .isStatic(); if (mainStatic || leftStatic || rightStatic) { - String assocName = a.isPresentName() ? a.getName() : "unnamed"; - Log.error(String.format("0xCDA61: The association '%s' must not use static modifiers.", + Log.error(String.format( + "0xCDA61: The association '%s' (or its roles) must not use static modifiers.", assocName), + a.get_SourcePositionStart()); + } + + //check whether the association or symbolRole use the abstract modifier + + boolean leftAbstract = a.getLeft().getModifier() != null && a.getLeft().getModifier() + .isAbstract(); + boolean rightAbstract = a.getRight().getModifier() != null && a.getRight().getModifier() + .isAbstract(); + boolean mainAbstract = a.getModifier() != null && a.getModifier().isAbstract(); + + if (leftAbstract || rightAbstract || mainAbstract) { + Log.error(String.format( + "0xCDA66: The association '%s' (or its roles) must not use an abstract modifier.", assocName), a.get_SourcePositionStart()); } + + //check whether the association or symbolRole use the local modifier + boolean leftLocal = a.getLeft().getModifier() != null && a.getLeft().getModifier().isLocal(); + boolean rightLocal = a.getRight().getModifier() != null && a.getRight().getModifier().isLocal(); + boolean mainLocal = a.getModifier() != null && a.getModifier().isLocal(); + + if (leftLocal || rightLocal || mainLocal) { + Log.error(String.format( + "0xCDA67: The association '%s' (or its roles) must not use a local modifier.", assocName), + a.get_SourcePositionStart()); + } } } +//TODO: LD check comments diff --git a/cdlang/src/test/java/de/monticore/testcdassociation/cocos/CDAssociationModifierCoCoTest.java b/cdlang/src/test/java/de/monticore/testcdassociation/cocos/CDAssociationModifierCoCoTest.java new file mode 100644 index 000000000..e753c486a --- /dev/null +++ b/cdlang/src/test/java/de/monticore/testcdassociation/cocos/CDAssociationModifierCoCoTest.java @@ -0,0 +1,76 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.testcdassociation.cocos; + +import de.monticore.cdassociation.cocos.ebnf.CDAssociationModifierCoCo; +import de.monticore.cdassociation.cocos.ebnf.CDAssociationNameUnique; +import de.monticore.cdbasis._ast.ASTCDCompilationUnit; +import de.monticore.testcdassociation.CDAssociationTestBasis; +import de.se_rwth.commons.logging.Log; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class CDAssociationModifierCoCoTest extends CDAssociationTestBasis { + + @Test + public void testValid() throws IOException { + coCoChecker.addCoCo(new CDAssociationNameUnique()); + final Optional optAST = p.parse(getFilePath( + "cdassociation/cocos/Valid.cd")); + assertTrue(optAST.isPresent()); + final ASTCDCompilationUnit ast = optAST.get(); + Log.getFindings().clear(); + coCoChecker.checkAll(ast); + assertTrue(Log.getFindings().isEmpty()); + } + + //visibility-modifiers tests + @Test + public void testVisibilityValid() throws IOException { + coCoChecker.addCoCo(new CDAssociationModifierCoCo()); + final Optional optAST = p.parse(getFilePath( + "cdassociation/cocos/CDAssociationModifierCoCoVisibilityValid.cd")); + assertTrue(optAST.isPresent()); + final ASTCDCompilationUnit ast = optAST.get(); + Log.getFindings().clear(); + createSymTab(ast); + coCoChecker.checkAll(ast); + assertTrue(Log.getFindings().isEmpty()); + } + + @Test + public void testVisibilityInValid() throws IOException { + coCoChecker.addCoCo(new CDAssociationModifierCoCo()); + final Optional optAST = p.parse(getFilePath( + "cdassociation/cocos/CDAssociationModifierCoCoVisibilityInvalid.cd")); + assertTrue(optAST.isPresent()); + final ASTCDCompilationUnit ast = optAST.get(); + Log.getFindings().clear(); + createSymTab(ast); + coCoChecker.checkAll(ast); + assertTrue(Log.getFindings().size() == 2); + } + + //TODO: LD do we need a spoitive test or upgrade InvalidTest? + //TODO: LD Improve (Log.getFindings().size() == 6)? + + // static/abstract/local modifier test + @Test + public void testModifierInvalid() throws IOException { + coCoChecker.addCoCo(new CDAssociationModifierCoCo()); + final Optional optAST = p.parse(getFilePath( + "cdassociation/cocos/CDAssociationModifierCoCoInvalid.cd")); + assertTrue(optAST.isPresent()); + final ASTCDCompilationUnit ast = optAST.get(); + Log.getFindings().clear(); + createSymTab(ast); + coCoChecker.checkAll(ast); + assertTrue(Log.getFindings().size() == 6); + Log.getFindings().clear(); + } + + //TODO: test readOnly +} diff --git a/cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationModifierCoCoInvalid.cd b/cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationModifierCoCoInvalid.cd new file mode 100644 index 000000000..65cb48d52 --- /dev/null +++ b/cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationModifierCoCoInvalid.cd @@ -0,0 +1,26 @@ +/* (c) https://github.com/MontiCore/monticore */ + +classdiagram CDAssociationModifierCoCoInvalid{ + + package p1 { + public class A { + int foo; + int bar; + int field; + } + class B extends A; + } + package p2 { + class C; + } + + static association Bar p1.A -> p2.C; + public association Bar1 static p1.A -> p2.C; + + local association localAssoc p1.A -> p2.C; + public association localRole local p1.A -> p2.C; + + abstract association abstractAssoc p1.A -> p2.C; + public association abstractRole abstract p1.A -> p2.C; + +} diff --git a/cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationModifierCoCoVisibilityInvalid.cd b/cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationModifierCoCoVisibilityInvalid.cd new file mode 100644 index 000000000..56e690222 --- /dev/null +++ b/cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationModifierCoCoVisibilityInvalid.cd @@ -0,0 +1,22 @@ +/* (c) https://github.com/MontiCore/monticore */ + +classdiagram CDAssociationModifierCoCoVisibilityInvalid{ + + package p1 { + public class A { + int foo; + int bar; + int field; + } + class B extends A; + } + package p2 { + class C; + } + + public association Bar public p1.A -> p2.C; + private association Bar1 public p1.A -> p2.C; + + private association Bar4 p1.A -> p2.C; + association Bar5 private p1.A -> p2.C; +} diff --git a/cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationModifierCoCoVisibilityValid.cd b/cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationModifierCoCoVisibilityValid.cd new file mode 100644 index 000000000..e3a73ce1a --- /dev/null +++ b/cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationModifierCoCoVisibilityValid.cd @@ -0,0 +1,19 @@ +/* (c) https://github.com/MontiCore/monticore */ + +classdiagram CDAssociationModifierCoCoVisibilityValid{ + + package p1 { + public class A { + int foo; + int bar; + int field; + } + class B extends A; + } + package p2 { + class C; + } + + public association Ba p1.A -> p2.C; + association Bar public p1.A -> p2.C; +} From 4924efd4a79a134f79e639abb5bf09383543c48e Mon Sep 17 00:00:00 2001 From: Leonie Dahle Date: Mon, 19 Oct 2026 15:40:48 +0200 Subject: [PATCH 8/8] Refactor CDAssociationModifierCoCo.java with helper methods and add static class tests --- .../cocos/ebnf/CDAssociationModifierCoCo.java | 140 ++++++++++-------- .../cocos/CDAssociationModifierCoCoTest.java | 24 +-- .../cocos/CDAssociationModifierCoCoInvalid.cd | 17 ++- .../cocos/CDAssociationModifierCoCoValid.cd | 22 +++ ...ssociationModifierCoCoVisibilityInvalid.cd | 22 --- ...DAssociationModifierCoCoVisibilityValid.cd | 19 --- 6 files changed, 121 insertions(+), 123 deletions(-) create mode 100644 cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationModifierCoCoValid.cd delete mode 100644 cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationModifierCoCoVisibilityInvalid.cd delete mode 100644 cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationModifierCoCoVisibilityValid.cd diff --git a/cdlang/src/main/java/de/monticore/cdassociation/cocos/ebnf/CDAssociationModifierCoCo.java b/cdlang/src/main/java/de/monticore/cdassociation/cocos/ebnf/CDAssociationModifierCoCo.java index 52558dc28..d698f9175 100644 --- a/cdlang/src/main/java/de/monticore/cdassociation/cocos/ebnf/CDAssociationModifierCoCo.java +++ b/cdlang/src/main/java/de/monticore/cdassociation/cocos/ebnf/CDAssociationModifierCoCo.java @@ -3,85 +3,109 @@ import de.monticore.cdassociation._ast.ASTCDAssociation; import de.monticore.cdassociation._cocos.CDAssociationASTCDAssociationCoCo; +import de.monticore.cdassociation._symboltable.ICDAssociationScope; +import de.monticore.cdbasis._symboltable.CDTypeSymbol; +import de.monticore.symbols.basicsymbols._symboltable.TypeSymbol; import de.monticore.umlmodifier._ast.ASTModifier; import de.se_rwth.commons.logging.Log; +import java.util.Optional; +import java.util.function.Predicate; + public class CDAssociationModifierCoCo implements CDAssociationASTCDAssociationCoCo { @Override public void check(ASTCDAssociation a) { - //----------------------------------check the visibility modifiers----------------------------------------------------------- - //main modifier - boolean hasMainVisibility = false; - if (a.getModifier() != null) { - ASTModifier mainModifier = a.getModifier(); - hasMainVisibility = mainModifier.isPublic() || mainModifier.isProtected() || mainModifier - .isPrivate(); - } - - //left modifier - boolean hasLeftVisibility = false; - if (a.getLeft().getModifier() != null) { - ASTModifier leftModifier = a.getLeft().getModifier(); - hasLeftVisibility = leftModifier.isPublic() || leftModifier.isProtected() || leftModifier - .isPrivate(); - } + String assocName = a.isPresentName() ? a.getName() : "unnamed"; - //right modifier - boolean hasRightVisibility = false; - if (a.getRight().getModifier() != null) { - ASTModifier rightModifier = a.getRight().getModifier(); - hasRightVisibility = rightModifier.isPublic() || rightModifier.isProtected() || rightModifier - .isPrivate(); - } + // Check for conflicting visibility modifiers + boolean hasMain = hasVisibility(a.getModifier()); + boolean hasRoleVisibility = hasVisibility(a.getLeft().getModifier()) || hasVisibility(a + .getRight().getModifier()); - if (hasMainVisibility && (hasLeftVisibility || hasRightVisibility)) { - Log.warn( - "Association cannot combine a main visibility modifier with individual role visibility modifiers.", - a.get_SourcePositionStart()); + if (hasMain && hasRoleVisibility) { + Log.warn(String.format( + "The association '%s' cannot combine a main visibility modifier with individual role visibility modifiers.", + assocName), a.get_SourcePositionStart()); } //----------------------------------check the rest modifiers----------------------------------------------------------- - String assocName = a.isPresentName() ? a.getName() : "unnamed"; - - //check whether the association or symbolRole use the static modifier - boolean mainStatic = a.getModifier() != null && a.getModifier().isStatic(); - boolean leftStatic = a.getLeft().getModifier() != null && a.getLeft().getModifier().isStatic(); - boolean rightStatic = a.getRight().getModifier() != null && a.getRight().getModifier() - .isStatic(); - - if (mainStatic || leftStatic || rightStatic) { - Log.error(String.format( - "0xCDA61: The association '%s' (or its roles) must not use static modifiers.", assocName), - a.get_SourcePositionStart()); - } - //check whether the association or symbolRole use the abstract modifier + //// Ensure that the association does not connect static classes + ICDAssociationScope scope = a.getEnclosingScope(); - boolean leftAbstract = a.getLeft().getModifier() != null && a.getLeft().getModifier() - .isAbstract(); - boolean rightAbstract = a.getRight().getModifier() != null && a.getRight().getModifier() - .isAbstract(); - boolean mainAbstract = a.getModifier() != null && a.getModifier().isAbstract(); + boolean staticClassLeft = isStaticType(scope.resolveType(a.getLeft().getName())); + boolean staticClassRight = isStaticType(scope.resolveType(a.getRight().getName())); - if (leftAbstract || rightAbstract || mainAbstract) { - Log.error(String.format( - "0xCDA66: The association '%s' (or its roles) must not use an abstract modifier.", + if (staticClassLeft || staticClassRight) { + Log.error(String.format("0xCDA66: The association '%s' cannot connect static classes.", assocName), a.get_SourcePositionStart()); } - //check whether the association or symbolRole use the local modifier - boolean leftLocal = a.getLeft().getModifier() != null && a.getLeft().getModifier().isLocal(); - boolean rightLocal = a.getRight().getModifier() != null && a.getRight().getModifier().isLocal(); - boolean mainLocal = a.getModifier() != null && a.getModifier().isLocal(); + //// Ensure that neither the association nor its roles use forbidden modifiers (static, abstract, local) + checkForbiddenModifier(a, ASTModifier::isStatic, "0xCDA61", String.format( + "The association '%s' (or its roles) must not use static modifiers.", assocName)); + checkForbiddenModifier(a, ASTModifier::isAbstract, "0xCDA67", String.format( + "The association '%s' (or its roles) must not use an abstract modifier.", assocName)); + checkForbiddenModifier(a, ASTModifier::isLocal, "0xCDA68", String.format( + "The association '%s' (or its roles) must not use a local modifier.", assocName)); - if (leftLocal || rightLocal || mainLocal) { - Log.error(String.format( - "0xCDA67: The association '%s' (or its roles) must not use a local modifier.", assocName), - a.get_SourcePositionStart()); + } + + /** + * Checks whether the given modifier specifies an explicit visibility + * (i.e., public, protected, or private). + * + * @param modifier the AST modifier to check (can be null) + * @return true if a public, protected, or private modifier is present, false otherwise + */ + private boolean hasVisibility(ASTModifier modifier) { + return modifier != null && (modifier.isPublic() || modifier.isProtected() || modifier + .isPrivate()); + } + + /** + * Safely checks a boolean flag on an ASTModifier, handling null values. + * + * @param modifier the AST modifier to check (can be null) + * @param check a predicate representing the flag to check + * @return true if the modifier is present and the check evaluates to true, false otherwise + */ + private boolean hasModifier(ASTModifier modifier, Predicate check) { + return modifier != null && check.test(modifier); + } + + /** + * Checks whether the given type symbol represents a static class. + * + * @param optSymbol an optional type symbol resolved from the scope + * @return true if the symbol is a static class, false otherwise + */ + private boolean isStaticType(Optional optSymbol) { + if (optSymbol.isPresent() && optSymbol.get() instanceof CDTypeSymbol typeSymbol) { + return typeSymbol.isPresentAstNode() && typeSymbol.getAstNode().getModifier().isStatic(); + } + return false; + } + + /** + * Checks whether a forbidden modifier is present on the association itself or its roles, + * and logs an error if the check evaluates to true. + * + * @param a the association AST node to check + * @param check a predicate representing the modifier flag to check + * @param errorCode the error code to log + * @param errorMessage the error message to log + */ + private void checkForbiddenModifier(ASTCDAssociation a, Predicate check, + String errorCode, String errorMessage) { + boolean main = hasModifier(a.getModifier(), check); + boolean left = hasModifier(a.getLeft().getModifier(), check); + boolean right = hasModifier(a.getRight().getModifier(), check); + if (main || left || right) { + Log.error(String.format("%s: %s", errorCode, errorMessage), a.get_SourcePositionStart()); } } } -//TODO: LD check comments diff --git a/cdlang/src/test/java/de/monticore/testcdassociation/cocos/CDAssociationModifierCoCoTest.java b/cdlang/src/test/java/de/monticore/testcdassociation/cocos/CDAssociationModifierCoCoTest.java index e753c486a..d926c355d 100644 --- a/cdlang/src/test/java/de/monticore/testcdassociation/cocos/CDAssociationModifierCoCoTest.java +++ b/cdlang/src/test/java/de/monticore/testcdassociation/cocos/CDAssociationModifierCoCoTest.java @@ -11,6 +11,7 @@ import java.io.IOException; import java.util.Optional; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; public class CDAssociationModifierCoCoTest extends CDAssociationTestBasis { @@ -29,10 +30,10 @@ public void testValid() throws IOException { //visibility-modifiers tests @Test - public void testVisibilityValid() throws IOException { + public void testModifiersValid() throws IOException { coCoChecker.addCoCo(new CDAssociationModifierCoCo()); final Optional optAST = p.parse(getFilePath( - "cdassociation/cocos/CDAssociationModifierCoCoVisibilityValid.cd")); + "cdassociation/cocos/CDAssociationModifierCoCoValid.cd")); assertTrue(optAST.isPresent()); final ASTCDCompilationUnit ast = optAST.get(); Log.getFindings().clear(); @@ -41,22 +42,6 @@ public void testVisibilityValid() throws IOException { assertTrue(Log.getFindings().isEmpty()); } - @Test - public void testVisibilityInValid() throws IOException { - coCoChecker.addCoCo(new CDAssociationModifierCoCo()); - final Optional optAST = p.parse(getFilePath( - "cdassociation/cocos/CDAssociationModifierCoCoVisibilityInvalid.cd")); - assertTrue(optAST.isPresent()); - final ASTCDCompilationUnit ast = optAST.get(); - Log.getFindings().clear(); - createSymTab(ast); - coCoChecker.checkAll(ast); - assertTrue(Log.getFindings().size() == 2); - } - - //TODO: LD do we need a spoitive test or upgrade InvalidTest? - //TODO: LD Improve (Log.getFindings().size() == 6)? - // static/abstract/local modifier test @Test public void testModifierInvalid() throws IOException { @@ -68,9 +53,8 @@ public void testModifierInvalid() throws IOException { Log.getFindings().clear(); createSymTab(ast); coCoChecker.checkAll(ast); - assertTrue(Log.getFindings().size() == 6); + assertEquals(13, Log.getFindings().size()); Log.getFindings().clear(); } - //TODO: test readOnly } diff --git a/cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationModifierCoCoInvalid.cd b/cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationModifierCoCoInvalid.cd index 65cb48d52..843fe7514 100644 --- a/cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationModifierCoCoInvalid.cd +++ b/cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationModifierCoCoInvalid.cd @@ -12,15 +12,24 @@ classdiagram CDAssociationModifierCoCoInvalid{ } package p2 { class C; + static class D; } - static association Bar p1.A -> p2.C; - public association Bar1 static p1.A -> p2.C; + public association visibilityAssocLeft private p1.A -> p2.C; + private association visibilityAssocRight p1.A -> p2.C public; + + static association staticAssoc p1.A -> p2.C; + public association leftRoleStatic static p1.A -> p2.C; + private association rightRoleStatic [0..1] p1.A -> p2.C [*] static; local association localAssoc p1.A -> p2.C; - public association localRole local p1.A -> p2.C; + public association leftLocalRole local p1.A -> p2.C; + private association rightRoleLocal [0..1] p1.A -> p2.C [*] local; abstract association abstractAssoc p1.A -> p2.C; - public association abstractRole abstract p1.A -> p2.C; + public association leftRoleAbstract abstract p1.A -> p2.C; + private association rightRoleAbstract [0..1] p1.A -> p2.C [*] abstract; + association staticClassLeft p2.D -> p1.A; + association staticClassRight p2.C -> p2.D; } diff --git a/cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationModifierCoCoValid.cd b/cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationModifierCoCoValid.cd new file mode 100644 index 000000000..b0ce79183 --- /dev/null +++ b/cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationModifierCoCoValid.cd @@ -0,0 +1,22 @@ +/* (c) https://github.com/MontiCore/monticore */ + +classdiagram CDAssociationModifierCoCoValid{ + + package p1 { + public class A { + int foo; + String field; + } + } + package p2 { + class B; + } + + public association assocVisibility p1.A -> p2.B; + association leftVisibility private p1.A -> p2.B; + association rightVisibility p1.A -> p2.B public; + + public association finalRole final p2.B -> p1.A final; + private association derivedRoles derived p2.B -> p1.A derived; + protected association readOnlyRoles readonly p1.A -> p2.B readonly; +} diff --git a/cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationModifierCoCoVisibilityInvalid.cd b/cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationModifierCoCoVisibilityInvalid.cd deleted file mode 100644 index 56e690222..000000000 --- a/cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationModifierCoCoVisibilityInvalid.cd +++ /dev/null @@ -1,22 +0,0 @@ -/* (c) https://github.com/MontiCore/monticore */ - -classdiagram CDAssociationModifierCoCoVisibilityInvalid{ - - package p1 { - public class A { - int foo; - int bar; - int field; - } - class B extends A; - } - package p2 { - class C; - } - - public association Bar public p1.A -> p2.C; - private association Bar1 public p1.A -> p2.C; - - private association Bar4 p1.A -> p2.C; - association Bar5 private p1.A -> p2.C; -} diff --git a/cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationModifierCoCoVisibilityValid.cd b/cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationModifierCoCoVisibilityValid.cd deleted file mode 100644 index e3a73ce1a..000000000 --- a/cdlang/src/test/resources/de/monticore/cdassociation/cocos/CDAssociationModifierCoCoVisibilityValid.cd +++ /dev/null @@ -1,19 +0,0 @@ -/* (c) https://github.com/MontiCore/monticore */ - -classdiagram CDAssociationModifierCoCoVisibilityValid{ - - package p1 { - public class A { - int foo; - int bar; - int field; - } - class B extends A; - } - package p2 { - class C; - } - - public association Ba p1.A -> p2.C; - association Bar public p1.A -> p2.C; -}