From 7035c66aa8d0452c31d5e2b4b52098a5f3b4ced5 Mon Sep 17 00:00:00 2001 From: Felix Jordan Date: Sun, 29 Mar 2026 15:34:52 +0200 Subject: [PATCH 01/16] feat(cdconf): ADAPTED_NAME_MAPPING matching strategies --- .../cdconformance/CDConfParameter.java | 9 +- .../DefaultCDConformanceContext.java | 14 ++- .../AdaptedRoleNameAssocIncStrategy.java | 50 ++++++++ .../AdaptedNameAttributeIncStrategy.java | 71 +++++++++++ .../method/AdaptedNameMethodIncStrategy.java | 110 ++++++++++++++++++ .../CDConformanceCheckerTest.java | 97 +++++++++++++++ .../associations/adapted_name/Reference.cd | 9 ++ .../adapted_name/invalid/WrongRoles.cd | 8 ++ .../adapted_name/valid/AdaptedRoles.cd | 12 ++ .../attributes/adapted_name/Reference.cd | 7 ++ .../adapted_name/invalid/WrongName.cd | 9 ++ .../adapted_name/valid/AdaptedName.cd | 10 ++ .../methods/adapted_name/Reference.cd | 8 ++ .../methods/adapted_name/invalid/WrongName.cd | 10 ++ .../methods/adapted_name/valid/AdaptedName.cd | 12 ++ 15 files changed, 434 insertions(+), 2 deletions(-) create mode 100644 cddiff/src/main/java/de/monticore/cdconformance/inc/association/AdaptedRoleNameAssocIncStrategy.java create mode 100644 cddiff/src/main/java/de/monticore/cdconformance/inc/attribute/AdaptedNameAttributeIncStrategy.java create mode 100644 cddiff/src/main/java/de/monticore/cdconformance/inc/method/AdaptedNameMethodIncStrategy.java create mode 100644 cddiff/src/test/resources/de/monticore/cdconformance/associations/adapted_name/Reference.cd create mode 100644 cddiff/src/test/resources/de/monticore/cdconformance/associations/adapted_name/invalid/WrongRoles.cd create mode 100644 cddiff/src/test/resources/de/monticore/cdconformance/associations/adapted_name/valid/AdaptedRoles.cd create mode 100644 cddiff/src/test/resources/de/monticore/cdconformance/attributes/adapted_name/Reference.cd create mode 100644 cddiff/src/test/resources/de/monticore/cdconformance/attributes/adapted_name/invalid/WrongName.cd create mode 100644 cddiff/src/test/resources/de/monticore/cdconformance/attributes/adapted_name/valid/AdaptedName.cd create mode 100644 cddiff/src/test/resources/de/monticore/cdconformance/methods/adapted_name/Reference.cd create mode 100644 cddiff/src/test/resources/de/monticore/cdconformance/methods/adapted_name/invalid/WrongName.cd create mode 100644 cddiff/src/test/resources/de/monticore/cdconformance/methods/adapted_name/valid/AdaptedName.cd diff --git a/cddiff/src/main/java/de/monticore/cdconformance/CDConfParameter.java b/cddiff/src/main/java/de/monticore/cdconformance/CDConfParameter.java index 8550ec582..145aadc62 100644 --- a/cddiff/src/main/java/de/monticore/cdconformance/CDConfParameter.java +++ b/cddiff/src/main/java/de/monticore/cdconformance/CDConfParameter.java @@ -25,7 +25,14 @@ public enum CDConfParameter { ALLOW_ADDITIONAL_PARAMETERS("when added, a concrete method can have additional" + "parameters compared to the reference method. By default, a method must have the same" + "parameters as the reference method. If combined with " + STRICT_PARAMETER_ORDER + " " - + "the additional parameters must be at the end of the parameter list."); + + "the additional parameters must be at the end of the parameter list."), + + ADAPTED_NAME_MAPPING("when added, names in the concrete CD that are derived via implicit name" + + " adaptation (using element type incarnations) are accepted as incarnations of the" + + " corresponding reference elements without requiring explicit stereotype mappings." + + " Applies to attributes, methods, and association roles. For example, 'assignedTickets'" + + " is accepted as an incarnation of 'assignedTasks' when 'Ticket' incarnates 'Task'," + + " and 'findTicket' as an incarnation of 'findTask'."); private final String description; diff --git a/cddiff/src/main/java/de/monticore/cdconformance/DefaultCDConformanceContext.java b/cddiff/src/main/java/de/monticore/cdconformance/DefaultCDConformanceContext.java index 47b595964..aeb976be3 100644 --- a/cddiff/src/main/java/de/monticore/cdconformance/DefaultCDConformanceContext.java +++ b/cddiff/src/main/java/de/monticore/cdconformance/DefaultCDConformanceContext.java @@ -6,6 +6,7 @@ import de.monticore.cdbasis._ast.ASTCDType; import de.monticore.cdconformance.inc.*; import de.monticore.cdconformance.inc.association.*; +import de.monticore.cdconformance.inc.attribute.AdaptedNameAttributeIncStrategy; import de.monticore.cdconformance.inc.attribute.CDAttributeMatchingStrategy; import de.monticore.cdconformance.inc.attribute.CompAttributeIncStrategy; import de.monticore.cdconformance.inc.attribute.EqNameAttributeIncStrategy; @@ -159,7 +160,18 @@ public static CDConformanceContext create(ASTCDCompilationUnit concreteCD, compTypeIncStrategy, concreteCD, referenceCD, mapping)); } } - + if (conformanceParams.contains(CDConfParameter.ADAPTED_NAME_MAPPING)) { + ExternalCandidatesMatchingStrategy typeMatcherForAdapted = + conformanceParams.contains(CDConfParameter.INHERITANCE) ? compSubTypeIncStrategy + : compTypeIncStrategy; + compAssocIncStrategy.addIncStrategy( + new AdaptedRoleNameAssocIncStrategy(typeMatcherForAdapted, concreteCD, referenceCD)); + compAttributeIncStrategy.addIncStrategy( + new AdaptedNameAttributeIncStrategy(typeMatcherForAdapted)); + compMethodIncStrategy.addIncStrategy( + new AdaptedNameMethodIncStrategy(typeMatcherForAdapted, mcTypeMatcher)); + } + return new DefaultCDConformanceContext(concreteCD, referenceCD, mapping, underspecifiedPlaceholderTypeName, conformanceParams, compTypeIncStrategy, compSubTypeIncStrategy, compAssocIncStrategy, compAttributeIncStrategy, diff --git a/cddiff/src/main/java/de/monticore/cdconformance/inc/association/AdaptedRoleNameAssocIncStrategy.java b/cddiff/src/main/java/de/monticore/cdconformance/inc/association/AdaptedRoleNameAssocIncStrategy.java new file mode 100644 index 000000000..625f01568 --- /dev/null +++ b/cddiff/src/main/java/de/monticore/cdconformance/inc/association/AdaptedRoleNameAssocIncStrategy.java @@ -0,0 +1,50 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.cdconformance.inc.association; + +import de.monticore.cdassociation._ast.ASTCDAssocSide; +import de.monticore.cdbasis._ast.ASTCDCompilationUnit; +import de.monticore.cdbasis._ast.ASTCDType; +import de.monticore.cdconcretization.util.NameUtil; +import de.monticore.cdmatcher.ExternalCandidatesMatchingStrategy; +import de.monticore.cdmatcher.MatchCDAssocsBySrcTypeAndTgtRole; + +import java.util.Optional; + +/** + * Matches associations by source type and target role, accepting role names that are derived via + * implicit name adaptation from the reference role name.
+ *
+ * For example, if the reference role is {@code assignedTasks} and the concrete type {@code Ticket} + * incarnates the reference type {@code Task}, then the concrete role {@code assignedTickets} is + * accepted as an incarnation of {@code assignedTasks}, because + * {@link NameUtil#adaptTemplatedName(String, String, String) adaptTemplatedName("assignedTasks", + * "Task", "Ticket")} produces {@code assignedTickets}. + */ +public class AdaptedRoleNameAssocIncStrategy extends MatchCDAssocsBySrcTypeAndTgtRole { + + public AdaptedRoleNameAssocIncStrategy( + ExternalCandidatesMatchingStrategy typeMatcher, ASTCDCompilationUnit srcCD, + ASTCDCompilationUnit tgtCD) { + super(typeMatcher, srcCD, tgtCD); + } + + @Override + protected boolean checkRole(ASTCDAssocSide concrete, ASTCDAssocSide reference) { + Optional conType = resolveConcreteCDTyp( + concrete.getMCQualifiedType().getMCQualifiedName().getQName()); + Optional refType = resolveReferenceCDTyp( + reference.getMCQualifiedType().getMCQualifiedName().getQName()); + + if (conType.isPresent() && refType.isPresent() + && typeMatcher.isMatched(conType.get(), refType.get())) { + if (reference.isPresentCDRole() && concrete.isPresentCDRole()) { + String refRoleName = reference.getCDRole().getName(); + String conRoleName = concrete.getCDRole().getName(); + return NameUtil.adaptTemplatedName(refRoleName, refType.get().getName(), + conType.get().getName()).map(adapted -> adapted.equals(conRoleName)).orElse(false); + } + } + return false; + } + +} diff --git a/cddiff/src/main/java/de/monticore/cdconformance/inc/attribute/AdaptedNameAttributeIncStrategy.java b/cddiff/src/main/java/de/monticore/cdconformance/inc/attribute/AdaptedNameAttributeIncStrategy.java new file mode 100644 index 000000000..3743b2440 --- /dev/null +++ b/cddiff/src/main/java/de/monticore/cdconformance/inc/attribute/AdaptedNameAttributeIncStrategy.java @@ -0,0 +1,71 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.cdconformance.inc.attribute; + +import de.monticore.cd4code.CD4CodeMill; +import de.monticore.cdbasis._ast.ASTCDAttribute; +import de.monticore.cdbasis._ast.ASTCDType; +import de.monticore.cdbasis._symboltable.CDTypeSymbol; +import de.monticore.cdconcretization.util.NameUtil; +import de.monticore.cdmatcher.BooleanMatchingStrategy; +import de.monticore.types.mcbasictypes._ast.ASTMCQualifiedType; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * Matches a concrete attribute to a reference attribute when the concrete attribute name is the + * implicit-name-adapted form of the reference attribute name.
+ *
+ * For example, {@code assignedTickets: Ticket} is matched to {@code assignedTasks: Task} when + * {@code Ticket} incarnates {@code Task}, because + * {@link NameUtil#adaptTemplatedName(String, String, String) + * adaptTemplatedName("assignedTasks", "Task", "Ticket")} produces {@code "assignedTickets"}. + */ +public class AdaptedNameAttributeIncStrategy implements CDAttributeMatchingStrategy { + + private final BooleanMatchingStrategy typeMatcher; + private ASTCDType referenceType; + + public AdaptedNameAttributeIncStrategy(BooleanMatchingStrategy typeMatcher) { + this.typeMatcher = typeMatcher; + } + + @Override + public List getMatchedElements(ASTCDAttribute concrete) { + return referenceType.getCDAttributeList().stream().filter(attr -> isMatched(concrete, attr)) + .collect(Collectors.toList()); + } + + @Override + public boolean isMatched(ASTCDAttribute concrete, ASTCDAttribute ref) { + Optional refAttrType = resolveCDType(ref); + Optional conAttrType = resolveCDType(concrete); + if (refAttrType.isPresent() && conAttrType.isPresent() + && typeMatcher.isMatched(conAttrType.get(), refAttrType.get())) { + return NameUtil.adaptTemplatedName(ref.getName(), refAttrType.get().getName(), + conAttrType.get().getName()).map(adapted -> adapted.equals(concrete.getName())) + .orElse(false); + } + return false; + } + + @Override + public void setReferenceType(ASTCDType referenceType) { + this.referenceType = referenceType; + } + + private Optional resolveCDType(ASTCDAttribute attribute) { + // Only qualified types can be CD types; primitives and collection types cannot + if (!(attribute.getMCType() instanceof ASTMCQualifiedType)) { + return Optional.empty(); + } + // Use global scope lookup to avoid NPE on types without enclosing scope (e.g., deep-cloned + // elements added during concretization that have not been through symbol table construction) + String typeName = ((ASTMCQualifiedType) attribute.getMCType()).getMCQualifiedName().getQName(); + return CD4CodeMill.globalScope().resolveCDTypeDown(typeName) + .filter(sym -> sym.isPresentAstNode()) + .map(CDTypeSymbol::getAstNode); + } + +} diff --git a/cddiff/src/main/java/de/monticore/cdconformance/inc/method/AdaptedNameMethodIncStrategy.java b/cddiff/src/main/java/de/monticore/cdconformance/inc/method/AdaptedNameMethodIncStrategy.java new file mode 100644 index 000000000..cb5b12087 --- /dev/null +++ b/cddiff/src/main/java/de/monticore/cdconformance/inc/method/AdaptedNameMethodIncStrategy.java @@ -0,0 +1,110 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.cdconformance.inc.method; + +import de.monticore.cd4code.CD4CodeMill; +import de.monticore.cd4codebasis._ast.ASTCDMethod; +import de.monticore.cdbasis._ast.ASTCDType; +import de.monticore.cdbasis._symboltable.CDTypeSymbol; +import de.monticore.cdconcretization.util.NameUtil; +import de.monticore.cdconformance.inc.mctype.MCTypeMatchingStrategy; +import de.monticore.cdmatcher.BooleanMatchingStrategy; +import de.monticore.types.mcbasictypes._ast.ASTMCQualifiedType; +import de.monticore.types.mcbasictypes._ast.ASTMCType; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * Matches a concrete method to a reference method when the concrete method name is the + * implicit-name-adapted form of the reference method name, derived by applying the type + * incarnation pairs of the return type and parameter types sequentially.
+ *
+ * For example, {@code findTicket(String id)} is matched to {@code findTask(String id)} when no + * CD-type parameter exists but the return type adapts the name, and + * {@code compareFooAndBar(Foo foo, Bar bar)} is matched to + * {@code compareInputAndOutput(Input input, Output output)} when {@code Foo} incarnates + * {@code Input} and {@code Bar} incarnates {@code Output}. + */ +public class AdaptedNameMethodIncStrategy implements CDMethodMatchingStrategy { + + private final BooleanMatchingStrategy typeMatcher; + private final MCTypeMatchingStrategy mcTypeMatcher; + private ASTCDType refType; + + public AdaptedNameMethodIncStrategy(BooleanMatchingStrategy typeMatcher, + MCTypeMatchingStrategy mcTypeMatcher) { + this.typeMatcher = typeMatcher; + this.mcTypeMatcher = mcTypeMatcher; + } + + @Override + public List getMatchedElements(ASTCDMethod concrete) { + return refType.getCDMethodList().stream().filter(method -> isMatched(concrete, method)) + .collect(Collectors.toList()); + } + + @Override + public boolean isMatched(ASTCDMethod concrete, ASTCDMethod ref) { + if (ref.getCDParameterList().size() != concrete.getCDParameterList().size()) { + return false; + } + + // Apply all relevant type pairs sequentially to compute the adapted reference method name + String adaptedName = ref.getName(); + + if (ref.getMCReturnType().isPresentMCType() && concrete.getMCReturnType().isPresentMCType()) { + Optional refRetType = resolveCDType(ref.getMCReturnType().getMCType()); + Optional conRetType = resolveCDType(concrete.getMCReturnType().getMCType()); + if (refRetType.isPresent() && conRetType.isPresent() + && typeMatcher.isMatched(conRetType.get(), refRetType.get())) { + adaptedName = NameUtil.adaptTemplatedName(adaptedName, refRetType.get().getName(), + conRetType.get().getName()).orElse(adaptedName); + } + } + + for (int i = 0; i < ref.getCDParameterList().size(); i++) { + Optional refParamType = resolveCDType( + ref.getCDParameterList().get(i).getMCType()); + Optional conParamType = resolveCDType( + concrete.getCDParameterList().get(i).getMCType()); + if (refParamType.isPresent() && conParamType.isPresent() + && typeMatcher.isMatched(conParamType.get(), refParamType.get())) { + adaptedName = NameUtil.adaptTemplatedName(adaptedName, refParamType.get().getName(), + conParamType.get().getName()).orElse(adaptedName); + } + } + + if (!adaptedName.equals(concrete.getName())) { + return false; + } + + // Verify parameter types are incarnation-compatible (in strict order) + for (int i = 0; i < ref.getCDParameterList().size(); i++) { + if (!mcTypeMatcher.isMatched(concrete.getCDParameterList().get(i).getMCType(), + ref.getCDParameterList().get(i).getMCType(), typeMatcher)) { + return false; + } + } + return true; + } + + @Override + public void setReferenceType(ASTCDType refType) { + this.refType = refType; + } + + private Optional resolveCDType(ASTMCType mcType) { + // Only qualified types can be CD types; primitives and collection types cannot + if (!(mcType instanceof ASTMCQualifiedType)) { + return Optional.empty(); + } + // Use global scope lookup to avoid NPE on types without enclosing scope (e.g., deep-cloned + // elements added during concretization that have not been through symbol table construction) + String typeName = ((ASTMCQualifiedType) mcType).getMCQualifiedName().getQName(); + return CD4CodeMill.globalScope().resolveCDTypeDown(typeName) + .filter(sym -> sym.isPresentAstNode()) + .map(CDTypeSymbol::getAstNode); + } + +} diff --git a/cddiff/src/test/java/de/monticore/cdconformance/CDConformanceCheckerTest.java b/cddiff/src/test/java/de/monticore/cdconformance/CDConformanceCheckerTest.java index bfeda31df..567e0e6ab 100644 --- a/cddiff/src/test/java/de/monticore/cdconformance/CDConformanceCheckerTest.java +++ b/cddiff/src/test/java/de/monticore/cdconformance/CDConformanceCheckerTest.java @@ -304,6 +304,103 @@ public void testAssocSameTypeMultipleRolesExplicitAssocNamesValid(String concret assertTrue(checker.checkConformance(conCD, refCD, "ref")); } + // ---- ADAPTED_NAME_MAPPING: attributes ---- + + @ParameterizedTest + @ValueSource(strings = { "AdaptedName.cd" }) + public void testAttributeAdaptedNameValid(String concrete) { + parseModels("attributes/adapted_name/valid/" + concrete, + "attributes/adapted_name/Reference.cd"); + checker = new CDConformanceChecker(Set.of(STEREOTYPE_MAPPING, NAME_MAPPING, + ADAPTED_NAME_MAPPING)); + assertTrue(checker.checkConformance(conCD, refCD, "ref")); + } + + @ParameterizedTest + @ValueSource(strings = { "WrongName.cd" }) + public void testAttributeAdaptedNameInvalid(String concrete) { + parseModels("attributes/adapted_name/invalid/" + concrete, + "attributes/adapted_name/Reference.cd"); + checker = new CDConformanceChecker(Set.of(STEREOTYPE_MAPPING, NAME_MAPPING, + ADAPTED_NAME_MAPPING)); + assertFalse(checker.checkConformance(conCD, refCD, "ref")); + } + + /** Adapted name is rejected when ADAPTED_NAME_MAPPING is not enabled. */ + @Test + public void testAttributeAdaptedNameRequiresParam() { + parseModels("attributes/adapted_name/valid/AdaptedName.cd", + "attributes/adapted_name/Reference.cd"); + checker = new CDConformanceChecker(Set.of(STEREOTYPE_MAPPING, NAME_MAPPING)); + assertFalse(checker.checkConformance(conCD, refCD, "ref")); + } + + // ---- ADAPTED_NAME_MAPPING: methods ---- + + @ParameterizedTest + @ValueSource(strings = { "AdaptedName.cd" }) + public void testMethodAdaptedNameValid(String concrete) { + parseModels("methods/adapted_name/valid/" + concrete, + "methods/adapted_name/Reference.cd"); + checker = new CDConformanceChecker(Set.of(STEREOTYPE_MAPPING, NAME_MAPPING, + ADAPTED_NAME_MAPPING, STRICT_PARAMETER_ORDER)); + assertTrue(checker.checkConformance(conCD, refCD, "ref")); + } + + @ParameterizedTest + @ValueSource(strings = { "WrongName.cd" }) + public void testMethodAdaptedNameInvalid(String concrete) { + parseModels("methods/adapted_name/invalid/" + concrete, + "methods/adapted_name/Reference.cd"); + checker = new CDConformanceChecker(Set.of(STEREOTYPE_MAPPING, NAME_MAPPING, + ADAPTED_NAME_MAPPING, STRICT_PARAMETER_ORDER)); + assertFalse(checker.checkConformance(conCD, refCD, "ref")); + } + + /** Adapted method name is rejected when ADAPTED_NAME_MAPPING is not enabled. */ + @Test + public void testMethodAdaptedNameRequiresParam() { + parseModels("methods/adapted_name/valid/AdaptedName.cd", + "methods/adapted_name/Reference.cd"); + checker = new CDConformanceChecker(Set.of(STEREOTYPE_MAPPING, NAME_MAPPING, + STRICT_PARAMETER_ORDER)); + assertFalse(checker.checkConformance(conCD, refCD, "ref")); + } + + // ---- ADAPTED_NAME_MAPPING: association roles ---- + // Uses custom (non-implicit) role names to specifically exercise AdaptedRoleNameAssocIncStrategy + // rather than ImplicitRoleNameAssocIncStrategy. + + @ParameterizedTest + @ValueSource(strings = { "AdaptedRoles.cd" }) + public void testAssocAdaptedRoleNameValid(String concrete) { + parseModels("associations/adapted_name/valid/" + concrete, + "associations/adapted_name/Reference.cd"); + checker = new CDConformanceChecker(Set.of(STEREOTYPE_MAPPING, NAME_MAPPING, + SRC_TARGET_ASSOC_MAPPING, ADAPTED_NAME_MAPPING)); + assertTrue(checker.checkConformance(conCD, refCD, "ref")); + } + + @ParameterizedTest + @ValueSource(strings = { "WrongRoles.cd" }) + public void testAssocAdaptedRoleNameInvalid(String concrete) { + parseModels("associations/adapted_name/invalid/" + concrete, + "associations/adapted_name/Reference.cd"); + checker = new CDConformanceChecker(Set.of(STEREOTYPE_MAPPING, NAME_MAPPING, + SRC_TARGET_ASSOC_MAPPING, ADAPTED_NAME_MAPPING)); + assertFalse(checker.checkConformance(conCD, refCD, "ref")); + } + + /** Custom adapted role names are rejected when ADAPTED_NAME_MAPPING is not enabled. */ + @Test + public void testAssocAdaptedRoleNameRequiresParam() { + parseModels("associations/adapted_name/valid/AdaptedRoles.cd", + "associations/adapted_name/Reference.cd"); + checker = new CDConformanceChecker(Set.of(STEREOTYPE_MAPPING, NAME_MAPPING, + SRC_TARGET_ASSOC_MAPPING)); + assertFalse(checker.checkConformance(conCD, refCD, "ref")); + } + /** * Example from KMR24 for multiple mappings. */ diff --git a/cddiff/src/test/resources/de/monticore/cdconformance/associations/adapted_name/Reference.cd b/cddiff/src/test/resources/de/monticore/cdconformance/associations/adapted_name/Reference.cd new file mode 100644 index 000000000..a80fce7a3 --- /dev/null +++ b/cddiff/src/test/resources/de/monticore/cdconformance/associations/adapted_name/Reference.cd @@ -0,0 +1,9 @@ +// Role names "myAccount" and "myPerson" are custom (not the implicit lowercase role names), +// so ImplicitRoleNameAssocIncStrategy would not match them. AdaptedRoleNameAssocIncStrategy +// is required. +classdiagram Reference { + class Account; + class Person; + + association Account (myAccount) -> (myPerson) Person[1]; +} diff --git a/cddiff/src/test/resources/de/monticore/cdconformance/associations/adapted_name/invalid/WrongRoles.cd b/cddiff/src/test/resources/de/monticore/cdconformance/associations/adapted_name/invalid/WrongRoles.cd new file mode 100644 index 000000000..0c6250a15 --- /dev/null +++ b/cddiff/src/test/resources/de/monticore/cdconformance/associations/adapted_name/invalid/WrongRoles.cd @@ -0,0 +1,8 @@ +// BankAccount incarnates Account, User incarnates Person, but the role names do not +// follow the template adaptation — should fail even with ADAPTED_NAME_MAPPING. +classdiagram WrongRoles { + <> class BankAccount; + <> class User; + + association BankAccount (randomRole) -> (anotherRole) User[1]; +} diff --git a/cddiff/src/test/resources/de/monticore/cdconformance/associations/adapted_name/valid/AdaptedRoles.cd b/cddiff/src/test/resources/de/monticore/cdconformance/associations/adapted_name/valid/AdaptedRoles.cd new file mode 100644 index 000000000..9895e76e3 --- /dev/null +++ b/cddiff/src/test/resources/de/monticore/cdconformance/associations/adapted_name/valid/AdaptedRoles.cd @@ -0,0 +1,12 @@ +// BankAccount incarnates Account, User incarnates Person. +// Role names adapted via the endpoint type incarnation pairs: +// adaptTemplatedName("myAccount", "Account", "BankAccount") = "myBankAccount" +// adaptTemplatedName("myPerson", "Person", "User") = "myUser" +// ADAPTED_NAME_MAPPING is required; ImplicitRoleNameAssocIncStrategy does not cover +// custom role names like "myAccount"/"myBankAccount". +classdiagram AdaptedRoles { + <> class BankAccount; + <> class User; + + association BankAccount (myBankAccount) -> (myUser) User[1]; +} diff --git a/cddiff/src/test/resources/de/monticore/cdconformance/attributes/adapted_name/Reference.cd b/cddiff/src/test/resources/de/monticore/cdconformance/attributes/adapted_name/Reference.cd new file mode 100644 index 000000000..0231b53c1 --- /dev/null +++ b/cddiff/src/test/resources/de/monticore/cdconformance/attributes/adapted_name/Reference.cd @@ -0,0 +1,7 @@ +classdiagram Reference { + class Task; + + class Board { + Task assignedTask; + } +} diff --git a/cddiff/src/test/resources/de/monticore/cdconformance/attributes/adapted_name/invalid/WrongName.cd b/cddiff/src/test/resources/de/monticore/cdconformance/attributes/adapted_name/invalid/WrongName.cd new file mode 100644 index 000000000..4a3d8b6a2 --- /dev/null +++ b/cddiff/src/test/resources/de/monticore/cdconformance/attributes/adapted_name/invalid/WrongName.cd @@ -0,0 +1,9 @@ +// Ticket incarnates Task, but the attribute name "item" is not the adapted form of +// "assignedTask" — should fail even with ADAPTED_NAME_MAPPING. +classdiagram WrongName { + <> class Ticket; + + class Board { + Ticket item; + } +} diff --git a/cddiff/src/test/resources/de/monticore/cdconformance/attributes/adapted_name/valid/AdaptedName.cd b/cddiff/src/test/resources/de/monticore/cdconformance/attributes/adapted_name/valid/AdaptedName.cd new file mode 100644 index 000000000..ab6dba927 --- /dev/null +++ b/cddiff/src/test/resources/de/monticore/cdconformance/attributes/adapted_name/valid/AdaptedName.cd @@ -0,0 +1,10 @@ +// Ticket incarnates Task. The attribute name "assignedTask" is adapted to "assignedTicket" +// via adaptTemplatedName("assignedTask", "Task", "Ticket") = "assignedTicket". +// ADAPTED_NAME_MAPPING is required for the conformance checker to accept this. +classdiagram AdaptedName { + <> class Ticket; + + class Board { + Ticket assignedTicket; + } +} diff --git a/cddiff/src/test/resources/de/monticore/cdconformance/methods/adapted_name/Reference.cd b/cddiff/src/test/resources/de/monticore/cdconformance/methods/adapted_name/Reference.cd new file mode 100644 index 000000000..670e2c373 --- /dev/null +++ b/cddiff/src/test/resources/de/monticore/cdconformance/methods/adapted_name/Reference.cd @@ -0,0 +1,8 @@ +classdiagram Reference { + class Task; + + class Processor { + Task findTask(); + void processTask(Task task); + } +} diff --git a/cddiff/src/test/resources/de/monticore/cdconformance/methods/adapted_name/invalid/WrongName.cd b/cddiff/src/test/resources/de/monticore/cdconformance/methods/adapted_name/invalid/WrongName.cd new file mode 100644 index 000000000..df50cc894 --- /dev/null +++ b/cddiff/src/test/resources/de/monticore/cdconformance/methods/adapted_name/invalid/WrongName.cd @@ -0,0 +1,10 @@ +// Ticket incarnates Task, but the method names do not follow the template adaptation — +// should fail even with ADAPTED_NAME_MAPPING. +classdiagram WrongName { + <> class Ticket; + + <> class TicketProcessor { + Ticket doSomething(); + void handleItem(Ticket ticket); + } +} diff --git a/cddiff/src/test/resources/de/monticore/cdconformance/methods/adapted_name/valid/AdaptedName.cd b/cddiff/src/test/resources/de/monticore/cdconformance/methods/adapted_name/valid/AdaptedName.cd new file mode 100644 index 000000000..b3fab0308 --- /dev/null +++ b/cddiff/src/test/resources/de/monticore/cdconformance/methods/adapted_name/valid/AdaptedName.cd @@ -0,0 +1,12 @@ +// Ticket incarnates Task. Method names adapted via the return/parameter type incarnation pair: +// adaptTemplatedName("findTask", "Task", "Ticket") = "findTicket" +// adaptTemplatedName("processTask", "Task", "Ticket") = "processTicket" +// ADAPTED_NAME_MAPPING is required for the conformance checker to accept this. +classdiagram AdaptedName { + <> class Ticket; + + <> class TicketProcessor { + Ticket findTicket(); + void processTicket(Ticket ticket); + } +} From 2b8e7a04bcd4df83fce2f9f36e08ba0dda160ee3 Mon Sep 17 00:00:00 2001 From: Felix Jordan Date: Tue, 24 Mar 2026 22:07:00 +0100 Subject: [PATCH 02/16] test(cdconc): examples from MODELS paper --- .../EvaluationConcretizationTest.java | 46 +++++++++++++------ .../MethodConcretizationTest.java | 8 ++-- .../repository/EcommerceDomain.cd | 19 ++++++++ .../repository/RepositoryOut.cd | 40 ++++++++++++++++ .../repository/RepositoryRef.cd | 15 ++++++ .../task-management/TaskManagementRef.cd | 28 +++++++++++ .../task-management/TicketSystemConc.cd | 14 ++++++ .../task-management/TicketSystemOut.cd | 28 +++++++++++ 8 files changed, 181 insertions(+), 17 deletions(-) create mode 100644 cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/repository/EcommerceDomain.cd create mode 100644 cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/repository/RepositoryOut.cd create mode 100644 cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/repository/RepositoryRef.cd create mode 100644 cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/task-management/TaskManagementRef.cd create mode 100644 cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/task-management/TicketSystemConc.cd create mode 100644 cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/task-management/TicketSystemOut.cd diff --git a/cddiff/src/test/java/de/monticore/cdconcretization/EvaluationConcretizationTest.java b/cddiff/src/test/java/de/monticore/cdconcretization/EvaluationConcretizationTest.java index 3e5d0959a..61f94ef66 100644 --- a/cddiff/src/test/java/de/monticore/cdconcretization/EvaluationConcretizationTest.java +++ b/cddiff/src/test/java/de/monticore/cdconcretization/EvaluationConcretizationTest.java @@ -18,7 +18,7 @@ class EvaluationConcretizationTest extends AbstractCDConcretizationTest { @Test void testBuilderAndMillPattern() { - // TODO Remove once we have explicit support for 'forEach' conformance check + // TODO Remove once we support method parameters in incarnation mapping confParameters.add(CDConfParameter.STRICT_PARAMETER_ORDER); CDConformanceChecker checker = testConcretizedConformsToRefAndExpectedOut( "evaluation/builder/DataModelConc.cd", "evaluation/builder/BuilderAndMillRef.cd", @@ -65,7 +65,7 @@ void testGetter() { @Test void testSetter() { - // TODO Remove once we have explicit support for 'forEach' conformance check + // TODO Remove once we support method parameters in incarnation mapping confParameters.add(CDConfParameter.STRICT_PARAMETER_ORDER); CDConformanceChecker checker = testConcretizedConformsToRefAndExpectedOut( "evaluation/getter-setter/DataModelConc.cd", "evaluation/getter-setter/SetterRef.cd", @@ -88,7 +88,7 @@ void testSetter() { @Test void testVisitorPattern() { - // TODO Remove once we have explicit support for 'forEach' conformance check + // TODO Remove once we support method parameters in incarnation mapping confParameters.add(CDConfParameter.STRICT_PARAMETER_ORDER); CDConformanceChecker checker = testConcretizedConformsToRefAndExpectedOut( "evaluation/visitor/VisitorConc.cd", "evaluation/visitor/VisitorRef.cd", @@ -120,7 +120,7 @@ void testVisitorPattern() { @Test @Disabled("shows limitation of current concept") void testCrossReferences() { - // TODO Remove once we have explicit support for 'forEach' conformance check + // TODO Remove once we support method parameters in incarnation mapping confParameters.add(CDConfParameter.STRICT_PARAMETER_ORDER); CDConformanceChecker checker = testConcretizedConformsToRefAndExpectedOut( "evaluation/cross-references/MicroserviceConc.cd", @@ -195,7 +195,7 @@ void mutualObservers() { @Test void testRepository() { - // TODO Remove once we have explicit support for 'forEach' conformance check + // TODO Remove once we support method parameters in incarnation mapping confParameters.add(CDConfParameter.STRICT_PARAMETER_ORDER); testConcretizedConformsToRefAndExpectedOut("evaluation/repository/DomainModel.cd", "evaluation/repository/RepositoryRef.cd", "evaluation/repository/RepositoryOut.cd"); @@ -206,7 +206,7 @@ class CRUDBackend { @Test void testCRUDBackend() { - // TODO Remove once we have explicit support for 'forEach' conformance check + // TODO Remove once we support method parameters in incarnation mapping confParameters.add(CDConfParameter.STRICT_PARAMETER_ORDER); testConcretizedConformsToRefAndExpectedOut("evaluation/crud-backend/DomainModel.cd", "evaluation/crud-backend/CRUDBackendRef.cd", "evaluation/crud-backend/CRUDBackendOut.cd"); @@ -216,7 +216,7 @@ void testCRUDBackend() { @Test void testMill() { - // TODO Remove once we have explicit support for 'forEach' conformance check + // TODO Remove once we support method parameters in incarnation mapping confParameters.add(CDConfParameter.STRICT_PARAMETER_ORDER); testConcretizedConformsToRefAndExpectedOut("evaluation/mill/LanguageInfrastructureConc.cd", "evaluation/mill/MillRef.cd", "evaluation/mill/MillOut.cd"); @@ -231,7 +231,7 @@ class StaticDelegator { @Test @Disabled void testStaticDelegator() { - // TODO Remove once we have explicit support for 'forEach' conformance check + // TODO Remove once we support method parameters in incarnation mapping confParameters.add(CDConfParameter.STRICT_PARAMETER_ORDER); testConcretizedConformsToRefAndExpectedOut( "evaluation/staticDelegator/StaticDelegatorConc.cd", @@ -241,7 +241,7 @@ void testStaticDelegator() { @Test void testStaticMethodExistsAttributeWorkaround() { - // TODO Remove once we have explicit support for 'forEach' conformance check + // TODO Remove once we support method parameters in incarnation mapping confParameters.add(CDConfParameter.STRICT_PARAMETER_ORDER); testConcretizedConformsToRefAndExpectedOut( "evaluation/staticDelegator/attrWorkaround/StaticExistsConc.cd", @@ -260,7 +260,7 @@ void testStaticMethodExistsAttributeWorkaround() { @Test @Disabled void testInstanceMethodExistsAttributeWorkaround() { - // TODO Remove once we have explicit support for 'forEach' conformance check + // TODO Remove once we support method parameters in incarnation mapping confParameters.add(CDConfParameter.STRICT_PARAMETER_ORDER); testConcretizedConformsToRefAndExpectedOut( "evaluation/staticDelegator/attrWorkaround/InstanceMethodExistsConc.cd", @@ -275,7 +275,7 @@ class TransitiveDependencies { @Test void testTransitiveDependencyInSyntacticalOrder() { - // TODO Remove once we have explicit support for 'forEach' conformance check + // TODO Remove once we support method parameters in incarnation mapping confParameters.add(CDConfParameter.STRICT_PARAMETER_ORDER); testConcretizedConformsToRefAndExpectedOut( "evaluation/transitiveDependencies/TransitiveDependenciesConc.cd", @@ -293,7 +293,7 @@ void testTransitiveDependencyInSyntacticalOrder() { @Test @Disabled void testTransitiveDependencySwappedOrder() { - // TODO Remove once we have explicit support for 'forEach' conformance check + // TODO Remove once we support method parameters in incarnation mapping confParameters.add(CDConfParameter.STRICT_PARAMETER_ORDER); testConcretizedConformsToRefAndExpectedOut( "evaluation/transitiveDependencies/TransitiveDependenciesConc.cd", @@ -313,5 +313,25 @@ void testConcreteEmpty() { } } - + + @Nested + class PaperExamples { + + @Test + void testTaskManagement() { + testConcretizedConformsToRefAndExpectedOut( + "evaluation/paper-examples/task-management/TicketSystemConc.cd", + "evaluation/paper-examples/task-management/TaskManagementRef.cd", + "evaluation/paper-examples/task-management/TicketSystemOut.cd"); + } + + @Test + void testRepository() { + confParameters.add(CDConfParameter.STRICT_PARAMETER_ORDER); + testConcretizedConformsToRefAndExpectedOut( + "evaluation/paper-examples/repository/EcommerceDomain.cd", + "evaluation/paper-examples/repository/RepositoryRef.cd", + "evaluation/paper-examples/repository/RepositoryOut.cd"); + } + } } diff --git a/cddiff/src/test/java/de/monticore/cdconcretization/MethodConcretizationTest.java b/cddiff/src/test/java/de/monticore/cdconcretization/MethodConcretizationTest.java index 411a194f7..edf3d4361 100644 --- a/cddiff/src/test/java/de/monticore/cdconcretization/MethodConcretizationTest.java +++ b/cddiff/src/test/java/de/monticore/cdconcretization/MethodConcretizationTest.java @@ -114,7 +114,7 @@ void testParameterTypeUnderspecifiedWithIncarnation() { @Test void testMethodForEachAttribute() { - // TODO Remove once we have explicit support for 'forEach' conformance check + // TODO Remove once we support method parameters in incarnation mapping confParameters.add(CDConfParameter.STRICT_PARAMETER_ORDER); testConcretizedConformsToRefAndExpectedOut("methods/forEach/ForEachAttributeConc.cd", "methods/forEach/ForEachAttributeRef.cd", "methods/forEach/ForEachAttributeOut.cd"); @@ -134,7 +134,7 @@ void testMethodForEachAttributeDifferentReturnType() { */ @Test void testMethodForEachAttributeMultipleParameters() { - // TODO Remove once we have explicit support for 'forEach' conformance check + // TODO Remove once we support method parameters in incarnation mapping confParameters.add(CDConfParameter.STRICT_PARAMETER_ORDER); testConcretizedConformsToRefAndExpectedOut( "methods/forEach/ForEachAttributeMultipleParametersConc.cd", @@ -185,7 +185,7 @@ void testMethodForEachTypeSameReturnTypeNoNameMatch() { @Test void testMethodForEachTypeSameParameterType() { - // TODO Remove once we have explicit support for 'forEach' conformance check + // TODO Remove once we support method parameters in incarnation mapping confParameters.add(CDConfParameter.STRICT_PARAMETER_ORDER); testConcretizedConformsToRefAndExpectedOut( "methods/forEach/ForEachTypeSameParameterTypeConc.cd", @@ -195,7 +195,7 @@ void testMethodForEachTypeSameParameterType() { @Test void testMethodForEachTypeSameParameterTypeNoNameMatch() { - // TODO Remove once we have explicit support for 'forEach' conformance check + // TODO Remove once we support method parameters in incarnation mapping confParameters.add(CDConfParameter.STRICT_PARAMETER_ORDER); testConcretizedConformsToRefAndExpectedOut( "methods/forEach/ForEachTypeSameParameterTypeConc.cd", diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/repository/EcommerceDomain.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/repository/EcommerceDomain.cd new file mode 100644 index 000000000..5a6dda546 --- /dev/null +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/repository/EcommerceDomain.cd @@ -0,0 +1,19 @@ +/* (c) https://github.com/MontiCore/monticore */ +import java.lang.String; + +classdiagram EcommerceDomain { + + <> class Product { + <> String sku; + double price; + } + + <> class Order { + <> long id; + } + + <> class Customer { + <> long id; + <> String email; + } +} diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/repository/RepositoryOut.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/repository/RepositoryOut.cd new file mode 100644 index 000000000..6095c24de --- /dev/null +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/repository/RepositoryOut.cd @@ -0,0 +1,40 @@ +import java.lang.String; + +classdiagram EcommerceDomain { + <> class Product { + <> String sku; + double price; + } + + <> class Order { + <> long id; + } + + <> class Customer { + <> long id; + <> String email; + } + + <>class ProductRepository { + <> Optional findBySku(String sku); + List findAll(); + Product save(Product product); + <> void removeBySku(String sku); + } + + <>class OrderRepository { + <> Optional findById(long id); + List findAll(); + Order save(Order order); + <> void removeById(long id); + } + + <>class CustomerRepository { + <> Optional findById(long id); + <> Optional findByEmail(String email); + List findAll(); + Customer save(Customer customer); + <> void removeById(long id); + <> void removeByEmail(String email); + } +} \ No newline at end of file diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/repository/RepositoryRef.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/repository/RepositoryRef.cd new file mode 100644 index 000000000..9d204f0d9 --- /dev/null +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/repository/RepositoryRef.cd @@ -0,0 +1,15 @@ +import java.lang.String; + +classdiagram RepositoryRef { + + class Entity { + any key; + } + + <> class EntityRepository { + <> Optional findByKey(any key); + List findAll(); + Entity save(Entity entity); + <> void removeByKey(any key); + } +} diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/task-management/TaskManagementRef.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/task-management/TaskManagementRef.cd new file mode 100644 index 000000000..aa8cefbe9 --- /dev/null +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/task-management/TaskManagementRef.cd @@ -0,0 +1,28 @@ +/* (c) https://github.com/MontiCore/monticore */ +import java.lang.String; + +classdiagram TaskManagementRef { + + class Task { + String title; + String description; + TaskStatus status; + } + + class Member { + String name; + String email; + } + + class Project { + String name; + } + + enum TaskStatus { + Open, InProgress, Done; + } + + association Member -> (assignedTasks) Task [*]; + association Project -> (members) Member [*]; + association Project -> Task [*]; +} \ No newline at end of file diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/task-management/TicketSystemConc.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/task-management/TicketSystemConc.cd new file mode 100644 index 000000000..3e524cce7 --- /dev/null +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/task-management/TicketSystemConc.cd @@ -0,0 +1,14 @@ +/* (c) https://github.com/MontiCore/monticore */ +import java.lang.String; + +classdiagram TicketSystemConc { + + <> class Ticket { + <> String summary; + <> String detail; + } + + <> class Workspace { + <> String label; + } +} \ No newline at end of file diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/task-management/TicketSystemOut.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/task-management/TicketSystemOut.cd new file mode 100644 index 000000000..c40eb4f27 --- /dev/null +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/task-management/TicketSystemOut.cd @@ -0,0 +1,28 @@ +/* (c) https://github.com/MontiCore/monticore */ +import java.lang.String; + +classdiagram TicketSystemConc { + + <> class Ticket { + <> String summary; + <> String detail; + TaskStatus status; + } + + <> class Workspace { + <> String label; + } + + class Member { + String name; + String email; + } + + enum TaskStatus { + Open, InProgress, Done; + } + + association Member -> (assignedTickets) Ticket [*]; + association Workspace -> (members) Member [*]; + association Workspace -> Ticket [*]; +} \ No newline at end of file From 1af9fba88828200142f900c31ee0e7c2f6588f7d Mon Sep 17 00:00:00 2001 From: Felix Jordan Date: Thu, 26 Mar 2026 19:30:21 +0100 Subject: [PATCH 03/16] test(cdconc): adjust tests for implicit name adaptation --- .../evaluation/crud-backend/CRUDBackendOut.cd | 4 ++-- .../evaluation/observer/mutualObservers/MutualObserversOut.cd | 2 +- .../cdconcretization/evaluation/repository/RepositoryOut.cd | 4 ++-- .../cdconcretization/evaluation/visitor/VisitorOut.cd | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/crud-backend/CRUDBackendOut.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/crud-backend/CRUDBackendOut.cd index 1243d9b2b..3927a507c 100644 --- a/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/crud-backend/CRUDBackendOut.cd +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/crud-backend/CRUDBackendOut.cd @@ -21,7 +21,7 @@ classdiagram DomainModel { <> class DepartmentRepository { <> Optional findByDepId(int depId); List findAll(); - Department save(Department entity); + Department save(Department department); <> void removeByDepId(int depId); } @@ -30,7 +30,7 @@ classdiagram DomainModel { <> Optional findById(String id); <> Optional findByTaxId(String taxId); List findAll(); - Employee save(Employee entity); + Employee save(Employee employee); <> void removeById(String id); <> void removeByTaxId(String taxId); } diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/observer/mutualObservers/MutualObserversOut.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/observer/mutualObservers/MutualObserversOut.cd index 84e7abb98..43571d799 100644 --- a/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/observer/mutualObservers/MutualObserversOut.cd +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/observer/mutualObservers/MutualObserversOut.cd @@ -13,6 +13,6 @@ classdiagram MutualObserversConc { void unregister(Attacker o); void notifyAll(); } - association watches [*] Attacker (observers) <-> (observers) Defender[*]; + association watches [*] Attacker (attackers) <-> (defenders) Defender[*]; } \ No newline at end of file diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/repository/RepositoryOut.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/repository/RepositoryOut.cd index 04c97703f..831e7f99e 100644 --- a/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/repository/RepositoryOut.cd +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/repository/RepositoryOut.cd @@ -18,7 +18,7 @@ classdiagram DomainModel { <>class DepartmentRepository { <> Optional findByDepId(int depId); List findAll(); - Department save(Department entity); + Department save(Department department); <> void removeByDepId(int depId); } @@ -27,7 +27,7 @@ classdiagram DomainModel { <> Optional findById(String id); <> Optional findByTaxId(String taxId); List findAll(); - Employee save(Employee entity); + Employee save(Employee employee); <> void removeById(String id); <> void removeByTaxId(String taxId); } diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/visitor/VisitorOut.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/visitor/VisitorOut.cd index 85e1b4bf9..e9a246d14 100644 --- a/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/visitor/VisitorOut.cd +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/visitor/VisitorOut.cd @@ -9,7 +9,7 @@ classdiagram VisitorConc { <> class LeafNode implements Node; <> interface NodeVisitor { - void visit(Node visitable); + void visit(Node node); <> void visit(RootNode rootNode); <> void visit(InnerNode innerNode); <> void visit(LeafNode leafNode); From 800619d3996465ad7b48de571b97cf3ec427a40e Mon Sep 17 00:00:00 2001 From: Felix Jordan Date: Thu, 26 Mar 2026 19:55:27 +0100 Subject: [PATCH 04/16] WIP: feature plan --- .../implicit-name-adaptation.md | 300 ++++++++++++++++++ .../name-adaptation-improvements.md | 75 +++++ .../evaluation/banking2/BankingOut.cd | 2 +- 3 files changed, 376 insertions(+), 1 deletion(-) create mode 100644 cddiff/doc/cdconcretization/implicit-name-adaptation.md create mode 100644 cddiff/doc/cdconcretization/name-adaptation-improvements.md diff --git a/cddiff/doc/cdconcretization/implicit-name-adaptation.md b/cddiff/doc/cdconcretization/implicit-name-adaptation.md new file mode 100644 index 000000000..5b56f89d7 --- /dev/null +++ b/cddiff/doc/cdconcretization/implicit-name-adaptation.md @@ -0,0 +1,300 @@ +# Implicit Name Adaptation + +## Overview + +CD concretization already supports **explicit** name adaptation via `<>` annotations: +when a reference method `findEntity(String id)` has `<>`, it gets renamed to +`findPerson(String id)` for a concrete incarnation `Person` of `Entity`. The variable for +substitution comes from the `forEach` stereotype value. + +This feature extends that concept to **implicit** dependencies. Rather than requiring an explicit +annotation, the system infers the substitution variable from the *type context* of each element: + +| Element | Implicit dependency (substitution variable/value) | +|---------------|--------------------------------------------------------------------------| +| Attribute | Attribute type → its concrete type incarnation | +| Method | Return type and each parameter type → their respective incarnations | +| Assoc. role | The association endpoint type → its concrete type incarnation | +| Assoc. name | Either endpoint type → its concrete type incarnation | + +Classes, interfaces, and enums have no implicit type dependency of this kind and are excluded. + +--- + +## Part 1 — Completion Phase + +### Flag + +`implicitNameAdaptationEnabled` (boolean, default `true`) on `ConcretizationCompleter`. +Mirrors the existing `forEachNameAdaptationEnabled` flag. + +### Adaptation Mechanism + +In all cases, `NameUtil.adaptTemplatedName(name, refTypeName, conTypeName)` is the adaptation +function. It tries three substitution patterns (exact, uncapitalized-prefix, +capitalized-infix) and returns `Optional.empty()` when none matches, so an unrelated name is +never accidentally changed. + +**Important — use specific pairs, not all context pairs.** +Each element must use only the type pair(s) derived from *its own* type dependencies. Using all +context pairs can cause a chaining bug: if both `Account→BankAccount` and `Bank→SEPABank` are in +context, applying both pairs sequentially turns `sourceAccount` into `sourceBankAccount` and then +into `sourceSEPABankAccount`. A future improvement to handle overlapping names safely is +documented in [name-adaptation-improvements.md](name-adaptation-improvements.md). + +--- + +### Attributes — `BaseAttributeInTypeCompleter` + +When an attribute incarnation is created for reference attribute `refAttr` whose type `T_ref` is +incarnated by `T_con`: + +``` +adaptedName = adaptTemplatedName(attrName, T_ref.getName(), T_con.getName()) +``` + +If adaptation succeeds and the name changed, set the new name and add a stereotype: +``` +<> adaptedAttrName: T_con; +``` + +Use the specific `(T_ref, T_con)` pair already available in the loop over `typeIncarnations`. + +--- + +### Methods — `BaseMethodInTypeCompleter` + +Two sub-cases arise when iterating over the cartesian product of return-type and parameter-type +incarnations: + +#### Method name adaptation + +Apply **all** relevant type pairs sequentially: the return type pair followed by each parameter +type pair (for parameters whose type is a CD type with an incarnation): + +``` +adaptedName = refMethod.getName() +for each (T_ref, T_con) from [returnTypePair, paramTypePairs...]: + adaptedName = adaptTemplatedName(adaptedName, T_ref.getName(), T_con.getName()) + .orElse(adaptedName) +``` + +This naturally handles methods with multiple reference-typed parameters: +`compareInputAndOutput(Input input, Output output)` with `Input→Foo`, `Output→Bar` +→ `compareFooAndBar`. + +If the name changes, add: +``` +<> adaptedMethodName(...) +``` + +#### Parameter name adaptation + +For each parameter `p` whose type `T_par_ref` is incarnated by `T_par_con`: + +``` +adaptedParamName = adaptTemplatedName(p.getName(), T_par_ref.getName(), T_par_con.getName()) +``` + +Set the adapted name if it changed. **No stereotype** is needed for parameter names since they +are not part of any matching key. + +> **Open question (see banking2 below):** the current expected output for `banking2` keeps +> `transfer(BankAccount targetAccount, ...)` — i.e., parameter name `targetAccount` is NOT adapted +> to `targetBankAccount` even though `adaptTemplatedName` would produce that. This must be +> clarified: should parameter name adaptation apply here, and if so, the expected output file must +> be updated? + +Use the specific return/parameter type pairs available inside the existing incarnation loops. + +--- + +### Associations + +#### New (missing) associations — `MissingAssociationsCDCompleter` + +Already implemented using specific endpoint type pairs. Role name and association name are adapted +using the left/right endpoint type pairs respectively. **No change needed.** + +#### Existing (matched) associations — `DefaultAssocCompleter` + +After `assocSideCompleter.completeAssocSide()` has copied role names from the reference: + +``` +adaptedRightRole = adaptTemplatedName(rightRole, T_rightRef.getName(), T_rightCon.getName()) +adaptedLeftRole = adaptTemplatedName(leftRole, T_leftRef.getName(), T_leftCon.getName()) + +// apply both endpoint type pairs sequentially (same pattern as method parameter types) +adaptedAssocName = assocName +adaptedAssocName = adaptTemplatedName(adaptedAssocName, T_leftRef.getName(), T_leftCon.getName()).orElse(adaptedAssocName) +adaptedAssocName = adaptTemplatedName(adaptedAssocName, T_rightRef.getName(), T_rightCon.getName()).orElse(adaptedAssocName) +``` + +The reference endpoint types `T_leftRef` / `T_rightRef` must be resolved from `rAssoc` (which is +passed to `completeAssociation`). The concrete incarnation types `T_leftCon` / `T_rightCon` must +be resolved from `cAssoc`. + +Use the specific endpoint type pairs resolved from `cAssoc`/`rAssoc`. The `CDCompletionContext` +is already available in `DefaultAssocCompleter` via the `context` field. + +--- + +## Part 2 — Conformance Phase + +### Motivation + +When implicit name adaptation is applied, adapted names are *no longer* detected as incarnations +by the existing matching strategies. For example, `findTicket(String id)` is not detected as +an incarnation of `findTask(String id)` by `EqNameMethodIncStrategy` or +`EqSignatureMethodIncStrategy`. + +The completers address this by adding `<>` stereotypes, making +`STAttributeIncStrategy` / `STMethodIncStrategy` the fallback matcher. However, we also want to +support the case where no stereotype is present — i.e., the concrete CD author wrote +`findTicket(String id)` by hand without a stereotype, and the conformance checker should still +recognise it as an incarnation. + +### New `CDConfParameter`: `ADAPTED_NAME_MAPPING` + +Replaces and generalises the earlier `IMPLICIT_ROLE_NAME_ADAPTATION` parameter (which was +association-only). When this parameter is present in the conformance parameters set, three new +matching strategies are added: + +--- + +### `AdaptedNameAttributeIncStrategy` + +Matches a concrete attribute `conAttr` to a reference attribute `refAttr` when: + +1. The declaring type of `conAttr` incarnates the declaring type of `refAttr` + (standard type matching, handled by the surrounding conformance check). +2. The type of `conAttr` incarnates the type of `refAttr`: `typeMatcher.isMatched(T_con, T_ref)`. +3. The name of `conAttr` is the adapted form of `refAttr`'s name: + ``` + adaptTemplatedName(refAttr.getName(), T_ref.getName(), T_con.getName()) + .map(adapted -> adapted.equals(conAttr.getName())) + .orElse(false) + ``` + +**Base class:** extend `CDAttributeMatchingStrategy` (like `EqNameAttributeIncStrategy`). + +**Registration in `DefaultCDConformanceContext.create()`:** +```java +if (conformanceParams.contains(CDConfParameter.ADAPTED_NAME_MAPPING)) { + compAttributeIncStrategy.addIncStrategy(new AdaptedNameAttributeIncStrategy(compTypeIncStrategy)); +} +``` + +--- + +### `AdaptedNameMethodIncStrategy` + +Matches a concrete method `conMethod` to a reference method `refMethod` when there exists at +least one type pair `(T_ref, T_con)` — derived from the method's parameter types or return type — +such that: + +1. `typeMatcher.isMatched(T_con, T_ref)` (the concrete type incarnates the reference type). +2. `adaptTemplatedName(refMethod.getName(), T_ref.getName(), T_con.getName()) + .map(adapted -> adapted.equals(conMethod.getName())).orElse(false)` — adapted names match, + OR the names are already equal (unchanged by adaptation). +3. Parameter counts are equal. +4. For each parameter pair `(conParam, refParam)`: + - The parameter type of `conParam` incarnates (or equals) the parameter type of `refParam` + via the type matcher. + - *(If parameter name adaptation was applied by the completer)* the parameter name of `conParam` + is either the adapted form of `refParam`'s name or the same name. + +The type pair candidates are: the return type pair and each parameter type pair (only those whose +type is a CD type with an incarnation). All relevant pairs are applied **sequentially** to the +name, so multiple substitutions can take place in one pass: + +``` +name = refMethod.getName() +for each (T_ref, T_con) from [returnTypePair] + [paramTypePairs...]: + name = adaptTemplatedName(name, T_ref.getName(), T_con.getName()).orElse(name) +``` + +Example: `compareInputAndOutput(Input input, Output output)` with `Input→Foo`, `Output→Bar` +→ after `(Input, Foo)`: `compareFooAndOutput` +→ after `(Output, Bar)`: `compareFooAndBar(Foo foo, Bar bar)` + +This is safe from the chaining-bug because only the element's *own* type pairs are iterated, not +all unrelated pairs from the context. + +> **Banking2 note:** `transfer(BankAccount targetAccount, double amount)` vs +> `transfer(Account targetAccount, double amount)`. The method name `transfer` is unchanged (no +> `Account` substring in `transfer`), so name adaptation alone is not the matching criterion here. +> Instead, `EqSignatureMethodIncStrategy` (with `METHOD_OVERLOADING`) already handles this via +> type-incarnation-aware parameter type matching (`MCTypeMatchingStrategy` + `cdTypeMatcher`). +> The `AdaptedNameMethodIncStrategy` is needed for cases where the METHOD NAME itself changes +> (e.g., `findTask → findTicket`). For the parameter name concern in `banking2`, see the open +> question in Part 1 above. + +**Base class:** extend `CDMethodMatchingStrategy`. + +**Registration in `DefaultCDConformanceContext.create()`:** +```java +if (conformanceParams.contains(CDConfParameter.ADAPTED_NAME_MAPPING)) { + compMethodIncStrategy.addIncStrategy( + new AdaptedNameMethodIncStrategy(compTypeIncStrategy, mcTypeMatcher, conformanceParams)); +} +``` + +--- + +### `AdaptedRoleNameAssocIncStrategy` (already exists) + +Previously gated on `IMPLICIT_ROLE_NAME_ADAPTATION`; re-gate on `ADAPTED_NAME_MAPPING` instead. +`IMPLICIT_ROLE_NAME_ADAPTATION` can be removed (or deprecated) once `ADAPTED_NAME_MAPPING` is in +place. + +**Registration:** +```java +if (conformanceParams.contains(CDConfParameter.ADAPTED_NAME_MAPPING)) { + ExternalCandidatesMatchingStrategy typeMatcherForAdapted = + conformanceParams.contains(CDConfParameter.INHERITANCE) + ? compSubTypeIncStrategy : compTypeIncStrategy; + compAssocIncStrategy.addIncStrategy( + new AdaptedRoleNameAssocIncStrategy(typeMatcherForAdapted, concreteCD, referenceCD)); +} +``` + +--- + +## Part 3 — Test Configuration + +`DEFAULT_CONFORMANCE_PARAMS` in `AbstractCDConcretizationTest` should include `ADAPTED_NAME_MAPPING` +(replacing `IMPLICIT_ROLE_NAME_ADAPTATION` once the rename is done). + +Expected output files for test cases where names are adapted (e.g., `task-management`, +potentially `banking2` for parameter names) must be updated to reflect adapted names. + +--- + +## Summary of Work Items + +| # | Component | Change | +|---|------------------------------------------|------------------------------------------------------------------------| +| 1 | `BaseAttributeInTypeCompleter` | Use specific `(T_ref, T_con)` pair instead of `adaptNameImplicitly` | +| 2 | `BaseMethodInTypeCompleter` | Use specific return/parameter type pairs instead of `adaptNameImplicitly` | +| 3 | `DefaultAssocCompleter` | Use specific endpoint type pairs instead of `adaptNameImplicitly` | +| 4 | `CDConfParameter` | Add `ADAPTED_NAME_MAPPING`; keep or deprecate `IMPLICIT_ROLE_NAME_ADAPTATION` | +| 5 | `AdaptedNameAttributeIncStrategy` | New class | +| 6 | `AdaptedNameMethodIncStrategy` | New class | +| 7 | `AdaptedRoleNameAssocIncStrategy` | Already exists; re-gate on `ADAPTED_NAME_MAPPING` | +| 8 | `DefaultCDConformanceContext` | Register new strategies under `ADAPTED_NAME_MAPPING` | +| 9 | `AbstractCDConcretizationTest` | Update `DEFAULT_CONFORMANCE_PARAMS` | +| 10| Expected output `.cd` files | Update where names are now adapted | + +## Open Questions + +1. **Parameter name adaptation in banking2:** ~~Open — resolved.~~ Parameter names ARE adapted. + `BankingOut.cd` must be updated: `targetAccount → targetBankAccount`, + `source → source` (no change; `source` contains no `Account`/`BankAccount` substring), + and `Transaction → (source) BankAccount [1]` role names are unchanged. + Concretely, `transfer(BankAccount targetBankAccount, double amount)` is the expected output. + +2. ~~**Method name with multiple type pairs** — resolved.~~ All type pairs from the method's own + return/parameter types are applied sequentially. E.g., + `compareInputAndOutput(Input input, Output output)` with `Input→Foo`, `Output→Bar` → + `compareFooAndBar(Foo foo, Bar bar)`. diff --git a/cddiff/doc/cdconcretization/name-adaptation-improvements.md b/cddiff/doc/cdconcretization/name-adaptation-improvements.md new file mode 100644 index 000000000..1eb758ced --- /dev/null +++ b/cddiff/doc/cdconcretization/name-adaptation-improvements.md @@ -0,0 +1,75 @@ +# Name Adaptation Improvements (Future Work) + +## Context + +`NameUtil.adaptTemplatedName(String name, String variable, String value)` currently takes a +single (variable, value) substitution pair. In the implicit name adaptation feature, callers +apply multiple pairs sequentially — one call per type pair. This is sufficient for the common +case, but breaks down when the type names overlap. + +## The Overlapping-Names Problem + +**Example:** Two type pairs in context: `Entity → PersonEntity` and `EntityRepository → PersonRepository`. + +Applying pairs naively in an arbitrary order: + +1. Apply `(Entity, PersonEntity)` first: + `findEntityRepository` → `findPersonEntityRepository` +2. Apply `(EntityRepository, PersonRepository)` second: + `findPersonEntityRepository` → `findPersonPersonRepository` ← wrong double-substitution + +Or in the other order: + +1. Apply `(EntityRepository, PersonRepository)` first: + `findEntityRepository` → `findPersonRepository` ← correct +2. Apply `(Entity, PersonEntity)` second: + `findPersonRepository` → no match (no `Entity` substring left) ← correct + +The correct result depends on applying the **longest matching variable first** and then +**protecting already-adapted regions** from further substitution. + +## Proposed Solution: Multi-Pair `adaptTemplatedName` + +Add an overload (or a new method) to `NameUtil`: + +```java +public static String adaptTemplatedName(String name, List> pairs) +``` + +**Algorithm:** + +1. **Sort pairs by decreasing match length** — longer variable names are tried before shorter + ones. This ensures `EntityRepository` is considered before `Entity` so the longer match wins. + +2. **Collect all non-overlapping matches** using a greedy left-to-right scan: + - For each position in `name`, try all remaining (unsorted-but-prioritised) pairs to find + the longest match at that position. + - When a match is found, record the substitution and advance past the matched region. + - Matched regions are **marked as adapted** and cannot be matched again by any other pair. + +3. **Apply substitutions** (right to left to preserve offsets) to produce the final name. + +**Example revisited:** +- Input: `findEntityRepository`, pairs: `[(Entity, PersonEntity), (EntityRepository, PersonRepository)]` +- Sorted by length: `[(EntityRepository, PersonRepository), (Entity, PersonEntity)]` +- Scan: at offset 4 (`EntityRepository`) → longest match wins → substitute → `findPersonRepository` +- Region `[4, 20)` is marked adapted; `Entity` at offset 4 is now inside that region → skipped +- Result: `findPersonRepository` ✓ + +## Relation to Existing Callers + +The current approach (multiple single-pair calls in sequence) in +`BaseAttributeInTypeCompleter`, `BaseMethodInTypeCompleter`, `DefaultAssocCompleter`, and +`MissingAssociationsCDCompleter` is correct as long as the type pairs for a given element do +not overlap. This is typically the case because each element's own type pairs are independent. + +The chaining bug that motivated using element-specific pairs (instead of all context pairs) would +**also** be addressed by this algorithm, since each region can only be adapted once. When this +improved API is available, it would be safe to pass all context type pairs — not just the +element-specific ones — and the algorithm would still produce correct results. + +## Implementation Note + +This is **not required** for the current implicit name adaptation feature. The element-specific +pair approach is sufficient. Implement this when overlapping type names become a real concern in +practice. diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/banking2/BankingOut.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/banking2/BankingOut.cd index e9387055e..e86573338 100644 --- a/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/banking2/BankingOut.cd +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/banking2/BankingOut.cd @@ -14,7 +14,7 @@ classdiagram BankingConc { AccountStatus status; void withdraw(double amount); void deposit(double amount); - void transfer(BankAccount targetAccount, double amount); + void transfer(BankAccount targetBankAccount, double amount); } interface Auditable { From cf0ff559a33e55402a30053ff4232146716cd07d Mon Sep 17 00:00:00 2001 From: Felix Jordan Date: Sun, 29 Mar 2026 17:40:15 +0200 Subject: [PATCH 05/16] fix(cdconf): resolve reference/concrete types in correct scope --- .../DefaultCDConformanceContext.java | 7 +++-- .../AdaptedNameAttributeIncStrategy.java | 28 +++++++++++-------- .../method/AdaptedNameMethodIncStrategy.java | 27 ++++++++++-------- 3 files changed, 36 insertions(+), 26 deletions(-) diff --git a/cddiff/src/main/java/de/monticore/cdconformance/DefaultCDConformanceContext.java b/cddiff/src/main/java/de/monticore/cdconformance/DefaultCDConformanceContext.java index aeb976be3..fa2a427ee 100644 --- a/cddiff/src/main/java/de/monticore/cdconformance/DefaultCDConformanceContext.java +++ b/cddiff/src/main/java/de/monticore/cdconformance/DefaultCDConformanceContext.java @@ -166,10 +166,13 @@ public static CDConformanceContext create(ASTCDCompilationUnit concreteCD, : compTypeIncStrategy; compAssocIncStrategy.addIncStrategy( new AdaptedRoleNameAssocIncStrategy(typeMatcherForAdapted, concreteCD, referenceCD)); + // Attributes and methods use exact incarnation matching (compTypeIncStrategy), not + // subtype matching: a supertype must not be treated as an adapted incarnation of a + // reference parameter type just because one of its subtypes incarnates that type. compAttributeIncStrategy.addIncStrategy( - new AdaptedNameAttributeIncStrategy(typeMatcherForAdapted)); + new AdaptedNameAttributeIncStrategy(compTypeIncStrategy, concreteCD, referenceCD)); compMethodIncStrategy.addIncStrategy( - new AdaptedNameMethodIncStrategy(typeMatcherForAdapted, mcTypeMatcher)); + new AdaptedNameMethodIncStrategy(compTypeIncStrategy, mcTypeMatcher, concreteCD, referenceCD)); } return new DefaultCDConformanceContext(concreteCD, referenceCD, mapping, diff --git a/cddiff/src/main/java/de/monticore/cdconformance/inc/attribute/AdaptedNameAttributeIncStrategy.java b/cddiff/src/main/java/de/monticore/cdconformance/inc/attribute/AdaptedNameAttributeIncStrategy.java index 3743b2440..c2993da85 100644 --- a/cddiff/src/main/java/de/monticore/cdconformance/inc/attribute/AdaptedNameAttributeIncStrategy.java +++ b/cddiff/src/main/java/de/monticore/cdconformance/inc/attribute/AdaptedNameAttributeIncStrategy.java @@ -1,10 +1,11 @@ /* (c) https://github.com/MontiCore/monticore */ package de.monticore.cdconformance.inc.attribute; -import de.monticore.cd4code.CD4CodeMill; import de.monticore.cdbasis._ast.ASTCDAttribute; +import de.monticore.cdbasis._ast.ASTCDCompilationUnit; import de.monticore.cdbasis._ast.ASTCDType; import de.monticore.cdbasis._symboltable.CDTypeSymbol; +import de.monticore.cdbasis._symboltable.ICDBasisScope; import de.monticore.cdconcretization.util.NameUtil; import de.monticore.cdmatcher.BooleanMatchingStrategy; import de.monticore.types.mcbasictypes._ast.ASTMCQualifiedType; @@ -25,10 +26,15 @@ public class AdaptedNameAttributeIncStrategy implements CDAttributeMatchingStrategy { private final BooleanMatchingStrategy typeMatcher; + private final ICDBasisScope conScope; + private final ICDBasisScope refScope; private ASTCDType referenceType; - public AdaptedNameAttributeIncStrategy(BooleanMatchingStrategy typeMatcher) { + public AdaptedNameAttributeIncStrategy(BooleanMatchingStrategy typeMatcher, + ASTCDCompilationUnit concreteCD, ASTCDCompilationUnit referenceCD) { this.typeMatcher = typeMatcher; + this.conScope = concreteCD.getEnclosingScope(); + this.refScope = referenceCD.getEnclosingScope(); } @Override @@ -39,8 +45,10 @@ public List getMatchedElements(ASTCDAttribute concrete) { @Override public boolean isMatched(ASTCDAttribute concrete, ASTCDAttribute ref) { - Optional refAttrType = resolveCDType(ref); - Optional conAttrType = resolveCDType(concrete); + Optional refAttrType = resolveCDType(ref.getMCType() instanceof ASTMCQualifiedType + ? ((ASTMCQualifiedType) ref.getMCType()).getMCQualifiedName().getQName() : null, refScope); + Optional conAttrType = resolveCDType(concrete.getMCType() instanceof ASTMCQualifiedType + ? ((ASTMCQualifiedType) concrete.getMCType()).getMCQualifiedName().getQName() : null, conScope); if (refAttrType.isPresent() && conAttrType.isPresent() && typeMatcher.isMatched(conAttrType.get(), refAttrType.get())) { return NameUtil.adaptTemplatedName(ref.getName(), refAttrType.get().getName(), @@ -55,16 +63,12 @@ public void setReferenceType(ASTCDType referenceType) { this.referenceType = referenceType; } - private Optional resolveCDType(ASTCDAttribute attribute) { - // Only qualified types can be CD types; primitives and collection types cannot - if (!(attribute.getMCType() instanceof ASTMCQualifiedType)) { + private Optional resolveCDType(String typeName, ICDBasisScope scope) { + if (typeName == null) { return Optional.empty(); } - // Use global scope lookup to avoid NPE on types without enclosing scope (e.g., deep-cloned - // elements added during concretization that have not been through symbol table construction) - String typeName = ((ASTMCQualifiedType) attribute.getMCType()).getMCQualifiedName().getQName(); - return CD4CodeMill.globalScope().resolveCDTypeDown(typeName) - .filter(sym -> sym.isPresentAstNode()) + return scope.resolveCDTypeDown(typeName) + .filter(CDTypeSymbol::isPresentAstNode) .map(CDTypeSymbol::getAstNode); } diff --git a/cddiff/src/main/java/de/monticore/cdconformance/inc/method/AdaptedNameMethodIncStrategy.java b/cddiff/src/main/java/de/monticore/cdconformance/inc/method/AdaptedNameMethodIncStrategy.java index cb5b12087..59ee27f98 100644 --- a/cddiff/src/main/java/de/monticore/cdconformance/inc/method/AdaptedNameMethodIncStrategy.java +++ b/cddiff/src/main/java/de/monticore/cdconformance/inc/method/AdaptedNameMethodIncStrategy.java @@ -1,10 +1,11 @@ /* (c) https://github.com/MontiCore/monticore */ package de.monticore.cdconformance.inc.method; -import de.monticore.cd4code.CD4CodeMill; import de.monticore.cd4codebasis._ast.ASTCDMethod; +import de.monticore.cdbasis._ast.ASTCDCompilationUnit; import de.monticore.cdbasis._ast.ASTCDType; import de.monticore.cdbasis._symboltable.CDTypeSymbol; +import de.monticore.cdbasis._symboltable.ICDBasisScope; import de.monticore.cdconcretization.util.NameUtil; import de.monticore.cdconformance.inc.mctype.MCTypeMatchingStrategy; import de.monticore.cdmatcher.BooleanMatchingStrategy; @@ -30,12 +31,17 @@ public class AdaptedNameMethodIncStrategy implements CDMethodMatchingStrategy { private final BooleanMatchingStrategy typeMatcher; private final MCTypeMatchingStrategy mcTypeMatcher; + private final ICDBasisScope conScope; + private final ICDBasisScope refScope; private ASTCDType refType; public AdaptedNameMethodIncStrategy(BooleanMatchingStrategy typeMatcher, - MCTypeMatchingStrategy mcTypeMatcher) { + MCTypeMatchingStrategy mcTypeMatcher, + ASTCDCompilationUnit concreteCD, ASTCDCompilationUnit referenceCD) { this.typeMatcher = typeMatcher; this.mcTypeMatcher = mcTypeMatcher; + this.conScope = concreteCD.getEnclosingScope(); + this.refScope = referenceCD.getEnclosingScope(); } @Override @@ -54,8 +60,8 @@ public boolean isMatched(ASTCDMethod concrete, ASTCDMethod ref) { String adaptedName = ref.getName(); if (ref.getMCReturnType().isPresentMCType() && concrete.getMCReturnType().isPresentMCType()) { - Optional refRetType = resolveCDType(ref.getMCReturnType().getMCType()); - Optional conRetType = resolveCDType(concrete.getMCReturnType().getMCType()); + Optional refRetType = resolveCDType(ref.getMCReturnType().getMCType(), refScope); + Optional conRetType = resolveCDType(concrete.getMCReturnType().getMCType(), conScope); if (refRetType.isPresent() && conRetType.isPresent() && typeMatcher.isMatched(conRetType.get(), refRetType.get())) { adaptedName = NameUtil.adaptTemplatedName(adaptedName, refRetType.get().getName(), @@ -65,9 +71,9 @@ public boolean isMatched(ASTCDMethod concrete, ASTCDMethod ref) { for (int i = 0; i < ref.getCDParameterList().size(); i++) { Optional refParamType = resolveCDType( - ref.getCDParameterList().get(i).getMCType()); + ref.getCDParameterList().get(i).getMCType(), refScope); Optional conParamType = resolveCDType( - concrete.getCDParameterList().get(i).getMCType()); + concrete.getCDParameterList().get(i).getMCType(), conScope); if (refParamType.isPresent() && conParamType.isPresent() && typeMatcher.isMatched(conParamType.get(), refParamType.get())) { adaptedName = NameUtil.adaptTemplatedName(adaptedName, refParamType.get().getName(), @@ -94,16 +100,13 @@ public void setReferenceType(ASTCDType refType) { this.refType = refType; } - private Optional resolveCDType(ASTMCType mcType) { - // Only qualified types can be CD types; primitives and collection types cannot + private Optional resolveCDType(ASTMCType mcType, ICDBasisScope scope) { if (!(mcType instanceof ASTMCQualifiedType)) { return Optional.empty(); } - // Use global scope lookup to avoid NPE on types without enclosing scope (e.g., deep-cloned - // elements added during concretization that have not been through symbol table construction) String typeName = ((ASTMCQualifiedType) mcType).getMCQualifiedName().getQName(); - return CD4CodeMill.globalScope().resolveCDTypeDown(typeName) - .filter(sym -> sym.isPresentAstNode()) + return scope.resolveCDTypeDown(typeName) + .filter(CDTypeSymbol::isPresentAstNode) .map(CDTypeSymbol::getAstNode); } From ad3a9d646d50b2c0662ff6d939ff74f3849ea1f7 Mon Sep 17 00:00:00 2001 From: Felix Jordan Date: Sun, 29 Mar 2026 19:09:10 +0200 Subject: [PATCH 06/16] feat(cdconc): basic implicit name adaptation working --- .../ConcretizationCompleter.java | 33 +++++-- .../cd/CDCompletionContext.java | 2 + .../cd/TypeDetailsCDCompleter.java | 5 + .../BaseAttributeInTypeCompleter.java | 11 ++- .../method/BaseMethodInTypeCompleter.java | 93 ++++++++++++++++--- .../EvaluationConcretizationTest.java | 2 + .../MethodConcretizationTest.java | 4 + .../banking/singleInc/BankingOut.cd | 5 +- .../banking/usageByExtension/BankingOut.cd | 5 +- .../ParameterAndReturnTypeMIOut.cd | 4 +- .../multiIncarnation/ParameterTypeMIOut.cd | 4 +- .../BothAssocSidesMINameAdaptationOut.cd | 15 +++ 12 files changed, 155 insertions(+), 28 deletions(-) create mode 100644 cddiff/src/test/resources/de/monticore/cdconcretization/multipleIncarnation/BothAssocSidesMINameAdaptationOut.cd diff --git a/cddiff/src/main/java/de/monticore/cdconcretization/ConcretizationCompleter.java b/cddiff/src/main/java/de/monticore/cdconcretization/ConcretizationCompleter.java index 3692ed00a..65efeb742 100644 --- a/cddiff/src/main/java/de/monticore/cdconcretization/ConcretizationCompleter.java +++ b/cddiff/src/main/java/de/monticore/cdconcretization/ConcretizationCompleter.java @@ -69,6 +69,15 @@ public class ConcretizationCompleter { * elements annotated with 'forEach'. */ private boolean forEachNameAdaptationEnabled = true; + + /** + * If true, names of reference elements being copied to the concrete CD are adapted using the + * implicit type incarnation bindings available in the context. For every reference type with a + * concrete incarnation, occurrences of the reference type name in any element name are replaced + * with the corresponding incarnation type name. This applies to association role names, + * association names, attribute names, method names, and parameter names. + */ + private boolean implicitNameAdaptationEnabled = true; /** * Name of the placeholder type that is used to mark underspecified types in the reference CD. See @@ -117,7 +126,8 @@ public void completeCD(ASTCDCompilationUnit concreteCD, ASTCDCompilationUnit ref * to perform the actual concretization. */ CDCompletionContext context = DefaultCompletionContext.create(concreteCD, referenceCD, mapping, - underspecifiedPlaceholderTypeName, forEachNameAdaptationEnabled, conformanceParams); + underspecifiedPlaceholderTypeName, forEachNameAdaptationEnabled, + implicitNameAdaptationEnabled, conformanceParams); // 1. introduce incarnation bindings for each incarnation CD4CodeTraverser traverser = CD4CodeMill.inheritanceTraverser(); @@ -157,7 +167,7 @@ public void completeCD(ASTCDCompilationUnit concreteCD, ASTCDCompilationUnit ref IAssocSideCompleter assocSideCompleter = new DefaultAssocSideCompleter(); IAssociationCompleter assocCompleter = new DefaultAssocCompleter(concreteCD, - assocSideCompleter); + assocSideCompleter, context); ChainBuilder completerChainBuilder = new ChainBuilder().add(new ImportsCompleter()).add( @@ -203,6 +213,10 @@ public void setUnderspecifiedPlaceholderTypeName(String underspecifiedPlaceholde public void setForEachNameAdaptationEnabled(boolean forEachNameAdaptationEnabled) { this.forEachNameAdaptationEnabled = forEachNameAdaptationEnabled; } + + public void setImplicitNameAdaptationEnabled(boolean implicitNameAdaptationEnabled) { + this.implicitNameAdaptationEnabled = implicitNameAdaptationEnabled; + } /*** * Provides default configurations for the matching strategies used in the concretization process. @@ -211,7 +225,8 @@ static class DefaultCompletionContext extends DefaultCDConformanceContext implem CDCompletionContext { private final boolean forEachNameAdaptationEnabled; - + private final boolean implicitNameAdaptationEnabled; + protected DefaultCompletionContext(ASTCDCompilationUnit concreteCD, ASTCDCompilationUnit referenceCD, String mapping, String underspecifiedPlaceholderTypeName, Set conformanceParams, @@ -220,11 +235,12 @@ protected DefaultCompletionContext(ASTCDCompilationUnit concreteCD, ExternalCandidatesMatchingStrategy assocIncStrategy, CDAttributeMatchingStrategy attributeIncStrategy, CDMethodMatchingStrategy methodIncStrategy, MCTypeMatchingStrategy mcTypeIncStrategy, - boolean forEachNameAdaptationEnabled) { + boolean forEachNameAdaptationEnabled, boolean implicitNameAdaptationEnabled) { super(concreteCD, referenceCD, mapping, underspecifiedPlaceholderTypeName, conformanceParams, typeIncStrategy, typeIncStrategyMatchingSubTypes, assocIncStrategy, attributeIncStrategy, methodIncStrategy, mcTypeIncStrategy); this.forEachNameAdaptationEnabled = forEachNameAdaptationEnabled; + this.implicitNameAdaptationEnabled = implicitNameAdaptationEnabled; } /** @@ -241,18 +257,23 @@ protected DefaultCompletionContext(ASTCDCompilationUnit concreteCD, */ public static DefaultCompletionContext create(ASTCDCompilationUnit concreteCD, ASTCDCompilationUnit referenceCD, String mapping, String underspecifiedPlaceholderTypeName, - boolean forEachNameAdaptationEnabled, Set conformanceParams) { + boolean forEachNameAdaptationEnabled, boolean implicitNameAdaptationEnabled, + Set conformanceParams) { CDConformanceContext context = DefaultCDConformanceContext.create(concreteCD, referenceCD, mapping, underspecifiedPlaceholderTypeName, conformanceParams); return new DefaultCompletionContext(concreteCD, referenceCD, mapping, underspecifiedPlaceholderTypeName, conformanceParams, context.getTypeIncStrategy(), context.getTypeIncStrategyMatchingSubTypes(), context.getAssociationIncStrategy(), context .getAttributeIncStrategy(), context.getMethodIncStrategy(), context - .getMCTypeIncStrategy(), forEachNameAdaptationEnabled); + .getMCTypeIncStrategy(), forEachNameAdaptationEnabled, + implicitNameAdaptationEnabled); } @Override public boolean isForEachNameAdaptationEnabled() { return forEachNameAdaptationEnabled; } + + @Override + public boolean isImplicitNameAdaptationEnabled() { return implicitNameAdaptationEnabled; } @Override public Set getTypeIncarnations(ASTCDType referenceType) { diff --git a/cddiff/src/main/java/de/monticore/cdconcretization/cd/CDCompletionContext.java b/cddiff/src/main/java/de/monticore/cdconcretization/cd/CDCompletionContext.java index 623e0eca6..e76b93d8a 100644 --- a/cddiff/src/main/java/de/monticore/cdconcretization/cd/CDCompletionContext.java +++ b/cddiff/src/main/java/de/monticore/cdconcretization/cd/CDCompletionContext.java @@ -16,6 +16,8 @@ public interface CDCompletionContext extends CDConformanceContext { boolean isForEachNameAdaptationEnabled(); + + boolean isImplicitNameAdaptationEnabled(); /** * Returns all incarnations of the given reference type in the scope of this context. diff --git a/cddiff/src/main/java/de/monticore/cdconcretization/cd/TypeDetailsCDCompleter.java b/cddiff/src/main/java/de/monticore/cdconcretization/cd/TypeDetailsCDCompleter.java index 9582bb0df..58d46eb8f 100644 --- a/cddiff/src/main/java/de/monticore/cdconcretization/cd/TypeDetailsCDCompleter.java +++ b/cddiff/src/main/java/de/monticore/cdconcretization/cd/TypeDetailsCDCompleter.java @@ -97,6 +97,11 @@ public String getUnderspecifiedPlaceholderTypeName() { public boolean isForEachNameAdaptationEnabled() { return parentContext.isForEachNameAdaptationEnabled(); } + + @Override + public boolean isImplicitNameAdaptationEnabled() { + return parentContext.isImplicitNameAdaptationEnabled(); + } @Override public Set getConformanceParams() { diff --git a/cddiff/src/main/java/de/monticore/cdconcretization/type/attribute/BaseAttributeInTypeCompleter.java b/cddiff/src/main/java/de/monticore/cdconcretization/type/attribute/BaseAttributeInTypeCompleter.java index eef699337..ec513e319 100644 --- a/cddiff/src/main/java/de/monticore/cdconcretization/type/attribute/BaseAttributeInTypeCompleter.java +++ b/cddiff/src/main/java/de/monticore/cdconcretization/type/attribute/BaseAttributeInTypeCompleter.java @@ -10,6 +10,7 @@ import de.monticore.cdconcretization.CompletionException; import de.monticore.cdconcretization.stereotype.StereotypeUtil; import de.monticore.cdconcretization.type.TypeCompletionContext; +import de.monticore.cdconcretization.util.NameUtil; import de.monticore.symbols.basicsymbols._ast.ASTType; import de.monticore.types.check.SymTypeExpression; import java.util.List; @@ -89,7 +90,15 @@ private void createAttributeIncarnations(ASTCDType concreteType, StereotypeUtil.addStereotype(attributeIncarnation.getModifier(), context.getMappingName(), referenceAttribute.getSymbol().getFullName()); } - + if (context.isImplicitNameAdaptationEnabled()) { + NameUtil.adaptTemplatedName(attributeIncarnation.getName(), + rAttributeType.getName(), cAttributeType.getName()).ifPresent(adapted -> { + attributeIncarnation.setName(adapted); + StereotypeUtil.addStereotype(attributeIncarnation.getModifier(), + context.getMappingName(), referenceAttribute.getSymbol().getFullName()); + }); + } + // 2. set type of incarnation // use FQ name to avoid messing with imports / name conflicts attributeIncarnation.setMCType(CD4CodeMill.mCQualifiedTypeBuilder().setMCQualifiedName( diff --git a/cddiff/src/main/java/de/monticore/cdconcretization/type/method/BaseMethodInTypeCompleter.java b/cddiff/src/main/java/de/monticore/cdconcretization/type/method/BaseMethodInTypeCompleter.java index 229a69746..deb5b80e3 100644 --- a/cddiff/src/main/java/de/monticore/cdconcretization/type/method/BaseMethodInTypeCompleter.java +++ b/cddiff/src/main/java/de/monticore/cdconcretization/type/method/BaseMethodInTypeCompleter.java @@ -5,15 +5,18 @@ import de.monticore.cd._symboltable.CDSymbolTables; import de.monticore.cd4code.CD4CodeMill; import de.monticore.cd4code.typescalculator.FullSynthesizeFromCD4Code; +import de.monticore.cdbasis._symboltable.ICDBasisScope; import de.monticore.cd4codebasis._ast.ASTCDMethod; import de.monticore.cd4codebasis._ast.ASTCDParameter; import de.monticore.cdbasis._ast.ASTCDType; +import de.monticore.cdbasis._symboltable.CDTypeSymbolTOP; import de.monticore.cdconcretization.ConcretizationHelper; import de.monticore.cdconcretization.stereotype.StereotypeUtil; import de.monticore.cdconcretization.type.TypeCompletionContext; import de.monticore.cdconcretization.util.MethodSignatureString; import de.monticore.cdconcretization.util.NameUtil; import de.monticore.symbols.basicsymbols._ast.ASTType; +import de.monticore.symbols.basicsymbols._symboltable.TypeSymbolTOP; import de.monticore.types.check.ISynthesize; import de.monticore.types.check.SymTypeExpression; import de.monticore.types.mcbasictypes._ast.ASTMCPrimitiveType; @@ -37,8 +40,7 @@ public void completeMethodInType(ASTCDType concreteType, ASTCDMethod referenceMe List allConcreteAttributesInHierarchy = CDSymbolTables.getMethodsInHierarchy( concreteType); List incarnations = allConcreteAttributesInHierarchy.stream().filter( - cMethod -> context.getIncarnationMapping().isIncarnation(cMethod, referenceMethod)).collect( - Collectors.toList()); + cMethod -> context.getIncarnationMapping().isIncarnation(cMethod, referenceMethod)).toList(); if (incarnations.isEmpty()) { createMethodIncarnations(concreteType, referenceMethod, context); } @@ -72,7 +74,7 @@ private void createMethodIncarnations(ASTCDType concreteType, ASTCDMethod refere .toList()); addMethodIncarnations(concreteType, referenceMethod, context, returnTypeIncarnations, - parameterTypeIncarnations); + parameterTypeIncarnations, context); } // TODO This is generic functionality not limited to methods. It should be extracted and reused @@ -192,6 +194,48 @@ else if (refMCType instanceof ASTMCMapType) { .getClass().getName()); } + private String adaptMethodNameFromTypePairs(String name, ASTCDMethod referenceMethod, + ASTMCReturnType returnTypeIncarnation, List parameterCombination, TypeCompletionContext context) { + String result = name; + if (referenceMethod.getMCReturnType().isPresentMCType() + && returnTypeIncarnation.isPresentMCType()) { + Optional refRetType = resolveReferenceCDType(referenceMethod.getMCReturnType().getMCType(), context); + Optional conRetType = resolveConcreteCDType(returnTypeIncarnation.getMCType(), context); + if (refRetType.isPresent() && conRetType.isPresent()) { + result = NameUtil.adaptTemplatedName(result, refRetType.get().getName(), + conRetType.get().getName()).orElse(result); + } + } + for (int i = 0; i < referenceMethod.getCDParameterList().size(); i++) { + Optional refParamType = resolveReferenceCDType( + referenceMethod.getCDParameterList().get(i).getMCType(), context); + Optional conParamType = resolveConcreteCDType(parameterCombination.get(i), context); + if (refParamType.isPresent() && conParamType.isPresent()) { + result = NameUtil.adaptTemplatedName(result, refParamType.get().getName(), + conParamType.get().getName()).orElse(result); + } + } + return result; + } + + private Optional resolveReferenceCDType(ASTMCType mcType, TypeCompletionContext context) { + return resolveCDType(mcType, context.getReferenceCD().getEnclosingScope()); + } + + private Optional resolveConcreteCDType(ASTMCType mcType, TypeCompletionContext context) { + return resolveCDType(mcType, context.getConcreteCD().getEnclosingScope()); + } + + private Optional resolveCDType(ASTMCType mcType, ICDBasisScope scope) { + if (!(mcType instanceof ASTMCQualifiedType)) { + return Optional.empty(); + } + String typeName = ((ASTMCQualifiedType) mcType).getMCQualifiedName().getQName(); + return scope.resolveCDTypeDown(typeName) + .filter(TypeSymbolTOP::isPresentAstNode) + .map(CDTypeSymbolTOP::getAstNode); + } + protected ASTMCTypeArgument createTypeArgument(ASTMCType mcType) { if (mcType instanceof ASTMCQualifiedType) { return CD4CodeMill.mCBasicTypeArgumentBuilder().setMCQualifiedType( @@ -211,15 +255,16 @@ else if ((mcType instanceof ASTMCPrimitiveType)) { * Adds one method for each return type and combination of the cartesian product of parameter * types. * - * @param concreteType the concrete type to add the method to - * @param referenceMethod the reference method to clone - * @param context the completion context - * @param returnTypeIncarnations the incarnations of the return type + * @param concreteType the concrete type to add the method to + * @param referenceMethod the reference method to clone + * @param context the completion context + * @param returnTypeIncarnations the incarnations of the return type * @param parameterTypeIncarnations the incarnations of each parameter type + * @param typeCompletionContext */ private void addMethodIncarnations(ASTCDType concreteType, ASTCDMethod referenceMethod, - TypeCompletionContext context, List returnTypeIncarnations, - List> parameterTypeIncarnations) { + TypeCompletionContext context, List returnTypeIncarnations, + List> parameterTypeIncarnations, TypeCompletionContext typeCompletionContext) { List> parameterCombinations = Lists.cartesianProduct(parameterTypeIncarnations); @@ -228,24 +273,46 @@ private void addMethodIncarnations(ASTCDType concreteType, ASTCDMethod reference ASTCDMethod methodClone = referenceMethod.deepClone(); // 1. decide name of method + // Apply implicit name adaptation first (before suffix), using the specific type pairs + // of the return type and parameters for this incarnation combination. + String methodName = referenceMethod.getName(); + if (context.isImplicitNameAdaptationEnabled()) { + methodName = adaptMethodNameFromTypePairs(methodName, referenceMethod, + returnTypeIncarnation, parameterCombination, context); + if (!methodName.equals(referenceMethod.getName())) { + StereotypeUtil.addStereotype(methodClone.getModifier(), context.getMappingName(), + MethodSignatureString.printSignatureIfOverloaded(referenceMethod.getSymbol())); + } + } if (returnTypeIncarnations.size() > 1) { // if we have more than one return type incarnation, we need to add a suffix to the new // methods name // TODO not necessarily! If we change the parameter signature at the same time, we can // keep the original method name! // -> see how we did it in ForEachMethodCompleter - methodClone.setName(referenceMethod.getName() + "_" + NameUtil - .escapeQualifiedNameAsIdentifier(returnTypeIncarnation.printType())); + methodName = methodName + "_" + NameUtil + .escapeQualifiedNameAsIdentifier(returnTypeIncarnation.printType()); } - + methodClone.setName(methodName); + // 2. set return type of the incarnation // use FQ name to avoid messing with imports / name conflicts methodClone.setMCReturnType(returnTypeIncarnation); - + // 3. set parameter types of the incarnation for (int i = 0; i < methodClone.getCDParameterList().size(); i++) { ASTCDParameter parameterClone = methodClone.getCDParameterList().get(i); parameterClone.setMCType(parameterCombination.get(i)); + if (context.isImplicitNameAdaptationEnabled()) { + Optional refParamType = resolveReferenceCDType( + referenceMethod.getCDParameterList().get(i).getMCType(), context); + Optional conParamType = resolveConcreteCDType(parameterCombination.get(i), context); + if (refParamType.isPresent() && conParamType.isPresent()) { + NameUtil.adaptTemplatedName(parameterClone.getName(), + refParamType.get().getName(), conParamType.get().getName()) + .ifPresent(parameterClone::setName); + } + } methodClone.setCDParameter(i, parameterClone); } diff --git a/cddiff/src/test/java/de/monticore/cdconcretization/EvaluationConcretizationTest.java b/cddiff/src/test/java/de/monticore/cdconcretization/EvaluationConcretizationTest.java index 61f94ef66..502a3bdeb 100644 --- a/cddiff/src/test/java/de/monticore/cdconcretization/EvaluationConcretizationTest.java +++ b/cddiff/src/test/java/de/monticore/cdconcretization/EvaluationConcretizationTest.java @@ -139,6 +139,8 @@ void singleInc() { @Test void banking2() { + // TODO Remove once we support method parameters in incarnation mapping + confParameters.add(CDConfParameter.STRICT_PARAMETER_ORDER); testConcretizedConformsToRefAndExpectedOut("evaluation/banking2/BankingConc.cd", "evaluation/banking2/BankingRef.cd", "evaluation/banking2/BankingOut.cd"); } diff --git a/cddiff/src/test/java/de/monticore/cdconcretization/MethodConcretizationTest.java b/cddiff/src/test/java/de/monticore/cdconcretization/MethodConcretizationTest.java index edf3d4361..c72ad2c17 100644 --- a/cddiff/src/test/java/de/monticore/cdconcretization/MethodConcretizationTest.java +++ b/cddiff/src/test/java/de/monticore/cdconcretization/MethodConcretizationTest.java @@ -34,6 +34,8 @@ void testMethodExistsInSuperClass() { @Test void testParameterTypeMI() { + // TODO Remove once we support method parameters in incarnation mapping + confParameters.add(CDConfParameter.STRICT_PARAMETER_ORDER); testConcretizedConformsToRefAndExpectedOut("methods/multiIncarnation/ParameterTypeMIConc.cd", "methods/multiIncarnation/Reference.cd", "methods/multiIncarnation/ParameterTypeMIOut.cd"); } @@ -62,6 +64,8 @@ void testReturnTypeMIOneExists() { @Test void testParameterAndReturnTypeMI() { + // TODO Remove once we support method parameters in incarnation mapping + confParameters.add(CDConfParameter.STRICT_PARAMETER_ORDER); testConcretizedConformsToRefAndExpectedOut( "methods/multiIncarnation/ParameterAndReturnTypeMIConc.cd", "methods/multiIncarnation/Reference.cd", diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/banking/singleInc/BankingOut.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/banking/singleInc/BankingOut.cd index 85ea90034..24d6ff80b 100644 --- a/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/banking/singleInc/BankingOut.cd +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/banking/singleInc/BankingOut.cd @@ -21,8 +21,9 @@ classdiagram Banking { // sends money from sourceAccount to targetAccount class Transaction { - BankAccount sourceAccount; - BankAccount targetAccount; + // TODO: BasicAttributeInTypeCompleter should not add the <> stereotype if CDConfParameter.ADAPTED_NAME_MAPPING is set + <>BankAccount sourceBankAccount; + <>BankAccount targetBankAccount; double amount; } diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/banking/usageByExtension/BankingOut.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/banking/usageByExtension/BankingOut.cd index 9e97cc366..d8ca5fbe7 100644 --- a/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/banking/usageByExtension/BankingOut.cd +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/banking/usageByExtension/BankingOut.cd @@ -24,8 +24,9 @@ classdiagram Banking { // sends money from sourceAccount to targetAccount class Transaction { - BankAccount sourceAccount; - BankAccount targetAccount; + // TODO: BasicAttributeInTypeCompleter should not add the <> stereotype if CDConfParameter.ADAPTED_NAME_MAPPING is set + <>BankAccount sourceBankAccount; + <>BankAccount targetBankAccount; double amount; } diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/methods/multiIncarnation/ParameterAndReturnTypeMIOut.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/methods/multiIncarnation/ParameterAndReturnTypeMIOut.cd index 3827eba23..81d0ad236 100644 --- a/cddiff/src/test/resources/de/monticore/cdconcretization/methods/multiIncarnation/ParameterAndReturnTypeMIOut.cd +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/methods/multiIncarnation/ParameterAndReturnTypeMIOut.cd @@ -7,8 +7,8 @@ classdiagram ParameterAndReturnTypeMIConc { <> class InstantTransaction; class Bank { - <> void createAccount(PersonalAccount account); - <> void createAccount(BusinessAccount account); + <> void createPersonalAccount(PersonalAccount personalAccount); + <> void createBusinessAccount(BusinessAccount businessAccount); <> NormalTransaction transfer_NormalTransaction(PersonalAccount from, PersonalAccount to, int amount); <> NormalTransaction transfer_NormalTransaction(PersonalAccount from, BusinessAccount to, int amount); <> NormalTransaction transfer_NormalTransaction(BusinessAccount from, PersonalAccount to, int amount); diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/methods/multiIncarnation/ParameterTypeMIOut.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/methods/multiIncarnation/ParameterTypeMIOut.cd index 49745169a..df3a3e5a0 100644 --- a/cddiff/src/test/resources/de/monticore/cdconcretization/methods/multiIncarnation/ParameterTypeMIOut.cd +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/methods/multiIncarnation/ParameterTypeMIOut.cd @@ -6,8 +6,8 @@ classdiagram ParameterTypeMIConc { class Transaction; class Bank { - <> void createAccount(PersonalAccount account); - <> void createAccount(BusinessAccount account); + <> void createPersonalAccount(PersonalAccount personalAccount); + <> void createBusinessAccount(BusinessAccount businessAccount); <> Transaction transfer(PersonalAccount from, PersonalAccount to, int amount); <> Transaction transfer(PersonalAccount from, BusinessAccount to, int amount); <> Transaction transfer(BusinessAccount from, PersonalAccount to, int amount); diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/multipleIncarnation/BothAssocSidesMINameAdaptationOut.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/multipleIncarnation/BothAssocSidesMINameAdaptationOut.cd new file mode 100644 index 000000000..65d771849 --- /dev/null +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/multipleIncarnation/BothAssocSidesMINameAdaptationOut.cd @@ -0,0 +1,15 @@ +classdiagram BothAssocSidesMIConc { + <> class SuperTeacher1; + <> class SuperTeacher2; + + <> class Teacher1 extends SuperTeacher2, SuperTeacher1; + <> class Teacher2 extends SuperTeacher2, SuperTeacher1; + + <> class Department1; + <> class Department2; + + association Teacher1 (hasTeacher_Teacher1) -- (teachesIn_Department1) Department1; + association Teacher1 (hasTeacher_Teacher1) -- (teachesIn_Department2) Department2; + association Teacher2 (hasTeacher_Teacher2) -- (teachesIn_Department1) Department1; + association Teacher2 (hasTeacher_Teacher2) -- (teachesIn_Department2) Department2; +} From 6c8366e1863c271ed69debb529ab7fe6f683a3e6 Mon Sep 17 00:00:00 2001 From: Felix Jordan Date: Sun, 29 Mar 2026 19:15:29 +0200 Subject: [PATCH 07/16] fix(cdconc): avoid adding duplicate stereotype during completion --- .../monticore/cdconcretization/stereotype/StereotypeUtil.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cddiff/src/main/java/de/monticore/cdconcretization/stereotype/StereotypeUtil.java b/cddiff/src/main/java/de/monticore/cdconcretization/stereotype/StereotypeUtil.java index 85e127b3d..b11313779 100644 --- a/cddiff/src/main/java/de/monticore/cdconcretization/stereotype/StereotypeUtil.java +++ b/cddiff/src/main/java/de/monticore/cdconcretization/stereotype/StereotypeUtil.java @@ -87,6 +87,10 @@ public static void addStereotype(ASTModifier modifier, String name, String conte ASTStereotype stereotype; if (modifier.isPresentStereotype()) { stereotype = modifier.getStereotype(); + // Avoid duplicates: skip if a stereotype with the same name and value already exists + if (stereotype.streamValues().anyMatch(v -> v.getName().equals(name) && v.getValue().equals(content))) { + return; + } } else { stereotype = CD4CodeMill.stereotypeBuilder().build(); From 3b72d9c92d8bc920a738a5a42f39b00e14556065 Mon Sep 17 00:00:00 2001 From: Felix Jordan Date: Sun, 29 Mar 2026 19:45:30 +0200 Subject: [PATCH 08/16] feat(cdconc): not add "ref" stereotype if ADAPTED_NAME_MAPPING is set --- cddiff/doc/cdconcretization/README.md | 40 ++++ .../feature-implicit-name-adaptation.md | 203 ++++++++++++++++++ .../BaseAttributeInTypeCompleter.java | 7 +- .../method/BaseMethodInTypeCompleter.java | 4 +- .../banking/singleInc/BankingOut.cd | 5 +- .../banking/usageByExtension/BankingOut.cd | 5 +- 6 files changed, 255 insertions(+), 9 deletions(-) create mode 100644 cddiff/doc/cdconcretization/README.md create mode 100644 cddiff/doc/cdconcretization/feature-implicit-name-adaptation.md diff --git a/cddiff/doc/cdconcretization/README.md b/cddiff/doc/cdconcretization/README.md new file mode 100644 index 000000000..e341911cc --- /dev/null +++ b/cddiff/doc/cdconcretization/README.md @@ -0,0 +1,40 @@ +# CD Completion + +CD Completion automatically extends an incomplete *concrete* Class Diagram so that it +conforms to a *reference* Class Diagram. The entry point is +`de.monticore.cdconcretization.ConcretizationCompleter`. + +## Key Concepts + +| Concept | Description | +|---|---| +| **Reference CD** | A CD that defines the structure a concrete CD must implement. | +| **Concrete CD** | An incomplete CD that is extended by the completer. | +| **Incarnation** | A concrete element that implements a reference element. | +| **Mapping** | A named set of stereotype-encoded incarnation bindings (e.g., `<>` or `<>`). | + +## Configuration Parameters (`CDConfParameter`) + +The completer and conformance checker share a set of parameters that control matching and +adaptation behaviour. Parameters relevant to completion are listed below. + +| Parameter | Effect | +|---|---| +| `STEREOTYPE_MAPPING` | Incarnations are identified by explicit `<>` stereotypes. | +| `NAME_MAPPING` | Incarnations are identified by equal element names. | +| `METHOD_OVERLOADING` | Method incarnations are matched by full signature (name + parameter types), not just name. | +| `STRICT_PARAMETER_ORDER` | Method parameters are matched by type in strict positional order, without requiring name equality. Without this, parameters must also match by name. | +| `INHERITANCE` | Attributes, methods, and associations required by a reference type may be defined in a supertype of the concrete incarnation type, rather than in the incarnation type itself. | +| `SRC_TARGET_ASSOC_MAPPING` | Association incarnations are matched by source type and target role name. | +| `ADAPTED_NAME_MAPPING` | Enables name-adapted incarnation matching: a concrete element whose name is the type-incarnation-adapted form of a reference element name is recognised as its incarnation — without requiring an explicit stereotype. See [Implicit Name Adaptation](feature-implicit-name-adaptation.md). | + +## Feature Documentation + +- [Implicit Name Adaptation](feature-implicit-name-adaptation.md) — how element names are + automatically adapted during completion based on type incarnation pairs, and how + `ADAPTED_NAME_MAPPING` affects stereotype generation. + +## Related Design / Planning Notes + +- [Implicit Name Adaptation (design notes)](implicit-name-adaptation.md) +- [Name Adaptation Improvements (future work)](name-adaptation-improvements.md) diff --git a/cddiff/doc/cdconcretization/feature-implicit-name-adaptation.md b/cddiff/doc/cdconcretization/feature-implicit-name-adaptation.md new file mode 100644 index 000000000..d2daaa2f4 --- /dev/null +++ b/cddiff/doc/cdconcretization/feature-implicit-name-adaptation.md @@ -0,0 +1,203 @@ +# Feature: Implicit Name Adaptation + +## Overview + +When the completer creates a new element (attribute, method, or association role) in the +concrete CD, it copies the name from the reference model by default. +**Implicit name adaptation** improves this by automatically renaming the element whenever its +name contains a reference type name as a recognisable substring — replacing it with the +corresponding concrete type name. + +The adaptation recognises three patterns. Using `Task → Ticket` as an example: + +| Pattern | Reference name | Adapted name | +|---|---|---| +| Exact match | `task` | `ticket` | +| Capitalised infix | `assignedTask` | `assignedTicket` | +| Capitalised prefix | `taskList` | `ticketList` | + +If none of the patterns match, the name is left unchanged and no adaptation is applied. + +The feature is enabled by default and can be disabled on `ConcretizationCompleter` via +`setImplicitNameAdaptationEnabled(false)`. + +--- + +## Affected Elements + +### Attributes + +An attribute's name is adapted using the type pair of its own declared type. If the attribute +type `T_ref` is incarnated by `T_con`, the completer tries to replace occurrences of +`T_ref`'s name in the attribute name with `T_con`'s name. + +**Example** — reference attribute `assignedTasks: Task`, concrete incarnation `Ticket` of `Task`: + +``` +// Reference CD +class Project { + Task assignedTasks; +} + +// Concrete CD (input) +<> class Ticket; +<> class Sprint; + +// Completed output +class Sprint { + Ticket assignedTickets; // "Tasks" → "Tickets" +} +``` + +### Methods + +A method's name is adapted using the type pairs of its return type and all parameter types, +applied in sequence. This means a method whose name references multiple types can be fully +adapted in a single pass. Parameter names are adapted the same way, each using its own +parameter type's pair. + +**Example 1** — adaptation via return type: + +``` +// Reference CD +class TaskRepository { + Task findTask(String id); +} + +// Concrete incarnation: Ticket → Task + +// Completed output +class TicketRepository { + Ticket findTicket(String id); // method name and return type both adapted +} +``` + +**Example 2** — adaptation via multiple parameter types: + +``` +// Reference CD +interface Comparator { + int compareInputAndOutput(Input a, Output b); +} + +// Concrete incarnations: Foo → Input, Bar → Output + +// Completed output +interface ConcreteComparator { + int compareFooAndBar(Foo a, Bar b); +} +``` + +### Association Roles and Names + +Each role name is adapted using the type pair of its own endpoint. The association name, if +present, is adapted using both endpoint type pairs in sequence. + +**Example**: + +``` +// Reference CD +association [*] (managedAccounts) Account <-> Bank; + +// Concrete incarnations: BankAccount → Account, SEPABank → Bank + +// Completed output +association [*] (managedBankAccounts) BankAccount <-> SEPABank; +``` + +--- + +## Interaction with `ADAPTED_NAME_MAPPING` + +The `ADAPTED_NAME_MAPPING` conformance parameter controls how the conformance checker +recognises implicitly adapted names as valid incarnations. This in turn affects whether the +completer adds a `<>` stereotype to adapted elements. + +### Without `ADAPTED_NAME_MAPPING` + +When this parameter is not set, the conformance checker has no strategy to recognise, for +example, `assignedTickets` as an incarnation of `assignedTasks`. To ensure conformance still +passes, the completer adds a `<>` stereotype to every element whose name was changed by +implicit adaptation. The stereotype-based matcher (`STAttributeIncStrategy` / +`STMethodIncStrategy`) then identifies it as a valid incarnation. + +``` +// Completed output — no ADAPTED_NAME_MAPPING +class Sprint { + <> Ticket assignedTickets; +} +``` + +### With `ADAPTED_NAME_MAPPING` (recommended) + +When this parameter is set, the conformance checker includes `AdaptedNameAttributeIncStrategy` +and `AdaptedNameMethodIncStrategy`, which recognise adapted names directly by applying the +same substitution patterns used during completion. No stereotype is needed, producing cleaner +output: + +``` +// Completed output — with ADAPTED_NAME_MAPPING +class Sprint { + Ticket assignedTickets; +} +``` + +This is the recommended configuration for auto-completed CDs. It also means a hand-written +concrete CD that follows the adapted naming convention will pass conformance checks without +requiring manual stereotype annotations. + +> **Note:** The `<>` stereotype is still added even with `ADAPTED_NAME_MAPPING` when the +> name was changed by the **multi-incarnation suffix** (e.g., `sourceAccount_BankAccount`), +> because that suffix is not derivable by name pattern matching alone. + +### Summary + +| Scenario | `<>` stereotype added? | Matched by | +|---|---|---| +| Name unchanged (single incarnation) | No | `EqNameAttributeIncStrategy` / `EqSignatureMethodIncStrategy` | +| Name unchanged (multiple incarnations, suffix added) | Yes | `STAttributeIncStrategy` / `STMethodIncStrategy` | +| Name adapted, `ADAPTED_NAME_MAPPING` **not** set | Yes | `STAttributeIncStrategy` / `STMethodIncStrategy` | +| Name adapted, `ADAPTED_NAME_MAPPING` set | No | `AdaptedNameAttributeIncStrategy` / `AdaptedNameMethodIncStrategy` | + +--- + +## Complete Example: Task Management + +**Reference CD:** + +``` +classdiagram TaskRef { + class Task { + String title; + Task assignedTask; + } + association Project -> (assignedTasks) Task [*]; +} +``` + +**Concrete CD (input):** + +``` +classdiagram TaskConc { + <> class Ticket { String title; } + <> class Sprint; +} +``` + +**Completed output** (with `ADAPTED_NAME_MAPPING`): + +``` +classdiagram TaskConc { + <> class Ticket { + String title; + Ticket assignedTicket; + } + <> class Sprint; + association Sprint -> (assignedTickets) Ticket [*]; +} +``` + +Both the attribute name (`assignedTask` → `assignedTicket`) and the association role name +(`assignedTasks` → `assignedTickets`) are adapted automatically. No `<>` stereotypes are +added to these adapted elements because `ADAPTED_NAME_MAPPING` is set and the conformance +checker can identify them by their adapted names directly. diff --git a/cddiff/src/main/java/de/monticore/cdconcretization/type/attribute/BaseAttributeInTypeCompleter.java b/cddiff/src/main/java/de/monticore/cdconcretization/type/attribute/BaseAttributeInTypeCompleter.java index ec513e319..74b6e3355 100644 --- a/cddiff/src/main/java/de/monticore/cdconcretization/type/attribute/BaseAttributeInTypeCompleter.java +++ b/cddiff/src/main/java/de/monticore/cdconcretization/type/attribute/BaseAttributeInTypeCompleter.java @@ -9,6 +9,7 @@ import de.monticore.cdbasis._ast.ASTCDType; import de.monticore.cdconcretization.CompletionException; import de.monticore.cdconcretization.stereotype.StereotypeUtil; +import de.monticore.cdconformance.CDConfParameter; import de.monticore.cdconcretization.type.TypeCompletionContext; import de.monticore.cdconcretization.util.NameUtil; import de.monticore.symbols.basicsymbols._ast.ASTType; @@ -94,8 +95,10 @@ private void createAttributeIncarnations(ASTCDType concreteType, NameUtil.adaptTemplatedName(attributeIncarnation.getName(), rAttributeType.getName(), cAttributeType.getName()).ifPresent(adapted -> { attributeIncarnation.setName(adapted); - StereotypeUtil.addStereotype(attributeIncarnation.getModifier(), - context.getMappingName(), referenceAttribute.getSymbol().getFullName()); + if (!context.getConformanceParams().contains(CDConfParameter.ADAPTED_NAME_MAPPING)) { + StereotypeUtil.addStereotype(attributeIncarnation.getModifier(), + context.getMappingName(), referenceAttribute.getSymbol().getFullName()); + } }); } diff --git a/cddiff/src/main/java/de/monticore/cdconcretization/type/method/BaseMethodInTypeCompleter.java b/cddiff/src/main/java/de/monticore/cdconcretization/type/method/BaseMethodInTypeCompleter.java index deb5b80e3..7d59295df 100644 --- a/cddiff/src/main/java/de/monticore/cdconcretization/type/method/BaseMethodInTypeCompleter.java +++ b/cddiff/src/main/java/de/monticore/cdconcretization/type/method/BaseMethodInTypeCompleter.java @@ -12,6 +12,7 @@ import de.monticore.cdbasis._symboltable.CDTypeSymbolTOP; import de.monticore.cdconcretization.ConcretizationHelper; import de.monticore.cdconcretization.stereotype.StereotypeUtil; +import de.monticore.cdconformance.CDConfParameter; import de.monticore.cdconcretization.type.TypeCompletionContext; import de.monticore.cdconcretization.util.MethodSignatureString; import de.monticore.cdconcretization.util.NameUtil; @@ -279,7 +280,8 @@ private void addMethodIncarnations(ASTCDType concreteType, ASTCDMethod reference if (context.isImplicitNameAdaptationEnabled()) { methodName = adaptMethodNameFromTypePairs(methodName, referenceMethod, returnTypeIncarnation, parameterCombination, context); - if (!methodName.equals(referenceMethod.getName())) { + if (!methodName.equals(referenceMethod.getName()) + && !context.getConformanceParams().contains(CDConfParameter.ADAPTED_NAME_MAPPING)) { StereotypeUtil.addStereotype(methodClone.getModifier(), context.getMappingName(), MethodSignatureString.printSignatureIfOverloaded(referenceMethod.getSymbol())); } diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/banking/singleInc/BankingOut.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/banking/singleInc/BankingOut.cd index 24d6ff80b..01b50e833 100644 --- a/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/banking/singleInc/BankingOut.cd +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/banking/singleInc/BankingOut.cd @@ -21,9 +21,8 @@ classdiagram Banking { // sends money from sourceAccount to targetAccount class Transaction { - // TODO: BasicAttributeInTypeCompleter should not add the <> stereotype if CDConfParameter.ADAPTED_NAME_MAPPING is set - <>BankAccount sourceBankAccount; - <>BankAccount targetBankAccount; + BankAccount sourceBankAccount; + BankAccount targetBankAccount; double amount; } diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/banking/usageByExtension/BankingOut.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/banking/usageByExtension/BankingOut.cd index d8ca5fbe7..5da2d0fb0 100644 --- a/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/banking/usageByExtension/BankingOut.cd +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/banking/usageByExtension/BankingOut.cd @@ -24,9 +24,8 @@ classdiagram Banking { // sends money from sourceAccount to targetAccount class Transaction { - // TODO: BasicAttributeInTypeCompleter should not add the <> stereotype if CDConfParameter.ADAPTED_NAME_MAPPING is set - <>BankAccount sourceBankAccount; - <>BankAccount targetBankAccount; + BankAccount sourceBankAccount; + BankAccount targetBankAccount; double amount; } From 81adfc2cb3f895a15817f0cd5344ba4203e09490 Mon Sep 17 00:00:00 2001 From: Felix Jordan Date: Sun, 29 Mar 2026 20:05:24 +0200 Subject: [PATCH 09/16] feat(cdconc): first working implicit assoc name adaptation --- .../association/DefaultAssocCompleter.java | 55 +++++++++++++++++- .../DefaultAssocSideCompleter.java | 8 ++- .../cd/MissingAssociationsCDCompleter.java | 57 ++++++++++++++++--- .../AssociationConcretizationTest.java | 26 ++++++++- .../cdconcretization/EvaluationOut.cd | 2 +- .../AssocNameImplicitNameAdaptConc.cd | 7 +++ .../AssocNameImplicitNameAdaptOut.cd | 7 +++ .../AssocNameImplicitNameAdaptRef.cd | 7 +++ .../AssocRoleImplicitNameAdaptConc.cd | 7 +++ .../AssocRoleImplicitNameAdaptOut.cd | 7 +++ .../AssocRoleImplicitNameAdaptRef.cd | 6 ++ .../BothAssocSidesMINameAdaptationOut.cd | 8 +-- .../BothAssocSidesMIOneAssocExistsOut.cd | 4 +- .../BothAssocSidesMIOut.cd | 8 +-- 14 files changed, 185 insertions(+), 24 deletions(-) create mode 100644 cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocNameImplicitNameAdaptConc.cd create mode 100644 cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocNameImplicitNameAdaptOut.cd create mode 100644 cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocNameImplicitNameAdaptRef.cd create mode 100644 cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocRoleImplicitNameAdaptConc.cd create mode 100644 cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocRoleImplicitNameAdaptOut.cd create mode 100644 cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocRoleImplicitNameAdaptRef.cd diff --git a/cddiff/src/main/java/de/monticore/cdconcretization/association/DefaultAssocCompleter.java b/cddiff/src/main/java/de/monticore/cdconcretization/association/DefaultAssocCompleter.java index 5bb71f1d9..dc24ffd48 100644 --- a/cddiff/src/main/java/de/monticore/cdconcretization/association/DefaultAssocCompleter.java +++ b/cddiff/src/main/java/de/monticore/cdconcretization/association/DefaultAssocCompleter.java @@ -7,19 +7,31 @@ import de.monticore.cdbasis._ast.ASTCDType; import de.monticore.cdconcretization.CompletionException; import de.monticore.cdconcretization.ConcretizationHelper; +import de.monticore.cdconcretization.cd.CDCompletionContext; +import de.monticore.cdconcretization.util.NameUtil; import de.monticore.cddiff.CDDiffUtil; +import de.se_rwth.commons.logging.Log; import java.util.LinkedHashSet; import java.util.Set; public class DefaultAssocCompleter implements IAssociationCompleter { private final ASTCDCompilationUnit ccd; - + private final IAssocSideCompleter assocSideCompleter; - + + /** Nullable; when non-null, implicit name adaptation is applied if enabled in context. */ + private final CDCompletionContext context; + public DefaultAssocCompleter(ASTCDCompilationUnit ccd, IAssocSideCompleter assocSideCompleter) { + this(ccd, assocSideCompleter, null); + } + + public DefaultAssocCompleter(ASTCDCompilationUnit ccd, IAssocSideCompleter assocSideCompleter, + CDCompletionContext context) { this.ccd = ccd; this.assocSideCompleter = assocSideCompleter; + this.context = context; } @Override @@ -41,6 +53,45 @@ public void completeAssociation(ASTCDAssociation cAssoc, ASTCDAssociation rAssoc break; } + // Apply implicit name adaptation for role names and association name using the + // specific endpoint type pairs only (avoids chaining bugs). + if (context != null && context.isImplicitNameAdaptationEnabled()) { + try { + ASTCDType rRightType = ConcretizationHelper.getAssocRightType(context.getReferenceCD(), + rAssoc); + ASTCDType rLeftType = ConcretizationHelper.getAssocLeftType(context.getReferenceCD(), + rAssoc); + ASTCDType cRightType = ConcretizationHelper.getAssocRightType(ccd, cAssoc); + ASTCDType cLeftType = ConcretizationHelper.getAssocLeftType(ccd, cAssoc); + // In REVERSE_DIRECTION the right side of cAssoc corresponds to the left side of rAssoc + ASTCDType rTypeForConRight = matchDirection == AssocMatchDirection.SAME_DIRECTION + ? rRightType : rLeftType; + ASTCDType rTypeForConLeft = matchDirection == AssocMatchDirection.SAME_DIRECTION + ? rLeftType : rRightType; + if (cAssoc.getRight().isPresentCDRole()) { + NameUtil.adaptTemplatedName(cAssoc.getRight().getCDRole().getName(), + rTypeForConRight.getName(), cRightType.getName()) + .ifPresent(n -> cAssoc.getRight().getCDRole().setName(n)); + } + if (cAssoc.getLeft().isPresentCDRole()) { + NameUtil.adaptTemplatedName(cAssoc.getLeft().getCDRole().getName(), + rTypeForConLeft.getName(), cLeftType.getName()) + .ifPresent(n -> cAssoc.getLeft().getCDRole().setName(n)); + } + if (cAssoc.isPresentName()) { + String name = cAssoc.getName(); + name = NameUtil.adaptTemplatedName(name, rTypeForConLeft.getName(), + cLeftType.getName()).orElse(name); + name = NameUtil.adaptTemplatedName(name, rTypeForConRight.getName(), + cRightType.getName()).orElse(name); + cAssoc.setName(name); + } + } catch (CompletionException e) { + Log.warn("0xCDCONC1: Could not resolve association endpoint types for implicit name" + + " adaptation, skipping."); + } + } + // Handle potential role name conflicts in a post-processing step renameRoleIfConflicting(cAssoc); } diff --git a/cddiff/src/main/java/de/monticore/cdconcretization/association/DefaultAssocSideCompleter.java b/cddiff/src/main/java/de/monticore/cdconcretization/association/DefaultAssocSideCompleter.java index c6ed98986..8874613fc 100644 --- a/cddiff/src/main/java/de/monticore/cdconcretization/association/DefaultAssocSideCompleter.java +++ b/cddiff/src/main/java/de/monticore/cdconcretization/association/DefaultAssocSideCompleter.java @@ -1,6 +1,7 @@ /* (c) https://github.com/MontiCore/monticore */ package de.monticore.cdconcretization.association; +import de.monticore.cd4code.CD4CodeMill; import de.monticore.cdassociation._ast.ASTCDAssocSide; import de.monticore.cdconcretization.CompletionException; @@ -33,7 +34,12 @@ else if (cAssocSide.isPresentCDCardinality() && rAssocSide.isPresentCDCardinalit private void completeAssociationRoleNames(ASTCDAssocSide cAssocSide, ASTCDAssocSide rAssocSide) { if (!cAssocSide.isPresentCDRole() && rAssocSide.isPresentCDRole()) { - cAssocSide.setCDRole(rAssocSide.getCDRole()); + // Build a fresh CDRole with just the name, rather than sharing the reference AST node. + // Sharing the node would allow implicit name adaptation (which calls setName()) to silently + // corrupt the reference association's role name, breaking subsequent mapping passes. + cAssocSide.setCDRole(CD4CodeMill.cDRoleBuilder() + .setName(rAssocSide.getCDRole().getName()) + .build()); } } diff --git a/cddiff/src/main/java/de/monticore/cdconcretization/cd/MissingAssociationsCDCompleter.java b/cddiff/src/main/java/de/monticore/cdconcretization/cd/MissingAssociationsCDCompleter.java index 58e6dda17..e6ad8a799 100644 --- a/cddiff/src/main/java/de/monticore/cdconcretization/cd/MissingAssociationsCDCompleter.java +++ b/cddiff/src/main/java/de/monticore/cdconcretization/cd/MissingAssociationsCDCompleter.java @@ -12,6 +12,7 @@ import de.monticore.cdconcretization.association.AssocMatchDirection; import de.monticore.cdconcretization.association.AssociationMatch; import de.monticore.cdconcretization.association.IAssociationCompleter; +import de.monticore.cdconcretization.util.NameUtil; import de.monticore.cddiff.CDDiffUtil; import de.monticore.cdmatcher.ExternalCandidatesMatchingStrategy; import de.monticore.cdmatcher.MatchCDAssocsGreedy; @@ -249,23 +250,61 @@ private void addAssociationIncarnations(ASTCDCompilationUnit concreteCD, .setMCQualifiedName(MCQualifiedNameFacade.createQualifiedName(rightTypeInc.getSymbol() .getInternalQualifiedName())).build()); - // If the right type does not have a role name, it is implicitly the type incarnation's - // name (first character lowercase) anyway. - // If a role name is already present and there are multiple incarnations of the - // type, append the type incarnation's name to it to avoid name conflicts. - // NOTE: leftTypesIncs, and rightTypeIncs are ONLY the sets of type incarnations that still need an - // association incarnation. We need to consider the TOTAL number of type incarnations to decide - // whether we need to append the type incarnation name or not. + // Capture original role names (from the reference) before implicit adaptation, so we can + // detect whether adaptation changed them and avoid adding a redundant suffix afterwards. + String originalRightRoleName = association.getRight().isPresentCDRole() + ? association.getRight().getCDRole().getName() : null; + String originalLeftRoleName = association.getLeft().isPresentCDRole() + ? association.getLeft().getCDRole().getName() : null; + + // Apply implicit name adaptation for role names and association name, using only the + // specific endpoint type incarnation pairs for this association (avoids chaining issues). + if (context.isImplicitNameAdaptationEnabled()) { + ASTCDType rRightType = ConcretizationHelper.getAssocRightType(context.getReferenceCD(), + referenceAssociation); + ASTCDType rLeftType = ConcretizationHelper.getAssocLeftType(context.getReferenceCD(), + referenceAssociation); + if (association.getRight().isPresentCDRole()) { + NameUtil.adaptTemplatedName(association.getRight().getCDRole().getName(), + rRightType.getName(), rightTypeInc.getName()).ifPresent( + n -> association.getRight().getCDRole().setName(n)); + } + if (association.getLeft().isPresentCDRole()) { + NameUtil.adaptTemplatedName(association.getLeft().getCDRole().getName(), + rLeftType.getName(), leftTypeInc.getName()).ifPresent( + n -> association.getLeft().getCDRole().setName(n)); + } + if (association.isPresentName()) { + // Apply both endpoint type pairs sequentially (left first, then right) + String assocName = association.getName(); + assocName = NameUtil.adaptTemplatedName(assocName, rLeftType.getName(), + leftTypeInc.getName()).orElse(assocName); + assocName = NameUtil.adaptTemplatedName(assocName, rRightType.getName(), + rightTypeInc.getName()).orElse(assocName); + association.setName(assocName); + } + } + + // If a role name is present and there are multiple incarnations of the type, append the + // type incarnation's name as a suffix to avoid name conflicts — unless implicit name + // adaptation already produced a unique name for this incarnation (in which case the suffix + // would be redundant and misleading). + // NOTE: leftTypesIncs and rightTypeIncs are ONLY the sets still needing an association + // incarnation. We use the TOTAL incarnation count to decide whether a suffix is needed. int totalRightTypeIncs = context.getIncarnationMapping().getIncarnations( ConcretizationHelper.getAssocRightType(context.getReferenceCD(), referenceAssociation)) .size(); - if (association.getRight().isPresentCDRole() && totalRightTypeIncs > 1) { + boolean rightRoleAdapted = originalRightRoleName != null && !originalRightRoleName.equals( + association.getRight().getCDRole().getName()); + if (association.getRight().isPresentCDRole() && totalRightTypeIncs > 1 && !rightRoleAdapted) { association.getRight().getCDRole().setName(association.getRight().getCDRole().getName() + "_" + rightTypeInc.getName()); } int totalLeftTypeIncs = context.getIncarnationMapping().getIncarnations(ConcretizationHelper .getAssocLeftType(context.getReferenceCD(), referenceAssociation)).size(); - if (association.getLeft().isPresentCDRole() && totalLeftTypeIncs > 1) { + boolean leftRoleAdapted = originalLeftRoleName != null && !originalLeftRoleName.equals( + association.getLeft().getCDRole().getName()); + if (association.getLeft().isPresentCDRole() && totalLeftTypeIncs > 1 && !leftRoleAdapted) { association.getLeft().getCDRole().setName(association.getLeft().getCDRole().getName() + "_" + leftTypeInc.getName()); } diff --git a/cddiff/src/test/java/de/monticore/cdconcretization/AssociationConcretizationTest.java b/cddiff/src/test/java/de/monticore/cdconcretization/AssociationConcretizationTest.java index 410d186fb..4fe795489 100644 --- a/cddiff/src/test/java/de/monticore/cdconcretization/AssociationConcretizationTest.java +++ b/cddiff/src/test/java/de/monticore/cdconcretization/AssociationConcretizationTest.java @@ -77,5 +77,29 @@ void testTypeMIOneAssocExists() { testConcretizedConformsToRefAndExpectedOut("associations/TypeMIOneAssocExistsConc.cd", "associations/TypeMIOneAssocExistsRef.cd", "associations/TypeMIOneAssocExistsOut.cd"); } - + + /** + * Tests that a role name copied from the reference into an existing association is implicitly + * adapted: "assignedTask" becomes "assignedTicket" because Ticket incarnates Task. + */ + @Test + void testAssocRoleImplicitNameAdaptation() { + testConcretizedConformsToRefAndExpectedOut( + "associations/AssocRoleImplicitNameAdaptConc.cd", + "associations/AssocRoleImplicitNameAdaptRef.cd", + "associations/AssocRoleImplicitNameAdaptOut.cd"); + } + + /** + * Tests that an association name copied from the reference into an existing association is + * implicitly adapted: "taskAssignment" becomes "ticketAssignment" because Ticket incarnates Task. + */ + @Test + void testAssocNameImplicitNameAdaptation() { + testConcretizedConformsToRefAndExpectedOut( + "associations/AssocNameImplicitNameAdaptConc.cd", + "associations/AssocNameImplicitNameAdaptRef.cd", + "associations/AssocNameImplicitNameAdaptOut.cd"); + } + } diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/EvaluationOut.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/EvaluationOut.cd index 8b9854117..4b6f15018 100644 --- a/cddiff/src/test/resources/de/monticore/cdconcretization/EvaluationOut.cd +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/EvaluationOut.cd @@ -27,5 +27,5 @@ classdiagram EvaluationConc { association staffEmployment Staff(partOf)<->(member)Employee [1..*]; association Staff--Project; <>association traineeProgram Administration(hostAdm)<-(trainee)Trainee[0..*]; - association departmentAssignment [1]Staff<->Administration [1]; + association administrationAssignment [1]Staff<->Administration [1]; } \ No newline at end of file diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocNameImplicitNameAdaptConc.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocNameImplicitNameAdaptConc.cd new file mode 100644 index 000000000..95622df11 --- /dev/null +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocNameImplicitNameAdaptConc.cd @@ -0,0 +1,7 @@ +/* (c) https://github.com/MontiCore/monticore */ +classdiagram AssocNameImplicitNameAdaptConc { + <> class Ticket; + <> class Sprint; + // existing association without name — DefaultAssocCompleter copies and adapts it + association Sprint -> Ticket [*]; +} diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocNameImplicitNameAdaptOut.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocNameImplicitNameAdaptOut.cd new file mode 100644 index 000000000..9d0f4311d --- /dev/null +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocNameImplicitNameAdaptOut.cd @@ -0,0 +1,7 @@ +/* (c) https://github.com/MontiCore/monticore */ +classdiagram AssocNameImplicitNameAdaptConc { + <> class Ticket; + <> class Sprint; + // association name "taskAssignment" copied from reference and adapted: "task" -> "ticket" + association ticketAssignment Sprint -> Ticket [*]; +} diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocNameImplicitNameAdaptRef.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocNameImplicitNameAdaptRef.cd new file mode 100644 index 000000000..808123b4d --- /dev/null +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocNameImplicitNameAdaptRef.cd @@ -0,0 +1,7 @@ +/* (c) https://github.com/MontiCore/monticore */ +classdiagram AssocNameImplicitNameAdaptRef { + class Task; + class Project; + // association name "taskAssignment" starts with uncapitalised "task" -> adapted to "ticketAssignment" + association taskAssignment Project -> Task [*]; +} diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocRoleImplicitNameAdaptConc.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocRoleImplicitNameAdaptConc.cd new file mode 100644 index 000000000..22239ab21 --- /dev/null +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocRoleImplicitNameAdaptConc.cd @@ -0,0 +1,7 @@ +/* (c) https://github.com/MontiCore/monticore */ +classdiagram AssocRoleImplicitNameAdaptConc { + <> class Ticket; + <> class Sprint; + // existing association without role name — DefaultAssocCompleter adds and adapts it + association Sprint -> Ticket [*]; +} diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocRoleImplicitNameAdaptOut.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocRoleImplicitNameAdaptOut.cd new file mode 100644 index 000000000..e53a03b11 --- /dev/null +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocRoleImplicitNameAdaptOut.cd @@ -0,0 +1,7 @@ +/* (c) https://github.com/MontiCore/monticore */ +classdiagram AssocRoleImplicitNameAdaptConc { + <> class Ticket; + <> class Sprint; + // role name "assignedTask" copied from reference and adapted: "Task" -> "Ticket" + association Sprint -> (assignedTicket) Ticket [*]; +} diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocRoleImplicitNameAdaptRef.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocRoleImplicitNameAdaptRef.cd new file mode 100644 index 000000000..e2ae2a7a2 --- /dev/null +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocRoleImplicitNameAdaptRef.cd @@ -0,0 +1,6 @@ +/* (c) https://github.com/MontiCore/monticore */ +classdiagram AssocRoleImplicitNameAdaptRef { + class Task; + class Project; + association Project -> (assignedTask) Task [*]; +} diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/multipleIncarnation/BothAssocSidesMINameAdaptationOut.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/multipleIncarnation/BothAssocSidesMINameAdaptationOut.cd index 65d771849..aadd20ad7 100644 --- a/cddiff/src/test/resources/de/monticore/cdconcretization/multipleIncarnation/BothAssocSidesMINameAdaptationOut.cd +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/multipleIncarnation/BothAssocSidesMINameAdaptationOut.cd @@ -8,8 +8,8 @@ classdiagram BothAssocSidesMIConc { <> class Department1; <> class Department2; - association Teacher1 (hasTeacher_Teacher1) -- (teachesIn_Department1) Department1; - association Teacher1 (hasTeacher_Teacher1) -- (teachesIn_Department2) Department2; - association Teacher2 (hasTeacher_Teacher2) -- (teachesIn_Department1) Department1; - association Teacher2 (hasTeacher_Teacher2) -- (teachesIn_Department2) Department2; + association Teacher1 (hasTeacher1) -- (teachesIn_Department1) Department1; + association Teacher1 (hasTeacher1) -- (teachesIn_Department2) Department2; + association Teacher2 (hasTeacher2) -- (teachesIn_Department1) Department1; + association Teacher2 (hasTeacher2) -- (teachesIn_Department2) Department2; } diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/multipleIncarnation/BothAssocSidesMIOneAssocExistsOut.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/multipleIncarnation/BothAssocSidesMIOneAssocExistsOut.cd index d0078dd88..95f0521f7 100644 --- a/cddiff/src/test/resources/de/monticore/cdconcretization/multipleIncarnation/BothAssocSidesMIOneAssocExistsOut.cd +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/multipleIncarnation/BothAssocSidesMIOneAssocExistsOut.cd @@ -18,8 +18,8 @@ classdiagram BothAssocSidesMIConc { } - association Teacher1(hasTeacher)--(teachesIn)Department1; - association Teacher2(hasTeacher)--(teachesIn)Department2; + association Teacher1(hasTeacher1)--(teachesIn)Department1; + association Teacher2(hasTeacher2)--(teachesIn)Department2; // no more associations are added here because each type incarnation already has one association } diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/multipleIncarnation/BothAssocSidesMIOut.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/multipleIncarnation/BothAssocSidesMIOut.cd index 65d771849..aadd20ad7 100644 --- a/cddiff/src/test/resources/de/monticore/cdconcretization/multipleIncarnation/BothAssocSidesMIOut.cd +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/multipleIncarnation/BothAssocSidesMIOut.cd @@ -8,8 +8,8 @@ classdiagram BothAssocSidesMIConc { <> class Department1; <> class Department2; - association Teacher1 (hasTeacher_Teacher1) -- (teachesIn_Department1) Department1; - association Teacher1 (hasTeacher_Teacher1) -- (teachesIn_Department2) Department2; - association Teacher2 (hasTeacher_Teacher2) -- (teachesIn_Department1) Department1; - association Teacher2 (hasTeacher_Teacher2) -- (teachesIn_Department2) Department2; + association Teacher1 (hasTeacher1) -- (teachesIn_Department1) Department1; + association Teacher1 (hasTeacher1) -- (teachesIn_Department2) Department2; + association Teacher2 (hasTeacher2) -- (teachesIn_Department1) Department1; + association Teacher2 (hasTeacher2) -- (teachesIn_Department2) Department2; } From 1780ee88c98609c3ce144eae155e366a3e719958 Mon Sep 17 00:00:00 2001 From: Felix Jordan Date: Sun, 29 Mar 2026 21:19:31 +0200 Subject: [PATCH 10/16] fix(cdmatcher): pass src/tgt element in correct order to checkRole --- .../MatchCDAssocsBySrcTypeAndTgtRole.java | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/cddiff/src/main/java/de/monticore/cdmatcher/MatchCDAssocsBySrcTypeAndTgtRole.java b/cddiff/src/main/java/de/monticore/cdmatcher/MatchCDAssocsBySrcTypeAndTgtRole.java index 5112ec8aa..d89a2bdeb 100644 --- a/cddiff/src/main/java/de/monticore/cdmatcher/MatchCDAssocsBySrcTypeAndTgtRole.java +++ b/cddiff/src/main/java/de/monticore/cdmatcher/MatchCDAssocsBySrcTypeAndTgtRole.java @@ -65,50 +65,55 @@ protected boolean check(ASTCDAssociation srcElem, ASTCDAssociation tgtElem) { return match; } - - /** Match two associations, assuming both are written in opposite orientations. */ + + /** + * Match two associations, assuming both are written in opposite orientations. + *

+ * Note: {@link #checkRole} is always called with the src side first and the tgt side second, + * consistent with {@link #check} and all {@code checkRole} overrides. + */ protected boolean checkReverse(ASTCDAssociation srcElem, ASTCDAssociation tgtElem) { - + boolean match = false; - + if ((tgtElem.getCDAssocDir().isDefinitiveNavigableRight() || !tgtElem.getCDAssocDir() .isDefinitiveNavigableLeft()) && (srcElem.getCDAssocDir().isDefinitiveNavigableLeft() || !srcElem.getCDAssocDir().isDefinitiveNavigableRight())) { match = checkReference(srcElem.getRightQualifiedName().getQName(), tgtElem - .getLeftQualifiedName().getQName()) && checkRole(tgtElem.getRight(), srcElem.getLeft()); + .getLeftQualifiedName().getQName()) && checkRole(srcElem.getLeft(), tgtElem.getRight()); } - + if ((tgtElem.getCDAssocDir().isDefinitiveNavigableLeft() || !tgtElem.getCDAssocDir() .isDefinitiveNavigableRight()) && (srcElem.getCDAssocDir().isDefinitiveNavigableRight() || !srcElem.getCDAssocDir().isDefinitiveNavigableLeft())) { match = match || (checkReference(srcElem.getLeftQualifiedName().getQName(), tgtElem - .getRightQualifiedName().getQName()) && checkRole(tgtElem.getLeft(), srcElem.getRight())); + .getRightQualifiedName().getQName()) && checkRole(srcElem.getRight(), tgtElem.getLeft())); } - + return match; } - + /** We check if the referenced types match using the provided type-matcher. */ protected boolean checkReference(String srcElem, String tgtElem) { Optional srcType = resolveConcreteCDTyp(srcElem); Optional tgtType = resolveReferenceCDTyp(tgtElem); - + if (srcType.isPresent() && tgtType.isPresent()) { return typeMatcher.isMatched(srcType.get(), tgtType.get()); } return false; } - + protected Optional resolveConcreteCDTyp(String qName) { return srcCD.getEnclosingScope().resolveCDTypeDown(qName).map(CDTypeSymbol::getAstNode); } - + protected Optional resolveReferenceCDTyp(String qName) { return tgtCD.getEnclosingScope().resolveCDTypeDown(qName).map(CDTypeSymbol::getAstNode); } - + protected boolean checkRole(ASTCDAssocSide srcElem, ASTCDAssocSide tgtElem) { return CDDiffUtil.inferRole(srcElem).equals(CDDiffUtil.inferRole(tgtElem)); } - + } From 421dd26d0306e8b211e048ab5cfa985a3984db2a Mon Sep 17 00:00:00 2001 From: Felix Jordan Date: Sun, 29 Mar 2026 21:48:05 +0200 Subject: [PATCH 11/16] test(cdconc): add 'archived' to paper example --- .../paper-examples/task-management/TicketSystemConc.cd | 1 + .../evaluation/paper-examples/task-management/TicketSystemOut.cd | 1 + 2 files changed, 2 insertions(+) diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/task-management/TicketSystemConc.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/task-management/TicketSystemConc.cd index 3e524cce7..56f302265 100644 --- a/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/task-management/TicketSystemConc.cd +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/task-management/TicketSystemConc.cd @@ -10,5 +10,6 @@ classdiagram TicketSystemConc { <> class Workspace { <> String label; + boolean archived; } } \ No newline at end of file diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/task-management/TicketSystemOut.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/task-management/TicketSystemOut.cd index c40eb4f27..743dbbbc7 100644 --- a/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/task-management/TicketSystemOut.cd +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/task-management/TicketSystemOut.cd @@ -11,6 +11,7 @@ classdiagram TicketSystemConc { <> class Workspace { <> String label; + boolean archived; } class Member { From d319cfcc48b555cf4f07366697d49c736606a3bf Mon Sep 17 00:00:00 2001 From: Felix Jordan Date: Sun, 29 Mar 2026 22:07:58 +0200 Subject: [PATCH 12/16] fix(cdconc): use original concrete assocs to decide if assoc should be added This allows: one cocnrete assoc in the ORIGINAL concrete to still match multiple associations while at the same time if "redundant" assocs for subtypes are defined in the reference CD these will be added to the concrete CD. --- .../cd/MissingAssociationsCDCompleter.java | 76 ++++++++++++------- 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/cddiff/src/main/java/de/monticore/cdconcretization/cd/MissingAssociationsCDCompleter.java b/cddiff/src/main/java/de/monticore/cdconcretization/cd/MissingAssociationsCDCompleter.java index e6ad8a799..572cc85ea 100644 --- a/cddiff/src/main/java/de/monticore/cdconcretization/cd/MissingAssociationsCDCompleter.java +++ b/cddiff/src/main/java/de/monticore/cdconcretization/cd/MissingAssociationsCDCompleter.java @@ -14,6 +14,7 @@ import de.monticore.cdconcretization.association.IAssociationCompleter; import de.monticore.cdconcretization.util.NameUtil; import de.monticore.cddiff.CDDiffUtil; +import de.monticore.cdconformance.CDConfParameter; import de.monticore.cdmatcher.ExternalCandidatesMatchingStrategy; import de.monticore.cdmatcher.MatchCDAssocsGreedy; import de.se_rwth.commons.logging.Log; @@ -44,30 +45,38 @@ public void complete(ASTCDCompilationUnit ccd, ASTCDCompilationUnit rcd, CDCompletionContext context) throws CompletionException { Log.debug("=== START finding missing associations ===", LOG_NAME); CDDiffUtil.refreshSymbolTable(ccd); - + + // Snapshot the associations present before this completion step begins. + // Only associations in this snapshot may legitimately "cover" a reference association via + // an inheritance relationship (when INHERITANCE is enabled). Associations added during this + // pass must not suppress adding further incarnations for related types in the same pass. + Set originalAssociations = new LinkedHashSet<>( + ccd.getCDDefinition().getCDAssociationsList()); + // Iterate over all associations in the reference class diagram for (ASTCDAssociation rAssoc : rcd.getCDDefinition().getCDAssociationsList()) { Log.debug("Finding matches for assoc: " + CD4CodeMill.prettyPrint(rAssoc, false), LOG_NAME); - + ExternalCandidatesMatchingStrategy greedyMatching = new MatchCDAssocsGreedy( context.getTypeIncStrategyMatchingSubTypes(), ccd, rcd); - - // Find all associations in the concrete class diagram that match the reference association - Set assocIncarnations = ccd.getCDDefinition().getCDAssociationsList() - .stream().filter(cAssoc -> context.getAssociationIncStrategy().isMatched(cAssoc, rAssoc)) - .collect(Collectors.toSet()); - + + // Use the snapshot — not the live list — so that associations added in this pass + // don't falsely suppress incarnations for related types later in the same pass. + Set assocIncarnations = originalAssociations.stream().filter( + cAssoc -> context.getAssociationIncStrategy().isMatched(cAssoc, rAssoc)).collect( + Collectors.toSet()); + Log.debug("Found normal matches: " + assocIncarnations.stream().map(a -> CD4CodeMill - .prettyPrint(a, false)).collect(Collectors.toList()), LOG_NAME); - - // Find associations that match greedily , but ensure that they don't match more than one + .prettyPrint(a, false)).toList(), LOG_NAME); + + // Find associations that match greedily, but ensure that they don't match more than one // element - Set assocGreedyMatches = ccd.getCDDefinition().getCDAssociationsList() - .stream().filter(cAssoc -> greedyMatching.isMatched(cAssoc, rAssoc) && greedyMatching - .getMatchedElements(cAssoc).size() < 2).collect(Collectors.toSet()); + Set assocGreedyMatches = originalAssociations.stream().filter( + cAssoc -> greedyMatching.isMatched(cAssoc, rAssoc) && greedyMatching.getMatchedElements( + cAssoc).size() < 2).collect(Collectors.toSet()); Log.debug("Found greedy matches: " + assocGreedyMatches.stream().map(a -> CD4CodeMill - .prettyPrint(a, false)).collect(Collectors.toList()), LOG_NAME); + .prettyPrint(a, false)).toList(), LOG_NAME); // Resolve the left and right types of the reference association in the reference class // diagram @@ -91,20 +100,20 @@ public void complete(ASTCDCompilationUnit ccd, ASTCDCompilationUnit rcd, // Process the type incarnations to find and handle matching associations // First, process left-type incarnations against right-type incarnations processTypeIncarnations(rLeftTypeIncarnations, rRightTypeIncarnations, leftTypeInc2Process, - assocIncarnations, assocGreedyMatches, ccd.getCDDefinition(), rAssoc, true); - + assocIncarnations, assocGreedyMatches, ccd.getCDDefinition(), rAssoc, true, context); + // Then, process right-type incarnations against left-type incarnations processTypeIncarnations(rRightTypeIncarnations, rLeftTypeIncarnations, rightTypeInc2Process, - assocIncarnations, assocGreedyMatches, ccd.getCDDefinition(), rAssoc, false); + assocIncarnations, assocGreedyMatches, ccd.getCDDefinition(), rAssoc, false, context); CDDiffUtil.refreshSymbolTable(ccd); Log.debug("DONE finding matches for assoc: " + CD4CodeMill.prettyPrint(rAssoc, false), LOG_NAME); Log.debug("remaining left type incarnations: " + leftTypeInc2Process.stream().map( - ASTCDType::getName).collect(Collectors.toList()), LOG_NAME); + ASTCDType::getName).toList(), LOG_NAME); Log.debug("remaining right type incarnations: " + rightTypeInc2Process.stream().map( - ASTCDType::getName).collect(Collectors.toList()), LOG_NAME); + ASTCDType::getName).toList(), LOG_NAME); // Finally, process any remaining type incarnations that still need to be handled // Process the remaining left-type incarnations against right-type incarnations @@ -133,22 +142,31 @@ public void complete(ASTCDCompilationUnit ccd, ASTCDCompilationUnit rcd, * @param leftToRight indicates if we process left types against right types (true) or vice versa * (false). This indicates whether the 'typeInc2Process' set contains left or right type * incarnations. + * @param context the completion context; used to check whether INHERITANCE is enabled * @throws CompletionException */ private void processTypeIncarnations(Set rTypeIncarnation, Set rOppositeTypeIncarnations, Set typeInc2Process, Set assocIncarnations, Set assocGreedyMatches, - ASTCDDefinition cd, ASTCDAssociation rAssoc, boolean leftToRight) throws CompletionException { - + ASTCDDefinition cd, ASTCDAssociation rAssoc, boolean leftToRight, + CDCompletionContext context) throws CompletionException { + + // When INHERITANCE is enabled, a user-provided supertype association may legitimately + // cover a reference association (e.g. the user intentionally wrote AbgleichsGruppe->Buchung + // to cover all subtypes). Without INHERITANCE, only a direct type match counts. + // Either way only snapshot associations are checked — see originalAssociations in complete(). + boolean useInheritance = context.getConformanceParams().contains(CDConfParameter.INHERITANCE); + // Iterate over each type incarnation in rTypeIncarnation for (ASTCDType typeInc : rTypeIncarnation) { - // Retrieve all supertypes for the current type incarnation from the cd - Set superTypes = CDDiffUtil.getAllSuperTypes(typeInc, cd); - + Set typesToCheck = useInheritance + ? CDDiffUtil.getAllSuperTypes(typeInc, cd) + : Set.of(typeInc); + // First, attempt to find a match among the specific association incarnations - Optional match = findAssociationToAnyOppositeTypeInc(superTypes, + Optional match = findAssociationToAnyOppositeTypeInc(typesToCheck, rOppositeTypeIncarnations, assocIncarnations, cd, leftToRight); - + // If a match is found, remove the current type incarnation from the set to be processed and // continue if (match.isPresent()) { @@ -161,10 +179,10 @@ private void processTypeIncarnations(Set rTypeIncarnation, LOG_NAME); continue; } - + if (greedyMatcherEnabled) { // If no match is found in specific incarnations, try matching against the greedy matches - match = findAssociationToAnyOppositeTypeInc(superTypes, rOppositeTypeIncarnations, + match = findAssociationToAnyOppositeTypeInc(typesToCheck, rOppositeTypeIncarnations, assocGreedyMatches, cd, leftToRight); // If a match is found among the greedy matches, remove the current type incarnation from From 222d3c9ba80ef8dd7b689a3125f1f703f3afabef Mon Sep 17 00:00:00 2001 From: Felix Jordan Date: Sun, 29 Mar 2026 22:08:41 +0200 Subject: [PATCH 13/16] test(cdconc): add ADAPTED_NAME_MAPPING to default params --- .../cdconcretization/AbstractCDConcretizationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cddiff/src/test/java/de/monticore/cdconcretization/AbstractCDConcretizationTest.java b/cddiff/src/test/java/de/monticore/cdconcretization/AbstractCDConcretizationTest.java index 460d78abf..e90902b71 100644 --- a/cddiff/src/test/java/de/monticore/cdconcretization/AbstractCDConcretizationTest.java +++ b/cddiff/src/test/java/de/monticore/cdconcretization/AbstractCDConcretizationTest.java @@ -34,7 +34,7 @@ public abstract class AbstractCDConcretizationTest { */ protected static final Set DEFAULT_CONFORMANCE_PARAMS = Set.of( STEREOTYPE_MAPPING, NAME_MAPPING, SRC_TARGET_ASSOC_MAPPING, INHERITANCE, - ALLOW_CARD_RESTRICTION, METHOD_OVERLOADING); + ALLOW_CARD_RESTRICTION, METHOD_OVERLOADING, ADAPTED_NAME_MAPPING); protected ASTCDCompilationUnit refCD; From 8b081320f188c2136a3331a6e2c521a4bab395c5 Mon Sep 17 00:00:00 2001 From: Felix Jordan Date: Sun, 29 Mar 2026 22:21:53 +0200 Subject: [PATCH 14/16] test(cdconc): additional tests showing subtype matching issues/question --- .../AssociationConcretizationTest.java | 14 ++++++++++++++ .../associations/AssocSubtypeTargetConc.cd | 7 +++++++ .../associations/AssocSubtypeTargetOut.cd | 10 ++++++++++ .../associations/AssocSubtypeTargetRef.cd | 8 ++++++++ 4 files changed, 39 insertions(+) create mode 100644 cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocSubtypeTargetConc.cd create mode 100644 cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocSubtypeTargetOut.cd create mode 100644 cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocSubtypeTargetRef.cd diff --git a/cddiff/src/test/java/de/monticore/cdconcretization/AssociationConcretizationTest.java b/cddiff/src/test/java/de/monticore/cdconcretization/AssociationConcretizationTest.java index 4fe795489..0258a7336 100644 --- a/cddiff/src/test/java/de/monticore/cdconcretization/AssociationConcretizationTest.java +++ b/cddiff/src/test/java/de/monticore/cdconcretization/AssociationConcretizationTest.java @@ -90,6 +90,20 @@ void testAssocRoleImplicitNameAdaptation() { "associations/AssocRoleImplicitNameAdaptOut.cd"); } + /** + * Tests that when the reference has two associations whose target types are in a subtype + * relationship, both associations are added as separate incarnations to the concrete CD. + * Specifically: {@code Container -> Item [*]} must not suppress adding + * {@code Container -> SpecialItem [*]} just because {@code SpecialItem extends Item} — each + * explicitly modelled reference association must produce its own concrete incarnation, + * regardless of inheritance among the concrete target types. + */ + @Test + void testAssocSubtypeAndSupertypeTarget() { + testConcretizedConformsToRefAndExpectedOut("associations/AssocSubtypeTargetConc.cd", + "associations/AssocSubtypeTargetRef.cd", "associations/AssocSubtypeTargetOut.cd"); + } + /** * Tests that an association name copied from the reference into an existing association is * implicitly adapted: "taskAssignment" becomes "ticketAssignment" because Ticket incarnates Task. diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocSubtypeTargetConc.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocSubtypeTargetConc.cd new file mode 100644 index 000000000..f3d2dd2ed --- /dev/null +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocSubtypeTargetConc.cd @@ -0,0 +1,7 @@ +/* (c) https://github.com/MontiCore/monticore */ +classdiagram AssocSubtypeTargetConc { + // concrete CD has the same types as the reference but no associations + class Container; + class Item; + class SpecialItem extends Item; +} diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocSubtypeTargetOut.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocSubtypeTargetOut.cd new file mode 100644 index 000000000..c31a798b1 --- /dev/null +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocSubtypeTargetOut.cd @@ -0,0 +1,10 @@ +/* (c) https://github.com/MontiCore/monticore */ +classdiagram AssocSubtypeTargetConc { + class Container; + class Item; + class SpecialItem extends Item; + // both associations are added: SpecialItem extends Item, but Item [*] must not suppress + // adding a separate incarnation for the explicitly modelled SpecialItem association + association Container -> (items) Item [*]; + association Container -> (specialItems) SpecialItem [*]; +} diff --git a/cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocSubtypeTargetRef.cd b/cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocSubtypeTargetRef.cd new file mode 100644 index 000000000..f761b4b65 --- /dev/null +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/associations/AssocSubtypeTargetRef.cd @@ -0,0 +1,8 @@ +/* (c) https://github.com/MontiCore/monticore */ +classdiagram AssocSubtypeTargetRef { + class Container; + class Item; + class SpecialItem extends Item; + association Container -> (items) Item [*]; + association Container -> (specialItems) SpecialItem [*]; +} From 5c72152b22bd20e4d3a4c9731eb4f107f1389ba1 Mon Sep 17 00:00:00 2001 From: Felix Jordan Date: Sun, 29 Mar 2026 22:26:51 +0200 Subject: [PATCH 15/16] chore: add .claude to gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index d63981094..55075ebd7 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,6 @@ gradlew gradlew.bat ~$* .intellijPlatform/ + +# AI +.claude/** \ No newline at end of file From 910ec6c77be4ebe6d975576ee59db59bc9c2f834 Mon Sep 17 00:00:00 2001 From: Felix Jordan Date: Tue, 7 Apr 2026 20:33:20 +0200 Subject: [PATCH 16/16] chore(cdconc): spotless --- .../ConcretizationCompleter.java | 12 +-- .../association/DefaultAssocCompleter.java | 37 ++++---- .../DefaultAssocSideCompleter.java | 3 +- .../cd/CDCompletionContext.java | 2 +- .../cd/MissingAssociationsCDCompleter.java | 72 +++++++-------- .../cd/TypeDetailsCDCompleter.java | 2 +- .../stereotype/StereotypeUtil.java | 3 +- .../BaseAttributeInTypeCompleter.java | 10 +-- .../method/BaseMethodInTypeCompleter.java | 87 ++++++++++--------- .../cdconformance/CDConfParameter.java | 2 +- .../DefaultCDConformanceContext.java | 19 ++-- .../AdaptedRoleNameAssocIncStrategy.java | 29 +++---- .../AdaptedNameAttributeIncStrategy.java | 37 ++++---- .../method/AdaptedNameMethodIncStrategy.java | 64 +++++++------- .../MatchCDAssocsBySrcTypeAndTgtRole.java | 22 ++--- .../AssociationConcretizationTest.java | 14 ++- .../EvaluationConcretizationTest.java | 8 +- .../CDConformanceCheckerTest.java | 33 ++++--- 18 files changed, 227 insertions(+), 229 deletions(-) diff --git a/cddiff/src/main/java/de/monticore/cdconcretization/ConcretizationCompleter.java b/cddiff/src/main/java/de/monticore/cdconcretization/ConcretizationCompleter.java index 65efeb742..103c2b15c 100644 --- a/cddiff/src/main/java/de/monticore/cdconcretization/ConcretizationCompleter.java +++ b/cddiff/src/main/java/de/monticore/cdconcretization/ConcretizationCompleter.java @@ -69,7 +69,7 @@ public class ConcretizationCompleter { * elements annotated with 'forEach'. */ private boolean forEachNameAdaptationEnabled = true; - + /** * If true, names of reference elements being copied to the concrete CD are adapted using the * implicit type incarnation bindings available in the context. For every reference type with a @@ -166,8 +166,8 @@ public void completeCD(ASTCDCompilationUnit concreteCD, ASTCDCompilationUnit ref new DefaultEnumConstantsCompleter()).build(); IAssocSideCompleter assocSideCompleter = new DefaultAssocSideCompleter(); - IAssociationCompleter assocCompleter = new DefaultAssocCompleter(concreteCD, - assocSideCompleter, context); + IAssociationCompleter assocCompleter = new DefaultAssocCompleter(concreteCD, assocSideCompleter, + context); ChainBuilder completerChainBuilder = new ChainBuilder().add(new ImportsCompleter()).add( @@ -213,7 +213,7 @@ public void setUnderspecifiedPlaceholderTypeName(String underspecifiedPlaceholde public void setForEachNameAdaptationEnabled(boolean forEachNameAdaptationEnabled) { this.forEachNameAdaptationEnabled = forEachNameAdaptationEnabled; } - + public void setImplicitNameAdaptationEnabled(boolean implicitNameAdaptationEnabled) { this.implicitNameAdaptationEnabled = implicitNameAdaptationEnabled; } @@ -226,7 +226,7 @@ static class DefaultCompletionContext extends DefaultCDConformanceContext implem private final boolean forEachNameAdaptationEnabled; private final boolean implicitNameAdaptationEnabled; - + protected DefaultCompletionContext(ASTCDCompilationUnit concreteCD, ASTCDCompilationUnit referenceCD, String mapping, String underspecifiedPlaceholderTypeName, Set conformanceParams, @@ -271,7 +271,7 @@ public static DefaultCompletionContext create(ASTCDCompilationUnit concreteCD, @Override public boolean isForEachNameAdaptationEnabled() { return forEachNameAdaptationEnabled; } - + @Override public boolean isImplicitNameAdaptationEnabled() { return implicitNameAdaptationEnabled; } diff --git a/cddiff/src/main/java/de/monticore/cdconcretization/association/DefaultAssocCompleter.java b/cddiff/src/main/java/de/monticore/cdconcretization/association/DefaultAssocCompleter.java index dc24ffd48..d853e5f9e 100644 --- a/cddiff/src/main/java/de/monticore/cdconcretization/association/DefaultAssocCompleter.java +++ b/cddiff/src/main/java/de/monticore/cdconcretization/association/DefaultAssocCompleter.java @@ -17,16 +17,16 @@ public class DefaultAssocCompleter implements IAssociationCompleter { private final ASTCDCompilationUnit ccd; - + private final IAssocSideCompleter assocSideCompleter; - + /** Nullable; when non-null, implicit name adaptation is applied if enabled in context. */ private final CDCompletionContext context; - + public DefaultAssocCompleter(ASTCDCompilationUnit ccd, IAssocSideCompleter assocSideCompleter) { this(ccd, assocSideCompleter, null); } - + public DefaultAssocCompleter(ASTCDCompilationUnit ccd, IAssocSideCompleter assocSideCompleter, CDCompletionContext context) { this.ccd = ccd; @@ -66,32 +66,33 @@ public void completeAssociation(ASTCDAssociation cAssoc, ASTCDAssociation rAssoc // In REVERSE_DIRECTION the right side of cAssoc corresponds to the left side of rAssoc ASTCDType rTypeForConRight = matchDirection == AssocMatchDirection.SAME_DIRECTION ? rRightType : rLeftType; - ASTCDType rTypeForConLeft = matchDirection == AssocMatchDirection.SAME_DIRECTION - ? rLeftType : rRightType; + ASTCDType rTypeForConLeft = matchDirection == AssocMatchDirection.SAME_DIRECTION ? rLeftType + : rRightType; if (cAssoc.getRight().isPresentCDRole()) { - NameUtil.adaptTemplatedName(cAssoc.getRight().getCDRole().getName(), - rTypeForConRight.getName(), cRightType.getName()) - .ifPresent(n -> cAssoc.getRight().getCDRole().setName(n)); + NameUtil.adaptTemplatedName(cAssoc.getRight().getCDRole().getName(), rTypeForConRight + .getName(), cRightType.getName()).ifPresent(n -> cAssoc.getRight().getCDRole() + .setName(n)); } if (cAssoc.getLeft().isPresentCDRole()) { - NameUtil.adaptTemplatedName(cAssoc.getLeft().getCDRole().getName(), - rTypeForConLeft.getName(), cLeftType.getName()) - .ifPresent(n -> cAssoc.getLeft().getCDRole().setName(n)); + NameUtil.adaptTemplatedName(cAssoc.getLeft().getCDRole().getName(), rTypeForConLeft + .getName(), cLeftType.getName()).ifPresent(n -> cAssoc.getLeft().getCDRole().setName( + n)); } if (cAssoc.isPresentName()) { String name = cAssoc.getName(); - name = NameUtil.adaptTemplatedName(name, rTypeForConLeft.getName(), - cLeftType.getName()).orElse(name); - name = NameUtil.adaptTemplatedName(name, rTypeForConRight.getName(), - cRightType.getName()).orElse(name); + name = NameUtil.adaptTemplatedName(name, rTypeForConLeft.getName(), cLeftType.getName()) + .orElse(name); + name = NameUtil.adaptTemplatedName(name, rTypeForConRight.getName(), cRightType.getName()) + .orElse(name); cAssoc.setName(name); } - } catch (CompletionException e) { + } + catch (CompletionException e) { Log.warn("0xCDCONC1: Could not resolve association endpoint types for implicit name" + " adaptation, skipping."); } } - + // Handle potential role name conflicts in a post-processing step renameRoleIfConflicting(cAssoc); } diff --git a/cddiff/src/main/java/de/monticore/cdconcretization/association/DefaultAssocSideCompleter.java b/cddiff/src/main/java/de/monticore/cdconcretization/association/DefaultAssocSideCompleter.java index 8874613fc..dced3db65 100644 --- a/cddiff/src/main/java/de/monticore/cdconcretization/association/DefaultAssocSideCompleter.java +++ b/cddiff/src/main/java/de/monticore/cdconcretization/association/DefaultAssocSideCompleter.java @@ -37,8 +37,7 @@ private void completeAssociationRoleNames(ASTCDAssocSide cAssocSide, ASTCDAssocS // Build a fresh CDRole with just the name, rather than sharing the reference AST node. // Sharing the node would allow implicit name adaptation (which calls setName()) to silently // corrupt the reference association's role name, breaking subsequent mapping passes. - cAssocSide.setCDRole(CD4CodeMill.cDRoleBuilder() - .setName(rAssocSide.getCDRole().getName()) + cAssocSide.setCDRole(CD4CodeMill.cDRoleBuilder().setName(rAssocSide.getCDRole().getName()) .build()); } } diff --git a/cddiff/src/main/java/de/monticore/cdconcretization/cd/CDCompletionContext.java b/cddiff/src/main/java/de/monticore/cdconcretization/cd/CDCompletionContext.java index e76b93d8a..129fb8061 100644 --- a/cddiff/src/main/java/de/monticore/cdconcretization/cd/CDCompletionContext.java +++ b/cddiff/src/main/java/de/monticore/cdconcretization/cd/CDCompletionContext.java @@ -16,7 +16,7 @@ public interface CDCompletionContext extends CDConformanceContext { boolean isForEachNameAdaptationEnabled(); - + boolean isImplicitNameAdaptationEnabled(); /** diff --git a/cddiff/src/main/java/de/monticore/cdconcretization/cd/MissingAssociationsCDCompleter.java b/cddiff/src/main/java/de/monticore/cdconcretization/cd/MissingAssociationsCDCompleter.java index 572cc85ea..3bdd5c025 100644 --- a/cddiff/src/main/java/de/monticore/cdconcretization/cd/MissingAssociationsCDCompleter.java +++ b/cddiff/src/main/java/de/monticore/cdconcretization/cd/MissingAssociationsCDCompleter.java @@ -45,30 +45,30 @@ public void complete(ASTCDCompilationUnit ccd, ASTCDCompilationUnit rcd, CDCompletionContext context) throws CompletionException { Log.debug("=== START finding missing associations ===", LOG_NAME); CDDiffUtil.refreshSymbolTable(ccd); - + // Snapshot the associations present before this completion step begins. // Only associations in this snapshot may legitimately "cover" a reference association via // an inheritance relationship (when INHERITANCE is enabled). Associations added during this // pass must not suppress adding further incarnations for related types in the same pass. - Set originalAssociations = new LinkedHashSet<>( - ccd.getCDDefinition().getCDAssociationsList()); - + Set originalAssociations = new LinkedHashSet<>(ccd.getCDDefinition() + .getCDAssociationsList()); + // Iterate over all associations in the reference class diagram for (ASTCDAssociation rAssoc : rcd.getCDDefinition().getCDAssociationsList()) { Log.debug("Finding matches for assoc: " + CD4CodeMill.prettyPrint(rAssoc, false), LOG_NAME); - + ExternalCandidatesMatchingStrategy greedyMatching = new MatchCDAssocsGreedy( context.getTypeIncStrategyMatchingSubTypes(), ccd, rcd); - + // Use the snapshot — not the live list — so that associations added in this pass // don't falsely suppress incarnations for related types later in the same pass. Set assocIncarnations = originalAssociations.stream().filter( cAssoc -> context.getAssociationIncStrategy().isMatched(cAssoc, rAssoc)).collect( Collectors.toSet()); - + Log.debug("Found normal matches: " + assocIncarnations.stream().map(a -> CD4CodeMill .prettyPrint(a, false)).toList(), LOG_NAME); - + // Find associations that match greedily, but ensure that they don't match more than one // element Set assocGreedyMatches = originalAssociations.stream().filter( @@ -101,7 +101,7 @@ public void complete(ASTCDCompilationUnit ccd, ASTCDCompilationUnit rcd, // First, process left-type incarnations against right-type incarnations processTypeIncarnations(rLeftTypeIncarnations, rRightTypeIncarnations, leftTypeInc2Process, assocIncarnations, assocGreedyMatches, ccd.getCDDefinition(), rAssoc, true, context); - + // Then, process right-type incarnations against left-type incarnations processTypeIncarnations(rRightTypeIncarnations, rLeftTypeIncarnations, rightTypeInc2Process, assocIncarnations, assocGreedyMatches, ccd.getCDDefinition(), rAssoc, false, context); @@ -148,25 +148,24 @@ public void complete(ASTCDCompilationUnit ccd, ASTCDCompilationUnit rcd, private void processTypeIncarnations(Set rTypeIncarnation, Set rOppositeTypeIncarnations, Set typeInc2Process, Set assocIncarnations, Set assocGreedyMatches, - ASTCDDefinition cd, ASTCDAssociation rAssoc, boolean leftToRight, - CDCompletionContext context) throws CompletionException { - + ASTCDDefinition cd, ASTCDAssociation rAssoc, boolean leftToRight, CDCompletionContext context) + throws CompletionException { + // When INHERITANCE is enabled, a user-provided supertype association may legitimately // cover a reference association (e.g. the user intentionally wrote AbgleichsGruppe->Buchung // to cover all subtypes). Without INHERITANCE, only a direct type match counts. // Either way only snapshot associations are checked — see originalAssociations in complete(). boolean useInheritance = context.getConformanceParams().contains(CDConfParameter.INHERITANCE); - + // Iterate over each type incarnation in rTypeIncarnation for (ASTCDType typeInc : rTypeIncarnation) { - Set typesToCheck = useInheritance - ? CDDiffUtil.getAllSuperTypes(typeInc, cd) - : Set.of(typeInc); - + Set typesToCheck = useInheritance ? CDDiffUtil.getAllSuperTypes(typeInc, cd) : Set + .of(typeInc); + // First, attempt to find a match among the specific association incarnations Optional match = findAssociationToAnyOppositeTypeInc(typesToCheck, rOppositeTypeIncarnations, assocIncarnations, cd, leftToRight); - + // If a match is found, remove the current type incarnation from the set to be processed and // continue if (match.isPresent()) { @@ -179,7 +178,7 @@ private void processTypeIncarnations(Set rTypeIncarnation, LOG_NAME); continue; } - + if (greedyMatcherEnabled) { // If no match is found in specific incarnations, try matching against the greedy matches match = findAssociationToAnyOppositeTypeInc(typesToCheck, rOppositeTypeIncarnations, @@ -270,11 +269,11 @@ private void addAssociationIncarnations(ASTCDCompilationUnit concreteCD, // Capture original role names (from the reference) before implicit adaptation, so we can // detect whether adaptation changed them and avoid adding a redundant suffix afterwards. - String originalRightRoleName = association.getRight().isPresentCDRole() - ? association.getRight().getCDRole().getName() : null; - String originalLeftRoleName = association.getLeft().isPresentCDRole() - ? association.getLeft().getCDRole().getName() : null; - + String originalRightRoleName = association.getRight().isPresentCDRole() ? association + .getRight().getCDRole().getName() : null; + String originalLeftRoleName = association.getLeft().isPresentCDRole() ? association + .getLeft().getCDRole().getName() : null; + // Apply implicit name adaptation for role names and association name, using only the // specific endpoint type incarnation pairs for this association (avoids chaining issues). if (context.isImplicitNameAdaptationEnabled()) { @@ -283,26 +282,26 @@ private void addAssociationIncarnations(ASTCDCompilationUnit concreteCD, ASTCDType rLeftType = ConcretizationHelper.getAssocLeftType(context.getReferenceCD(), referenceAssociation); if (association.getRight().isPresentCDRole()) { - NameUtil.adaptTemplatedName(association.getRight().getCDRole().getName(), - rRightType.getName(), rightTypeInc.getName()).ifPresent( - n -> association.getRight().getCDRole().setName(n)); + NameUtil.adaptTemplatedName(association.getRight().getCDRole().getName(), rRightType + .getName(), rightTypeInc.getName()).ifPresent(n -> association.getRight() + .getCDRole().setName(n)); } if (association.getLeft().isPresentCDRole()) { - NameUtil.adaptTemplatedName(association.getLeft().getCDRole().getName(), - rLeftType.getName(), leftTypeInc.getName()).ifPresent( - n -> association.getLeft().getCDRole().setName(n)); + NameUtil.adaptTemplatedName(association.getLeft().getCDRole().getName(), rLeftType + .getName(), leftTypeInc.getName()).ifPresent(n -> association.getLeft().getCDRole() + .setName(n)); } if (association.isPresentName()) { // Apply both endpoint type pairs sequentially (left first, then right) String assocName = association.getName(); - assocName = NameUtil.adaptTemplatedName(assocName, rLeftType.getName(), - leftTypeInc.getName()).orElse(assocName); - assocName = NameUtil.adaptTemplatedName(assocName, rRightType.getName(), - rightTypeInc.getName()).orElse(assocName); + assocName = NameUtil.adaptTemplatedName(assocName, rLeftType.getName(), leftTypeInc + .getName()).orElse(assocName); + assocName = NameUtil.adaptTemplatedName(assocName, rRightType.getName(), rightTypeInc + .getName()).orElse(assocName); association.setName(assocName); } } - + // If a role name is present and there are multiple incarnations of the type, append the // type incarnation's name as a suffix to avoid name conflicts — unless implicit name // adaptation already produced a unique name for this incarnation (in which case the suffix @@ -314,7 +313,8 @@ private void addAssociationIncarnations(ASTCDCompilationUnit concreteCD, .size(); boolean rightRoleAdapted = originalRightRoleName != null && !originalRightRoleName.equals( association.getRight().getCDRole().getName()); - if (association.getRight().isPresentCDRole() && totalRightTypeIncs > 1 && !rightRoleAdapted) { + if (association.getRight().isPresentCDRole() && totalRightTypeIncs > 1 + && !rightRoleAdapted) { association.getRight().getCDRole().setName(association.getRight().getCDRole().getName() + "_" + rightTypeInc.getName()); } diff --git a/cddiff/src/main/java/de/monticore/cdconcretization/cd/TypeDetailsCDCompleter.java b/cddiff/src/main/java/de/monticore/cdconcretization/cd/TypeDetailsCDCompleter.java index 58d46eb8f..ff3d54666 100644 --- a/cddiff/src/main/java/de/monticore/cdconcretization/cd/TypeDetailsCDCompleter.java +++ b/cddiff/src/main/java/de/monticore/cdconcretization/cd/TypeDetailsCDCompleter.java @@ -97,7 +97,7 @@ public String getUnderspecifiedPlaceholderTypeName() { public boolean isForEachNameAdaptationEnabled() { return parentContext.isForEachNameAdaptationEnabled(); } - + @Override public boolean isImplicitNameAdaptationEnabled() { return parentContext.isImplicitNameAdaptationEnabled(); diff --git a/cddiff/src/main/java/de/monticore/cdconcretization/stereotype/StereotypeUtil.java b/cddiff/src/main/java/de/monticore/cdconcretization/stereotype/StereotypeUtil.java index b11313779..a5c7250b8 100644 --- a/cddiff/src/main/java/de/monticore/cdconcretization/stereotype/StereotypeUtil.java +++ b/cddiff/src/main/java/de/monticore/cdconcretization/stereotype/StereotypeUtil.java @@ -88,7 +88,8 @@ public static void addStereotype(ASTModifier modifier, String name, String conte if (modifier.isPresentStereotype()) { stereotype = modifier.getStereotype(); // Avoid duplicates: skip if a stereotype with the same name and value already exists - if (stereotype.streamValues().anyMatch(v -> v.getName().equals(name) && v.getValue().equals(content))) { + if (stereotype.streamValues().anyMatch(v -> v.getName().equals(name) && v.getValue().equals( + content))) { return; } } diff --git a/cddiff/src/main/java/de/monticore/cdconcretization/type/attribute/BaseAttributeInTypeCompleter.java b/cddiff/src/main/java/de/monticore/cdconcretization/type/attribute/BaseAttributeInTypeCompleter.java index 74b6e3355..9f3fcca36 100644 --- a/cddiff/src/main/java/de/monticore/cdconcretization/type/attribute/BaseAttributeInTypeCompleter.java +++ b/cddiff/src/main/java/de/monticore/cdconcretization/type/attribute/BaseAttributeInTypeCompleter.java @@ -92,16 +92,16 @@ private void createAttributeIncarnations(ASTCDType concreteType, referenceAttribute.getSymbol().getFullName()); } if (context.isImplicitNameAdaptationEnabled()) { - NameUtil.adaptTemplatedName(attributeIncarnation.getName(), - rAttributeType.getName(), cAttributeType.getName()).ifPresent(adapted -> { + NameUtil.adaptTemplatedName(attributeIncarnation.getName(), rAttributeType.getName(), + cAttributeType.getName()).ifPresent(adapted -> { attributeIncarnation.setName(adapted); if (!context.getConformanceParams().contains(CDConfParameter.ADAPTED_NAME_MAPPING)) { - StereotypeUtil.addStereotype(attributeIncarnation.getModifier(), - context.getMappingName(), referenceAttribute.getSymbol().getFullName()); + StereotypeUtil.addStereotype(attributeIncarnation.getModifier(), context + .getMappingName(), referenceAttribute.getSymbol().getFullName()); } }); } - + // 2. set type of incarnation // use FQ name to avoid messing with imports / name conflicts attributeIncarnation.setMCType(CD4CodeMill.mCQualifiedTypeBuilder().setMCQualifiedName( diff --git a/cddiff/src/main/java/de/monticore/cdconcretization/type/method/BaseMethodInTypeCompleter.java b/cddiff/src/main/java/de/monticore/cdconcretization/type/method/BaseMethodInTypeCompleter.java index 7d59295df..68330c9ad 100644 --- a/cddiff/src/main/java/de/monticore/cdconcretization/type/method/BaseMethodInTypeCompleter.java +++ b/cddiff/src/main/java/de/monticore/cdconcretization/type/method/BaseMethodInTypeCompleter.java @@ -41,7 +41,8 @@ public void completeMethodInType(ASTCDType concreteType, ASTCDMethod referenceMe List allConcreteAttributesInHierarchy = CDSymbolTables.getMethodsInHierarchy( concreteType); List incarnations = allConcreteAttributesInHierarchy.stream().filter( - cMethod -> context.getIncarnationMapping().isIncarnation(cMethod, referenceMethod)).toList(); + cMethod -> context.getIncarnationMapping().isIncarnation(cMethod, referenceMethod)) + .toList(); if (incarnations.isEmpty()) { createMethodIncarnations(concreteType, referenceMethod, context); } @@ -196,47 +197,52 @@ else if (refMCType instanceof ASTMCMapType) { } private String adaptMethodNameFromTypePairs(String name, ASTCDMethod referenceMethod, - ASTMCReturnType returnTypeIncarnation, List parameterCombination, TypeCompletionContext context) { + ASTMCReturnType returnTypeIncarnation, List parameterCombination, + TypeCompletionContext context) { String result = name; - if (referenceMethod.getMCReturnType().isPresentMCType() - && returnTypeIncarnation.isPresentMCType()) { - Optional refRetType = resolveReferenceCDType(referenceMethod.getMCReturnType().getMCType(), context); - Optional conRetType = resolveConcreteCDType(returnTypeIncarnation.getMCType(), context); + if (referenceMethod.getMCReturnType().isPresentMCType() && returnTypeIncarnation + .isPresentMCType()) { + Optional refRetType = resolveReferenceCDType(referenceMethod.getMCReturnType() + .getMCType(), context); + Optional conRetType = resolveConcreteCDType(returnTypeIncarnation.getMCType(), + context); if (refRetType.isPresent() && conRetType.isPresent()) { - result = NameUtil.adaptTemplatedName(result, refRetType.get().getName(), - conRetType.get().getName()).orElse(result); + result = NameUtil.adaptTemplatedName(result, refRetType.get().getName(), conRetType.get() + .getName()).orElse(result); } } for (int i = 0; i < referenceMethod.getCDParameterList().size(); i++) { - Optional refParamType = resolveReferenceCDType( - referenceMethod.getCDParameterList().get(i).getMCType(), context); - Optional conParamType = resolveConcreteCDType(parameterCombination.get(i), context); + Optional refParamType = resolveReferenceCDType(referenceMethod.getCDParameterList() + .get(i).getMCType(), context); + Optional conParamType = resolveConcreteCDType(parameterCombination.get(i), + context); if (refParamType.isPresent() && conParamType.isPresent()) { - result = NameUtil.adaptTemplatedName(result, refParamType.get().getName(), - conParamType.get().getName()).orElse(result); + result = NameUtil.adaptTemplatedName(result, refParamType.get().getName(), conParamType + .get().getName()).orElse(result); } } return result; } - - private Optional resolveReferenceCDType(ASTMCType mcType, TypeCompletionContext context) { + + private Optional resolveReferenceCDType(ASTMCType mcType, + TypeCompletionContext context) { return resolveCDType(mcType, context.getReferenceCD().getEnclosingScope()); } - - private Optional resolveConcreteCDType(ASTMCType mcType, TypeCompletionContext context) { + + private Optional resolveConcreteCDType(ASTMCType mcType, + TypeCompletionContext context) { return resolveCDType(mcType, context.getConcreteCD().getEnclosingScope()); } - + private Optional resolveCDType(ASTMCType mcType, ICDBasisScope scope) { if (!(mcType instanceof ASTMCQualifiedType)) { return Optional.empty(); } String typeName = ((ASTMCQualifiedType) mcType).getMCQualifiedName().getQName(); - return scope.resolveCDTypeDown(typeName) - .filter(TypeSymbolTOP::isPresentAstNode) - .map(CDTypeSymbolTOP::getAstNode); + return scope.resolveCDTypeDown(typeName).filter(TypeSymbolTOP::isPresentAstNode).map( + CDTypeSymbolTOP::getAstNode); } - + protected ASTMCTypeArgument createTypeArgument(ASTMCType mcType) { if (mcType instanceof ASTMCQualifiedType) { return CD4CodeMill.mCBasicTypeArgumentBuilder().setMCQualifiedType( @@ -256,16 +262,17 @@ else if ((mcType instanceof ASTMCPrimitiveType)) { * Adds one method for each return type and combination of the cartesian product of parameter * types. * - * @param concreteType the concrete type to add the method to - * @param referenceMethod the reference method to clone - * @param context the completion context - * @param returnTypeIncarnations the incarnations of the return type + * @param concreteType the concrete type to add the method to + * @param referenceMethod the reference method to clone + * @param context the completion context + * @param returnTypeIncarnations the incarnations of the return type * @param parameterTypeIncarnations the incarnations of each parameter type * @param typeCompletionContext */ private void addMethodIncarnations(ASTCDType concreteType, ASTCDMethod referenceMethod, - TypeCompletionContext context, List returnTypeIncarnations, - List> parameterTypeIncarnations, TypeCompletionContext typeCompletionContext) { + TypeCompletionContext context, List returnTypeIncarnations, + List> parameterTypeIncarnations, + TypeCompletionContext typeCompletionContext) { List> parameterCombinations = Lists.cartesianProduct(parameterTypeIncarnations); @@ -280,8 +287,8 @@ private void addMethodIncarnations(ASTCDType concreteType, ASTCDMethod reference if (context.isImplicitNameAdaptationEnabled()) { methodName = adaptMethodNameFromTypePairs(methodName, referenceMethod, returnTypeIncarnation, parameterCombination, context); - if (!methodName.equals(referenceMethod.getName()) - && !context.getConformanceParams().contains(CDConfParameter.ADAPTED_NAME_MAPPING)) { + if (!methodName.equals(referenceMethod.getName()) && !context.getConformanceParams() + .contains(CDConfParameter.ADAPTED_NAME_MAPPING)) { StereotypeUtil.addStereotype(methodClone.getModifier(), context.getMappingName(), MethodSignatureString.printSignatureIfOverloaded(referenceMethod.getSymbol())); } @@ -292,27 +299,27 @@ private void addMethodIncarnations(ASTCDType concreteType, ASTCDMethod reference // TODO not necessarily! If we change the parameter signature at the same time, we can // keep the original method name! // -> see how we did it in ForEachMethodCompleter - methodName = methodName + "_" + NameUtil - .escapeQualifiedNameAsIdentifier(returnTypeIncarnation.printType()); + methodName = methodName + "_" + NameUtil.escapeQualifiedNameAsIdentifier( + returnTypeIncarnation.printType()); } methodClone.setName(methodName); - + // 2. set return type of the incarnation // use FQ name to avoid messing with imports / name conflicts methodClone.setMCReturnType(returnTypeIncarnation); - + // 3. set parameter types of the incarnation for (int i = 0; i < methodClone.getCDParameterList().size(); i++) { ASTCDParameter parameterClone = methodClone.getCDParameterList().get(i); parameterClone.setMCType(parameterCombination.get(i)); if (context.isImplicitNameAdaptationEnabled()) { - Optional refParamType = resolveReferenceCDType( - referenceMethod.getCDParameterList().get(i).getMCType(), context); - Optional conParamType = resolveConcreteCDType(parameterCombination.get(i), context); + Optional refParamType = resolveReferenceCDType(referenceMethod + .getCDParameterList().get(i).getMCType(), context); + Optional conParamType = resolveConcreteCDType(parameterCombination.get(i), + context); if (refParamType.isPresent() && conParamType.isPresent()) { - NameUtil.adaptTemplatedName(parameterClone.getName(), - refParamType.get().getName(), conParamType.get().getName()) - .ifPresent(parameterClone::setName); + NameUtil.adaptTemplatedName(parameterClone.getName(), refParamType.get().getName(), + conParamType.get().getName()).ifPresent(parameterClone::setName); } } methodClone.setCDParameter(i, parameterClone); diff --git a/cddiff/src/main/java/de/monticore/cdconformance/CDConfParameter.java b/cddiff/src/main/java/de/monticore/cdconformance/CDConfParameter.java index 145aadc62..991b02b8c 100644 --- a/cddiff/src/main/java/de/monticore/cdconformance/CDConfParameter.java +++ b/cddiff/src/main/java/de/monticore/cdconformance/CDConfParameter.java @@ -26,7 +26,7 @@ public enum CDConfParameter { + "parameters compared to the reference method. By default, a method must have the same" + "parameters as the reference method. If combined with " + STRICT_PARAMETER_ORDER + " " + "the additional parameters must be at the end of the parameter list."), - + ADAPTED_NAME_MAPPING("when added, names in the concrete CD that are derived via implicit name" + " adaptation (using element type incarnations) are accepted as incarnations of the" + " corresponding reference elements without requiring explicit stereotype mappings." diff --git a/cddiff/src/main/java/de/monticore/cdconformance/DefaultCDConformanceContext.java b/cddiff/src/main/java/de/monticore/cdconformance/DefaultCDConformanceContext.java index fa2a427ee..d5fa0a8c7 100644 --- a/cddiff/src/main/java/de/monticore/cdconformance/DefaultCDConformanceContext.java +++ b/cddiff/src/main/java/de/monticore/cdconformance/DefaultCDConformanceContext.java @@ -161,20 +161,19 @@ public static CDConformanceContext create(ASTCDCompilationUnit concreteCD, } } if (conformanceParams.contains(CDConfParameter.ADAPTED_NAME_MAPPING)) { - ExternalCandidatesMatchingStrategy typeMatcherForAdapted = - conformanceParams.contains(CDConfParameter.INHERITANCE) ? compSubTypeIncStrategy - : compTypeIncStrategy; - compAssocIncStrategy.addIncStrategy( - new AdaptedRoleNameAssocIncStrategy(typeMatcherForAdapted, concreteCD, referenceCD)); + ExternalCandidatesMatchingStrategy typeMatcherForAdapted = conformanceParams + .contains(CDConfParameter.INHERITANCE) ? compSubTypeIncStrategy : compTypeIncStrategy; + compAssocIncStrategy.addIncStrategy(new AdaptedRoleNameAssocIncStrategy(typeMatcherForAdapted, + concreteCD, referenceCD)); // Attributes and methods use exact incarnation matching (compTypeIncStrategy), not // subtype matching: a supertype must not be treated as an adapted incarnation of a // reference parameter type just because one of its subtypes incarnates that type. - compAttributeIncStrategy.addIncStrategy( - new AdaptedNameAttributeIncStrategy(compTypeIncStrategy, concreteCD, referenceCD)); - compMethodIncStrategy.addIncStrategy( - new AdaptedNameMethodIncStrategy(compTypeIncStrategy, mcTypeMatcher, concreteCD, referenceCD)); + compAttributeIncStrategy.addIncStrategy(new AdaptedNameAttributeIncStrategy( + compTypeIncStrategy, concreteCD, referenceCD)); + compMethodIncStrategy.addIncStrategy(new AdaptedNameMethodIncStrategy(compTypeIncStrategy, + mcTypeMatcher, concreteCD, referenceCD)); } - + return new DefaultCDConformanceContext(concreteCD, referenceCD, mapping, underspecifiedPlaceholderTypeName, conformanceParams, compTypeIncStrategy, compSubTypeIncStrategy, compAssocIncStrategy, compAttributeIncStrategy, diff --git a/cddiff/src/main/java/de/monticore/cdconformance/inc/association/AdaptedRoleNameAssocIncStrategy.java b/cddiff/src/main/java/de/monticore/cdconformance/inc/association/AdaptedRoleNameAssocIncStrategy.java index 625f01568..30332fb18 100644 --- a/cddiff/src/main/java/de/monticore/cdconformance/inc/association/AdaptedRoleNameAssocIncStrategy.java +++ b/cddiff/src/main/java/de/monticore/cdconformance/inc/association/AdaptedRoleNameAssocIncStrategy.java @@ -21,30 +21,29 @@ * "Task", "Ticket")} produces {@code assignedTickets}. */ public class AdaptedRoleNameAssocIncStrategy extends MatchCDAssocsBySrcTypeAndTgtRole { - - public AdaptedRoleNameAssocIncStrategy( - ExternalCandidatesMatchingStrategy typeMatcher, ASTCDCompilationUnit srcCD, - ASTCDCompilationUnit tgtCD) { + + public AdaptedRoleNameAssocIncStrategy(ExternalCandidatesMatchingStrategy typeMatcher, + ASTCDCompilationUnit srcCD, ASTCDCompilationUnit tgtCD) { super(typeMatcher, srcCD, tgtCD); } - + @Override protected boolean checkRole(ASTCDAssocSide concrete, ASTCDAssocSide reference) { - Optional conType = resolveConcreteCDTyp( - concrete.getMCQualifiedType().getMCQualifiedName().getQName()); - Optional refType = resolveReferenceCDTyp( - reference.getMCQualifiedType().getMCQualifiedName().getQName()); - - if (conType.isPresent() && refType.isPresent() - && typeMatcher.isMatched(conType.get(), refType.get())) { + Optional conType = resolveConcreteCDTyp(concrete.getMCQualifiedType() + .getMCQualifiedName().getQName()); + Optional refType = resolveReferenceCDTyp(reference.getMCQualifiedType() + .getMCQualifiedName().getQName()); + + if (conType.isPresent() && refType.isPresent() && typeMatcher.isMatched(conType.get(), refType + .get())) { if (reference.isPresentCDRole() && concrete.isPresentCDRole()) { String refRoleName = reference.getCDRole().getName(); String conRoleName = concrete.getCDRole().getName(); - return NameUtil.adaptTemplatedName(refRoleName, refType.get().getName(), - conType.get().getName()).map(adapted -> adapted.equals(conRoleName)).orElse(false); + return NameUtil.adaptTemplatedName(refRoleName, refType.get().getName(), conType.get() + .getName()).map(adapted -> adapted.equals(conRoleName)).orElse(false); } } return false; } - + } diff --git a/cddiff/src/main/java/de/monticore/cdconformance/inc/attribute/AdaptedNameAttributeIncStrategy.java b/cddiff/src/main/java/de/monticore/cdconformance/inc/attribute/AdaptedNameAttributeIncStrategy.java index c2993da85..e2f9ee10a 100644 --- a/cddiff/src/main/java/de/monticore/cdconformance/inc/attribute/AdaptedNameAttributeIncStrategy.java +++ b/cddiff/src/main/java/de/monticore/cdconformance/inc/attribute/AdaptedNameAttributeIncStrategy.java @@ -24,52 +24,49 @@ * adaptTemplatedName("assignedTasks", "Task", "Ticket")} produces {@code "assignedTickets"}. */ public class AdaptedNameAttributeIncStrategy implements CDAttributeMatchingStrategy { - + private final BooleanMatchingStrategy typeMatcher; private final ICDBasisScope conScope; private final ICDBasisScope refScope; private ASTCDType referenceType; - + public AdaptedNameAttributeIncStrategy(BooleanMatchingStrategy typeMatcher, ASTCDCompilationUnit concreteCD, ASTCDCompilationUnit referenceCD) { this.typeMatcher = typeMatcher; this.conScope = concreteCD.getEnclosingScope(); this.refScope = referenceCD.getEnclosingScope(); } - + @Override public List getMatchedElements(ASTCDAttribute concrete) { return referenceType.getCDAttributeList().stream().filter(attr -> isMatched(concrete, attr)) .collect(Collectors.toList()); } - + @Override public boolean isMatched(ASTCDAttribute concrete, ASTCDAttribute ref) { Optional refAttrType = resolveCDType(ref.getMCType() instanceof ASTMCQualifiedType ? ((ASTMCQualifiedType) ref.getMCType()).getMCQualifiedName().getQName() : null, refScope); - Optional conAttrType = resolveCDType(concrete.getMCType() instanceof ASTMCQualifiedType - ? ((ASTMCQualifiedType) concrete.getMCType()).getMCQualifiedName().getQName() : null, conScope); - if (refAttrType.isPresent() && conAttrType.isPresent() - && typeMatcher.isMatched(conAttrType.get(), refAttrType.get())) { - return NameUtil.adaptTemplatedName(ref.getName(), refAttrType.get().getName(), - conAttrType.get().getName()).map(adapted -> adapted.equals(concrete.getName())) - .orElse(false); + Optional conAttrType = resolveCDType(concrete + .getMCType() instanceof ASTMCQualifiedType ? ((ASTMCQualifiedType) concrete.getMCType()) + .getMCQualifiedName().getQName() : null, conScope); + if (refAttrType.isPresent() && conAttrType.isPresent() && typeMatcher.isMatched(conAttrType + .get(), refAttrType.get())) { + return NameUtil.adaptTemplatedName(ref.getName(), refAttrType.get().getName(), conAttrType + .get().getName()).map(adapted -> adapted.equals(concrete.getName())).orElse(false); } return false; } - + @Override - public void setReferenceType(ASTCDType referenceType) { - this.referenceType = referenceType; - } - + public void setReferenceType(ASTCDType referenceType) { this.referenceType = referenceType; } + private Optional resolveCDType(String typeName, ICDBasisScope scope) { if (typeName == null) { return Optional.empty(); } - return scope.resolveCDTypeDown(typeName) - .filter(CDTypeSymbol::isPresentAstNode) - .map(CDTypeSymbol::getAstNode); + return scope.resolveCDTypeDown(typeName).filter(CDTypeSymbol::isPresentAstNode).map( + CDTypeSymbol::getAstNode); } - + } diff --git a/cddiff/src/main/java/de/monticore/cdconformance/inc/method/AdaptedNameMethodIncStrategy.java b/cddiff/src/main/java/de/monticore/cdconformance/inc/method/AdaptedNameMethodIncStrategy.java index 59ee27f98..e0f9d1e82 100644 --- a/cddiff/src/main/java/de/monticore/cdconformance/inc/method/AdaptedNameMethodIncStrategy.java +++ b/cddiff/src/main/java/de/monticore/cdconformance/inc/method/AdaptedNameMethodIncStrategy.java @@ -28,86 +28,84 @@ * {@code Input} and {@code Bar} incarnates {@code Output}. */ public class AdaptedNameMethodIncStrategy implements CDMethodMatchingStrategy { - + private final BooleanMatchingStrategy typeMatcher; private final MCTypeMatchingStrategy mcTypeMatcher; private final ICDBasisScope conScope; private final ICDBasisScope refScope; private ASTCDType refType; - + public AdaptedNameMethodIncStrategy(BooleanMatchingStrategy typeMatcher, - MCTypeMatchingStrategy mcTypeMatcher, - ASTCDCompilationUnit concreteCD, ASTCDCompilationUnit referenceCD) { + MCTypeMatchingStrategy mcTypeMatcher, ASTCDCompilationUnit concreteCD, + ASTCDCompilationUnit referenceCD) { this.typeMatcher = typeMatcher; this.mcTypeMatcher = mcTypeMatcher; this.conScope = concreteCD.getEnclosingScope(); this.refScope = referenceCD.getEnclosingScope(); } - + @Override public List getMatchedElements(ASTCDMethod concrete) { - return refType.getCDMethodList().stream().filter(method -> isMatched(concrete, method)) - .collect(Collectors.toList()); + return refType.getCDMethodList().stream().filter(method -> isMatched(concrete, method)).collect( + Collectors.toList()); } - + @Override public boolean isMatched(ASTCDMethod concrete, ASTCDMethod ref) { if (ref.getCDParameterList().size() != concrete.getCDParameterList().size()) { return false; } - + // Apply all relevant type pairs sequentially to compute the adapted reference method name String adaptedName = ref.getName(); - + if (ref.getMCReturnType().isPresentMCType() && concrete.getMCReturnType().isPresentMCType()) { Optional refRetType = resolveCDType(ref.getMCReturnType().getMCType(), refScope); - Optional conRetType = resolveCDType(concrete.getMCReturnType().getMCType(), conScope); - if (refRetType.isPresent() && conRetType.isPresent() - && typeMatcher.isMatched(conRetType.get(), refRetType.get())) { + Optional conRetType = resolveCDType(concrete.getMCReturnType().getMCType(), + conScope); + if (refRetType.isPresent() && conRetType.isPresent() && typeMatcher.isMatched(conRetType + .get(), refRetType.get())) { adaptedName = NameUtil.adaptTemplatedName(adaptedName, refRetType.get().getName(), conRetType.get().getName()).orElse(adaptedName); } } - + for (int i = 0; i < ref.getCDParameterList().size(); i++) { - Optional refParamType = resolveCDType( - ref.getCDParameterList().get(i).getMCType(), refScope); - Optional conParamType = resolveCDType( - concrete.getCDParameterList().get(i).getMCType(), conScope); - if (refParamType.isPresent() && conParamType.isPresent() - && typeMatcher.isMatched(conParamType.get(), refParamType.get())) { + Optional refParamType = resolveCDType(ref.getCDParameterList().get(i).getMCType(), + refScope); + Optional conParamType = resolveCDType(concrete.getCDParameterList().get(i) + .getMCType(), conScope); + if (refParamType.isPresent() && conParamType.isPresent() && typeMatcher.isMatched(conParamType + .get(), refParamType.get())) { adaptedName = NameUtil.adaptTemplatedName(adaptedName, refParamType.get().getName(), conParamType.get().getName()).orElse(adaptedName); } } - + if (!adaptedName.equals(concrete.getName())) { return false; } - + // Verify parameter types are incarnation-compatible (in strict order) for (int i = 0; i < ref.getCDParameterList().size(); i++) { - if (!mcTypeMatcher.isMatched(concrete.getCDParameterList().get(i).getMCType(), - ref.getCDParameterList().get(i).getMCType(), typeMatcher)) { + if (!mcTypeMatcher.isMatched(concrete.getCDParameterList().get(i).getMCType(), ref + .getCDParameterList().get(i).getMCType(), typeMatcher)) { return false; } } return true; } - + @Override - public void setReferenceType(ASTCDType refType) { - this.refType = refType; - } - + public void setReferenceType(ASTCDType refType) { this.refType = refType; } + private Optional resolveCDType(ASTMCType mcType, ICDBasisScope scope) { if (!(mcType instanceof ASTMCQualifiedType)) { return Optional.empty(); } String typeName = ((ASTMCQualifiedType) mcType).getMCQualifiedName().getQName(); - return scope.resolveCDTypeDown(typeName) - .filter(CDTypeSymbol::isPresentAstNode) - .map(CDTypeSymbol::getAstNode); + return scope.resolveCDTypeDown(typeName).filter(CDTypeSymbol::isPresentAstNode).map( + CDTypeSymbol::getAstNode); } - + } diff --git a/cddiff/src/main/java/de/monticore/cdmatcher/MatchCDAssocsBySrcTypeAndTgtRole.java b/cddiff/src/main/java/de/monticore/cdmatcher/MatchCDAssocsBySrcTypeAndTgtRole.java index d89a2bdeb..2c6add0ba 100644 --- a/cddiff/src/main/java/de/monticore/cdmatcher/MatchCDAssocsBySrcTypeAndTgtRole.java +++ b/cddiff/src/main/java/de/monticore/cdmatcher/MatchCDAssocsBySrcTypeAndTgtRole.java @@ -65,7 +65,7 @@ protected boolean check(ASTCDAssociation srcElem, ASTCDAssociation tgtElem) { return match; } - + /** * Match two associations, assuming both are written in opposite orientations. *

@@ -73,47 +73,47 @@ protected boolean check(ASTCDAssociation srcElem, ASTCDAssociation tgtElem) { * consistent with {@link #check} and all {@code checkRole} overrides. */ protected boolean checkReverse(ASTCDAssociation srcElem, ASTCDAssociation tgtElem) { - + boolean match = false; - + if ((tgtElem.getCDAssocDir().isDefinitiveNavigableRight() || !tgtElem.getCDAssocDir() .isDefinitiveNavigableLeft()) && (srcElem.getCDAssocDir().isDefinitiveNavigableLeft() || !srcElem.getCDAssocDir().isDefinitiveNavigableRight())) { match = checkReference(srcElem.getRightQualifiedName().getQName(), tgtElem .getLeftQualifiedName().getQName()) && checkRole(srcElem.getLeft(), tgtElem.getRight()); } - + if ((tgtElem.getCDAssocDir().isDefinitiveNavigableLeft() || !tgtElem.getCDAssocDir() .isDefinitiveNavigableRight()) && (srcElem.getCDAssocDir().isDefinitiveNavigableRight() || !srcElem.getCDAssocDir().isDefinitiveNavigableLeft())) { match = match || (checkReference(srcElem.getLeftQualifiedName().getQName(), tgtElem .getRightQualifiedName().getQName()) && checkRole(srcElem.getRight(), tgtElem.getLeft())); } - + return match; } - + /** We check if the referenced types match using the provided type-matcher. */ protected boolean checkReference(String srcElem, String tgtElem) { Optional srcType = resolveConcreteCDTyp(srcElem); Optional tgtType = resolveReferenceCDTyp(tgtElem); - + if (srcType.isPresent() && tgtType.isPresent()) { return typeMatcher.isMatched(srcType.get(), tgtType.get()); } return false; } - + protected Optional resolveConcreteCDTyp(String qName) { return srcCD.getEnclosingScope().resolveCDTypeDown(qName).map(CDTypeSymbol::getAstNode); } - + protected Optional resolveReferenceCDTyp(String qName) { return tgtCD.getEnclosingScope().resolveCDTypeDown(qName).map(CDTypeSymbol::getAstNode); } - + protected boolean checkRole(ASTCDAssocSide srcElem, ASTCDAssocSide tgtElem) { return CDDiffUtil.inferRole(srcElem).equals(CDDiffUtil.inferRole(tgtElem)); } - + } diff --git a/cddiff/src/test/java/de/monticore/cdconcretization/AssociationConcretizationTest.java b/cddiff/src/test/java/de/monticore/cdconcretization/AssociationConcretizationTest.java index 0258a7336..0ef2c969f 100644 --- a/cddiff/src/test/java/de/monticore/cdconcretization/AssociationConcretizationTest.java +++ b/cddiff/src/test/java/de/monticore/cdconcretization/AssociationConcretizationTest.java @@ -77,19 +77,18 @@ void testTypeMIOneAssocExists() { testConcretizedConformsToRefAndExpectedOut("associations/TypeMIOneAssocExistsConc.cd", "associations/TypeMIOneAssocExistsRef.cd", "associations/TypeMIOneAssocExistsOut.cd"); } - + /** * Tests that a role name copied from the reference into an existing association is implicitly * adapted: "assignedTask" becomes "assignedTicket" because Ticket incarnates Task. */ @Test void testAssocRoleImplicitNameAdaptation() { - testConcretizedConformsToRefAndExpectedOut( - "associations/AssocRoleImplicitNameAdaptConc.cd", + testConcretizedConformsToRefAndExpectedOut("associations/AssocRoleImplicitNameAdaptConc.cd", "associations/AssocRoleImplicitNameAdaptRef.cd", "associations/AssocRoleImplicitNameAdaptOut.cd"); } - + /** * Tests that when the reference has two associations whose target types are in a subtype * relationship, both associations are added as separate incarnations to the concrete CD. @@ -103,17 +102,16 @@ void testAssocSubtypeAndSupertypeTarget() { testConcretizedConformsToRefAndExpectedOut("associations/AssocSubtypeTargetConc.cd", "associations/AssocSubtypeTargetRef.cd", "associations/AssocSubtypeTargetOut.cd"); } - + /** * Tests that an association name copied from the reference into an existing association is * implicitly adapted: "taskAssignment" becomes "ticketAssignment" because Ticket incarnates Task. */ @Test void testAssocNameImplicitNameAdaptation() { - testConcretizedConformsToRefAndExpectedOut( - "associations/AssocNameImplicitNameAdaptConc.cd", + testConcretizedConformsToRefAndExpectedOut("associations/AssocNameImplicitNameAdaptConc.cd", "associations/AssocNameImplicitNameAdaptRef.cd", "associations/AssocNameImplicitNameAdaptOut.cd"); } - + } diff --git a/cddiff/src/test/java/de/monticore/cdconcretization/EvaluationConcretizationTest.java b/cddiff/src/test/java/de/monticore/cdconcretization/EvaluationConcretizationTest.java index 502a3bdeb..55afa0f66 100644 --- a/cddiff/src/test/java/de/monticore/cdconcretization/EvaluationConcretizationTest.java +++ b/cddiff/src/test/java/de/monticore/cdconcretization/EvaluationConcretizationTest.java @@ -315,10 +315,10 @@ void testConcreteEmpty() { } } - + @Nested class PaperExamples { - + @Test void testTaskManagement() { testConcretizedConformsToRefAndExpectedOut( @@ -326,7 +326,7 @@ void testTaskManagement() { "evaluation/paper-examples/task-management/TaskManagementRef.cd", "evaluation/paper-examples/task-management/TicketSystemOut.cd"); } - + @Test void testRepository() { confParameters.add(CDConfParameter.STRICT_PARAMETER_ORDER); @@ -335,5 +335,7 @@ void testRepository() { "evaluation/paper-examples/repository/RepositoryRef.cd", "evaluation/paper-examples/repository/RepositoryOut.cd"); } + } + } diff --git a/cddiff/src/test/java/de/monticore/cdconformance/CDConformanceCheckerTest.java b/cddiff/src/test/java/de/monticore/cdconformance/CDConformanceCheckerTest.java index 567e0e6ab..aa936c96e 100644 --- a/cddiff/src/test/java/de/monticore/cdconformance/CDConformanceCheckerTest.java +++ b/cddiff/src/test/java/de/monticore/cdconformance/CDConformanceCheckerTest.java @@ -305,7 +305,7 @@ public void testAssocSameTypeMultipleRolesExplicitAssocNamesValid(String concret } // ---- ADAPTED_NAME_MAPPING: attributes ---- - + @ParameterizedTest @ValueSource(strings = { "AdaptedName.cd" }) public void testAttributeAdaptedNameValid(String concrete) { @@ -315,7 +315,7 @@ public void testAttributeAdaptedNameValid(String concrete) { ADAPTED_NAME_MAPPING)); assertTrue(checker.checkConformance(conCD, refCD, "ref")); } - + @ParameterizedTest @ValueSource(strings = { "WrongName.cd" }) public void testAttributeAdaptedNameInvalid(String concrete) { @@ -325,7 +325,7 @@ public void testAttributeAdaptedNameInvalid(String concrete) { ADAPTED_NAME_MAPPING)); assertFalse(checker.checkConformance(conCD, refCD, "ref")); } - + /** Adapted name is rejected when ADAPTED_NAME_MAPPING is not enabled. */ @Test public void testAttributeAdaptedNameRequiresParam() { @@ -334,43 +334,40 @@ public void testAttributeAdaptedNameRequiresParam() { checker = new CDConformanceChecker(Set.of(STEREOTYPE_MAPPING, NAME_MAPPING)); assertFalse(checker.checkConformance(conCD, refCD, "ref")); } - + // ---- ADAPTED_NAME_MAPPING: methods ---- - + @ParameterizedTest @ValueSource(strings = { "AdaptedName.cd" }) public void testMethodAdaptedNameValid(String concrete) { - parseModels("methods/adapted_name/valid/" + concrete, - "methods/adapted_name/Reference.cd"); + parseModels("methods/adapted_name/valid/" + concrete, "methods/adapted_name/Reference.cd"); checker = new CDConformanceChecker(Set.of(STEREOTYPE_MAPPING, NAME_MAPPING, ADAPTED_NAME_MAPPING, STRICT_PARAMETER_ORDER)); assertTrue(checker.checkConformance(conCD, refCD, "ref")); } - + @ParameterizedTest @ValueSource(strings = { "WrongName.cd" }) public void testMethodAdaptedNameInvalid(String concrete) { - parseModels("methods/adapted_name/invalid/" + concrete, - "methods/adapted_name/Reference.cd"); + parseModels("methods/adapted_name/invalid/" + concrete, "methods/adapted_name/Reference.cd"); checker = new CDConformanceChecker(Set.of(STEREOTYPE_MAPPING, NAME_MAPPING, ADAPTED_NAME_MAPPING, STRICT_PARAMETER_ORDER)); assertFalse(checker.checkConformance(conCD, refCD, "ref")); } - + /** Adapted method name is rejected when ADAPTED_NAME_MAPPING is not enabled. */ @Test public void testMethodAdaptedNameRequiresParam() { - parseModels("methods/adapted_name/valid/AdaptedName.cd", - "methods/adapted_name/Reference.cd"); + parseModels("methods/adapted_name/valid/AdaptedName.cd", "methods/adapted_name/Reference.cd"); checker = new CDConformanceChecker(Set.of(STEREOTYPE_MAPPING, NAME_MAPPING, STRICT_PARAMETER_ORDER)); assertFalse(checker.checkConformance(conCD, refCD, "ref")); } - + // ---- ADAPTED_NAME_MAPPING: association roles ---- // Uses custom (non-implicit) role names to specifically exercise AdaptedRoleNameAssocIncStrategy // rather than ImplicitRoleNameAssocIncStrategy. - + @ParameterizedTest @ValueSource(strings = { "AdaptedRoles.cd" }) public void testAssocAdaptedRoleNameValid(String concrete) { @@ -380,7 +377,7 @@ public void testAssocAdaptedRoleNameValid(String concrete) { SRC_TARGET_ASSOC_MAPPING, ADAPTED_NAME_MAPPING)); assertTrue(checker.checkConformance(conCD, refCD, "ref")); } - + @ParameterizedTest @ValueSource(strings = { "WrongRoles.cd" }) public void testAssocAdaptedRoleNameInvalid(String concrete) { @@ -390,7 +387,7 @@ public void testAssocAdaptedRoleNameInvalid(String concrete) { SRC_TARGET_ASSOC_MAPPING, ADAPTED_NAME_MAPPING)); assertFalse(checker.checkConformance(conCD, refCD, "ref")); } - + /** Custom adapted role names are rejected when ADAPTED_NAME_MAPPING is not enabled. */ @Test public void testAssocAdaptedRoleNameRequiresParam() { @@ -400,7 +397,7 @@ public void testAssocAdaptedRoleNameRequiresParam() { SRC_TARGET_ASSOC_MAPPING)); assertFalse(checker.checkConformance(conCD, refCD, "ref")); } - + /** * Example from KMR24 for multiple mappings. */