diff --git a/.claude/commands/implement-extensions.md b/.claude/commands/implement-extensions.md index 9cd1220f..73ff90a8 100644 --- a/.claude/commands/implement-extensions.md +++ b/.claude/commands/implement-extensions.md @@ -5,9 +5,10 @@ argument-hint: # /implement-extensions -Spawn the 4-role agent team (researcher → implementer → tester → reviewer, all -Opus) for **$ARGUMENTS** — a `*Extensions.cs` file under `SysML2.NET/Extend/` -whose `Compute*` methods are still stubs throwing `NotSupportedException`. +Spawn the 4-role agent team (researcher → implementer → tester → reviewer) for +**$ARGUMENTS** — a `*Extensions.cs` file under `SysML2.NET/Extend/` whose +`Compute*` methods are still stubs throwing `NotSupportedException`. Per-role +models are picked dynamically based on stub complexity (see step 3.5). The team template is at `C:\Users\atheate\.claude\team-templates\extension-impl.md` (v2). Read it first — its role prompts are the source of truth; this command body @@ -67,15 +68,71 @@ methods. These are the stubs to implement. If the count is 0, stop — the file has no stubs left. +### 3.5. Grade complexity, pick models + +For each stub method, look at its `` OCL block (or note its absence) and +tally these signals: + +- **trivial signals** + - No OCL block — spec-text-only redefinition with sibling precedent (e.g. + `FeatureMembership::ownedMemberFeature` mirrors + `OwningMembershipExtensions.ComputeOwnedMemberElement`) + - OCL is a single `OfType` / `selectByKind` filter on `OwnedRelationship` + or `ownedMember` +- **standard signals** + - OCL has `->select` / `->reject` / scalar chain navigation + - OCL has `->union` of single-step paths + - OCL has a single `oclAsType` cast or a single `oclIsKindOf` test +- **complex signals** + - OCL has `->closure(...)` (cycle protection needed: BFS/DFS with `visited` set) + - OCL has nested `let` / `if-then-else` / multiple `oclAsType` + - OCL has multi-step `->union` (e.g. + `ownedMembership.OfType().Union(otherChain.OfType())`) + - Cross-interface recursion (e.g. `Supertypes(false)`, recursive + `ImportedMemberships(excluded)`) +- **bump-up signal** + - Total method count > 15 promotes the whole task one tier + - Even one complex signal anywhere → task is complex + +Grade the task overall as **trivial / standard / complex** using the worst signal +observed. Then pick a model per role from this default table: + +| Role | Drives the choice | trivial | standard | complex | +|---|---|---|---|---| +| Researcher | OCL density × method count | Haiku | Sonnet | Opus | +| Implementer | OCL operator complexity | Sonnet | Sonnet | Opus | +| Tester (targeted fixture) | populated-case fixture wiring complexity | Sonnet | Sonnet | Opus | +| Tester (regression sweep, if any) | OCL semantics needed to assert real behavior in sibling fixtures | Sonnet | Sonnet | Opus | +| Reviewer | diff size × OCL density | Sonnet | Sonnet | Opus | + +Per-role asymmetry is encouraged. Examples: +- Trivial impl + a regression sweep that touches 8 sibling tests asserting + moderate OCL → Sonnet implementer + Sonnet regression-sweep tester (still + Sonnet because the OCL is moderate, not complex). +- Standard impl with a single `->closure` method buried in the list → bump + the implementer to Opus only, keep the rest at Sonnet. +- Trivial 2-method spec-text-only file → Haiku researcher, Sonnet for the + rest. + +Record the per-role selection. It will be presented to the user in step 4 and +applied at every `Agent(...)` spawn in steps 5–9. + ### 4. Sanity check with the user Use `AskUserQuestion` to present: - The auto-derived paths (test fixture, interface, reference template, notes file). - The list of stub methods (or a count if there are many). -- Two options: "Implement all" or "Implement a subset" (let the user paste a method - list as a custom answer). - -If they pick subset, narrow the method list. Otherwise proceed with all. +- The complexity grade and the per-role model selection from step 3.5. +- Two questions: + 1. Scope: "Implement all" or "Implement a subset" (let the user paste a method + list as a custom answer). + 2. Models: "Use the dynamic per-role selection above" or override with + "All Opus" / "All Sonnet" / "Custom" (let the user paste a per-role + mapping). + +If they pick subset, narrow the method list. If they override the model +selection, apply that override at every `Agent(...)` spawn below. Otherwise +proceed with the dynamic defaults. ### 5. Spawn the researcher (FIRST role — produces the notes file the others read) @@ -83,9 +140,12 @@ Read the v2 team template at `C:\Users\atheate\.claude\team-templates\extension- to refresh the role prompts. Substitute the placeholders from step 2 + the method list from step 4. -Spawn the **researcher** as `Agent({subagent_type: "general-purpose", model: "opus"})` -with the v2 researcher prompt. Foreground (not `run_in_background`) — the next -roles depend on the notes file. +Spawn the **researcher** as +`Agent({subagent_type: "general-purpose", model: })` +with the v2 researcher prompt, where `` is the model picked +in step 3.5 (Haiku for trivial, Sonnet for standard, Opus for complex — or the +user's step-4 override). Foreground (not `run_in_background`) — the next roles +depend on the notes file. The researcher MUST: - Treat the OCL ``/`` body in the XMI as the canonical @@ -111,14 +171,18 @@ isolated context. The only thing they share is the researcher's notes file on disk. Spawn 1 — **implementer**: -`Agent({subagent_type: "general-purpose", model: "opus"})` with the v2 -implementer prompt. The prompt MUST instruct the implementer to read -`{{NOTES_FILE}}` first. - -Spawn 2 — **tester**: -`Agent({subagent_type: "general-purpose", model: "opus"})` with the v2 tester -prompt. The prompt MUST instruct the tester to read `{{NOTES_FILE}}` first -(each method has a "Test plan" section there). +`Agent({subagent_type: "general-purpose", model: })` with the +v2 implementer prompt, where `` is the model picked in +step 3.5 (Sonnet for trivial/standard, Opus for complex — or the user's step-4 +override). The prompt MUST instruct the implementer to read `{{NOTES_FILE}}` +first. + +Spawn 2 — **tester (targeted fixture)**: +`Agent({subagent_type: "general-purpose", model: })` with the v2 +tester prompt, where `` is the **targeted-fixture** tester model +picked in step 3.5 (Sonnet for trivial/standard, Opus for complex — or the +user's step-4 override). The prompt MUST instruct the tester to read +`{{NOTES_FILE}}` first (each method has a "Test plan" section there). **Parallel-mode caveat for the tester**: when spawned in parallel with the implementer, the tester runs ONLY `dotnet build` on the test project (confirms @@ -167,25 +231,58 @@ If failures exist, identify those of the form: These are pre-existing tests in sibling fixtures that asserted the stubs throw — they now fail because our new implementations make those paths succeed. Dispatch the tester back (via `SendMessage` to the still-running tester if available, else -a fresh `Agent` call) with the failing-test list and instructions to update those +a fresh `Agent` call with `model: ` from +step 3.5) with the failing-test list and instructions to update those assertions to assert real behavior. The regression sweep is in-scope per the template. +**Critical**: do NOT brief the tester as "replace the stale `Throws` assertion". +Brief it as "**expand each touched test to cover every distinct branch implied +by the production OCL**". This means, for each touched sibling test: +- **Filter discrimination** — for every `OfType()` / `selectByKind(X)`, + add a sibling element of a non-X kind to the fixture and assert it is + excluded. +- **Predicate completeness** — for every `Where(...)` predicate composed of + `or` / `and` / equality clauses, add fixtures that exercise each clause both + true and false (e.g. for `direction = In or Inout`, add an `In` feature, an + `Inout` feature, an `Out` feature, and an undirected feature; assert the + first two are included and the last two excluded). +- **Owned vs. inherited** — when the OCL unions an owned collection with an + inherited one (`X.union(inheritedMembership.selectByKind(...))`), wire a + Specialization in the fixture and confirm the inherited branch surfaces. + When the OCL is inheritance-only (`inheritedMemberships.selectByKind(...)`), + also wire a sibling owned member and confirm it does NOT surface. +- **Null-projection guard** — when the LINQ chain ends with + `.Where(x => x != null)` (defending against a Select that may yield null), + construct a case where the projection yields null and assert it is filtered + out. + +A "single happy-path positive case + null + empty" pattern is **insufficient** +for the regression sweep — it leaves filter, predicate, and inheritance branches +untested. The original stub-blocker test only asserted one positive case because +that's all that *could* be asserted while the upstream was stubbed; once the +stub is gone, the full OCL surface is in scope. + **Parallel-spawn opportunity**: if step 7's verification surfaced targeted-fixture test-assertion fixes that were deferred to this step (i.e. there is BOTH (a) work for the targeted-fixture tester re-dispatch on `{{TEST_FILE}}` AND (b) work for the regression-sweep tester on sibling `*ExtensionsTestFixture.cs` files), spawn the two roles in a single orchestrator message with TWO -`Agent(...)` tool calls, both foreground. They edit disjoint files so this is -safe. If only one of (a) or (b) has work, spawn only that one. +`Agent(...)` tool calls, both foreground. Use the targeted-fixture +`` for (a) and the `` for (b) +(both from step 3.5, or the user's step-4 override). They edit disjoint files +so this is safe. If only one of (a) or (b) has work, spawn only that one. Iterate until 100% green or the user opts out. ### 9. Spawn the reviewer (LAST role — verdict only) -`Agent({subagent_type: "general-purpose", model: "opus"})` with the v2 reviewer -prompt. Foreground. The reviewer cross-checks `{{PRODUCTION_FILE}}` and -`{{TEST_FILE}}` against `{{NOTES_FILE}}` and produces an "OK / NEEDS FIX" verdict. +`Agent({subagent_type: "general-purpose", model: })` with the v2 +reviewer prompt, where `` is the model picked in step 3.5 +(Sonnet for trivial/standard, Opus for complex — or the user's step-4 +override). Foreground. The reviewer cross-checks `{{PRODUCTION_FILE}}` and +`{{TEST_FILE}}` against `{{NOTES_FILE}}` and produces an "OK / NEEDS FIX" +verdict. If the verdict is "NEEDS FIX", dispatch the implementer or tester back to action the findings (the reviewer never edits). @@ -206,8 +303,12 @@ Do NOT auto-commit. The user reviews and commits. ## Notes for the orchestrator (you, the main agent) -- Use the **Opus** model for all four roles — they handle OCL→C# translation - best and the user has explicitly preferred Opus for this workflow. +- Pick the model per role using the complexity-grading rubric in step 3.5. + Default tiers are Haiku (researcher only, trivial task), Sonnet (most cases), + Opus (only when OCL has `->closure` / multi-step `->union` / cross-interface + recursion, or method count > 15). The user can override "all Opus" / + "all Sonnet" / "Custom" at the step-4 sanity check. Per-role asymmetry is + encouraged (e.g. trivial impl + Opus regression-sweep tester). - Spawn each role **foreground** (not `run_in_background`). The implementer and tester in step 6 are spawned in parallel by issuing TWO `Agent(...)` tool calls in a single orchestrator message — both still foreground, just diff --git a/.gitignore b/.gitignore index 3f8b414b..39d3b5ff 100644 --- a/.gitignore +++ b/.gitignore @@ -356,3 +356,4 @@ MigrationBackup/ *.bak /switcher.json /.claude/settings.local.json +/.team-notes/* diff --git a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.ElementProcessing.cs b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.ElementProcessing.cs index 0eff0f78..91f55de6 100644 --- a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.ElementProcessing.cs +++ b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.ElementProcessing.cs @@ -227,7 +227,7 @@ internal void ProcessRuleElement(EncodedTextWriter writer, IClass umlClass, Rule case ValueLiteralElement valueLiteralElement: if (valueLiteralElement.QueryIsQualifiedName()) { - writer.WriteSafeString($"SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder,{ruleGenerationContext.CurrentVariableName}, writerContext);{Environment.NewLine}"); + writer.WriteSafeString($"SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder,{ruleGenerationContext.CurrentVariableName}, writerContext, poco);{Environment.NewLine}"); if (!ruleGenerationContext.IsNextElementNewLineTerminal()) { @@ -306,7 +306,7 @@ internal void ProcessAssignmentElement(EncodedTextWriter writer, IClass umlClass writer.WriteSafeString($"{Environment.NewLine}if({cursorToUse.CursorVariableName}.Current != null){Environment.NewLine}"); writer.WriteSafeString($"{{{Environment.NewLine}"); - writer.WriteSafeString($"SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder,{cursorToUse.CursorVariableName}.Current, writerContext);{Environment.NewLine}"); + writer.WriteSafeString($"SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder,{cursorToUse.CursorVariableName}.Current, writerContext, poco);{Environment.NewLine}"); writer.WriteSafeString($"{cursorToUse.CursorVariableName}.Move();{Environment.NewLine}"); writer.WriteSafeString("}"); } @@ -383,7 +383,7 @@ internal void ProcessAssignmentElement(EncodedTextWriter writer, IClass umlClass case ValueLiteralElement valueLiteralElement when valueLiteralElement.QueryIsQualifiedName(): if (isPartOfMultipleAlternative) { - writer.WriteSafeString($"SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder,poco.{targetPropertyName}, writerContext);{Environment.NewLine}"); + writer.WriteSafeString($"SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder,poco.{targetPropertyName}, writerContext, poco);{Environment.NewLine}"); if (!ruleGenerationContext.IsNextElementNewLineTerminal()) { @@ -394,7 +394,7 @@ internal void ProcessAssignmentElement(EncodedTextWriter writer, IClass umlClass { writer.WriteSafeString($"{Environment.NewLine}if (poco.{targetPropertyName} != null){Environment.NewLine}"); writer.WriteSafeString($"{{{Environment.NewLine}"); - writer.WriteSafeString($"SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder,poco.{targetPropertyName}, writerContext);{Environment.NewLine}"); + writer.WriteSafeString($"SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder,poco.{targetPropertyName}, writerContext, poco);{Environment.NewLine}"); if (!ruleGenerationContext.IsNextElementNewLineTerminal()) { diff --git a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.cs b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.cs index 036dc236..b8fda281 100644 --- a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.cs +++ b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.cs @@ -691,7 +691,7 @@ private bool TryEmitQualifiedNameOrChainAlternatives(EncodedTextWriter writer, I writer.WriteSafeString($"}}{Environment.NewLine}"); writer.WriteSafeString($"else if ({variableName}.{resolvedPropertyName} != null){Environment.NewLine}"); writer.WriteSafeString($"{{{Environment.NewLine}"); - writer.WriteSafeString($"SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder,{variableName}.{resolvedPropertyName}, writerContext);{Environment.NewLine}"); + writer.WriteSafeString($"SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder,{variableName}.{resolvedPropertyName}, writerContext, poco);{Environment.NewLine}"); writer.WriteSafeString($"stringBuilder.Append(' ');{Environment.NewLine}"); writer.WriteSafeString($"}}{Environment.NewLine}"); diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Writers/TextualNotationBuilderTestFixture.cs b/SysML2.NET.Serializer.TextualNotation.Tests/Writers/TextualNotationBuilderTestFixture.cs index 0b2a3ccb..b3db7e66 100644 --- a/SysML2.NET.Serializer.TextualNotation.Tests/Writers/TextualNotationBuilderTestFixture.cs +++ b/SysML2.NET.Serializer.TextualNotation.Tests/Writers/TextualNotationBuilderTestFixture.cs @@ -108,6 +108,7 @@ public void Verify_that_textual_notation_is_produced_from_Quantities_root_namesp Assert.That(textualNotation, Does.Contain("Quantities")); Assert.That(textualNotation, Does.Contain("private import ScalarValues::NumericalValue")); Assert.That(textualNotation, Does.Contain("isBound: Boolean")); + Assert.That(textualNotation, Does.Contain(":>> elements")); }); } } diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/AnnotationTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/AnnotationTextualNotationBuilder.cs index 7fda2739..cd0a132e 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/AnnotationTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/AnnotationTextualNotationBuilder.cs @@ -93,7 +93,7 @@ public static void BuildAnnotation(SysML2.NET.Core.POCO.Root.Annotations.IAnnota if (poco.AnnotatedElement != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.AnnotatedElement, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.AnnotatedElement, writerContext, poco); stringBuilder.Append(' '); } diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/ConjugationTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/ConjugationTextualNotationBuilder.cs index 6c1511bc..d403618b 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/ConjugationTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/ConjugationTextualNotationBuilder.cs @@ -49,7 +49,7 @@ public static void BuildOwnedConjugation(SysML2.NET.Core.POCO.Core.Types.IConjug } else if (poco.OriginalType != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.OriginalType, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.OriginalType, writerContext, poco); stringBuilder.Append(' '); } @@ -79,7 +79,7 @@ public static void BuildConjugation(SysML2.NET.Core.POCO.Core.Types.IConjugation } else if (poco.ConjugatedType != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.ConjugatedType, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.ConjugatedType, writerContext, poco); stringBuilder.Append(' '); } @@ -91,7 +91,7 @@ public static void BuildConjugation(SysML2.NET.Core.POCO.Core.Types.IConjugation } else if (poco.OriginalType != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.OriginalType, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.OriginalType, writerContext, poco); stringBuilder.Append(' '); } diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/CrossSubsettingTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/CrossSubsettingTextualNotationBuilder.cs index b4838edf..ac2d6b96 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/CrossSubsettingTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/CrossSubsettingTextualNotationBuilder.cs @@ -49,7 +49,7 @@ public static void BuildOwnedCrossSubsetting(SysML2.NET.Core.POCO.Core.Features. } else if (poco.CrossedFeature != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.CrossedFeature, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.CrossedFeature, writerContext, poco); stringBuilder.Append(' '); } diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/DifferencingTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/DifferencingTextualNotationBuilder.cs index ff90c2c9..8f5add2d 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/DifferencingTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/DifferencingTextualNotationBuilder.cs @@ -46,7 +46,7 @@ public static void BuildDifferencing(SysML2.NET.Core.POCO.Core.Types.IDifferenci var ownedRelatedElementCursor = writerContext.CursorCache.GetOrCreateCursor(poco.Id, "ownedRelatedElement", poco.OwnedRelatedElement); if (poco.DifferencingType != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.DifferencingType, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.DifferencingType, writerContext, poco); stringBuilder.Append(' '); } else diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/DisjoiningTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/DisjoiningTextualNotationBuilder.cs index 3157fa16..2a60e68f 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/DisjoiningTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/DisjoiningTextualNotationBuilder.cs @@ -49,7 +49,7 @@ public static void BuildOwnedDisjoining(SysML2.NET.Core.POCO.Core.Types.IDisjoin } else if (poco.DisjoiningType != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.DisjoiningType, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.DisjoiningType, writerContext, poco); stringBuilder.Append(' '); } @@ -79,7 +79,7 @@ public static void BuildDisjoining(SysML2.NET.Core.POCO.Core.Types.IDisjoining p } else if (poco.TypeDisjoined != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.TypeDisjoined, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.TypeDisjoined, writerContext, poco); stringBuilder.Append(' '); } @@ -91,7 +91,7 @@ public static void BuildDisjoining(SysML2.NET.Core.POCO.Core.Types.IDisjoining p } else if (poco.DisjoiningType != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.DisjoiningType, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.DisjoiningType, writerContext, poco); stringBuilder.Append(' '); } diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/FeatureChainingTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/FeatureChainingTextualNotationBuilder.cs index 57a320dd..2a7816a9 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/FeatureChainingTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/FeatureChainingTextualNotationBuilder.cs @@ -46,7 +46,7 @@ public static void BuildOwnedFeatureChaining(SysML2.NET.Core.POCO.Core.Features. if (poco.ChainingFeature != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.ChainingFeature, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.ChainingFeature, writerContext, poco); stringBuilder.Append(' '); } diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/FeatureInvertingTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/FeatureInvertingTextualNotationBuilder.cs index 1e06b06b..786561cd 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/FeatureInvertingTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/FeatureInvertingTextualNotationBuilder.cs @@ -49,7 +49,7 @@ public static void BuildOwnedFeatureInverting(SysML2.NET.Core.POCO.Core.Features } else if (poco.InvertingFeature != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.InvertingFeature, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.InvertingFeature, writerContext, poco); stringBuilder.Append(' '); } @@ -84,7 +84,7 @@ public static void BuildFeatureInverting(SysML2.NET.Core.POCO.Core.Features.IFea } else if (poco.FeatureInverted != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.FeatureInverted, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.FeatureInverted, writerContext, poco); stringBuilder.Append(' '); } @@ -96,7 +96,7 @@ public static void BuildFeatureInverting(SysML2.NET.Core.POCO.Core.Features.IFea } else if (poco.InvertingFeature != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.InvertingFeature, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.InvertingFeature, writerContext, poco); stringBuilder.Append(' '); } diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/FeatureTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/FeatureTextualNotationBuilder.cs index 66bc3394..fc571c51 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/FeatureTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/FeatureTextualNotationBuilder.cs @@ -996,7 +996,7 @@ public static void BuildFunctionReferenceArgument(SysML2.NET.Core.POCO.Core.Feat /// The that contains the entire textual notation public static void BuildFeatureReference(SysML2.NET.Core.POCO.Core.Features.IFeature poco, TextualNotationWriterContext writerContext, StringBuilder stringBuilder) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco, writerContext, poco); stringBuilder.Append(' '); } diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/FeatureTypingTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/FeatureTypingTextualNotationBuilder.cs index 8028335d..180dabfb 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/FeatureTypingTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/FeatureTypingTextualNotationBuilder.cs @@ -49,7 +49,7 @@ public static void BuildOwnedFeatureTyping(SysML2.NET.Core.POCO.Core.Features.IF } else if (poco.Type != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.Type, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.Type, writerContext, poco); stringBuilder.Append(' '); } @@ -67,7 +67,7 @@ public static void BuildReferenceTyping(SysML2.NET.Core.POCO.Core.Features.IFeat if (poco.Type != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.Type, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.Type, writerContext, poco); stringBuilder.Append(' '); } diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/IntersectingTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/IntersectingTextualNotationBuilder.cs index d69eae02..ec73827c 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/IntersectingTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/IntersectingTextualNotationBuilder.cs @@ -46,7 +46,7 @@ public static void BuildIntersecting(SysML2.NET.Core.POCO.Core.Types.IIntersecti var ownedRelatedElementCursor = writerContext.CursorCache.GetOrCreateCursor(poco.Id, "ownedRelatedElement", poco.OwnedRelatedElement); if (poco.IntersectingType != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.IntersectingType, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.IntersectingType, writerContext, poco); stringBuilder.Append(' '); } else diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/MembershipImportTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/MembershipImportTextualNotationBuilder.cs index 755bcc04..3697eba6 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/MembershipImportTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/MembershipImportTextualNotationBuilder.cs @@ -46,7 +46,7 @@ public static void BuildMembershipImport(SysML2.NET.Core.POCO.Root.Namespaces.IM if (poco.ImportedMembership != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.ImportedMembership, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.ImportedMembership, writerContext, poco); stringBuilder.Append(' '); } diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/MembershipTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/MembershipTextualNotationBuilder.cs index de5c1314..2742edc8 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/MembershipTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/MembershipTextualNotationBuilder.cs @@ -84,7 +84,7 @@ public static void BuildAliasMember(SysML2.NET.Core.POCO.Root.Namespaces.IMember if (poco.MemberElement != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.MemberElement, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.MemberElement, writerContext, poco); stringBuilder.Append(' '); } RelationshipTextualNotationBuilder.BuildRelationshipBody(poco, writerContext, stringBuilder); @@ -103,7 +103,7 @@ public static void BuildFeatureChainMember(SysML2.NET.Core.POCO.Root.Namespaces. if (poco.MemberElement != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.MemberElement, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.MemberElement, writerContext, poco); stringBuilder.Append(' '); } else @@ -157,7 +157,7 @@ public static void BuildElementReferenceMember(SysML2.NET.Core.POCO.Root.Namespa if (poco.MemberElement != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.MemberElement, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.MemberElement, writerContext, poco); stringBuilder.Append(' '); } diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/RedefinitionTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/RedefinitionTextualNotationBuilder.cs index 638ee0cc..93499b38 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/RedefinitionTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/RedefinitionTextualNotationBuilder.cs @@ -49,7 +49,7 @@ public static void BuildOwnedRedefinition(SysML2.NET.Core.POCO.Core.Features.IRe } else if (poco.RedefinedFeature != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.RedefinedFeature, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.RedefinedFeature, writerContext, poco); stringBuilder.Append(' '); } @@ -67,7 +67,7 @@ public static void BuildFlowFeatureRedefinition(SysML2.NET.Core.POCO.Core.Featur if (poco.RedefinedFeature != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.RedefinedFeature, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.RedefinedFeature, writerContext, poco); stringBuilder.Append(' '); } @@ -85,7 +85,7 @@ public static void BuildParameterRedefinition(SysML2.NET.Core.POCO.Core.Features if (poco.RedefinedFeature != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.RedefinedFeature, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.RedefinedFeature, writerContext, poco); stringBuilder.Append(' '); } diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/ReferenceSubsettingTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/ReferenceSubsettingTextualNotationBuilder.cs index b36fa6ba..ac8be967 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/ReferenceSubsettingTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/ReferenceSubsettingTextualNotationBuilder.cs @@ -49,7 +49,7 @@ public static void BuildOwnedReferenceSubsetting(SysML2.NET.Core.POCO.Core.Featu } else if (poco.ReferencedFeature != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.ReferencedFeature, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.ReferencedFeature, writerContext, poco); stringBuilder.Append(' '); } @@ -70,7 +70,7 @@ public static void BuildFlowEndSubsetting(SysML2.NET.Core.POCO.Core.Features.IRe } else if (poco.ReferencedFeature != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.ReferencedFeature, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.ReferencedFeature, writerContext, poco); stringBuilder.Append(' '); } diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/SharedTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/SharedTextualNotationBuilder.cs index 2ba68d1d..879ebcdb 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/SharedTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/SharedTextualNotationBuilder.cs @@ -79,7 +79,7 @@ public static void BuildDependencyDeclaration(SysML2.NET.Core.POCO.Root.Dependen if (clientCursor.Current != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, clientCursor.Current, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, clientCursor.Current, writerContext, poco); clientCursor.Move(); } @@ -89,7 +89,7 @@ public static void BuildDependencyDeclaration(SysML2.NET.Core.POCO.Root.Dependen if (clientCursor.Current != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, clientCursor.Current, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, clientCursor.Current, writerContext, poco); clientCursor.Move(); } clientCursor.Move(); @@ -99,7 +99,7 @@ public static void BuildDependencyDeclaration(SysML2.NET.Core.POCO.Root.Dependen if (supplierCursor.Current != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, supplierCursor.Current, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, supplierCursor.Current, writerContext, poco); supplierCursor.Move(); } @@ -109,7 +109,7 @@ public static void BuildDependencyDeclaration(SysML2.NET.Core.POCO.Root.Dependen if (supplierCursor.Current != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, supplierCursor.Current, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, supplierCursor.Current, writerContext, poco); supplierCursor.Move(); } supplierCursor.Move(); diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/SpecializationTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/SpecializationTextualNotationBuilder.cs index d3c9e553..25a80480 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/SpecializationTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/SpecializationTextualNotationBuilder.cs @@ -62,7 +62,7 @@ public static void BuildSpecificType(SysML2.NET.Core.POCO.Core.Types.ISpecializa } else if (poco.Specific != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.Specific, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.Specific, writerContext, poco); stringBuilder.Append(' '); } @@ -83,7 +83,7 @@ public static void BuildGeneralType(SysML2.NET.Core.POCO.Core.Types.ISpecializat } else if (poco.General != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.General, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.General, writerContext, poco); stringBuilder.Append(' '); } diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/SubclassificationTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/SubclassificationTextualNotationBuilder.cs index ec811230..c03b6424 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/SubclassificationTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/SubclassificationTextualNotationBuilder.cs @@ -46,7 +46,7 @@ public static void BuildOwnedSubclassification(SysML2.NET.Core.POCO.Core.Classif if (poco.Superclassifier != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.Superclassifier, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.Superclassifier, writerContext, poco); stringBuilder.Append(' '); } @@ -73,14 +73,14 @@ public static void BuildSubclassification(SysML2.NET.Core.POCO.Core.Classifiers. if (poco.Subclassifier != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.Subclassifier, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.Subclassifier, writerContext, poco); stringBuilder.Append(' '); } stringBuilder.Append(" :> "); if (poco.Superclassifier != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.Superclassifier, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.Superclassifier, writerContext, poco); stringBuilder.Append(' '); } RelationshipTextualNotationBuilder.BuildRelationshipBody(poco, writerContext, stringBuilder); diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/SubsettingTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/SubsettingTextualNotationBuilder.cs index 3065fc00..ba2ab78a 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/SubsettingTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/SubsettingTextualNotationBuilder.cs @@ -49,7 +49,7 @@ public static void BuildOwnedSubsetting(SysML2.NET.Core.POCO.Core.Features.ISubs } else if (poco.SubsettedFeature != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.SubsettedFeature, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.SubsettedFeature, writerContext, poco); stringBuilder.Append(' '); } diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/TypeFeaturingTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/TypeFeaturingTextualNotationBuilder.cs index fdf745ff..b1679e32 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/TypeFeaturingTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/TypeFeaturingTextualNotationBuilder.cs @@ -46,7 +46,7 @@ public static void BuildOwnedTypeFeaturing(SysML2.NET.Core.POCO.Core.Features.IT if (poco.FeaturingType != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.FeaturingType, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.FeaturingType, writerContext, poco); stringBuilder.Append(' '); } @@ -73,14 +73,14 @@ public static void BuildTypeFeaturing(SysML2.NET.Core.POCO.Core.Features.ITypeFe if (poco.FeatureOfType != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.FeatureOfType, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.FeatureOfType, writerContext, poco); stringBuilder.Append(' '); } stringBuilder.Append("by "); if (poco.FeaturingType != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.FeaturingType, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.FeaturingType, writerContext, poco); stringBuilder.Append(' '); } RelationshipTextualNotationBuilder.BuildRelationshipBody(poco, writerContext, stringBuilder); diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/TypeTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/TypeTextualNotationBuilder.cs index cb889fd7..be014762 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/TypeTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/TypeTextualNotationBuilder.cs @@ -916,7 +916,7 @@ public static void BuildFunctionBodyPart(SysML2.NET.Core.POCO.Core.Types.IType p /// The that contains the entire textual notation public static void BuildInstantiatedTypeReference(SysML2.NET.Core.POCO.Core.Types.IType poco, TextualNotationWriterContext writerContext, StringBuilder stringBuilder) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco, writerContext, poco); stringBuilder.Append(' '); } diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/UnioningTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/UnioningTextualNotationBuilder.cs index dd740b35..50558cb8 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/UnioningTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/UnioningTextualNotationBuilder.cs @@ -46,7 +46,7 @@ public static void BuildUnioning(SysML2.NET.Core.POCO.Core.Types.IUnioning poco, var ownedRelatedElementCursor = writerContext.CursorCache.GetOrCreateCursor(poco.Id, "ownedRelatedElement", poco.OwnedRelatedElement); if (poco.UnioningType != null) { - SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.UnioningType, writerContext); + SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder, poco.UnioningType, writerContext, poco); stringBuilder.Append(' '); } else diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/SharedTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/SharedTextualNotationBuilder.cs index ce599d39..b8aae3c2 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/SharedTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/SharedTextualNotationBuilder.cs @@ -21,6 +21,7 @@ namespace SysML2.NET.Serializer.TextualNotation.Writers { using System; + using System.Collections.Generic; using System.Linq; using System.Text; @@ -584,168 +585,154 @@ internal static void AppendRegularComment(StringBuilder stringBuilder, string bo } /// - /// Appends the shortest resolvable name of the for the textual notation, - /// per KerML specification section 8.2.3.5. - /// When the carries a non-null - /// , the method walks up the - /// namespace chain to find whether the element's simple name resolves via imports - /// (per 8.2.3.5.4 Full Resolution). If so, the simple name is used; otherwise the full - /// qualifiedName is emitted. + /// Appends the shortest resolvable name of the + /// for the textual notation, per KerML specification section + /// 8.2.3.5. + /// The local scope of the reference is derived as + /// sourcePoco?.owningNamespace ?? writerContext.ContextNamespace. The resolver + /// walks up from that local scope through each containing namespace, asking + /// the per-scope simple-name index cached on whether + /// the target's resolves locally. The first hit + /// wins. If no scope along the chain resolves the simple name to the target, the full + /// is emitted as a fallback. + /// Membership references in import declarations bypass shortening — the path + /// identifies WHAT is imported and must remain fully qualified. /// /// The to append to - /// The that needs to have its name appended - /// The providing the serialization context - internal static void AppendQualifiedName(StringBuilder stringBuilder, IElement poco, TextualNotationWriterContext writerContext) + /// The referenced whose name is appended + /// The providing the cache + root + /// + /// The at whose syntactic position the reference appears (typically the + /// relationship POCO whose property is being unparsed). Its owningNamespace is the + /// local scope of the reference. Pass the enclosing-builder's poco at every call site. + /// + internal static void AppendQualifiedName(StringBuilder stringBuilder, IElement target, TextualNotationWriterContext writerContext, IElement sourcePoco) { - if (poco is IMembership membership) + if (target == null) { - // Membership references (used in import declarations) must retain - // their full qualified name — the path identifies WHAT is being imported. - stringBuilder.Append(membership.MemberElement.qualifiedName); + return; } - else + + if (target is IMembership membership) { - // Element references (used in type/feature references) can be shortened - // when the element is resolvable by its simple name via imports (8.2.3.5). - stringBuilder.Append(QueryShortestResolvableName(poco, writerContext?.ContextNamespace)); + // Membership references (used in import declarations) must retain + // their full qualified name — the path identifies WHAT is being imported. + stringBuilder.Append(membership.MemberElement?.qualifiedName); + return; } - } - /// - /// Determines the shortest name that will resolve to the given - /// per the KerML name resolution rules (8.2.3.5). - /// Per 8.2.3.5.4, full resolution walks up from the local namespace through - /// containing namespaces to the global namespace. If the element's simple name resolves - /// via the membership (which includes imported memberships) of any namespace in - /// that chain, the simple name is sufficient. - /// - /// The referenced to resolve - /// - /// The root being serialized, or null to fall back to the full qualified name - /// - /// The shortest name that resolves to the element - private static string QueryShortestResolvableName(IElement element, INamespace contextNamespace) - { - if (element == null) + var simpleName = target.EscapedName(); + + if (string.IsNullOrWhiteSpace(simpleName)) { - return string.Empty; + stringBuilder.Append(target.qualifiedName ?? string.Empty); + return; } - var simpleName = element.EscapedName(); + var scope = QueryLocalScope(sourcePoco) ?? writerContext?.ContextNamespace; - if (!string.IsNullOrWhiteSpace(simpleName) && contextNamespace != null) + while (scope != null) { - // Per 8.2.3.5.4, full resolution walks up from the local namespace through - // containing namespaces. The contextNamespace is the root namespace being - // serialized; imports that make the simple name resolvable may be on any - // descendant namespace (e.g. a LibraryPackage nested inside the root). - // We check the context namespace and all its descendant namespaces. - if (QueryIsResolvableInNamespaceTree(contextNamespace, element, simpleName)) + var index = writerContext.GetOrBuildSimpleNameIndex(scope); + + if (index.TryGetValue(simpleName, out var elements) && elements.Contains(target)) { - return simpleName; + stringBuilder.Append(simpleName); + return; } - } - return element.qualifiedName ?? string.Empty; - } + INamespace nextScope; - /// - /// Recursively checks whether the given is resolvable by its - /// within the or any of its - /// descendant namespaces (per 8.2.3.5.4 full resolution, which walks up from the local - /// namespace — checking descendants ensures we cover all import scopes in the output model). - /// - /// The namespace to check (including descendants) - /// The element to find - /// The simple name to match - /// true if the element is found by simple name in the namespace tree - private static bool QueryIsResolvableInNamespaceTree(INamespace @namespace, IElement element, string simpleName) - { - try - { - if (QueryIsResolvableBySimpleName(@namespace, element, simpleName)) + try { - return true; + nextScope = scope.owningNamespace; } - } - catch (NotSupportedException) - { - // membership may not be fully implemented for this namespace - } - - try - { - foreach (var childNamespace in @namespace.ownedMember.OfType()) + catch (NotSupportedException) { - if (QueryIsResolvableInNamespaceTree(childNamespace, element, simpleName)) - { - return true; - } + // owningNamespace not implemented for this scope — stop the upward walk + // and fall through to the qualifiedName fallback below. + break; } - } - catch (NotSupportedException) - { - // ownedMember may not be fully implemented for this namespace + + scope = nextScope; } - return false; + stringBuilder.Append(target.qualifiedName ?? string.Empty); } /// - /// Checks whether the given is resolvable by its - /// within the 's - /// memberships (owned, imported, and inherited). + /// Returns the local where a reference rooted at + /// syntactically appears, by climbing the source's + /// containment chain until a Namespace is reached. + /// The chain is followed in this priority order at each hop: + /// + /// The current element itself, if it is already an . + /// The when the + /// current element is an . Required for relationship POCOs + /// such as / / + /// whose is null because they are owned via + /// ownedRelationship, not via a membership. + /// when non-null. + /// as a final fallback. + /// + /// A visited set guards against accidental cycles in malformed models. /// - /// The namespace to check - /// The element to find - /// The simple name to match - /// true if the element is found by simple name in the namespace - private static bool QueryIsResolvableBySimpleName(INamespace @namespace, IElement element, string simpleName) + /// The source bearing the reference, or . + /// The local , or when none can be derived. + private static INamespace QueryLocalScope(IElement sourcePoco) { - // Check ownedMembership first (non-derived, always available) - foreach (var ownedMember in @namespace.ownedMembership) + if (sourcePoco == null) { - if (ownedMember is IOwningMembership owningMembership - && owningMembership.OwnedRelatedElement.Contains(element)) + return null; + } + + var visited = new HashSet(); + var current = sourcePoco; + + while (current != null && visited.Add(current)) + { + if (current is INamespace asNamespace) { - return true; + return asNamespace; } - if (ownedMember.MemberElement == element) + if (current is IRelationship relationship && relationship.OwningRelatedElement != null) { - return true; + current = relationship.OwningRelatedElement; + continue; } - } - // Check imports: walk ownedImport (ownedRelationship filtered to IImport) to find - // MembershipImports that reference the element directly by its membership. - foreach (var import in @namespace.ownedImport) - { - if (import is IMembershipImport membershipImport - && membershipImport.ImportedMembership is IMembership importedMembership - && importedMembership.MemberElement == element) + INamespace owningNs = null; + + try + { + owningNs = current.owningNamespace; + } + catch (NotSupportedException) { - return true; + // owningNamespace not implemented — fall through to owner walk. } - if (import is INamespaceImport namespaceImport) + if (owningNs != null) { - var importedNs = namespaceImport.ImportedNamespace; + return owningNs; + } - if (importedNs != null) - { - foreach (var visibleMember in importedNs.ownedMembership) - { - if (visibleMember.MemberElement == element) - { - return true; - } - } - } + IElement nextOwner = null; + + try + { + nextOwner = current.owner; } + catch (NotSupportedException) + { + nextOwner = null; + } + + current = nextOwner; } - return false; + return null; } } } diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/TextualNotationWriterContext.cs b/SysML2.NET.Serializer.TextualNotation/Writers/TextualNotationWriterContext.cs index caf35af0..011b119a 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/TextualNotationWriterContext.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/TextualNotationWriterContext.cs @@ -21,24 +21,39 @@ namespace SysML2.NET.Serializer.TextualNotation.Writers { using System; + using System.Collections.Generic; + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Core.Types; + using SysML2.NET.Core.POCO.Root.Elements; using SysML2.NET.Core.POCO.Root.Namespaces; /// /// Provides the serialization context for the textual notation builders. /// Carries the for cursor-based element traversal, /// the context for qualified name resolution (KerML 8.2.3.5), - /// and future writer settings (indentation, short/long notation preferences, etc.). + /// a per-namespace simple-name index cache used by + /// , and future writer + /// settings (indentation, short/long notation preferences, etc.). /// public class TextualNotationWriterContext : IDisposable { + /// + /// Lazily-populated per-namespace simple-name index. For each + /// scope previously queried, maps each visible simple name (member shortName / name) + /// to the set of s reachable by that simple name from that scope. + /// Built on demand in . + /// + private readonly Dictionary>> simpleNameIndex + = new Dictionary>>(); + /// /// Initializes a new instance of the class. /// /// - /// The root being serialized. Used by - /// to determine - /// the shortest resolvable name for cross-references. + /// The root being serialized. Used as the upper-bound of the + /// upward-walk in , and + /// as the fallback local scope when a source POCO has no owningNamespace. /// public TextualNotationWriterContext(INamespace contextNamespace) { @@ -53,10 +68,53 @@ public TextualNotationWriterContext(INamespace contextNamespace) /// /// Gets the root being serialized, providing the - /// context for qualified name resolution per KerML specification section 8.2.3.5. + /// upper bound for the upward-walk performed in qualified name resolution + /// per KerML specification section 8.2.3.5. /// public INamespace ContextNamespace { get; } + /// + /// Returns the cached simple-name index for the given , building + /// it on first access. The index maps every simple name that resolves locally in + /// to the set of s reachable by that + /// simple name. The set covers: + /// + /// Owned memberships (scope.ownedMembership). + /// Direct imports (scope.ownedImport) — both IMembershipImport + /// and INamespaceImport. + /// Inherited feature memberships when is an + /// IType, by walking type.featureMembership (which already unions owned + /// and inherited per TypeExtensions.ComputeFeatureMembership). + /// + /// + /// The whose simple-name index is requested. + /// The simple-name → set lookup for . + internal IReadOnlyDictionary> GetOrBuildSimpleNameIndex(INamespace scope) + { + if (scope == null) + { + return EmptyIndex; + } + + if (this.simpleNameIndex.TryGetValue(scope, out var cached)) + { + return cached; + } + + var index = new Dictionary>(StringComparer.Ordinal); + + BuildOwnedAndImportedEntries(scope, index); + + if (scope is IType type) + { + BuildInheritedEntries(type, index); + } + + this.simpleNameIndex[scope] = index; + + return index; + } + /// /// Performs application-defined tasks associated with freeing, releasing, or resetting /// unmanaged resources. @@ -65,5 +123,159 @@ public void Dispose() { this.CursorCache.Dispose(); } + + /// + /// Single shared empty result returned by when + /// the requested scope is . + /// + private static readonly IReadOnlyDictionary> EmptyIndex + = new Dictionary>(StringComparer.Ordinal); + + /// + /// Adds entries for 's ownedMembership and direct + /// ownedImports into . Handles both + /// and ; for the latter, + /// the imported namespace's ownedMembership entries are projected into the index + /// keyed by the member-relative names exposed by the import. + /// + /// The namespace whose owned + imported members are indexed. + /// The destination index to populate. + private static void BuildOwnedAndImportedEntries(INamespace scope, Dictionary> index) + { + try + { + foreach (var ownedMember in scope.ownedMembership) + { + AddMembershipEntry(index, ownedMember); + } + } + catch (NotSupportedException) + { + // ownedMembership may not be implemented for this namespace; skip. + } + + try + { + foreach (var ownedImport in scope.ownedImport) + { + if (ownedImport is IMembershipImport membershipImport + && membershipImport.ImportedMembership is IMembership importedMembership) + { + AddMembershipEntry(index, importedMembership); + } + else if (ownedImport is INamespaceImport namespaceImport + && namespaceImport.ImportedNamespace != null) + { + foreach (var importedMember in namespaceImport.ImportedNamespace.ownedMembership) + { + AddMembershipEntry(index, importedMember); + } + } + } + } + catch (NotSupportedException) + { + // ownedImport / ImportedMemberships may not be implemented; skip. + } + } + + /// + /// Adds entries for inherited memberships of into + /// , keyed by each inherited 's + /// MemberShortName / MemberName and mapped to its MemberElement. + /// Walks the transitive supertype graph (via AllSupertypes) and indexes + /// each supertype's own ownedMembership entries directly. This is + /// intentionally different from + /// TypeExtensions.ComputeInheritedMembership: that operation runs the + /// RemoveRedefinedFeatures filter (which strips inherited Features that have + /// been redefined by the local Type), so references such as :>> elements + /// — whose very purpose is to redefine elements — would be excluded by name + /// resolution that relied on it. For name-resolution purposes the redefined target + /// MUST stay reachable, so we bypass that filter and index the raw owned memberships + /// of every transitive supertype. + /// + /// The type whose transitive-supertype memberships are indexed. + /// The destination index to populate. + private static void BuildInheritedEntries(IType type, Dictionary> index) + { + List supertypes; + + try + { + supertypes = type.AllSupertypes(); + } + catch (NotSupportedException) + { + return; + } + + foreach (var supertype in supertypes) + { + if (supertype == null || ReferenceEquals(supertype, type)) + { + continue; + } + + try + { + foreach (var ownedMember in supertype.ownedMembership) + { + AddMembershipEntry(index, ownedMember); + } + } + catch (NotSupportedException) + { + // ownedMembership not implemented for this supertype; skip. + } + } + } + + /// + /// Adds the of + /// to the index under both its and + /// , when present. + /// + /// The destination index to populate. + /// The membership whose target is indexed. + private static void AddMembershipEntry(Dictionary> index, IMembership membership) + { + if (membership == null) + { + return; + } + + var target = membership.MemberElement; + + if (target == null) + { + return; + } + + AddIndexEntry(index, membership.MemberShortName, target); + AddIndexEntry(index, membership.MemberName, target); + } + + /// + /// Adds to under + /// when the name is non-blank. + /// + /// The destination index to populate. + /// The simple name to use as the index key. + /// The element to record under . + private static void AddIndexEntry(Dictionary> index, string simpleName, IElement element) + { + if (string.IsNullOrWhiteSpace(simpleName) || element == null) + { + return; + } + + if (!index.TryGetValue(simpleName, out var bucket)) + { + bucket = new HashSet(); + index[simpleName] = bucket; + } + + bucket.Add(element); + } } } diff --git a/SysML2.NET.Tests/Extend/FeatureMembershipExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/FeatureMembershipExtensionsTestFixture.cs index dc9b93b0..e005e739 100644 --- a/SysML2.NET.Tests/Extend/FeatureMembershipExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/FeatureMembershipExtensionsTestFixture.cs @@ -1,44 +1,95 @@ -// ------------------------------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------- // -// +// // Copyright 2022-2026 Starion Group S.A. -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// +// // // ------------------------------------------------------------------------------------------------ namespace SysML2.NET.Tests.Extend { using System; - + using NUnit.Framework; - + + using SysML2.NET.Core.POCO.Core.Features; using SysML2.NET.Core.POCO.Core.Types; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Root.Namespaces; + using SysML2.NET.Exceptions; + using SysML2.NET.Extensions; + + using Type = SysML2.NET.Core.POCO.Core.Types.Type; [TestFixture] public class FeatureMembershipExtensionsTestFixture { [Test] - public void ComputeOwnedMemberFeature_ThrowsNotSupportedException() + public void VerifyComputeOwnedMemberFeature() { - Assert.That(() => ((IFeatureMembership)null).ComputeOwnedMemberFeature(), Throws.TypeOf()); + Assert.That(() => ((IFeatureMembership)null).ComputeOwnedMemberFeature(), Throws.TypeOf()); + + var featureMembership = new FeatureMembership(); + + Assert.That(() => featureMembership.ComputeOwnedMemberFeature(), Throws.TypeOf()); + + var owningType = new Type(); + var feature = new Feature(); + + owningType.AssignOwnership(featureMembership, feature); + + Assert.That(featureMembership.ComputeOwnedMemberFeature(), Is.SameAs(feature)); + + // NOTE: wiring a non-IFeature element as the sole OwnedRelatedElement is not possible via the + // public AssignOwnership API (it validates that FeatureMembership requires an IFeature target). + // To cover the as-cast-returns-null path we directly set OwningRelatedElement on a fresh + // membership so that OwnedRelatedElement[0] is a plain Namespace (which is not an IFeature). + var nonFeatureMembership = new FeatureMembership(); + var nonFeatureElement = new Namespace(); + + ((IContainedRelationship)nonFeatureMembership).OwnedRelatedElement.Add(nonFeatureElement); + + Assert.That(nonFeatureMembership.ComputeOwnedMemberFeature(), Is.Null); } - + [Test] - public void ComputeOwningType_ThrowsNotSupportedException() + public void VerifyComputeOwningType() { - Assert.That(() => ((IFeatureMembership)null).ComputeOwningType(), Throws.TypeOf()); + Assert.That(() => ((IFeatureMembership)null).ComputeOwningType(), Throws.TypeOf()); + + var featureMembership = new FeatureMembership(); + + Assert.That(featureMembership.ComputeOwningType(), Is.Null); + + var owningType = new Type(); + var feature = new Feature(); + + owningType.AssignOwnership(featureMembership, feature); + + Assert.That(featureMembership.ComputeOwningType(), Is.SameAs(owningType)); + + // NOTE: assigning a non-IType as OwningRelatedElement is rejected by the public AssignOwnership + // guard (IFeatureMembership requires an IType source). We therefore directly set the backing + // field via the IContainedRelationship explicit interface to exercise the as-cast-returns-null + // path, which proves ComputeOwningType returns null when the owner is not an IType. + var nonTypeMembership = new FeatureMembership(); + var nonTypeOwner = new Namespace(); + + ((IContainedRelationship)nonTypeMembership).OwningRelatedElement = nonTypeOwner; + + Assert.That(nonTypeMembership.ComputeOwningType(), Is.Null); } } } diff --git a/SysML2.NET.Tests/Extend/TypeExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/TypeExtensionsTestFixture.cs index 6533519c..c8a357e7 100644 --- a/SysML2.NET.Tests/Extend/TypeExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/TypeExtensionsTestFixture.cs @@ -266,12 +266,21 @@ public void VerifyComputeOwnedFeature() Assert.That(subject.ComputeOwnedFeature(), Has.Count.EqualTo(0)); - // populated case depends on FeatureMembershipExtensions.ComputeOwnedMemberFeature, which is still a stub. var feature = new Feature(); var featureMembership = new FeatureMembership(); subject.AssignOwnership(featureMembership, feature); - Assert.That(() => subject.ComputeOwnedFeature(), Throws.TypeOf()); + // An OwningMembership (non-FeatureMembership) sibling must be excluded. + var nonFeature = new Feature(); + var owningMembership = new OwningMembership(); + subject.AssignOwnership(owningMembership, nonFeature); + + using (Assert.EnterMultipleScope()) + { + Assert.That(subject.ComputeOwnedFeature(), Has.Count.EqualTo(1)); + Assert.That(subject.ComputeOwnedFeature(), Does.Contain(feature)); + Assert.That(subject.ComputeOwnedFeature(), Does.Not.Contain(nonFeature)); + } } [Test] @@ -283,12 +292,26 @@ public void VerifyComputeFeature() Assert.That(subject.ComputeFeature(), Has.Count.EqualTo(0)); - // populated case depends on FeatureMembershipExtensions.ComputeOwnedMemberFeature, which is still a stub. var feature = new Feature(); var featureMembership = new FeatureMembership(); subject.AssignOwnership(featureMembership, feature); - Assert.That(() => subject.ComputeFeature(), Throws.TypeOf()); + Assert.That(subject.ComputeFeature(), Is.EquivalentTo([feature])); + + // Inherited FeatureMembership via Specialization must be INCLUDED in feature. + var supertype = new Type(); + var specialization = new Specialization { Specific = subject, General = supertype }; + subject.AssignOwnership(specialization); + + var inheritedFeature = new Feature(); + var inheritedFeatureMembership = new FeatureMembership { Visibility = VisibilityKind.Public }; + supertype.AssignOwnership(inheritedFeatureMembership, inheritedFeature); + + using (Assert.EnterMultipleScope()) + { + Assert.That(subject.ComputeFeature(), Does.Contain(feature)); + Assert.That(subject.ComputeFeature(), Does.Contain(inheritedFeature)); + } } [Test] @@ -300,12 +323,21 @@ public void VerifyComputeOwnedEndFeature() Assert.That(subject.ComputeOwnedEndFeature(), Has.Count.EqualTo(0)); - // populated case depends on FeatureMembershipExtensions.ComputeOwnedMemberFeature, which is still a stub. var endFeature = new Feature { IsEnd = true }; var endMembership = new FeatureMembership(); subject.AssignOwnership(endMembership, endFeature); - Assert.That(() => subject.ComputeOwnedEndFeature(), Throws.TypeOf()); + // A non-end (IsEnd = false) feature must be EXCLUDED. + var nonEndFeature = new Feature { IsEnd = false }; + var nonEndMembership = new FeatureMembership(); + subject.AssignOwnership(nonEndMembership, nonEndFeature); + + using (Assert.EnterMultipleScope()) + { + Assert.That(subject.ComputeOwnedEndFeature(), Has.Count.EqualTo(1)); + Assert.That(subject.ComputeOwnedEndFeature(), Does.Contain(endFeature)); + Assert.That(subject.ComputeOwnedEndFeature(), Does.Not.Contain(nonEndFeature)); + } } [Test] @@ -317,12 +349,37 @@ public void VerifyComputeEndFeature() Assert.That(subject.ComputeEndFeature(), Has.Count.EqualTo(0)); - // populated case depends on FeatureMembershipExtensions.ComputeOwnedMemberFeature, which is still a stub. var endFeature = new Feature { IsEnd = true }; var endMembership = new FeatureMembership(); subject.AssignOwnership(endMembership, endFeature); - Assert.That(() => subject.ComputeEndFeature(), Throws.TypeOf()); + // A non-end (IsEnd = false) feature must be EXCLUDED. + var nonEndFeature = new Feature { IsEnd = false }; + var nonEndMembership = new FeatureMembership(); + subject.AssignOwnership(nonEndMembership, nonEndFeature); + + using (Assert.EnterMultipleScope()) + { + Assert.That(subject.ComputeEndFeature(), Has.Count.EqualTo(1)); + Assert.That(subject.ComputeEndFeature(), Does.Contain(endFeature)); + Assert.That(subject.ComputeEndFeature(), Does.Not.Contain(nonEndFeature)); + } + + // An inherited end feature (via Specialization) must also be INCLUDED. + var supertype = new Type(); + var specialization = new Specialization { Specific = subject, General = supertype }; + subject.AssignOwnership(specialization); + + var inheritedEndFeature = new Feature { IsEnd = true }; + var inheritedEndMembership = new FeatureMembership { Visibility = VisibilityKind.Public }; + supertype.AssignOwnership(inheritedEndMembership, inheritedEndFeature); + + using (Assert.EnterMultipleScope()) + { + Assert.That(subject.ComputeEndFeature(), Does.Contain(endFeature)); + Assert.That(subject.ComputeEndFeature(), Does.Contain(inheritedEndFeature)); + Assert.That(subject.ComputeEndFeature(), Does.Not.Contain(nonEndFeature)); + } } [Test] @@ -518,12 +575,21 @@ public void VerifyComputeDirectedFeature() Assert.That(subject.ComputeDirectedFeature(), Has.Count.EqualTo(0)); - // populated case depends on FeatureMembershipExtensions.ComputeOwnedMemberFeature, which is still a stub. var directedFeature = new Feature { Direction = FeatureDirectionKind.In }; var directedMembership = new FeatureMembership(); subject.AssignOwnership(directedMembership, directedFeature); - Assert.That(() => subject.ComputeDirectedFeature(), Throws.TypeOf()); + // An undirected feature (Direction = null) must be EXCLUDED. + var undirectedFeature = new Feature { Direction = null }; + var undirectedMembership = new FeatureMembership(); + subject.AssignOwnership(undirectedMembership, undirectedFeature); + + using (Assert.EnterMultipleScope()) + { + Assert.That(subject.ComputeDirectedFeature(), Has.Count.EqualTo(1)); + Assert.That(subject.ComputeDirectedFeature(), Does.Contain(directedFeature)); + Assert.That(subject.ComputeDirectedFeature(), Does.Not.Contain(undirectedFeature)); + } } [Test] @@ -535,12 +601,33 @@ public void VerifyComputeInput() Assert.That(subject.ComputeInput(), Has.Count.EqualTo(0)); - // populated case depends on FeatureMembershipExtensions.ComputeOwnedMemberFeature, which is still a stub. var inFeature = new Feature { Direction = FeatureDirectionKind.In }; var inMembership = new FeatureMembership(); subject.AssignOwnership(inMembership, inFeature); - Assert.That(() => subject.ComputeInput(), Throws.TypeOf()); + // An Inout feature must also be INCLUDED (the `or` second branch). + var inoutFeature = new Feature { Direction = FeatureDirectionKind.Inout }; + var inoutMembership = new FeatureMembership(); + subject.AssignOwnership(inoutMembership, inoutFeature); + + // An Out feature must be EXCLUDED. + var outFeature = new Feature { Direction = FeatureDirectionKind.Out }; + var outMembership = new FeatureMembership(); + subject.AssignOwnership(outMembership, outFeature); + + // An undirected feature must be EXCLUDED. + var undirectedFeature = new Feature { Direction = null }; + var undirectedMembership = new FeatureMembership(); + subject.AssignOwnership(undirectedMembership, undirectedFeature); + + using (Assert.EnterMultipleScope()) + { + Assert.That(subject.ComputeInput(), Has.Count.EqualTo(2)); + Assert.That(subject.ComputeInput(), Does.Contain(inFeature)); + Assert.That(subject.ComputeInput(), Does.Contain(inoutFeature)); + Assert.That(subject.ComputeInput(), Does.Not.Contain(outFeature)); + Assert.That(subject.ComputeInput(), Does.Not.Contain(undirectedFeature)); + } } [Test] @@ -552,12 +639,33 @@ public void VerifyComputeOutput() Assert.That(subject.ComputeOutput(), Has.Count.EqualTo(0)); - // populated case depends on FeatureMembershipExtensions.ComputeOwnedMemberFeature, which is still a stub. var outFeature = new Feature { Direction = FeatureDirectionKind.Out }; var outMembership = new FeatureMembership(); subject.AssignOwnership(outMembership, outFeature); - Assert.That(() => subject.ComputeOutput(), Throws.TypeOf()); + // An Inout feature must also be INCLUDED (the `or` second branch). + var inoutFeature = new Feature { Direction = FeatureDirectionKind.Inout }; + var inoutMembership = new FeatureMembership(); + subject.AssignOwnership(inoutMembership, inoutFeature); + + // An In feature must be EXCLUDED. + var inFeature = new Feature { Direction = FeatureDirectionKind.In }; + var inMembership = new FeatureMembership(); + subject.AssignOwnership(inMembership, inFeature); + + // An undirected feature must be EXCLUDED. + var undirectedFeature = new Feature { Direction = null }; + var undirectedMembership = new FeatureMembership(); + subject.AssignOwnership(undirectedMembership, undirectedFeature); + + using (Assert.EnterMultipleScope()) + { + Assert.That(subject.ComputeOutput(), Has.Count.EqualTo(2)); + Assert.That(subject.ComputeOutput(), Does.Contain(outFeature)); + Assert.That(subject.ComputeOutput(), Does.Contain(inoutFeature)); + Assert.That(subject.ComputeOutput(), Does.Not.Contain(inFeature)); + Assert.That(subject.ComputeOutput(), Does.Not.Contain(undirectedFeature)); + } } [Test] @@ -749,12 +857,32 @@ public void VerifyComputeFeatureMembership() Assert.That(subject.ComputeFeatureMembership(), Has.Count.EqualTo(0)); - // populated case depends on FeatureMembershipExtensions.ComputeOwnedMemberFeature, which is still a stub. var feature = new Feature(); var featureMembership = new FeatureMembership(); subject.AssignOwnership(featureMembership, feature); - Assert.That(() => subject.ComputeFeatureMembership(), Throws.TypeOf()); + // An OwningMembership (non-FeatureMembership) owned by the subject must be EXCLUDED. + var nonFeature = new Feature(); + var owningMembership = new OwningMembership(); + subject.AssignOwnership(owningMembership, nonFeature); + + Assert.That(subject.ComputeFeatureMembership(), Is.EquivalentTo([featureMembership])); + + // Inherited FeatureMembership via Specialization must be INCLUDED. + var supertype = new Type(); + var specialization = new Specialization { Specific = subject, General = supertype }; + subject.AssignOwnership(specialization); + + var inheritedFeature = new Feature(); + var inheritedFeatureMembership = new FeatureMembership { Visibility = VisibilityKind.Public }; + supertype.AssignOwnership(inheritedFeatureMembership, inheritedFeature); + + using (Assert.EnterMultipleScope()) + { + Assert.That(subject.ComputeFeatureMembership(), Does.Contain(featureMembership)); + Assert.That(subject.ComputeFeatureMembership(), Does.Contain(inheritedFeatureMembership)); + Assert.That(subject.ComputeFeatureMembership(), Does.Not.Contain(owningMembership)); + } } [Test] @@ -767,18 +895,30 @@ public void VerifyComputeInheritedFeature() // empty case: no inherited memberships → no inherited features. Assert.That(subject.ComputeInheritedFeature(), Has.Count.EqualTo(0)); - // populated case depends on FeatureMembershipExtensions.ComputeOwnedMemberFeature, which is still - // a stub: the OCL is `inheritedMembership->selectByKind(FeatureMembership).memberFeature`, and - // resolving `memberFeature` on a populated FeatureMembership reads `ownedMemberFeature` → stub. var supertype = new Type(); var specialization = new Specialization { Specific = subject, General = supertype }; subject.AssignOwnership(specialization); var inheritedFeature = new Feature(); - var inheritedFeatureMembership = new FeatureMembership(); + var inheritedFeatureMembership = new FeatureMembership { Visibility = VisibilityKind.Public }; supertype.AssignOwnership(inheritedFeatureMembership, inheritedFeature); - Assert.That(() => subject.ComputeInheritedFeature(), Throws.TypeOf()); + // An inherited non-FM OwningMembership on the supertype must be EXCLUDED. + var nonFmElement = new Feature(); + var inheritedOwningMembership = new OwningMembership { Visibility = VisibilityKind.Public }; + supertype.AssignOwnership(inheritedOwningMembership, nonFmElement); + + // Own features of the subject must NOT surface in inheritedFeature. + var ownFeature = new Feature(); + var ownFeatureMembership = new FeatureMembership(); + subject.AssignOwnership(ownFeatureMembership, ownFeature); + + using (Assert.EnterMultipleScope()) + { + Assert.That(subject.ComputeInheritedFeature(), Does.Contain(inheritedFeature)); + Assert.That(subject.ComputeInheritedFeature(), Does.Not.Contain(nonFmElement)); + Assert.That(subject.ComputeInheritedFeature(), Does.Not.Contain(ownFeature)); + } } [Test] diff --git a/SysML2.NET/Extend/FeatureMembershipExtensions.cs b/SysML2.NET/Extend/FeatureMembershipExtensions.cs index 0cfea517..05482a89 100644 --- a/SysML2.NET/Extend/FeatureMembershipExtensions.cs +++ b/SysML2.NET/Extend/FeatureMembershipExtensions.cs @@ -28,6 +28,7 @@ namespace SysML2.NET.Core.POCO.Core.Types using SysML2.NET.Core.POCO.Root.Annotations; using SysML2.NET.Core.POCO.Root.Elements; using SysML2.NET.Core.POCO.Root.Namespaces; + using SysML2.NET.Exceptions; /// /// The class provides extensions methods for @@ -44,10 +45,16 @@ internal static class FeatureMembershipExtensions /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IFeature ComputeOwnedMemberFeature(this IFeatureMembership featureMembershipSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (featureMembershipSubject == null) + { + throw new ArgumentNullException(nameof(featureMembershipSubject)); + } + + return featureMembershipSubject.OwnedRelatedElement.Count != 1 + ? throw new IncompleteModelException($"{nameof(featureMembershipSubject)} must have exactly one related element") + : featureMembershipSubject.OwnedRelatedElement[0] as IFeature; } /// @@ -59,10 +66,11 @@ internal static IFeature ComputeOwnedMemberFeature(this IFeatureMembership featu /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IType ComputeOwningType(this IFeatureMembership featureMembershipSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return featureMembershipSubject == null + ? throw new ArgumentNullException(nameof(featureMembershipSubject)) + : featureMembershipSubject.OwningRelatedElement as IType; } }