diff --git a/SysML2.NET.Tests/Extend/ConnectorExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/ConnectorExtensionsTestFixture.cs index 16538453..a254f8ba 100644 --- a/SysML2.NET.Tests/Extend/ConnectorExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/ConnectorExtensionsTestFixture.cs @@ -1,68 +1,310 @@ -// ------------------------------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------- // -// +// // 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.Kernel.Associations; using SysML2.NET.Core.POCO.Kernel.Connectors; + using SysML2.NET.Extensions; + + using PocoType = SysML2.NET.Core.POCO.Core.Types.Type; [TestFixture] public class ConnectorExtensionsTestFixture { [Test] - public void ComputeAssociation_ThrowsNotSupportedException() + public void VerifyComputeAssociation() { - Assert.That(() => ((IConnector)null).ComputeAssociation(), Throws.TypeOf()); + Assert.That(() => ((IConnector)null).ComputeAssociation(), Throws.TypeOf()); + + var connector = new Connector(); + + // Empty: no OwnedRelationship → returns empty list. + Assert.That(connector.ComputeAssociation(), Is.Empty); + + // Negative: FeatureTyping pointing at a non-Association Type → excluded. + var nonAssociationType = new PocoType(); + var typingToNonAssociation = new FeatureTyping { Type = nonAssociationType }; + connector.AssignOwnership(typingToNonAssociation); + + Assert.That(connector.ComputeAssociation(), Is.Empty); + + // Positive: FeatureTyping pointing at an Association → returned. + var association1 = new Association(); + var typingToAssociation1 = new FeatureTyping { Type = association1 }; + connector.AssignOwnership(typingToAssociation1); + + Assert.That(connector.ComputeAssociation(), Is.EqualTo(new[] { association1 })); + + // Populated: second Association also returned, in iteration order. + var association2 = new Association(); + var typingToAssociation2 = new FeatureTyping { Type = association2 }; + connector.AssignOwnership(typingToAssociation2); + + Assert.That(connector.ComputeAssociation(), Is.EqualTo(new[] { association1, association2 })); } - + [Test] - public void ComputeConnectorEnd_ThrowsNotSupportedException() + public void VerifyComputeConnectorEnd() { - Assert.That(() => ((IConnector)null).ComputeConnectorEnd(), Throws.TypeOf()); + Assert.That(() => ((IConnector)null).ComputeConnectorEnd(), Throws.TypeOf()); + + var connector = new Connector(); + + // Empty: no features → empty list. + Assert.That(connector.ComputeConnectorEnd(), Is.Empty); + + // Negative: feature with IsEnd = false → excluded. + var nonEndFeature = new Feature { IsEnd = false }; + var nonEndMembership = new FeatureMembership(); + connector.AssignOwnership(nonEndMembership, nonEndFeature); + + Assert.That(connector.ComputeConnectorEnd(), Is.Empty); + + // Positive: feature with IsEnd = true → returned. + var endFeature1 = new Feature { IsEnd = true }; + var endMembership1 = new FeatureMembership(); + connector.AssignOwnership(endMembership1, endFeature1); + + Assert.That(connector.ComputeConnectorEnd(), Is.EqualTo(new[] { endFeature1 })); + + // Discrimination: second IsEnd=true also returned, IsEnd=false remains excluded. + var endFeature2 = new Feature { IsEnd = true }; + var endMembership2 = new FeatureMembership(); + connector.AssignOwnership(endMembership2, endFeature2); + + Assert.That(connector.ComputeConnectorEnd(), Is.EqualTo(new[] { endFeature1, endFeature2 })); } - + [Test] - public void ComputeDefaultFeaturingType_ThrowsNotSupportedException() + public void VerifyComputeDefaultFeaturingType() { - Assert.That(() => ((IConnector)null).ComputeDefaultFeaturingType(), Throws.TypeOf()); + Assert.That(() => ((IConnector)null).ComputeDefaultFeaturingType(), Throws.TypeOf()); + + var connector = new Connector(); + + // Empty: no relatedFeatures (no connectorEnds) → null. + Assert.That(connector.ComputeDefaultFeaturingType(), Is.Null); + + // Set up helper: creates a connector end Feature with a ReferenceSubsetting pointing at + // a relatedTarget Feature, and adds the end to the connector via FeatureMembership. + // Returns the relatedTarget Feature so callers can wire featuringType on it. + static Feature AddConnectorEnd(Connector owningConnector, Feature relatedTarget) + { + var endFeature = new Feature { IsEnd = true }; + var referenceSubsetting = new ReferenceSubsetting { ReferencedFeature = relatedTarget }; + endFeature.AssignOwnership(referenceSubsetting); + + var membership = new FeatureMembership(); + owningConnector.AssignOwnership(membership, endFeature); + + return endFeature; + } + + // Single relatedFeature with no featuringType → closure = {relatedTarget itself}; + // relatedTarget.IsFeaturedWithin(relatedTarget): featuringType is empty → All() is + // vacuously true → relatedTarget qualifies as a common featuring type. + // nearestCommonFeaturingTypes = {relatedTarget} (nothing dominates it) → returns relatedTarget. + var relatedTarget1 = new Feature(); + AddConnectorEnd(connector, relatedTarget1); + + Assert.That(connector.ComputeDefaultFeaturingType(), Is.SameAs(relatedTarget1)); + + // Two relatedFeatures sharing exactly one common featuringType T: + // Build a fresh connector to avoid state from the previous case. + var connector2 = new Connector(); + var commonType = new PocoType(); + + var sharedTarget1 = new Feature(); + var typeFeaturing1 = new TypeFeaturing { FeatureOfType = sharedTarget1, FeaturingType = commonType }; + sharedTarget1.AssignOwnership(typeFeaturing1); + + var sharedTarget2 = new Feature(); + var typeFeaturing2 = new TypeFeaturing { FeatureOfType = sharedTarget2, FeaturingType = commonType }; + sharedTarget2.AssignOwnership(typeFeaturing2); + + AddConnectorEnd(connector2, sharedTarget1); + AddConnectorEnd(connector2, sharedTarget2); + + Assert.That(connector2.ComputeDefaultFeaturingType(), Is.SameAs(commonType)); + + // Disjoint featuringTypes → no common type → null. + var connector3 = new Connector(); + var typeForFirst = new PocoType(); + var typeForSecond = new PocoType(); + + var disjointTarget1 = new Feature(); + var typeFeaturingDisjoint1 = new TypeFeaturing { FeatureOfType = disjointTarget1, FeaturingType = typeForFirst }; + disjointTarget1.AssignOwnership(typeFeaturingDisjoint1); + + var disjointTarget2 = new Feature(); + var typeFeaturingDisjoint2 = new TypeFeaturing { FeatureOfType = disjointTarget2, FeaturingType = typeForSecond }; + disjointTarget2.AssignOwnership(typeFeaturingDisjoint2); + + AddConnectorEnd(connector3, disjointTarget1); + AddConnectorEnd(connector3, disjointTarget2); + + Assert.That(connector3.ComputeDefaultFeaturingType(), Is.Null); + + // Cycle: featureA.featuringType = [featureB], featureB.featuringType = [featureA]. + // The closure must terminate (cycle-safe BFS); result is deterministic. + var connector4 = new Connector(); + var cycleFeatureA = new Feature(); + var cycleFeatureB = new Feature(); + + var typeFeaturingAtB = new TypeFeaturing { FeatureOfType = cycleFeatureA, FeaturingType = cycleFeatureB }; + cycleFeatureA.AssignOwnership(typeFeaturingAtB); + + var typeFeaturingBtoA = new TypeFeaturing { FeatureOfType = cycleFeatureB, FeaturingType = cycleFeatureA }; + cycleFeatureB.AssignOwnership(typeFeaturingBtoA); + + AddConnectorEnd(connector4, cycleFeatureA); + AddConnectorEnd(connector4, cycleFeatureB); + + // Must not throw or infinite-loop; result may be null or one of the features depending + // on the IsFeaturedWithin evaluation — we just verify it terminates and returns a value. + Assert.That(() => connector4.ComputeDefaultFeaturingType(), Throws.Nothing); } - + [Test] - public void ComputeRelatedFeature_ThrowsNotSupportedException() + public void VerifyComputeRelatedFeature() { - Assert.That(() => ((IConnector)null).ComputeRelatedFeature(), Throws.TypeOf()); + Assert.That(() => ((IConnector)null).ComputeRelatedFeature(), Throws.TypeOf()); + + var connector = new Connector(); + + // Empty: no connectorEnds → empty list. + Assert.That(connector.ComputeRelatedFeature(), Is.Empty); + + // ConnectorEnd with null ownedReferenceSubsetting → filtered out → empty. + var endWithNoSubsetting = new Feature { IsEnd = true }; + var noSubsettingMembership = new FeatureMembership(); + connector.AssignOwnership(noSubsettingMembership, endWithNoSubsetting); + + Assert.That(connector.ComputeRelatedFeature(), Is.Empty); + + // ConnectorEnd with a ReferenceSubsetting whose SubsettedFeature is set → that feature returned. + var targetFeature1 = new Feature(); + var referenceSubsetting1 = new ReferenceSubsetting { ReferencedFeature = targetFeature1 }; + var endWithSubsetting1 = new Feature { IsEnd = true }; + endWithSubsetting1.AssignOwnership(referenceSubsetting1); + + var subsettingMembership1 = new FeatureMembership(); + connector.AssignOwnership(subsettingMembership1, endWithSubsetting1); + + Assert.That(connector.ComputeRelatedFeature(), Is.EqualTo(new[] { targetFeature1 })); + + // Second end with a distinct SubsettedFeature → both returned in connector-end order. + var targetFeature2 = new Feature(); + var referenceSubsetting2 = new ReferenceSubsetting { ReferencedFeature = targetFeature2 }; + var endWithSubsetting2 = new Feature { IsEnd = true }; + endWithSubsetting2.AssignOwnership(referenceSubsetting2); + + var subsettingMembership2 = new FeatureMembership(); + connector.AssignOwnership(subsettingMembership2, endWithSubsetting2); + + Assert.That(connector.ComputeRelatedFeature(), Is.EqualTo(new[] { targetFeature1, targetFeature2 })); } - + [Test] - public void ComputeSourceFeature_ThrowsNotSupportedException() + public void VerifyComputeSourceFeature() { - Assert.That(() => ((IConnector)null).ComputeSourceFeature(), Throws.TypeOf()); + Assert.That(() => ((IConnector)null).ComputeSourceFeature(), Throws.TypeOf()); + + var connector = new Connector(); + + // Empty: no relatedFeatures → null. + Assert.That(connector.ComputeSourceFeature(), Is.Null); + + // Wire one connectorEnd with a ReferencedFeature so relatedFeature is populated. + var targetFeature1 = new Feature(); + var referenceSubsetting1 = new ReferenceSubsetting { ReferencedFeature = targetFeature1 }; + var endFeature1 = new Feature { IsEnd = true }; + endFeature1.AssignOwnership(referenceSubsetting1); + + var membership1 = new FeatureMembership(); + connector.AssignOwnership(membership1, endFeature1); + + // Single relatedFeature → that feature is the source. + Assert.That(connector.ComputeSourceFeature(), Is.SameAs(targetFeature1)); + + // Two relatedFeatures → first is returned (not the second). + var targetFeature2 = new Feature(); + var referenceSubsetting2 = new ReferenceSubsetting { ReferencedFeature = targetFeature2 }; + var endFeature2 = new Feature { IsEnd = true }; + endFeature2.AssignOwnership(referenceSubsetting2); + + var membership2 = new FeatureMembership(); + connector.AssignOwnership(membership2, endFeature2); + + Assert.That(connector.ComputeSourceFeature(), Is.SameAs(targetFeature1)); } - + [Test] - public void ComputeTargetFeature_ThrowsNotSupportedException() + public void VerifyComputeTargetFeature() { - Assert.That(() => ((IConnector)null).ComputeTargetFeature(), Throws.TypeOf()); + Assert.That(() => ((IConnector)null).ComputeTargetFeature(), Throws.TypeOf()); + + var connector = new Connector(); + + // Zero relatedFeatures → empty list (not null). + Assert.That(connector.ComputeTargetFeature(), Is.Empty); + + // One relatedFeature → still empty (size < 2). + var targetFeature1 = new Feature(); + var referenceSubsetting1 = new ReferenceSubsetting { ReferencedFeature = targetFeature1 }; + var endFeature1 = new Feature { IsEnd = true }; + endFeature1.AssignOwnership(referenceSubsetting1); + + var membership1 = new FeatureMembership(); + connector.AssignOwnership(membership1, endFeature1); + + Assert.That(connector.ComputeTargetFeature(), Is.Empty); + + // Two relatedFeatures [target1, target2] → targetFeature = [target2]. + var targetFeature2 = new Feature(); + var referenceSubsetting2 = new ReferenceSubsetting { ReferencedFeature = targetFeature2 }; + var endFeature2 = new Feature { IsEnd = true }; + endFeature2.AssignOwnership(referenceSubsetting2); + + var membership2 = new FeatureMembership(); + connector.AssignOwnership(membership2, endFeature2); + + Assert.That(connector.ComputeTargetFeature(), Is.EqualTo(new[] { targetFeature2 })); + + // Three relatedFeatures [target1, target2, target3] → targetFeature = [target2, target3]. + var targetFeature3 = new Feature(); + var referenceSubsetting3 = new ReferenceSubsetting { ReferencedFeature = targetFeature3 }; + var endFeature3 = new Feature { IsEnd = true }; + endFeature3.AssignOwnership(referenceSubsetting3); + + var membership3 = new FeatureMembership(); + connector.AssignOwnership(membership3, endFeature3); + + Assert.That(connector.ComputeTargetFeature(), Is.EqualTo(new[] { targetFeature2, targetFeature3 })); } } } diff --git a/SysML2.NET.Tests/Extend/TransitionUsageExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/TransitionUsageExtensionsTestFixture.cs index 8b0e1050..c8789e81 100644 --- a/SysML2.NET.Tests/Extend/TransitionUsageExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/TransitionUsageExtensionsTestFixture.cs @@ -35,6 +35,8 @@ namespace SysML2.NET.Tests.Extend using SysML2.NET.Core.Systems.States; using SysML2.NET.Extensions; + using PocoFeature = SysML2.NET.Core.POCO.Core.Features.Feature; + [TestFixture] public class TransitionUsageExtensionsTestFixture { @@ -149,13 +151,80 @@ public void VerifyComputeTarget() // No Succession in ownedMember → succession is null → null. Assert.That(transitionUsage.ComputeTarget(), Is.Null); - // Succession present but targetFeature access hits ConnectorExtensions.ComputeTargetFeature stub. - // For Later: populated path depends on ConnectorExtensions.ComputeTargetFeature - // at SysML2.NET/Extend/ConnectorExtensions.cs:171, which is still a stub. - var succession = new Succession(); - transitionUsage.AssignOwnership(new OwningMembership(), succession); + // Helper: wire a connector end onto the given succession. + // Creates a Feature(IsEnd=true) with a ReferenceSubsetting pointing at relatedTarget, + // wrapped in a FeatureMembership owned by the succession. + static void AddSuccessionEnd(Succession owningSuccession, IFeature relatedTarget) + { + var endFeature = new PocoFeature { IsEnd = true }; + var referenceSubsetting = new ReferenceSubsetting { ReferencedFeature = relatedTarget }; + endFeature.AssignOwnership(referenceSubsetting); + + var featureMembership = new FeatureMembership(); + owningSuccession.AssignOwnership(featureMembership, endFeature); + } + + // Succession present, one connector end → relatedFeature has 1 element → targetFeature is empty + // (GetRange(1, 0)) → firstTargetFeature is null → null. + var successionOneEnd = new Succession(); + transitionUsage.AssignOwnership(new OwningMembership(), successionOneEnd); + AddSuccessionEnd(successionOneEnd, new PocoFeature()); + + Assert.That(transitionUsage.ComputeTarget(), Is.Null); + + // Succession present, two connector ends, second end's relatedTarget is a plain Feature. + // featureTarget of Feature (no chaining) = Feature itself → not IActionUsage → null. + var transitionUsage2 = new TransitionUsage(); + var successionNonAction = new Succession(); + transitionUsage2.AssignOwnership(new OwningMembership(), successionNonAction); + + var nonActionTarget = new PocoFeature(); + AddSuccessionEnd(successionNonAction, new PocoFeature()); + AddSuccessionEnd(successionNonAction, nonActionTarget); + + Assert.That(transitionUsage2.ComputeTarget(), Is.Null); + + // firstTargetFeature.featureTarget == null → null. + // Wire the second end's relatedTarget with a FeatureChaining whose ChainingFeature = null, + // which makes featureTarget derive to null. + var transitionUsage3 = new TransitionUsage(); + var successionNullFeatureTarget = new Succession(); + transitionUsage3.AssignOwnership(new OwningMembership(), successionNullFeatureTarget); + + var relatedTargetWithNullChain = new PocoFeature(); + relatedTargetWithNullChain.AssignOwnership(new FeatureChaining { ChainingFeature = null }); + + AddSuccessionEnd(successionNullFeatureTarget, new PocoFeature()); + AddSuccessionEnd(successionNullFeatureTarget, relatedTargetWithNullChain); + + // relatedTargetWithNullChain.featureTarget = null (last ChainingFeature is null) → null. + Assert.That(transitionUsage3.ComputeTarget(), Is.Null); + + // POSITIVE case: two connector ends, the second end's relatedTarget is an ActionUsage. + // ActionUsage has no chaining → featureTarget = itself = IActionUsage → returned. + var transitionUsage4 = new TransitionUsage(); + var successionPositive = new Succession(); + transitionUsage4.AssignOwnership(new OwningMembership(), successionPositive); + + var actionUsageTarget = new ActionUsage(); + AddSuccessionEnd(successionPositive, new PocoFeature()); + AddSuccessionEnd(successionPositive, actionUsageTarget); + + Assert.That(transitionUsage4.ComputeTarget(), Is.SameAs(actionUsageTarget)); + + // Three connector ends: source, targetEnd2 (ActionUsage), targetEnd3 (plain Feature). + // targetFeature = [targetEnd2, targetEnd3]; firstTargetFeature = targetEnd2 (ActionUsage) + // → featureTarget = itself → returned. + var transitionUsage5 = new TransitionUsage(); + var successionThreeEnds = new Succession(); + transitionUsage5.AssignOwnership(new OwningMembership(), successionThreeEnds); + + var actionUsageTarget5 = new ActionUsage(); + AddSuccessionEnd(successionThreeEnds, new PocoFeature()); + AddSuccessionEnd(successionThreeEnds, actionUsageTarget5); + AddSuccessionEnd(successionThreeEnds, new PocoFeature()); - Assert.That(() => transitionUsage.ComputeTarget(), Throws.TypeOf()); + Assert.That(transitionUsage5.ComputeTarget(), Is.SameAs(actionUsageTarget5)); } [Test] diff --git a/SysML2.NET/Extend/ConnectorExtensions.cs b/SysML2.NET/Extend/ConnectorExtensions.cs index a84872d2..57b9359e 100644 --- a/SysML2.NET/Extend/ConnectorExtensions.cs +++ b/SysML2.NET/Extend/ConnectorExtensions.cs @@ -22,15 +22,11 @@ namespace SysML2.NET.Core.POCO.Kernel.Connectors { using System; using System.Collections.Generic; + using System.Linq; - using SysML2.NET.Core.Core.Types; - using SysML2.NET.Core.Root.Namespaces; using SysML2.NET.Core.POCO.Core.Features; using SysML2.NET.Core.POCO.Core.Types; using SysML2.NET.Core.POCO.Kernel.Associations; - using SysML2.NET.Core.POCO.Root.Annotations; - using SysML2.NET.Core.POCO.Root.Elements; - using SysML2.NET.Core.POCO.Root.Namespaces; /// /// The class provides extensions methods for @@ -47,10 +43,14 @@ internal static class ConnectorExtensions /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeAssociation(this IConnector connectorSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return connectorSubject == null + ? throw new ArgumentNullException(nameof(connectorSubject)) + : [..connectorSubject.OwnedRelationship + .OfType() + .Select(featureTyping => featureTyping.Type) + .OfType()]; } /// @@ -62,10 +62,11 @@ internal static List ComputeAssociation(this IConnector connectorS /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeConnectorEnd(this IConnector connectorSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return connectorSubject == null + ? throw new ArgumentNullException(nameof(connectorSubject)) + : [..connectorSubject.feature.Where(memberFeature => memberFeature.IsEnd)]; } /// @@ -94,10 +95,38 @@ internal static List ComputeConnectorEnd(this IConnector connectorSubj /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IType ComputeDefaultFeaturingType(this IConnector connectorSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (connectorSubject == null) + { + throw new ArgumentNullException(nameof(connectorSubject)); + } + + var relatedFeatures = connectorSubject.relatedFeature; + + if (relatedFeatures.Count == 0) + { + return null; + } + + var fullClosure = ComputeFeaturingTypeClosure(relatedFeatures); + + var commonFeaturingTypes = fullClosure + .Where(candidate => relatedFeatures.All(relatedFeature => relatedFeature.IsFeaturedWithin(candidate))) + .ToList(); + + if (commonFeaturingTypes.Count == 0) + { + return null; + } + + var nearestCommonFeaturingTypes = commonFeaturingTypes + .Where(candidateType => !commonFeaturingTypes.Any(successorType => + successorType != candidateType + && ComputeFeaturingTypeClosure([successorType]).Contains(candidateType))) + .ToList(); + + return nearestCommonFeaturingTypes.Count == 0 ? null : nearestCommonFeaturingTypes[0]; } /// @@ -116,10 +145,14 @@ internal static IType ComputeDefaultFeaturingType(this IConnector connectorSubje /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeRelatedFeature(this IConnector connectorSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return connectorSubject == null + ? throw new ArgumentNullException(nameof(connectorSubject)) + : [..connectorSubject.connectorEnd + .Select(connectorEndFeature => connectorEndFeature.ownedReferenceSubsetting) + .Where(referenceSubsetting => referenceSubsetting != null) + .Select(referenceSubsetting => referenceSubsetting.SubsettedFeature)]; } /// @@ -140,10 +173,16 @@ internal static List ComputeRelatedFeature(this IConnector connectorSu /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IFeature ComputeSourceFeature(this IConnector connectorSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (connectorSubject == null) + { + throw new ArgumentNullException(nameof(connectorSubject)); + } + + var relatedFeatures = connectorSubject.relatedFeature; + + return relatedFeatures.Count == 0 ? null : relatedFeatures[0]; } /// @@ -167,11 +206,61 @@ internal static IFeature ComputeSourceFeature(this IConnector connectorSubject) /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeTargetFeature(this IConnector connectorSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (connectorSubject == null) + { + throw new ArgumentNullException(nameof(connectorSubject)); + } + + var relatedFeatures = connectorSubject.relatedFeature; + + return relatedFeatures.Count < 2 ? [] : relatedFeatures.GetRange(1, relatedFeatures.Count - 1); } + /// + /// Computes the OCL closure(featuringType) over a seed set of nodes. + /// + /// + /// The featuringType navigation is declared on only — pure + /// nodes that are not also are sinks. The walk + /// is breadth-first and uses a visited-set to guarantee + /// termination on cyclic featuringType graphs. The seed elements are included in the + /// result, mirroring OCL closure semantics. + /// + /// + /// The seed collection of elements from which to begin the closure. + /// + /// + /// The closure of the seed under the featuringType navigation, in BFS visit order. + /// + private static List ComputeFeaturingTypeClosure(IEnumerable seed) + { + var visited = new HashSet(); + var result = new List(); + var queue = new Queue(seed); + + while (queue.Count > 0) + { + var current = queue.Dequeue(); + + if (current == null || !visited.Add(current)) + { + continue; + } + + result.Add(current); + + if (current is IFeature currentFeature) + { + foreach (var featuringType in currentFeature.featuringType.Where(featuringType => featuringType != null && !visited.Contains(featuringType))) + { + queue.Enqueue(featuringType); + } + } + } + + return result; + } } }