From 8f13ca039b98a15d8e864e9cff92d3e76a999648 Mon Sep 17 00:00:00 2001 From: atheate Date: Wed, 25 Mar 2026 09:06:58 +0100 Subject: [PATCH 1/5] [WIP] Element Extensions --- SysML2.NET/Extend/ElementExtensions.cs | 47 ++++++++++++++++---------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/SysML2.NET/Extend/ElementExtensions.cs b/SysML2.NET/Extend/ElementExtensions.cs index eeb28540..882ee464 100644 --- a/SysML2.NET/Extend/ElementExtensions.cs +++ b/SysML2.NET/Extend/ElementExtensions.cs @@ -22,7 +22,11 @@ namespace SysML2.NET.Core.POCO.Root.Elements { using System; using System.Collections.Generic; + using System.Linq; + using System.Runtime.CompilerServices; + using System.Threading; + using SysML2.NET.Core.POCO.Kernel.Packages; using SysML2.NET.Core.POCO.Root.Annotations; using SysML2.NET.Core.POCO.Root.Namespaces; @@ -41,10 +45,9 @@ internal static class ElementExtensions /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeDocumentation(this IElement elementSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return [..elementSubject.ownedElement.OfType()]; } /// @@ -56,10 +59,21 @@ internal static List ComputeDocumentation(this IElement elementS /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static bool ComputeIsLibraryElement(this IElement elementSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + var owner = elementSubject.owner; + + while (owner != null) + { + if (owner is ILibraryPackage) + { + return true; + } + + owner = owner.owner; + } + + return false; } /// @@ -86,10 +100,9 @@ internal static string ComputeName(this IElement elementSubject) /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeOwnedAnnotation(this IElement elementSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return [..elementSubject.OwnedRelationship.OfType().Where(x => x.AnnotatedElement == elementSubject)]; } /// @@ -101,10 +114,9 @@ internal static List ComputeOwnedAnnotation(this IElement elementSu /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeOwnedElement(this IElement elementSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return [..elementSubject.OwnedRelationship.SelectMany(x => x.OwnedRelatedElement)]; } /// @@ -116,10 +128,9 @@ internal static List ComputeOwnedElement(this IElement elementSubject) /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IElement ComputeOwner(this IElement elementSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return elementSubject.OwningRelationship?.OwningRelatedElement; } /// @@ -131,10 +142,9 @@ internal static IElement ComputeOwner(this IElement elementSubject) /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IOwningMembership ComputeOwningMembership(this IElement elementSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return elementSubject.OwningRelationship as IOwningMembership; } /// @@ -146,10 +156,9 @@ internal static IOwningMembership ComputeOwningMembership(this IElement elementS /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static INamespace ComputeOwningNamespace(this IElement elementSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return elementSubject.owningMembership?.membershipOwningNamespace; } /// @@ -191,12 +200,16 @@ internal static string ComputeShortName(this IElement elementSubject) /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeTextualRepresentation(this IElement elementSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); - } + if (elementSubject == null) + { + throw new ArgumentNullException(nameof(elementSubject)); + } + return [..elementSubject.ownedElement.OfType()]; + } + /// /// Return name, if that is not null, otherwise the shortName, if that is not null, otherwise null. If /// the returned value is non-null, it is returned as-is if it has the form of a basic name, or, From 46e29a91aa39637e5eb53fd934e3a7df53d6002d Mon Sep 17 00:00:00 2001 From: atheate Date: Mon, 30 Mar 2026 10:19:47 +0200 Subject: [PATCH 2/5] [WIP] missing correct multiplicity on operation --- .../Extend/ElementExtensionsTestFixture.cs | 184 +++++++++++++++--- SysML2.NET/Extend/ElementExtensions.cs | 130 +++++++++---- SysML2.NET/Extend/MembershipExtensions.cs | 2 +- SysML2.NET/Extend/NamespaceExtensions.cs | 9 +- .../Extend/OwningMembershipExtensions.cs | 3 +- SysML2.NET/Extensions/StringExtensions.cs | 80 ++++++++ .../SymbolicKeywordKindExtensions.cs | 72 +++++++ SysML2.NET/LexicalRules/Keywords.cs | 47 +++++ .../LexicalRules/SymbolicKeywordKind.cs | 58 ++++++ SysML2.NET/SysML2.NET.csproj | 1 + 10 files changed, 513 insertions(+), 73 deletions(-) create mode 100644 SysML2.NET/Extensions/StringExtensions.cs create mode 100644 SysML2.NET/Extensions/SymbolicKeywordKindExtensions.cs create mode 100644 SysML2.NET/LexicalRules/Keywords.cs create mode 100644 SysML2.NET/LexicalRules/SymbolicKeywordKind.cs diff --git a/SysML2.NET.Tests/Extend/ElementExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/ElementExtensionsTestFixture.cs index dcfd0bfa..0734fe25 100644 --- a/SysML2.NET.Tests/Extend/ElementExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/ElementExtensionsTestFixture.cs @@ -21,78 +21,214 @@ namespace SysML2.NET.Tests.Extend { using System; - + using System.Linq; + using NUnit.Framework; - + + using SysML2.NET.Core.POCO.Root.Annotations; using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Root.Namespaces; + using SysML2.NET.Core.POCO.Systems.DefinitionAndUsage; + using SysML2.NET.Extensions; [TestFixture] public class ElementExtensionsTestFixture { [Test] - public void ComputeDocumentation_ThrowsNotSupportedException() + public void VerifyComputeDocumentation() { - Assert.That(() => ((IElement)null).ComputeDocumentation(), Throws.TypeOf()); + Assert.That(() => ((IElement)null).ComputeDocumentation(), Throws.TypeOf()); + + var element = new Definition(); + var documentation = new Documentation(); + var annotation = new Annotation(); + + Assert.That(() => element.ComputeDocumentation(), Has.Count.EqualTo(0)); + + element.AssignOwnership(annotation, documentation); + + Assert.That(element.ComputeDocumentation(), Is.EquivalentTo([documentation])); } [Test] - public void ComputeIsLibraryElement_ThrowsNotSupportedException() + public void VerifyComputeIsLibraryElement() { - Assert.That(() => ((IElement)null).ComputeIsLibraryElement(), Throws.TypeOf()); + Assert.That(() => ((IElement)null).ComputeIsLibraryElement(), Throws.TypeOf()); + + var element = new Definition(); + var documentation = new Documentation(); + var annotation = new Annotation(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(() => element.ComputeIsLibraryElement(), Is.False); + Assert.That(() => documentation.ComputeIsLibraryElement(), Is.False); + } + + element.AssignOwnership(annotation, documentation); + + using (Assert.EnterMultipleScope()) + { + Assert.That(() =>element.ComputeIsLibraryElement(), Is.False); + Assert.That(() => documentation.ComputeIsLibraryElement(), Throws.TypeOf()); + } } [Test] - public void ComputeName_ThrowsNotSupportedException() + public void VerifyComputeName() { - Assert.That(() => ((IElement)null).ComputeName(), Throws.TypeOf()); + Assert.That(() => ((IElement)null).ComputeName(), Throws.TypeOf()); + + var element = new Definition(); + + Assert.That(() => element.ComputeName(), Is.Null); + + element.DeclaredName = "definitionName"; + Assert.That(() => element.ComputeName(), Is.EqualTo("definitionName")); } [Test] - public void ComputeOwnedAnnotation_ThrowsNotSupportedException() + public void VerifyComputeOwnedAnnotation() { - Assert.That(() => ((IElement)null).ComputeOwnedAnnotation(), Throws.TypeOf()); + Assert.That(() => ((IElement)null).ComputeOwnedAnnotation(), Throws.TypeOf()); + + var element = new Definition(); + var documentation = new Documentation(); + var annotation = new Annotation(); + element.AssignOwnership(annotation, documentation); + Assert.That(() => element.ComputeOwnedAnnotation(), Has.Count.EqualTo(0)); + + annotation.AnnotatedElement = element; + Assert.That(() => element.ComputeOwnedAnnotation(), Is.EquivalentTo([annotation])); } [Test] - public void ComputeOwnedElement_ThrowsNotSupportedException() + public void VerifyComputeOwnedElement() { - Assert.That(() => ((IElement)null).ComputeOwnedElement(), Throws.TypeOf()); + Assert.That(() => ((IElement)null).ComputeOwnedElement(), Throws.TypeOf()); + + var element = new Definition(); + var documentation = new Documentation(); + var annotation = new Annotation(); + Assert.That(() => element.ComputeOwnedElement(), Has.Count.EqualTo(0)); + + element.AssignOwnership(annotation, documentation); + Assert.That(() => element.ComputeDocumentation(), Is.EquivalentTo([documentation])); } [Test] - public void ComputeOwner_ThrowsNotSupportedException() + public void VerifyComputeOwner() { - Assert.That(() => ((IElement)null).ComputeOwner(), Throws.TypeOf()); + Assert.That(() => ((IElement)null).ComputeOwner(), Throws.TypeOf()); + + var element = new Definition(); + var documentation = new Documentation(); + var annotation = new Annotation(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(() => element.ComputeOwner(), Is.Null); + Assert.That(() => documentation.ComputeOwner(), Is.Null); + } + + element.AssignOwnership(annotation, documentation); + + using (Assert.EnterMultipleScope()) + { + Assert.That(() => element.ComputeOwner(), Is.Null); + Assert.That(() => documentation.ComputeOwner(), Is.EqualTo(element)); + } } [Test] - public void ComputeOwningMembership_ThrowsNotSupportedException() + public void VerifyComputeOwningMembership() { - Assert.That(() => ((IElement)null).ComputeOwningMembership(), Throws.TypeOf()); + Assert.That(() => ((IElement)null).ComputeOwningMembership(), Throws.TypeOf()); + + var element = new Definition(); + var documentation = new Documentation(); + var annotation = new Annotation(); + + Assert.That(() => documentation.ComputeOwningMembership(), Is.Null); + + element.AssignOwnership(annotation, documentation); + + using (Assert.EnterMultipleScope()) + { + Assert.That(() => documentation.ComputeOwningMembership(), Is.Null); + Assert.That(() => element.ComputeOwningMembership(), Is.Null); + } + + var membership = new OwningMembership(); + var namespaceElement = new Namespace(); + + namespaceElement.AssignOwnership(membership, element); + Assert.That(() => element.ComputeOwningMembership(), Is.EqualTo(membership)); } [Test] - public void ComputeOwningNamespace_ThrowsNotSupportedException() + public void VerifyComputeOwningNamespace() { - Assert.That(() => ((IElement)null).ComputeOwningNamespace(), Throws.TypeOf()); + Assert.That(() => ((IElement)null).ComputeOwningNamespace(), Throws.TypeOf()); + + var element = new Definition(); + var documentation = new Documentation(); + var annotation = new Annotation(); + element.AssignOwnership(annotation, documentation); + + Assert.That(() => documentation.ComputeOwningNamespace(), Is.Null); + var membership = new OwningMembership(); + var namespaceElement = new Namespace(); + + namespaceElement.AssignOwnership(membership, element); + Assert.That(() => element.ComputeOwningNamespace(), Is.EqualTo(namespaceElement)); } [Test] - public void ComputeQualifiedName_ThrowsNotSupportedException() + public void VerifyComputeQualifiedName() { - Assert.That(() => ((IElement)null).ComputeQualifiedName(), Throws.TypeOf()); + Assert.That(() => ((IElement)null).ComputeQualifiedName(), Throws.TypeOf()); + + var element = new Definition(); + + Assert.That(() => element.ComputeQualifiedName(), Is.Null); + + var membership = new OwningMembership(); + var namespaceElement = new Namespace(); + + namespaceElement.AssignOwnership(membership, element); + element.DeclaredName = "name"; + + Assert.That(() => element.ComputeQualifiedName(), Is.EqualTo("name")); } [Test] - public void ComputeShortName_ThrowsNotSupportedException() + public void VerifyComputeShortName() { - Assert.That(() => ((IElement)null).ComputeShortName(), Throws.TypeOf()); + Assert.That(() => ((IElement)null).ComputeShortName(), Throws.TypeOf()); + + var element = new Definition(); + + Assert.That(() => element.ComputeShortName(), Is.Null); + + element.DeclaredShortName = "shortName"; + Assert.That(() => element.ComputeShortName(), Is.EqualTo("shortName")); } [Test] - public void ComputeTextualRepresentation_ThrowsNotSupportedException() + public void VerifyComputeTextualRepresentation() { - Assert.That(() => ((IElement)null).ComputeTextualRepresentation(), Throws.TypeOf()); + Assert.That(() => ((IElement)null).ComputeTextualRepresentation(), Throws.TypeOf()); + + var element = new Definition(); + var textualRepresentation = new TextualRepresentation(); + var annotation = new Annotation(); + + Assert.That(() => element.ComputeTextualRepresentation(), Has.Count.EqualTo(0)); + + element.AssignOwnership(annotation, textualRepresentation); + + Assert.That(element.ComputeTextualRepresentation(), Is.EquivalentTo([textualRepresentation])); } } } diff --git a/SysML2.NET/Extend/ElementExtensions.cs b/SysML2.NET/Extend/ElementExtensions.cs index 882ee464..0b45b350 100644 --- a/SysML2.NET/Extend/ElementExtensions.cs +++ b/SysML2.NET/Extend/ElementExtensions.cs @@ -23,12 +23,10 @@ namespace SysML2.NET.Core.POCO.Root.Elements using System; using System.Collections.Generic; using System.Linq; - using System.Runtime.CompilerServices; - using System.Threading; - using SysML2.NET.Core.POCO.Kernel.Packages; using SysML2.NET.Core.POCO.Root.Annotations; using SysML2.NET.Core.POCO.Root.Namespaces; + using SysML2.NET.Extensions; /// /// The class provides extensions methods for @@ -47,7 +45,7 @@ internal static class ElementExtensions /// internal static List ComputeDocumentation(this IElement elementSubject) { - return [..elementSubject.ownedElement.OfType()]; + return elementSubject == null ? throw new ArgumentNullException(nameof(elementSubject)) : [..elementSubject.ownedElement.OfType()]; } /// @@ -61,19 +59,7 @@ internal static List ComputeDocumentation(this IElement elementS /// internal static bool ComputeIsLibraryElement(this IElement elementSubject) { - var owner = elementSubject.owner; - - while (owner != null) - { - if (owner is ILibraryPackage) - { - return true; - } - - owner = owner.owner; - } - - return false; + return elementSubject == null ? throw new ArgumentNullException(nameof(elementSubject)) : elementSubject.LibraryNamespace() != null; } /// @@ -85,10 +71,9 @@ internal static bool ComputeIsLibraryElement(this IElement elementSubject) /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static string ComputeName(this IElement elementSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return elementSubject == null ? throw new ArgumentNullException(nameof(elementSubject)) : elementSubject.EffectiveName(); } /// @@ -102,7 +87,7 @@ internal static string ComputeName(this IElement elementSubject) /// internal static List ComputeOwnedAnnotation(this IElement elementSubject) { - return [..elementSubject.OwnedRelationship.OfType().Where(x => x.AnnotatedElement == elementSubject)]; + return elementSubject == null ? throw new ArgumentNullException(nameof(elementSubject)) : [..elementSubject.OwnedRelationship.OfType().Where(x => x.AnnotatedElement == elementSubject)]; } /// @@ -116,7 +101,7 @@ internal static List ComputeOwnedAnnotation(this IElement elementSu /// internal static List ComputeOwnedElement(this IElement elementSubject) { - return [..elementSubject.OwnedRelationship.SelectMany(x => x.OwnedRelatedElement)]; + return elementSubject == null ? throw new ArgumentNullException(nameof(elementSubject)) : [..elementSubject.OwnedRelationship.SelectMany(x => x.OwnedRelatedElement)]; } /// @@ -130,7 +115,7 @@ internal static List ComputeOwnedElement(this IElement elementSubject) /// internal static IElement ComputeOwner(this IElement elementSubject) { - return elementSubject.OwningRelationship?.OwningRelatedElement; + return elementSubject == null ? throw new ArgumentNullException(nameof(elementSubject)) :elementSubject.OwningRelationship?.OwningRelatedElement; } /// @@ -144,7 +129,7 @@ internal static IElement ComputeOwner(this IElement elementSubject) /// internal static IOwningMembership ComputeOwningMembership(this IElement elementSubject) { - return elementSubject.OwningRelationship as IOwningMembership; + return elementSubject == null ? throw new ArgumentNullException(nameof(elementSubject)) : elementSubject.OwningRelationship as IOwningMembership; } /// @@ -158,7 +143,7 @@ internal static IOwningMembership ComputeOwningMembership(this IElement elementS /// internal static INamespace ComputeOwningNamespace(this IElement elementSubject) { - return elementSubject.owningMembership?.membershipOwningNamespace; + return elementSubject == null ? throw new ArgumentNullException(nameof(elementSubject)) : elementSubject.owningMembership?.membershipOwningNamespace; } /// @@ -170,10 +155,42 @@ internal static INamespace ComputeOwningNamespace(this IElement elementSubject) /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static string ComputeQualifiedName(this IElement elementSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (elementSubject == null) + { + throw new ArgumentNullException(nameof(elementSubject)); + } + + if (elementSubject.owningNamespace == null) + { + return null; + } + + if (elementSubject.name != null) + { + var membersWithTheName = elementSubject.owningNamespace.member.Where(x => x.name == elementSubject.name).ToList(); + + if (membersWithTheName.IndexOf(elementSubject) != 0) + { + return null; + } + } + + if (elementSubject.owningNamespace.owner == null) + { + return elementSubject.EscapedName(); + } + + var parentQualifiedName = elementSubject.owningNamespace.qualifiedName; + var currentEscaped = elementSubject.EscapedName(); + + if (string.IsNullOrWhiteSpace(parentQualifiedName) || string.IsNullOrWhiteSpace(currentEscaped)) + { + return null; + } + + return $"{parentQualifiedName}::{currentEscaped}"; } /// @@ -188,7 +205,7 @@ internal static string ComputeQualifiedName(this IElement elementSubject) [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static string ComputeShortName(this IElement elementSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return elementSubject == null ? throw new ArgumentNullException(nameof(elementSubject)) : elementSubject.EffectiveShortName(); } /// @@ -202,12 +219,7 @@ internal static string ComputeShortName(this IElement elementSubject) /// internal static List ComputeTextualRepresentation(this IElement elementSubject) { - if (elementSubject == null) - { - throw new ArgumentNullException(nameof(elementSubject)); - } - - return [..elementSubject.ownedElement.OfType()]; + return elementSubject == null ? throw new ArgumentNullException(nameof(elementSubject)) : [..elementSubject.ownedElement.OfType()]; } /// @@ -222,10 +234,26 @@ internal static List ComputeTextualRepresentation(this I /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static string ComputeEscapedNameOperation(this IElement elementSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (elementSubject == null) + { + throw new ArgumentNullException(nameof(elementSubject)); + } + + var targetName = elementSubject.name; + + if (string.IsNullOrWhiteSpace(targetName)) + { + targetName = elementSubject.shortName; + } + + if (string.IsNullOrWhiteSpace(targetName)) + { + return null; + } + + return targetName.QueryIsBasicName() ? targetName : targetName.ToUnrestrictedName(); } /// @@ -238,10 +266,9 @@ internal static string ComputeEscapedNameOperation(this IElement elementSubject) /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static string ComputeEffectiveShortNameOperation(this IElement elementSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return elementSubject == null ? throw new ArgumentNullException(nameof(elementSubject)) : elementSubject.DeclaredShortName; } /// @@ -253,10 +280,9 @@ internal static string ComputeEffectiveShortNameOperation(this IElement elementS /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static string ComputeEffectiveNameOperation(this IElement elementSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return elementSubject == null ? throw new ArgumentNullException(nameof(elementSubject)) : elementSubject.DeclaredName; } /// @@ -268,10 +294,9 @@ internal static string ComputeEffectiveNameOperation(this IElement elementSubjec /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static INamespace ComputeLibraryNamespaceOperation(this IElement elementSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return elementSubject == null ? throw new ArgumentNullException(nameof(elementSubject)) : elementSubject.OwningRelationship?.LibraryNamespace(); } /// @@ -289,10 +314,29 @@ internal static INamespace ComputeLibraryNamespaceOperation(this IElement elemen /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static string ComputePathOperation(this IElement elementSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (elementSubject == null) + { + throw new ArgumentNullException(nameof(elementSubject)); + } + + var qualifiedName = elementSubject.qualifiedName; + + if (!string.IsNullOrWhiteSpace(qualifiedName)) + { + return qualifiedName; + } + + if (elementSubject.OwningRelationship == null) + { + return string.Empty; + } + + var ownedRelatedElementsIndex = elementSubject.OwningRelationship.OwnedRelatedElement.ToList().IndexOf(elementSubject) +1; + var parentPath = elementSubject.OwningRelationship.Path(); + + return string.IsNullOrWhiteSpace(parentPath) ? $"/{ownedRelatedElementsIndex}": $"{parentPath}/{ownedRelatedElementsIndex}"; } } } diff --git a/SysML2.NET/Extend/MembershipExtensions.cs b/SysML2.NET/Extend/MembershipExtensions.cs index a565e8a3..c5eec4c3 100644 --- a/SysML2.NET/Extend/MembershipExtensions.cs +++ b/SysML2.NET/Extend/MembershipExtensions.cs @@ -60,7 +60,7 @@ internal static string ComputeMemberElementId(this IMembership membershipSubject [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static INamespace ComputeMembershipOwningNamespace(this IMembership membershipSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return membershipSubject == null ? throw new ArgumentNullException() : membershipSubject.OwningRelatedElement as INamespace; } /// diff --git a/SysML2.NET/Extend/NamespaceExtensions.cs b/SysML2.NET/Extend/NamespaceExtensions.cs index adbcb160..5284b4b6 100644 --- a/SysML2.NET/Extend/NamespaceExtensions.cs +++ b/SysML2.NET/Extend/NamespaceExtensions.cs @@ -22,6 +22,7 @@ namespace SysML2.NET.Core.POCO.Root.Namespaces { using System; using System.Collections.Generic; + using System.Linq; using SysML2.NET.Core.Root.Namespaces; using SysML2.NET.Core.POCO.Root.Annotations; @@ -45,7 +46,7 @@ internal static class NamespaceExtensions [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeImportedMembership(this INamespace namespaceSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return namespaceSubject == null ? throw new ArgumentNullException(nameof(namespaceSubject)) : namespaceSubject.ImportedMemberships((INamespace)null); } /// @@ -60,7 +61,7 @@ internal static List ComputeImportedMembership(this INamespace name [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeMember(this INamespace namespaceSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return namespaceSubject == null ? throw new ArgumentNullException(nameof(namespaceSubject)) : [..namespaceSubject.membership.Select(x => x.MemberElement)]; } /// @@ -75,7 +76,7 @@ internal static List ComputeMember(this INamespace namespaceSubject) [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeMembership(this INamespace namespaceSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return namespaceSubject == null ? throw new ArgumentNullException(nameof(namespaceSubject)) : [..namespaceSubject.ownedMembership.Union(namespaceSubject.importedMembership)]; } /// @@ -120,7 +121,7 @@ internal static List ComputeOwnedMember(this INamespace namespaceSubje [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeOwnedMembership(this INamespace namespaceSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return namespaceSubject == null ? throw new ArgumentNullException(nameof(namespaceSubject)) : [..namespaceSubject.OwnedRelationship.OfType()]; } /// diff --git a/SysML2.NET/Extend/OwningMembershipExtensions.cs b/SysML2.NET/Extend/OwningMembershipExtensions.cs index e9adbc72..7ff31a93 100644 --- a/SysML2.NET/Extend/OwningMembershipExtensions.cs +++ b/SysML2.NET/Extend/OwningMembershipExtensions.cs @@ -22,6 +22,7 @@ namespace SysML2.NET.Core.POCO.Root.Namespaces { using System; using System.Collections.Generic; + using System.Linq; using SysML2.NET.Core.Root.Namespaces; using SysML2.NET.Core.POCO.Root.Annotations; @@ -45,7 +46,7 @@ internal static class OwningMembershipExtensions [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IElement ComputeOwnedMemberElement(this IOwningMembership owningMembershipSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return owningMembershipSubject == null ? throw new ArgumentNullException(nameof(owningMembershipSubject)) : owningMembershipSubject.OwnedRelatedElement.SingleOrDefault(); } /// diff --git a/SysML2.NET/Extensions/StringExtensions.cs b/SysML2.NET/Extensions/StringExtensions.cs new file mode 100644 index 00000000..6e543f05 --- /dev/null +++ b/SysML2.NET/Extensions/StringExtensions.cs @@ -0,0 +1,80 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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.Extensions +{ + using System.Text; + using System.Text.RegularExpressions; + + using SysML2.NET.LexicalRules; + + /// + /// Provides extensions methods for the class + /// + public static class StringExtensions + { + /// + /// Verifies that a name can be qualified as basic name. cfr. Section 7.2.2 of the SysML 2 Specification for the full definition + /// + /// The name to verifies + /// true if the name is a basic name, false otherwise + public static bool QueryIsBasicName(this string name) + { + return Regex.IsMatch(name, "^[a-zA-Z_][a-zA-Z0-9_]*$") && !Keywords.ReservedKeywords.Contains(name); + } + + /// + /// Converts a non-basic name to an unrestricted name. cfr. Section 8.2.2.3 of KerML specification + /// + /// A non-basic name + /// The unrestricted name + public static string ToUnrestrictedName(this string name) + { + if (string.IsNullOrEmpty(name)) + { + return "''"; + } + + var stringBuilder = new StringBuilder(); + + stringBuilder.Append("'"); + + foreach (var character in name) + { + switch (character) + { + case '\'': stringBuilder.Append(@"\'"); break; + case '\"': stringBuilder.Append(@"\"""); break; + case '\b': stringBuilder.Append(@"\b"); break; + case '\f': stringBuilder.Append(@"\f"); break; + case '\t': stringBuilder.Append(@"\t"); break; + case '\n': stringBuilder.Append(@"\n"); break; + case '\\': stringBuilder.Append(@"\\"); break; + default: + stringBuilder.Append(character); + break; + } + } + + stringBuilder.Append("'"); + return stringBuilder.ToString(); + } + } +} diff --git a/SysML2.NET/Extensions/SymbolicKeywordKindExtensions.cs b/SysML2.NET/Extensions/SymbolicKeywordKindExtensions.cs new file mode 100644 index 00000000..484c093d --- /dev/null +++ b/SysML2.NET/Extensions/SymbolicKeywordKindExtensions.cs @@ -0,0 +1,72 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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.Extensions +{ + using System; + + using SysML2.NET.LexicalRules; + + /// + /// Provides extensions methods for the enumeration + /// + public static class SymbolicKeywordKindExtensions + { + /// + /// Gets the associated keyword of a + /// + /// The instance + /// The associated keyword + /// If the provided is not recognized + public static string GetKeyword(this SymbolicKeywordKind symbolicKeywordKind) + { + return symbolicKeywordKind switch + { + SymbolicKeywordKind.DefinedBy => "defined by", + SymbolicKeywordKind.Specializes => "specializes", + SymbolicKeywordKind.Subsets => "subsets", + SymbolicKeywordKind.References => "references", + SymbolicKeywordKind.Crosses => "crosses", + SymbolicKeywordKind.Redefines => "redefines", + _ => throw new ArgumentOutOfRangeException(nameof(symbolicKeywordKind)) + }; + } + + /// + /// Gets the associated symbol of a + /// + /// The instance + /// The associated symbol + /// If the provided is not recognized + public static string GetSymbol(this SymbolicKeywordKind symbolicKeywordKind) + { + return symbolicKeywordKind switch + { + SymbolicKeywordKind.DefinedBy => ":", + SymbolicKeywordKind.Specializes => ":>", + SymbolicKeywordKind.Subsets => ":>", + SymbolicKeywordKind.References => "::>", + SymbolicKeywordKind.Crosses => "=>", + SymbolicKeywordKind.Redefines => ":>>", + _ => throw new ArgumentOutOfRangeException(nameof(symbolicKeywordKind)) + }; + } + } +} diff --git a/SysML2.NET/LexicalRules/Keywords.cs b/SysML2.NET/LexicalRules/Keywords.cs new file mode 100644 index 00000000..61285e82 --- /dev/null +++ b/SysML2.NET/LexicalRules/Keywords.cs @@ -0,0 +1,47 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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.LexicalRules +{ + using System.Collections.Frozen; + using System.Collections.Generic; + + /// + /// Provides access to specification defined keywords + /// + public static class Keywords + { + /// + /// Provides all specification defined reserved keywords + /// + public static readonly FrozenSet ReservedKeywords = new List + { + "about", "abstract", "accept", "action", "actor", "after", "alias", "all", "allocate", "allocation", "analysis", + "and", "as", "assert", "assign", "assume", "at", "attribute", "bind", "binding", "by", "calc", "case", "comment", "concern", "connect", "connection", "constant", + "constraint", "crosses", "decide", "def", "default", "defined", "dependency", "derived", "do", "doc", "else", "end", "entry", "enum", "event", "exhibit", "exit", + "expose", "false", "filter", "first", "flow", "for", "fork", "frame", "from", "hastype", "if", "implies", "import", "in", "include", "individual", "inout", + "interface", "istype", "item", "join", "language", "library", "locale", "loop", "merge", "message", "meta", "metadata", "nonunique", "not", "null", "objective", + "occurrence", "of", "or", "ordered", "out", "package", "parallel", "part", "perform", "port", "private", "protected", "public", "redefines", "ref", "references", + "render", "rendering", "rep", "require", "requirement", "return", "satisfy", "send", "snapshot", "specializes", "stakeholder", "standard", "state", "subject", + "subsets", "succession", "terminate", "then", "timeslice", "to", "transition", "true", "until", "use", "variant", "variation", "verification", "verify", "via", + "view", "viewpoint", "when", "while", "xor" + }.ToFrozenSet(); + } +} diff --git a/SysML2.NET/LexicalRules/SymbolicKeywordKind.cs b/SysML2.NET/LexicalRules/SymbolicKeywordKind.cs new file mode 100644 index 00000000..f3128d32 --- /dev/null +++ b/SysML2.NET/LexicalRules/SymbolicKeywordKind.cs @@ -0,0 +1,58 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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.LexicalRules +{ + /// + /// Defines keyword that could also have symbolic representation + /// + public enum SymbolicKeywordKind + { + /// + /// Declares the DEFINED_BY case + /// + DefinedBy, + + /// + /// Declares the SPECIALIZES case + /// + Specializes, + + /// + /// Declares the SUBSETS case + /// + Subsets, + + /// + /// Declares the REFERENCES case + /// + References, + + /// + /// Declares the CROSSES case + /// + Crosses, + + /// + /// Declares the REDEFINES case + /// + Redefines + } +} diff --git a/SysML2.NET/SysML2.NET.csproj b/SysML2.NET/SysML2.NET.csproj index 1de788c4..6f03c9a4 100644 --- a/SysML2.NET/SysML2.NET.csproj +++ b/SysML2.NET/SysML2.NET.csproj @@ -27,6 +27,7 @@ + From a7afbf8a8142a05472d2895e52f8b9c9db4beef4 Mon Sep 17 00:00:00 2001 From: atheate Date: Fri, 3 Apr 2026 11:46:37 +0200 Subject: [PATCH 3/5] Fix #121: Implementation of IElement Extensions method, including operation --- .../Extend/ElementExtensionsTestFixture.cs | 132 ++++++++++++++---- .../Extend/MembershipExtensionsTestFixture.cs | 2 +- .../OwningMembershipExtensionsTestFixture.cs | 4 +- SysML2.NET/Extend/ElementExtensions.cs | 2 +- SysML2.NET/Extend/NamespaceExtensions.cs | 30 +++- .../Extend/OwningMembershipExtensions.cs | 6 +- 6 files changed, 136 insertions(+), 40 deletions(-) diff --git a/SysML2.NET.Tests/Extend/ElementExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/ElementExtensionsTestFixture.cs index 0734fe25..00951b68 100644 --- a/SysML2.NET.Tests/Extend/ElementExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/ElementExtensionsTestFixture.cs @@ -21,10 +21,10 @@ namespace SysML2.NET.Tests.Extend { using System; - using System.Linq; using NUnit.Framework; + using SysML2.NET.Core.POCO.Kernel.Packages; using SysML2.NET.Core.POCO.Root.Annotations; using SysML2.NET.Core.POCO.Root.Elements; using SysML2.NET.Core.POCO.Root.Namespaces; @@ -43,7 +43,7 @@ public void VerifyComputeDocumentation() var documentation = new Documentation(); var annotation = new Annotation(); - Assert.That(() => element.ComputeDocumentation(), Has.Count.EqualTo(0)); + Assert.That(element.ComputeDocumentation, Has.Count.EqualTo(0)); element.AssignOwnership(annotation, documentation); @@ -61,16 +61,16 @@ public void VerifyComputeIsLibraryElement() using (Assert.EnterMultipleScope()) { - Assert.That(() => element.ComputeIsLibraryElement(), Is.False); - Assert.That(() => documentation.ComputeIsLibraryElement(), Is.False); + Assert.That(element.ComputeIsLibraryElement, Is.False); + Assert.That(documentation.ComputeIsLibraryElement, Is.False); } element.AssignOwnership(annotation, documentation); using (Assert.EnterMultipleScope()) { - Assert.That(() =>element.ComputeIsLibraryElement(), Is.False); - Assert.That(() => documentation.ComputeIsLibraryElement(), Throws.TypeOf()); + Assert.That(element.ComputeIsLibraryElement, Is.False); + Assert.That(documentation.ComputeIsLibraryElement, Throws.TypeOf()); } } @@ -81,10 +81,10 @@ public void VerifyComputeName() var element = new Definition(); - Assert.That(() => element.ComputeName(), Is.Null); + Assert.That(element.ComputeName, Is.Null); element.DeclaredName = "definitionName"; - Assert.That(() => element.ComputeName(), Is.EqualTo("definitionName")); + Assert.That(element.ComputeName, Is.EqualTo("definitionName")); } [Test] @@ -96,10 +96,10 @@ public void VerifyComputeOwnedAnnotation() var documentation = new Documentation(); var annotation = new Annotation(); element.AssignOwnership(annotation, documentation); - Assert.That(() => element.ComputeOwnedAnnotation(), Has.Count.EqualTo(0)); + Assert.That(element.ComputeOwnedAnnotation, Has.Count.EqualTo(0)); annotation.AnnotatedElement = element; - Assert.That(() => element.ComputeOwnedAnnotation(), Is.EquivalentTo([annotation])); + Assert.That(element.ComputeOwnedAnnotation, Is.EquivalentTo([annotation])); } [Test] @@ -110,10 +110,10 @@ public void VerifyComputeOwnedElement() var element = new Definition(); var documentation = new Documentation(); var annotation = new Annotation(); - Assert.That(() => element.ComputeOwnedElement(), Has.Count.EqualTo(0)); + Assert.That(element.ComputeOwnedElement, Has.Count.EqualTo(0)); element.AssignOwnership(annotation, documentation); - Assert.That(() => element.ComputeDocumentation(), Is.EquivalentTo([documentation])); + Assert.That(element.ComputeDocumentation, Is.EquivalentTo([documentation])); } [Test] @@ -127,16 +127,16 @@ public void VerifyComputeOwner() using (Assert.EnterMultipleScope()) { - Assert.That(() => element.ComputeOwner(), Is.Null); - Assert.That(() => documentation.ComputeOwner(), Is.Null); + Assert.That(element.ComputeOwner, Is.Null); + Assert.That(documentation.ComputeOwner, Is.Null); } element.AssignOwnership(annotation, documentation); using (Assert.EnterMultipleScope()) { - Assert.That(() => element.ComputeOwner(), Is.Null); - Assert.That(() => documentation.ComputeOwner(), Is.EqualTo(element)); + Assert.That(element.ComputeOwner, Is.Null); + Assert.That(documentation.ComputeOwner, Is.EqualTo(element)); } } @@ -149,21 +149,21 @@ public void VerifyComputeOwningMembership() var documentation = new Documentation(); var annotation = new Annotation(); - Assert.That(() => documentation.ComputeOwningMembership(), Is.Null); + Assert.That(documentation.ComputeOwningMembership, Is.Null); element.AssignOwnership(annotation, documentation); using (Assert.EnterMultipleScope()) { - Assert.That(() => documentation.ComputeOwningMembership(), Is.Null); - Assert.That(() => element.ComputeOwningMembership(), Is.Null); + Assert.That(documentation.ComputeOwningMembership, Is.Null); + Assert.That(element.ComputeOwningMembership, Is.Null); } var membership = new OwningMembership(); var namespaceElement = new Namespace(); namespaceElement.AssignOwnership(membership, element); - Assert.That(() => element.ComputeOwningMembership(), Is.EqualTo(membership)); + Assert.That(element.ComputeOwningMembership, Is.EqualTo(membership)); } [Test] @@ -176,12 +176,12 @@ public void VerifyComputeOwningNamespace() var annotation = new Annotation(); element.AssignOwnership(annotation, documentation); - Assert.That(() => documentation.ComputeOwningNamespace(), Is.Null); + Assert.That(documentation.ComputeOwningNamespace, Is.Null); var membership = new OwningMembership(); var namespaceElement = new Namespace(); namespaceElement.AssignOwnership(membership, element); - Assert.That(() => element.ComputeOwningNamespace(), Is.EqualTo(namespaceElement)); + Assert.That(element.ComputeOwningNamespace, Is.EqualTo(namespaceElement)); } [Test] @@ -191,15 +191,43 @@ public void VerifyComputeQualifiedName() var element = new Definition(); - Assert.That(() => element.ComputeQualifiedName(), Is.Null); + Assert.That(element.ComputeQualifiedName, Is.Null); var membership = new OwningMembership(); + var secondMembership = new OwningMembership(); + var namespaceElement = new Namespace(); namespaceElement.AssignOwnership(membership, element); element.DeclaredName = "name"; + + Assert.That(element.ComputeQualifiedName, Is.EqualTo("name")); + + var secondElement = new Definition() + { + DeclaredName = "name" + }; + + namespaceElement.AssignOwnership(secondMembership, secondElement); + + using (Assert.EnterMultipleScope()) + { + Assert.That(element.ComputeQualifiedName, Is.EqualTo("name")); + Assert.That(secondElement.ComputeQualifiedName, Is.Null); + } + + var packageOwner = new Package() + { + DeclaredName = "owner" + }; - Assert.That(() => element.ComputeQualifiedName(), Is.EqualTo("name")); + var packageMembership = new OwningMembership(); + packageOwner.AssignOwnership(packageMembership, namespaceElement); + + Assert.That(element.ComputeQualifiedName, Is.Null); + + namespaceElement.DeclaredName = "namespace"; + Assert.That(element.ComputeQualifiedName, Is.EqualTo("namespace::name")); } [Test] @@ -209,10 +237,10 @@ public void VerifyComputeShortName() var element = new Definition(); - Assert.That(() => element.ComputeShortName(), Is.Null); + Assert.That(element.ComputeShortName, Is.Null); element.DeclaredShortName = "shortName"; - Assert.That(() => element.ComputeShortName(), Is.EqualTo("shortName")); + Assert.That(element.ComputeShortName, Is.EqualTo("shortName")); } [Test] @@ -224,11 +252,61 @@ public void VerifyComputeTextualRepresentation() var textualRepresentation = new TextualRepresentation(); var annotation = new Annotation(); - Assert.That(() => element.ComputeTextualRepresentation(), Has.Count.EqualTo(0)); + Assert.That(element.ComputeTextualRepresentation, Has.Count.EqualTo(0)); element.AssignOwnership(annotation, textualRepresentation); Assert.That(element.ComputeTextualRepresentation(), Is.EquivalentTo([textualRepresentation])); } + + [Test] + public void VerifyComputePathOperation() + { + Assert.That(() => ((IElement)null).ComputePathOperation(), Throws.TypeOf()); + + var element = new Definition(); + + Assert.That(element.ComputePathOperation, Is.Empty); + element.DeclaredName = "name"; + + Assert.That(element.ComputePathOperation, Is.Empty); + + var membership = new OwningMembership(); + var secondMembership = new OwningMembership(); + var namespaceElement = new Namespace(); + + namespaceElement.AssignOwnership(membership, element); + + Assert.That(element.ComputePathOperation, Is.EqualTo("name")); + + var secondElement = new Definition() + { + DeclaredName = "name" + }; + + namespaceElement.AssignOwnership(secondMembership, secondElement); + + using (Assert.EnterMultipleScope()) + { + Assert.That(element.ComputePathOperation, Is.EqualTo("name")); + Assert.That(secondElement.ComputePathOperation, Throws.TypeOf()); + } + } + + [Test] + public void VerifyComputeEscapedNameOperation() + { + Assert.That(() => ((IElement)null).ComputeEscapedNameOperation(), Throws.TypeOf()); + + var element = new Definition() + { + DeclaredName = "basic" + }; + + Assert.That(element.ComputeEscapedNameOperation, Is.EqualTo("basic")); + + element.DeclaredName = "non basic"; + Assert.That(element.ComputeEscapedNameOperation, Is.EqualTo("\'non basic\'")); + } } } diff --git a/SysML2.NET.Tests/Extend/MembershipExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/MembershipExtensionsTestFixture.cs index c10bcf6a..a304b730 100644 --- a/SysML2.NET.Tests/Extend/MembershipExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/MembershipExtensionsTestFixture.cs @@ -38,7 +38,7 @@ public void ComputeMemberElementId_ThrowsNotSupportedException() [Test] public void ComputeMembershipOwningNamespace_ThrowsNotSupportedException() { - Assert.That(() => ((IMembership)null).ComputeMembershipOwningNamespace(), Throws.TypeOf()); + Assert.That(() => ((IMembership)null).ComputeMembershipOwningNamespace(), Throws.TypeOf()); } } } diff --git a/SysML2.NET.Tests/Extend/OwningMembershipExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/OwningMembershipExtensionsTestFixture.cs index 4cc689cb..61b8b449 100644 --- a/SysML2.NET.Tests/Extend/OwningMembershipExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/OwningMembershipExtensionsTestFixture.cs @@ -32,7 +32,7 @@ public class OwningMembershipExtensionsTestFixture [Test] public void ComputeOwnedMemberElement_ThrowsNotSupportedException() { - Assert.That(() => ((IOwningMembership)null).ComputeOwnedMemberElement(), Throws.TypeOf()); + Assert.That(() => ((IOwningMembership)null).ComputeOwnedMemberElement(), Throws.TypeOf()); } [Test] @@ -44,7 +44,7 @@ public void ComputeOwnedMemberElementId_ThrowsNotSupportedException() [Test] public void ComputeOwnedMemberName_ThrowsNotSupportedException() { - Assert.That(() => ((IOwningMembership)null).ComputeOwnedMemberName(), Throws.TypeOf()); + Assert.That(() => ((IOwningMembership)null).ComputeOwnedMemberName(), Throws.TypeOf()); } [Test] diff --git a/SysML2.NET/Extend/ElementExtensions.cs b/SysML2.NET/Extend/ElementExtensions.cs index 0b45b350..bc3acc0b 100644 --- a/SysML2.NET/Extend/ElementExtensions.cs +++ b/SysML2.NET/Extend/ElementExtensions.cs @@ -336,7 +336,7 @@ internal static string ComputePathOperation(this IElement elementSubject) var ownedRelatedElementsIndex = elementSubject.OwningRelationship.OwnedRelatedElement.ToList().IndexOf(elementSubject) +1; var parentPath = elementSubject.OwningRelationship.Path(); - return string.IsNullOrWhiteSpace(parentPath) ? $"/{ownedRelatedElementsIndex}": $"{parentPath}/{ownedRelatedElementsIndex}"; + return $"{parentPath}/{ownedRelatedElementsIndex}"; } } } diff --git a/SysML2.NET/Extend/NamespaceExtensions.cs b/SysML2.NET/Extend/NamespaceExtensions.cs index 5284b4b6..c890c1a8 100644 --- a/SysML2.NET/Extend/NamespaceExtensions.cs +++ b/SysML2.NET/Extend/NamespaceExtensions.cs @@ -21,6 +21,7 @@ namespace SysML2.NET.Core.POCO.Root.Namespaces { using System; + using System.Collections.Frozen; using System.Collections.Generic; using System.Linq; @@ -46,7 +47,7 @@ internal static class NamespaceExtensions [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeImportedMembership(this INamespace namespaceSubject) { - return namespaceSubject == null ? throw new ArgumentNullException(nameof(namespaceSubject)) : namespaceSubject.ImportedMemberships((INamespace)null); + return namespaceSubject == null ? throw new ArgumentNullException(nameof(namespaceSubject)) : namespaceSubject.ImportedMemberships([]); } /// @@ -88,10 +89,9 @@ internal static List ComputeMembership(this INamespace namespaceSub /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeOwnedImport(this INamespace namespaceSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return namespaceSubject == null ? throw new ArgumentNullException(nameof(namespaceSubject)) : [..namespaceSubject.OwnedRelationship.OfType()]; } /// @@ -204,10 +204,30 @@ internal static List ComputeVisibleMembershipsOperation(this INames /// /// The expected collection of /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeImportedMembershipsOperation(this INamespace namespaceSubject, List excluded) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + var importedMemberships = namespaceSubject.ownedImport.Where(x => !excluded.Contains(x.importOwningNamespace)) + .SelectMany(x => x.ImportedMemberships(excluded)) + .Distinct() + .ToList(); + + var ownedMembershipNames = namespaceSubject.ownedMembership + .Select(m => m.MemberName) + .Where(name => name != null) + .ToHashSet(StringComparer.Ordinal); + + var importedMembershipsNameFrequency = importedMemberships + .GroupBy(x => x.MemberName, StringComparer.Ordinal) + .ToFrozenDictionary(g => g.Key, g => g.Count(), StringComparer.Ordinal); + + var nonCollidingImportedMemberships = importedMemberships.Where(x => + { + var name = x.MemberName; + + return !string.IsNullOrWhiteSpace(name) && !ownedMembershipNames.Contains(name) && (!importedMembershipsNameFrequency.TryGetValue(name, out var frequency) || frequency <= 1); + }).ToList(); + + return nonCollidingImportedMemberships; } /// diff --git a/SysML2.NET/Extend/OwningMembershipExtensions.cs b/SysML2.NET/Extend/OwningMembershipExtensions.cs index 7ff31a93..fdd513d0 100644 --- a/SysML2.NET/Extend/OwningMembershipExtensions.cs +++ b/SysML2.NET/Extend/OwningMembershipExtensions.cs @@ -43,10 +43,9 @@ internal static class OwningMembershipExtensions /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IElement ComputeOwnedMemberElement(this IOwningMembership owningMembershipSubject) { - return owningMembershipSubject == null ? throw new ArgumentNullException(nameof(owningMembershipSubject)) : owningMembershipSubject.OwnedRelatedElement.SingleOrDefault(); + return owningMembershipSubject == null ? throw new ArgumentNullException(nameof(owningMembershipSubject)) : owningMembershipSubject.OwnedRelatedElement.Single(); } /// @@ -73,10 +72,9 @@ internal static string ComputeOwnedMemberElementId(this IOwningMembership owning /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static string ComputeOwnedMemberName(this IOwningMembership owningMembershipSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return owningMembershipSubject == null ? throw new ArgumentNullException(nameof(owningMembershipSubject)) : owningMembershipSubject.ownedMemberElement.name; } /// From 3c84d282d6b4274f3f5d858cb1a908813bebd6f4 Mon Sep 17 00:00:00 2001 From: atheate Date: Tue, 7 Apr 2026 10:03:10 +0200 Subject: [PATCH 4/5] Handle null case for the importedMembershipOperation and provided answer about PR's comments --- .../NullableStringEqualityComparer.cs | 65 +++++++++++++++++++ SysML2.NET/Extend/ElementExtensions.cs | 2 +- SysML2.NET/Extend/NamespaceExtensions.cs | 10 +-- SysML2.NET/Extensions/StringExtensions.cs | 2 +- 4 files changed, 72 insertions(+), 7 deletions(-) create mode 100644 SysML2.NET/Comparer/NullableStringEqualityComparer.cs diff --git a/SysML2.NET/Comparer/NullableStringEqualityComparer.cs b/SysML2.NET/Comparer/NullableStringEqualityComparer.cs new file mode 100644 index 00000000..cd904818 --- /dev/null +++ b/SysML2.NET/Comparer/NullableStringEqualityComparer.cs @@ -0,0 +1,65 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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.Comparer +{ + using System; + using System.Collections.Generic; + + /// + /// Provides equality comparison for string, handling null case + /// + public sealed class NullSafeStringComparer : IEqualityComparer + { + /// Determines whether the specified objects are equal. + /// The first object of type to compare. + /// The second object of type to compare. + /// + /// if the specified objects are equal; otherwise, . + public bool Equals(string x, string y) + { + if (x == null && y == null) + { + return true; + } + + if (x == null || y == null) + { + return false; + } + + return string.Equals(x, y, StringComparison.Ordinal); + } + + /// Returns a hash code for the specified object. + /// The for which a hash code is to be returned. + /// A hash code for the specified object. + /// The type of is a reference type and is . + public int GetHashCode(string? obj) + { + return obj?.GetHashCode() ?? 0; + } + + /// + /// Gets the instance of the + /// + public static readonly NullSafeStringComparer Instance = new(); + } +} diff --git a/SysML2.NET/Extend/ElementExtensions.cs b/SysML2.NET/Extend/ElementExtensions.cs index bc3acc0b..1b21d230 100644 --- a/SysML2.NET/Extend/ElementExtensions.cs +++ b/SysML2.NET/Extend/ElementExtensions.cs @@ -253,7 +253,7 @@ internal static string ComputeEscapedNameOperation(this IElement elementSubject) return null; } - return targetName.QueryIsBasicName() ? targetName : targetName.ToUnrestrictedName(); + return targetName.QueryIsValidBasicName() ? targetName : targetName.ToUnrestrictedName(); } /// diff --git a/SysML2.NET/Extend/NamespaceExtensions.cs b/SysML2.NET/Extend/NamespaceExtensions.cs index c890c1a8..90dbc00d 100644 --- a/SysML2.NET/Extend/NamespaceExtensions.cs +++ b/SysML2.NET/Extend/NamespaceExtensions.cs @@ -25,6 +25,7 @@ namespace SysML2.NET.Core.POCO.Root.Namespaces using System.Collections.Generic; using System.Linq; + using SysML2.NET.Comparer; using SysML2.NET.Core.Root.Namespaces; using SysML2.NET.Core.POCO.Root.Annotations; using SysML2.NET.Core.POCO.Root.Elements; @@ -213,18 +214,17 @@ internal static List ComputeImportedMembershipsOperation(this IName var ownedMembershipNames = namespaceSubject.ownedMembership .Select(m => m.MemberName) - .Where(name => name != null) - .ToHashSet(StringComparer.Ordinal); + .ToHashSet(NullSafeStringComparer.Instance); var importedMembershipsNameFrequency = importedMemberships - .GroupBy(x => x.MemberName, StringComparer.Ordinal) - .ToFrozenDictionary(g => g.Key, g => g.Count(), StringComparer.Ordinal); + .GroupBy(x => x.MemberName, NullSafeStringComparer.Instance) + .ToFrozenDictionary(g => g.Key, g => g.Count(), NullSafeStringComparer.Instance); var nonCollidingImportedMemberships = importedMemberships.Where(x => { var name = x.MemberName; - return !string.IsNullOrWhiteSpace(name) && !ownedMembershipNames.Contains(name) && (!importedMembershipsNameFrequency.TryGetValue(name, out var frequency) || frequency <= 1); + return !ownedMembershipNames.Contains(name) && (!importedMembershipsNameFrequency.TryGetValue(name, out var frequency) || frequency <= 1); }).ToList(); return nonCollidingImportedMemberships; diff --git a/SysML2.NET/Extensions/StringExtensions.cs b/SysML2.NET/Extensions/StringExtensions.cs index 6e543f05..4aad8298 100644 --- a/SysML2.NET/Extensions/StringExtensions.cs +++ b/SysML2.NET/Extensions/StringExtensions.cs @@ -35,7 +35,7 @@ public static class StringExtensions /// /// The name to verifies /// true if the name is a basic name, false otherwise - public static bool QueryIsBasicName(this string name) + public static bool QueryIsValidBasicName(this string name) { return Regex.IsMatch(name, "^[a-zA-Z_][a-zA-Z0-9_]*$") && !Keywords.ReservedKeywords.Contains(name); } From 526e8e3e525bd11fda9b38d466d7380517a9cc81 Mon Sep 17 00:00:00 2001 From: atheate Date: Wed, 8 Apr 2026 13:27:42 +0200 Subject: [PATCH 5/5] Added Custom exception to flag a model rule violation --- SysML2.NET/Exceptions/SysML2ModelException.cs | 48 +++++++++++++++++++ .../Extend/OwningMembershipExtensions.cs | 8 +++- 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 SysML2.NET/Exceptions/SysML2ModelException.cs diff --git a/SysML2.NET/Exceptions/SysML2ModelException.cs b/SysML2.NET/Exceptions/SysML2ModelException.cs new file mode 100644 index 00000000..24f390ae --- /dev/null +++ b/SysML2.NET/Exceptions/SysML2ModelException.cs @@ -0,0 +1,48 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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.Exceptions +{ + using System; + + /// + /// The provide custom exception that should be used when a SysML 2 rule violation appears into a model + /// + public class SysMl2ModelException: Exception + { + /// Initializes a new instance of the class. + public SysMl2ModelException() + { + } + + /// Initializes a new instance of the class with a specified error message. + /// The message that describes the error. + public SysMl2ModelException(string message) : base(message) + { + } + + /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference ( in Visual Basic) if no inner exception is specified. + public SysMl2ModelException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/SysML2.NET/Extend/OwningMembershipExtensions.cs b/SysML2.NET/Extend/OwningMembershipExtensions.cs index fdd513d0..7cf09487 100644 --- a/SysML2.NET/Extend/OwningMembershipExtensions.cs +++ b/SysML2.NET/Extend/OwningMembershipExtensions.cs @@ -27,6 +27,7 @@ namespace SysML2.NET.Core.POCO.Root.Namespaces using SysML2.NET.Core.Root.Namespaces; using SysML2.NET.Core.POCO.Root.Annotations; using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Exceptions; /// /// The class provides extensions methods for @@ -45,7 +46,12 @@ internal static class OwningMembershipExtensions /// internal static IElement ComputeOwnedMemberElement(this IOwningMembership owningMembershipSubject) { - return owningMembershipSubject == null ? throw new ArgumentNullException(nameof(owningMembershipSubject)) : owningMembershipSubject.OwnedRelatedElement.Single(); + if (owningMembershipSubject == null) + { + throw new ArgumentNullException(nameof(owningMembershipSubject)); + } + + return owningMembershipSubject.OwnedRelatedElement.Count != 1 ? throw new SysMl2ModelException($"{nameof(owningMembershipSubject)} must have exactly one related element") : owningMembershipSubject.OwnedRelatedElement.Single(); } ///