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 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/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/main/java/de/monticore/cdconcretization/ConcretizationCompleter.java b/cddiff/src/main/java/de/monticore/cdconcretization/ConcretizationCompleter.java index 3692ed00a..103c2b15c 100644 --- a/cddiff/src/main/java/de/monticore/cdconcretization/ConcretizationCompleter.java +++ b/cddiff/src/main/java/de/monticore/cdconcretization/ConcretizationCompleter.java @@ -70,6 +70,15 @@ public class ConcretizationCompleter { */ 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 * {@link UnderspecifiedPlaceholderType}. @@ -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(); @@ -156,8 +166,8 @@ public void completeCD(ASTCDCompilationUnit concreteCD, ASTCDCompilationUnit ref new DefaultEnumConstantsCompleter()).build(); IAssocSideCompleter assocSideCompleter = new DefaultAssocSideCompleter(); - IAssociationCompleter assocCompleter = new DefaultAssocCompleter(concreteCD, - assocSideCompleter); + IAssociationCompleter assocCompleter = new DefaultAssocCompleter(concreteCD, assocSideCompleter, + context); ChainBuilder completerChainBuilder = new ChainBuilder().add(new ImportsCompleter()).add( @@ -204,6 +214,10 @@ 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,6 +225,7 @@ 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, @@ -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,19 +257,24 @@ 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) { return getIncarnationMapping().getIncarnations(referenceType); 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..d853e5f9e 100644 --- a/cddiff/src/main/java/de/monticore/cdconcretization/association/DefaultAssocCompleter.java +++ b/cddiff/src/main/java/de/monticore/cdconcretization/association/DefaultAssocCompleter.java @@ -7,7 +7,10 @@ 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; @@ -17,9 +20,18 @@ public class DefaultAssocCompleter implements IAssociationCompleter { 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,46 @@ 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..dced3db65 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,11 @@ 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/CDCompletionContext.java b/cddiff/src/main/java/de/monticore/cdconcretization/cd/CDCompletionContext.java index 623e0eca6..129fb8061 100644 --- a/cddiff/src/main/java/de/monticore/cdconcretization/cd/CDCompletionContext.java +++ b/cddiff/src/main/java/de/monticore/cdconcretization/cd/CDCompletionContext.java @@ -17,6 +17,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/MissingAssociationsCDCompleter.java b/cddiff/src/main/java/de/monticore/cdconcretization/cd/MissingAssociationsCDCompleter.java index 58e6dda17..3bdd5c025 100644 --- a/cddiff/src/main/java/de/monticore/cdconcretization/cd/MissingAssociationsCDCompleter.java +++ b/cddiff/src/main/java/de/monticore/cdconcretization/cd/MissingAssociationsCDCompleter.java @@ -12,7 +12,9 @@ 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.cdconformance.CDConfParameter; import de.monticore.cdmatcher.ExternalCandidatesMatchingStrategy; import de.monticore.cdmatcher.MatchCDAssocsGreedy; import de.se_rwth.commons.logging.Log; @@ -44,6 +46,13 @@ public void complete(ASTCDCompilationUnit ccd, ASTCDCompilationUnit rcd, 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); @@ -51,22 +60,23 @@ public void complete(ASTCDCompilationUnit ccd, ASTCDCompilationUnit rcd, 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); + .prettyPrint(a, false)).toList(), LOG_NAME); - // Find associations that match greedily , but ensure that they don't match more than one + // 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 @@ -90,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 @@ -132,20 +142,28 @@ 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 @@ -163,7 +181,7 @@ private void processTypeIncarnations(Set rTypeIncarnation, 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 @@ -249,23 +267,62 @@ 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/main/java/de/monticore/cdconcretization/cd/TypeDetailsCDCompleter.java b/cddiff/src/main/java/de/monticore/cdconcretization/cd/TypeDetailsCDCompleter.java index 9582bb0df..ff3d54666 100644 --- a/cddiff/src/main/java/de/monticore/cdconcretization/cd/TypeDetailsCDCompleter.java +++ b/cddiff/src/main/java/de/monticore/cdconcretization/cd/TypeDetailsCDCompleter.java @@ -98,6 +98,11 @@ public boolean isForEachNameAdaptationEnabled() { return parentContext.isForEachNameAdaptationEnabled(); } + @Override + public boolean isImplicitNameAdaptationEnabled() { + return parentContext.isImplicitNameAdaptationEnabled(); + } + @Override public Set getConformanceParams() { return parentContext.getConformanceParams(); 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..a5c7250b8 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,11 @@ 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(); 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..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 @@ -9,7 +9,9 @@ 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; import de.monticore.types.check.SymTypeExpression; import java.util.List; @@ -89,6 +91,16 @@ 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); + if (!context.getConformanceParams().contains(CDConfParameter.ADAPTED_NAME_MAPPING)) { + StereotypeUtil.addStereotype(attributeIncarnation.getModifier(), context + .getMappingName(), referenceAttribute.getSymbol().getFullName()); + } + }); + } // 2. set type of incarnation // use FQ name to avoid messing with imports / name conflicts 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..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 @@ -5,15 +5,19 @@ 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.cdconformance.CDConfParameter; 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 +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)).collect( - Collectors.toList()); + cMethod -> context.getIncarnationMapping().isIncarnation(cMethod, referenceMethod)) + .toList(); if (incarnations.isEmpty()) { createMethodIncarnations(concreteType, referenceMethod, context); } @@ -72,7 +76,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 +196,53 @@ 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( @@ -216,10 +267,12 @@ else if ((mcType instanceof ASTMCPrimitiveType)) { * @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) { + List> parameterTypeIncarnations, + TypeCompletionContext typeCompletionContext) { List> parameterCombinations = Lists.cartesianProduct(parameterTypeIncarnations); @@ -228,15 +281,28 @@ 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()) && !context.getConformanceParams() + .contains(CDConfParameter.ADAPTED_NAME_MAPPING)) { + 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 @@ -246,6 +312,16 @@ private void addMethodIncarnations(ASTCDType concreteType, ASTCDMethod reference 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/main/java/de/monticore/cdconformance/CDConfParameter.java b/cddiff/src/main/java/de/monticore/cdconformance/CDConfParameter.java index 8550ec582..991b02b8c 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..d5fa0a8c7 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,6 +160,19 @@ 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)); + // 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)); + } return new DefaultCDConformanceContext(concreteCD, referenceCD, mapping, underspecifiedPlaceholderTypeName, conformanceParams, compTypeIncStrategy, 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..30332fb18 --- /dev/null +++ b/cddiff/src/main/java/de/monticore/cdconformance/inc/association/AdaptedRoleNameAssocIncStrategy.java @@ -0,0 +1,49 @@ +/* (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..e2f9ee10a --- /dev/null +++ b/cddiff/src/main/java/de/monticore/cdconformance/inc/attribute/AdaptedNameAttributeIncStrategy.java @@ -0,0 +1,72 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.cdconformance.inc.attribute; + +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; + +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 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); + } + return false; + } + + @Override + 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); + } + +} 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..e0f9d1e82 --- /dev/null +++ b/cddiff/src/main/java/de/monticore/cdconformance/inc/method/AdaptedNameMethodIncStrategy.java @@ -0,0 +1,111 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.cdconformance.inc.method; + +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; +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 final ICDBasisScope conScope; + private final ICDBasisScope refScope; + private ASTCDType refType; + + public AdaptedNameMethodIncStrategy(BooleanMatchingStrategy typeMatcher, + 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()); + } + + @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())) { + 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())) { + 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, 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); + } + +} diff --git a/cddiff/src/main/java/de/monticore/cdmatcher/MatchCDAssocsBySrcTypeAndTgtRole.java b/cddiff/src/main/java/de/monticore/cdmatcher/MatchCDAssocsBySrcTypeAndTgtRole.java index 5112ec8aa..2c6add0ba 100644 --- a/cddiff/src/main/java/de/monticore/cdmatcher/MatchCDAssocsBySrcTypeAndTgtRole.java +++ b/cddiff/src/main/java/de/monticore/cdmatcher/MatchCDAssocsBySrcTypeAndTgtRole.java @@ -66,7 +66,12 @@ 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; @@ -75,14 +80,14 @@ protected boolean checkReverse(ASTCDAssociation srcElem, ASTCDAssociation tgtEle .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; 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; diff --git a/cddiff/src/test/java/de/monticore/cdconcretization/AssociationConcretizationTest.java b/cddiff/src/test/java/de/monticore/cdconcretization/AssociationConcretizationTest.java index 410d186fb..0ef2c969f 100644 --- a/cddiff/src/test/java/de/monticore/cdconcretization/AssociationConcretizationTest.java +++ b/cddiff/src/test/java/de/monticore/cdconcretization/AssociationConcretizationTest.java @@ -78,4 +78,40 @@ void testTypeMIOneAssocExists() { "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 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. + */ + @Test + void testAssocNameImplicitNameAdaptation() { + 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 3e5d0959a..55afa0f66 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", @@ -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"); } @@ -195,7 +197,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 +208,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 +218,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 +233,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 +243,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 +262,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 +277,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 +295,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", @@ -314,4 +316,26 @@ 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..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", @@ -114,7 +118,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 +138,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 +189,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 +199,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/java/de/monticore/cdconformance/CDConformanceCheckerTest.java b/cddiff/src/test/java/de/monticore/cdconformance/CDConformanceCheckerTest.java index bfeda31df..aa936c96e 100644 --- a/cddiff/src/test/java/de/monticore/cdconformance/CDConformanceCheckerTest.java +++ b/cddiff/src/test/java/de/monticore/cdconformance/CDConformanceCheckerTest.java @@ -304,6 +304,100 @@ 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/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/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 [*]; +} 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..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,8 +21,8 @@ classdiagram Banking { // sends money from sourceAccount to targetAccount class Transaction { - BankAccount sourceAccount; - BankAccount targetAccount; + 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..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,8 +24,8 @@ classdiagram Banking { // sends money from sourceAccount to targetAccount class Transaction { - BankAccount sourceAccount; - BankAccount targetAccount; + BankAccount sourceBankAccount; + BankAccount targetBankAccount; double amount; } 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 { 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/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..56f302265 --- /dev/null +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/task-management/TicketSystemConc.cd @@ -0,0 +1,15 @@ +/* (c) https://github.com/MontiCore/monticore */ +import java.lang.String; + +classdiagram TicketSystemConc { + + <> class Ticket { + <> String summary; + <> String detail; + } + + <> 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 new file mode 100644 index 000000000..743dbbbc7 --- /dev/null +++ b/cddiff/src/test/resources/de/monticore/cdconcretization/evaluation/paper-examples/task-management/TicketSystemOut.cd @@ -0,0 +1,29 @@ +/* (c) https://github.com/MontiCore/monticore */ +import java.lang.String; + +classdiagram TicketSystemConc { + + <> class Ticket { + <> String summary; + <> String detail; + TaskStatus status; + } + + <> class Workspace { + <> String label; + boolean archived; + } + + 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 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); 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..aadd20ad7 --- /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 (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; } 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); + } +}