From e64cb3d0c6a46f2f40daf1d871ff0c6ebfeaec80 Mon Sep 17 00:00:00 2001 From: Tom Bursch Date: Wed, 22 Jul 2026 22:39:39 +0200 Subject: [PATCH] Refactor and Add CoCos for development --- .../java/de/monticore/cdgen/CDGenTool.java | 5 +- .../cdgen/cocos/CD2JavaGenCoCos.java | 1 + .../cdgen/cocos/CDAssociationUnique.java | 41 ++++++++-------- .../cocos/CDAssociationUniqueInHierarchy.java | 15 +++--- .../cocos/CDNoAttributesInInterfaces.java | 3 +- .../cocos/CDNoOutgoingAssocs4Interfaces.java | 17 +++++-- .../CDNoOutgoingAssocs4LibraryTypes.java | 40 ++++++++++++++++ .../cdgen/cocos/CDSingleClassInheritance.java | 3 +- .../cdgen/cocos/CDAssociationUniqueTest.java | 7 +++ .../CDNoOutgoingAssocs4LibraryTypesTest.java | 48 +++++++++++++++++++ .../CDAssociationUniqueInHierarchyValid.cd | 28 +++++------ 11 files changed, 160 insertions(+), 48 deletions(-) create mode 100644 cdlang/src/main/java/de/monticore/cdgen/cocos/CDNoOutgoingAssocs4LibraryTypes.java create mode 100644 cdlang/src/test/java/de/monticore/cdgen/cocos/CDNoOutgoingAssocs4LibraryTypesTest.java diff --git a/cdlang/src/main/java/de/monticore/cdgen/CDGenTool.java b/cdlang/src/main/java/de/monticore/cdgen/CDGenTool.java index 6b648136c..01a411473 100644 --- a/cdlang/src/main/java/de/monticore/cdgen/CDGenTool.java +++ b/cdlang/src/main/java/de/monticore/cdgen/CDGenTool.java @@ -13,12 +13,14 @@ import de.monticore.cd4analysis.trafo.CDAssociationCreateFieldsFromAllRoles; import de.monticore.cd4analysis.trafo.CDAssociationCreateFieldsFromNavigableRoles; import de.monticore.cd4code.CD4CodeMill; +import de.monticore.cd4code._cocos.CD4CodeCoCoChecker; import de.monticore.cd4code._symboltable.ICD4CodeArtifactScope; import de.monticore.cd4code._visitor.CD4CodeTraverser; import de.monticore.cdbasis.CDBasisMill; import de.monticore.cdbasis._ast.ASTCDClass; import de.monticore.cdbasis._ast.ASTCDCompilationUnit; import de.monticore.cdbasis.trafo.CDBasisDefaultPackageTrafo; +import de.monticore.cdgen.cocos.CD2JavaGenCoCos; import de.monticore.cdinterfaceandenum._ast.ASTCDEnum; import de.monticore.cdinterfaceandenum._ast.ASTCDInterface; import de.monticore.generating.GeneratorSetup; @@ -395,7 +397,8 @@ public void runBeforeSTCoCos(ASTCDCompilationUnit ast) { * @param ast the original ast */ public void runCoCos(ASTCDCompilationUnit ast) { - super.runCoCos(ast); + CD4CodeCoCoChecker checker = new CD2JavaGenCoCos().getCheckerForAllCoCos(); + checker.checkAll(ast); } @Override diff --git a/cdlang/src/main/java/de/monticore/cdgen/cocos/CD2JavaGenCoCos.java b/cdlang/src/main/java/de/monticore/cdgen/cocos/CD2JavaGenCoCos.java index 587fe0559..25af572b3 100644 --- a/cdlang/src/main/java/de/monticore/cdgen/cocos/CD2JavaGenCoCos.java +++ b/cdlang/src/main/java/de/monticore/cdgen/cocos/CD2JavaGenCoCos.java @@ -12,6 +12,7 @@ protected void addCheckerForAllCoCos(CD4CodeCoCoChecker checker) { checker.addCoCo(new CDAssociationUniqueInHierarchy()); checker.addCoCo(new CDNoAttributesInInterfaces()); checker.addCoCo(new CDNoOutgoingAssocs4Interfaces()); + checker.addCoCo(new CDNoOutgoingAssocs4LibraryTypes()); checker.addCoCo(new CDSingleClassInheritance()); } diff --git a/cdlang/src/main/java/de/monticore/cdgen/cocos/CDAssociationUnique.java b/cdlang/src/main/java/de/monticore/cdgen/cocos/CDAssociationUnique.java index c63bb5f32..79eb20fe4 100644 --- a/cdlang/src/main/java/de/monticore/cdgen/cocos/CDAssociationUnique.java +++ b/cdlang/src/main/java/de/monticore/cdgen/cocos/CDAssociationUnique.java @@ -4,9 +4,11 @@ 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 de.monticore.cdbasis._cocos.CDBasisASTCDDefinitionCoCo; -import de.monticore.cdbasis._symboltable.CDTypeSymbol; +import de.monticore.symbols.basicsymbols._symboltable.TypeSymbol; +import de.monticore.types.check.SymTypeExpression; +import de.monticore.types.mcbasictypes._ast.ASTMCQualifiedName; +import de.monticore.types3.TypeCheck3; import de.se_rwth.commons.logging.Log; import org.apache.commons.lang3.StringUtils; @@ -40,8 +42,8 @@ public void check(ASTCDDefinition node) { if (assoc1.getCDAssocDir().isDefinitiveNavigableRight() && assoc2.getCDAssocDir() .isDefinitiveNavigableRight() && deriveRoleName(assoc1, AssocSide.RIGHT).equals( deriveRoleName(assoc2, AssocSide.RIGHT))) { - checkRef(node, findTypeByFullName(assoc1, assoc1.getLeftQualifiedName().getQName()), - findTypeByFullName(assoc2, assoc2.getLeftQualifiedName().getQName()), assoc1); + checkRef(node, findTypeByFullName(assoc1.getLeftQualifiedName()), + findTypeByFullName(assoc2.getLeftQualifiedName()), assoc1); } // if they allow navigation from right to left and share a left role-name, @@ -49,23 +51,23 @@ public void check(ASTCDDefinition node) { if (assoc1.getCDAssocDir().isDefinitiveNavigableLeft() && assoc2.getCDAssocDir() .isDefinitiveNavigableLeft() && deriveRoleName(assoc1, AssocSide.LEFT).equals( deriveRoleName(assoc2, AssocSide.LEFT))) { - checkRef(node, findTypeByFullName(assoc1, assoc1.getRightQualifiedName().getQName()), - findTypeByFullName(assoc2, assoc2.getRightQualifiedName().getQName()), assoc1); + checkRef(node, findTypeByFullName(assoc1.getRightQualifiedName()), + findTypeByFullName(assoc2.getRightQualifiedName()), assoc1); } // We also consider a left-to-right role name and navigation match ... if (assoc1.getCDAssocDir().isDefinitiveNavigableLeft() && assoc2.getCDAssocDir() .isDefinitiveNavigableRight() && deriveRoleName(assoc1, AssocSide.LEFT).equals( deriveRoleName(assoc2, AssocSide.RIGHT))) { - checkRef(node, findTypeByFullName(assoc1, assoc1.getRightQualifiedName().getQName()), - findTypeByFullName(assoc2, assoc2.getLeftQualifiedName().getQName()), assoc1); + checkRef(node, findTypeByFullName(assoc1.getRightQualifiedName()), + findTypeByFullName(assoc2.getLeftQualifiedName()), assoc1); } // ... as well as a right-to-left match if (assoc1.getCDAssocDir().isDefinitiveNavigableRight() && assoc2.getCDAssocDir() .isDefinitiveNavigableLeft() && deriveRoleName(assoc1, AssocSide.RIGHT).equals( deriveRoleName(assoc2, AssocSide.LEFT))) { - checkRef(node, findTypeByFullName(assoc1, assoc1.getLeftQualifiedName().getQName()), - findTypeByFullName(assoc2, assoc2.getRightQualifiedName().getQName()), assoc1); + checkRef(node, findTypeByFullName(assoc1.getLeftQualifiedName()), + findTypeByFullName(assoc2.getRightQualifiedName()), assoc1); } } } @@ -75,24 +77,25 @@ public void check(ASTCDDefinition node) { /** * helper-method to find types by full-name */ - protected ASTCDType findTypeByFullName(ASTCDAssociation node, String fullName) { + protected TypeSymbol findTypeByFullName(ASTMCQualifiedName qualifiedName) { - Optional optSymbol = node.getEnclosingScope().resolveCDType(fullName); - if (optSymbol.isPresent()) { - return optSymbol.get().getAstNode(); + final SymTypeExpression typeExpression = TypeCheck3.symTypeFromAST(qualifiedName); + if (typeExpression.hasTypeInfo()) { + return typeExpression.getTypeInfo(); } - Log.error("0xCDCE2: Could not find: " + fullName + "."); + // This should never be reached, the symbol table completer should have logged an error before and exited + Log.debug("Cannot find symbol " + qualifiedName.getQName() + ".", CDAssociationUnique.class.getName()); return null; } /** Check if type2 is the same as type1. */ - protected void checkRef(ASTCDDefinition node, ASTCDType type1, ASTCDType type2, - ASTCDAssociation assoc1) { + protected void checkRef(ASTCDDefinition node, TypeSymbol type1, TypeSymbol type2, + ASTCDAssociation assoc) { if (type1.equals(type2)) { Log.error(String.format("0xCDCE1: %s has a duplicate association to %s", type1.getName(), - type2.getName()), assoc1.isPresent_SourcePositionStart() ? assoc1 - .get_SourcePositionStart() : null, assoc1.isPresent_SourcePositionEnd() ? assoc1 + type2.getName()), assoc.isPresent_SourcePositionStart() ? assoc + .get_SourcePositionStart() : null, assoc.isPresent_SourcePositionEnd() ? assoc .get_SourcePositionEnd() : null); } } diff --git a/cdlang/src/main/java/de/monticore/cdgen/cocos/CDAssociationUniqueInHierarchy.java b/cdlang/src/main/java/de/monticore/cdgen/cocos/CDAssociationUniqueInHierarchy.java index 60921f77b..d3d3d4ffa 100644 --- a/cdlang/src/main/java/de/monticore/cdgen/cocos/CDAssociationUniqueInHierarchy.java +++ b/cdlang/src/main/java/de/monticore/cdgen/cocos/CDAssociationUniqueInHierarchy.java @@ -4,6 +4,7 @@ import de.monticore.cdassociation._ast.ASTCDAssociation; import de.monticore.cdbasis._ast.ASTCDDefinition; import de.monticore.cdbasis._ast.ASTCDType; +import de.monticore.symbols.basicsymbols._ast.ASTType; import de.monticore.symbols.basicsymbols._symboltable.TypeSymbol; import de.se_rwth.commons.logging.Log; @@ -20,26 +21,26 @@ protected void checkRef(ASTCDDefinition node, ASTCDType type1, ASTCDType type2, ASTCDAssociation assoc1) { super.checkRef(node, type1, type2, assoc1); // We now also check if the types are in a sub/super-type relation - checkSuper(type1, type2); - checkSuper(type2, type1); + checkSuper(type1, type2, assoc); + checkSuper(type2, type1, assoc); } /** Check if type2 is a super-type of type1. */ - protected void checkSuper(ASTCDType type1, ASTCDType type2) { + protected void checkSuper(TypeSymbol type1, TypeSymbol type2, ASTCDAssociation assoc) { Stack typesToVisit = new Stack<>(); // getSymbol().getSuperClassesOnly() did not work for some reason - type1.getSymbol().getSuperClassesOnly().forEach(s -> typesToVisit.push(s.getTypeInfo())); + type1.getSuperClassesOnly().forEach(s -> typesToVisit.push(s.getTypeInfo())); // getSymbol().getInterfaces() did not work for some reason - type1.getSymbol().getInterfaceList().forEach(s -> typesToVisit.push(s.getTypeInfo())); + type1.getInterfaceList().forEach(s -> typesToVisit.push(s.getTypeInfo())); while (!typesToVisit.isEmpty()) { final TypeSymbol nextType = typesToVisit.pop(); - if (nextType.getFullName().equals(type2.getSymbol().getFullName())) { + if (nextType.getFullName().equals(type2.getFullName())) { Log.error(String.format("0xCDCE6: %s redefines an association of %s.", type1.getName(), - type2.getName())); + type2.getName()), assoc.get_SourcePositionStart(), assoc.get_SourcePositionEnd()); return; } diff --git a/cdlang/src/main/java/de/monticore/cdgen/cocos/CDNoAttributesInInterfaces.java b/cdlang/src/main/java/de/monticore/cdgen/cocos/CDNoAttributesInInterfaces.java index 48f567c8c..305249045 100644 --- a/cdlang/src/main/java/de/monticore/cdgen/cocos/CDNoAttributesInInterfaces.java +++ b/cdlang/src/main/java/de/monticore/cdgen/cocos/CDNoAttributesInInterfaces.java @@ -14,7 +14,8 @@ public class CDNoAttributesInInterfaces implements CD4CodeBasisASTCDInterfaceCoC @Override public void check(ASTCDInterface node) { if (node.getCDAttributeList() != null && !node.getCDAttributeList().isEmpty()) { - Log.error(String.format(ERROR_MESSAGE, node.getName()), node.get_SourcePositionStart()); + Log.error(String.format(ERROR_MESSAGE, node.getName()), + node.get_SourcePositionStart(), node.get_SourcePositionEnd()); } } diff --git a/cdlang/src/main/java/de/monticore/cdgen/cocos/CDNoOutgoingAssocs4Interfaces.java b/cdlang/src/main/java/de/monticore/cdgen/cocos/CDNoOutgoingAssocs4Interfaces.java index ad367d551..47e9fe455 100644 --- a/cdlang/src/main/java/de/monticore/cdgen/cocos/CDNoOutgoingAssocs4Interfaces.java +++ b/cdlang/src/main/java/de/monticore/cdgen/cocos/CDNoOutgoingAssocs4Interfaces.java @@ -1,10 +1,13 @@ /* (c) https://github.com/MontiCore/monticore */ package de.monticore.cdgen.cocos; +import de.monticore.cd4code.CD4CodeMill; import de.monticore.cdassociation._cocos.CDAssociationASTCDAssociationCoCo; import de.monticore.cdassociation._ast.ASTCDAssociation; import de.monticore.cdinterfaceandenum._ast.ASTCDInterface; +import de.monticore.types.check.SymTypeExpression; import de.monticore.types.mcbasictypes._ast.ASTMCQualifiedType; +import de.monticore.types3.TypeCheck3; import de.se_rwth.commons.logging.Log; public class CDNoOutgoingAssocs4Interfaces implements CDAssociationASTCDAssociationCoCo { @@ -28,12 +31,16 @@ public void check(ASTCDAssociation node) { protected void checkSide(ASTMCQualifiedType type, ASTCDAssociation context) { // Resolve the symbol for the type and check if it's an interface - context.getEnclosingScope().resolveCDType(type.printType()).ifPresent(symbol -> { - if (symbol.getAstNode() instanceof ASTCDInterface) { - Log.error(String.format(ERROR_MESSAGE, symbol.getName()), context - .get_SourcePositionStart()); + final SymTypeExpression typeExpression = TypeCheck3.symTypeFromAST(type); + if (typeExpression.hasTypeInfo() && !typeExpression.getTypeInfo().isPresentAstNode()) { + if (typeExpression.isObjectType() + && typeExpression.hasTypeInfo() + && CD4CodeMill.typeDispatcher().isOOSymbolsOOType(typeExpression.getTypeInfo()) + && CD4CodeMill.typeDispatcher().asOOSymbolsOOType(typeExpression.getTypeInfo()).isIsInterface()) { + Log.error(String.format(ERROR_MESSAGE, type.printType()), + context.get_SourcePositionStart(), context.get_SourcePositionEnd()); } - }); + } } } diff --git a/cdlang/src/main/java/de/monticore/cdgen/cocos/CDNoOutgoingAssocs4LibraryTypes.java b/cdlang/src/main/java/de/monticore/cdgen/cocos/CDNoOutgoingAssocs4LibraryTypes.java new file mode 100644 index 000000000..0da638bb6 --- /dev/null +++ b/cdlang/src/main/java/de/monticore/cdgen/cocos/CDNoOutgoingAssocs4LibraryTypes.java @@ -0,0 +1,40 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.cdgen.cocos; + +import de.monticore.cdassociation._ast.ASTCDAssociation; +import de.monticore.cdassociation._cocos.CDAssociationASTCDAssociationCoCo; +import de.monticore.symbols.basicsymbols._symboltable.TypeSymbol; +import de.monticore.types.check.SymTypeExpression; +import de.monticore.types.mcbasictypes._ast.ASTMCQualifiedType; +import de.monticore.types3.TypeCheck3; +import de.se_rwth.commons.logging.Log; + +public class CDNoOutgoingAssocs4LibraryTypes implements CDAssociationASTCDAssociationCoCo { + + public static final String ERROR_CODE = "0xCDCE7"; + public static final String ERROR_MESSAGE = ERROR_CODE + + ": Cannot add outgoing associations to imported library type %s."; + + @Override + public void check(ASTCDAssociation node) { + // An association is outgoing from the left side if it is navigable from left to right + if (node.getCDAssocDir().isDefinitiveNavigableRight()) { + checkSide(node.getLeft().getMCQualifiedType(), node); + } + // An association is outgoing from the right side if it is navigable from right to left + if (node.getCDAssocDir().isDefinitiveNavigableLeft()) { + checkSide(node.getRight().getMCQualifiedType(), node); + } + + } + + protected void checkSide(ASTMCQualifiedType type, ASTCDAssociation context) { + // Resolve the symbol for the type and check if it does not have an ASTNode, i.e., is imported + final SymTypeExpression typeExpression = TypeCheck3.symTypeFromAST(type); + if (typeExpression.hasTypeInfo() && !typeExpression.getTypeInfo().isPresentAstNode()) { + Log.error(String.format(ERROR_MESSAGE, type.printType()), + context.get_SourcePositionStart(), context.get_SourcePositionEnd()); + } + } + +} diff --git a/cdlang/src/main/java/de/monticore/cdgen/cocos/CDSingleClassInheritance.java b/cdlang/src/main/java/de/monticore/cdgen/cocos/CDSingleClassInheritance.java index bca3296fd..3e1469479 100644 --- a/cdlang/src/main/java/de/monticore/cdgen/cocos/CDSingleClassInheritance.java +++ b/cdlang/src/main/java/de/monticore/cdgen/cocos/CDSingleClassInheritance.java @@ -14,7 +14,8 @@ public class CDSingleClassInheritance implements CDBasisASTCDClassCoCo { @Override public void check(ASTCDClass node) { if (node.getSuperclassList().size() > 1) { - Log.error(String.format(ERROR_MESSAGE, node.getName()), node.get_SourcePositionStart()); + Log.error(String.format(ERROR_MESSAGE, node.getName()), + node.get_SourcePositionStart(), node.get_SourcePositionEnd()); } } diff --git a/cdlang/src/test/java/de/monticore/cdgen/cocos/CDAssociationUniqueTest.java b/cdlang/src/test/java/de/monticore/cdgen/cocos/CDAssociationUniqueTest.java index 696635cf7..3fc698cfe 100644 --- a/cdlang/src/test/java/de/monticore/cdgen/cocos/CDAssociationUniqueTest.java +++ b/cdlang/src/test/java/de/monticore/cdgen/cocos/CDAssociationUniqueTest.java @@ -84,4 +84,11 @@ public void testDuplicatesDifferentClassesWithExplicitRoles() throws IOException runTestForErrorCode(model, ERROR_CODE); } + @Test + public void testDuplicatesWithC2MC() throws IOException { + String model = "classdiagram DuplicateAssocs {" + " class A;" + + " association A <-> java.lang.Integer;" + " association A <-> java.lang.Integer;" + "}"; + runTestForErrorCode(model, ERROR_CODE); + } + } diff --git a/cdlang/src/test/java/de/monticore/cdgen/cocos/CDNoOutgoingAssocs4LibraryTypesTest.java b/cdlang/src/test/java/de/monticore/cdgen/cocos/CDNoOutgoingAssocs4LibraryTypesTest.java new file mode 100644 index 000000000..c50998433 --- /dev/null +++ b/cdlang/src/test/java/de/monticore/cdgen/cocos/CDNoOutgoingAssocs4LibraryTypesTest.java @@ -0,0 +1,48 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.cdgen.cocos; + +import de.monticore.cd4code._cocos.CD4CodeCoCoChecker; +import de.monticore.cd4code.cocos.AbstractJavaGenCoCoTest; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +public class CDNoOutgoingAssocs4LibraryTypesTest extends AbstractJavaGenCoCoTest { + + @Override + protected CD4CodeCoCoChecker createChecker() { + CD4CodeCoCoChecker checker = new CD4CodeCoCoChecker(); + checker.addCoCo(new CDNoOutgoingAssocs4LibraryTypes()); + return checker; + } + + @Test + public void testValidAssociation() throws IOException { + String model = "classdiagram Valid {" + " interface I;" + " class C;" + + " association C -> I;" + "}"; + runTest(model, false); + } + + @Test + public void testInvalidDirectedAssociation() throws IOException { + String model = "classdiagram Invalid {" + " class C;" + + " association java.lang.Integer -> C;" + "}"; + runTestForErrorCode(model, CDNoOutgoingAssocs4LibraryTypes.ERROR_CODE); + } + + @Test + public void testInvalidBidirectionalAssociation() throws IOException { + String model = "classdiagram Invalid {" + " class C;" + + " association java.lang.Integer <-> C;" + "}"; + runTestForErrorCode(model, CDNoOutgoingAssocs4LibraryTypes.ERROR_CODE); + } + + @Test + public void testUndirectedAssociation() throws IOException { + // An undirected association is not definitively navigable, so it does not trigger an error. + String model = "classdiagram Valid {" + " class C;" + + " association java.lang.Integer -- C;" + "}"; + runTest(model, false); + } + +} diff --git a/cdlang/src/test/resources/de/monticore/cd4code/cocos/CDAssociationUniqueInHierarchyValid.cd b/cdlang/src/test/resources/de/monticore/cd4code/cocos/CDAssociationUniqueInHierarchyValid.cd index c4da4a19b..d5c52491e 100644 --- a/cdlang/src/test/resources/de/monticore/cd4code/cocos/CDAssociationUniqueInHierarchyValid.cd +++ b/cdlang/src/test/resources/de/monticore/cd4code/cocos/CDAssociationUniqueInHierarchyValid.cd @@ -17,25 +17,25 @@ classdiagram CDAssociationUniqueInHierarchyValid { } package p2 { - class A2; - class B2; + class A2; + class B2; - class superclass2; - class subclass2 extends superclass2; + class superclass2; + class subclass2 extends superclass2; - association superclass2 -> (foo) A2; - association subclass2 -> (bar) B2; + association superclass2 -> (foo) A2; + association subclass2 -> (bar) B2; } - package p3 { - class A3; - class B3; + package p3 { + class A3; + class B3; - class superclass3; - class subclass3 extends superclass3; + class superclass3; + class subclass3 extends superclass3; - association p3.superclass3 -> (foo) A3; - association p3.subclass3 (foo) <- B3; - } + association p3.superclass3 -> (foo) A3; + association p3.subclass3 (foo) <- B3; + } }