diff --git a/SysML2.NET.Tests/Extend/StateUsageExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/StateUsageExtensionsTestFixture.cs
index d064123c..02f6c48b 100644
--- a/SysML2.NET.Tests/Extend/StateUsageExtensionsTestFixture.cs
+++ b/SysML2.NET.Tests/Extend/StateUsageExtensionsTestFixture.cs
@@ -1,56 +1,258 @@
// -------------------------------------------------------------------------------------------------
//
-//
+//
// 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.Classifiers;
+ using SysML2.NET.Core.POCO.Core.Features;
+ using SysML2.NET.Core.POCO.Core.Types;
+ using SysML2.NET.Core.POCO.Kernel.Behaviors;
+ using SysML2.NET.Core.POCO.Root.Namespaces;
+ using SysML2.NET.Core.POCO.Systems.Actions;
using SysML2.NET.Core.POCO.Systems.States;
+ using SysML2.NET.Core.Systems.States;
+ using SysML2.NET.Extensions;
[TestFixture]
public class StateUsageExtensionsTestFixture
{
[Test]
- public void ComputeDoAction_ThrowsNotSupportedException()
+ public void VerifyComputeDoAction()
{
- Assert.That(() => ((IStateUsage)null).ComputeDoAction(), Throws.TypeOf());
+ Assert.That(() => ((IStateUsage)null).ComputeDoAction(), Throws.TypeOf());
+
+ // Empty: no StateSubactionMembership at all → null.
+ var emptyStateUsage = new StateUsage();
+
+ Assert.That(emptyStateUsage.ComputeDoAction(), Is.Null);
+
+ // Wrong kind: one StateSubactionMembership of kind Entry only → null (action is never accessed).
+ var stateUsageWithEntry = new StateUsage();
+ var entryAction = new ActionUsage();
+ stateUsageWithEntry.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Entry }, entryAction);
+
+ Assert.That(stateUsageWithEntry.ComputeDoAction(), Is.Null);
+
+ // Matching kind: one StateSubactionMembership of kind Do → NotSupportedException until
+ // StateSubactionMembershipExtensions.ComputeAction stub is resolved.
+ // For Later: depends on StateSubactionMembershipExtensions.ComputeAction stub
+ var stateUsageWithDo = new StateUsage();
+ var doAction = new ActionUsage();
+ stateUsageWithDo.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Do }, doAction);
+
+ Assert.That(() => stateUsageWithDo.ComputeDoAction(), Throws.TypeOf());
+
+ // All three kinds present → first matching Do membership's action is accessed → NotSupportedException.
+ // For Later: depends on StateSubactionMembershipExtensions.ComputeAction stub
+ var stateUsageAllKinds = new StateUsage();
+ var allKindsEntry = new ActionUsage();
+ var allKindsDo = new ActionUsage();
+ var allKindsExit = new ActionUsage();
+ stateUsageAllKinds.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Entry }, allKindsEntry);
+ stateUsageAllKinds.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Do }, allKindsDo);
+ stateUsageAllKinds.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Exit }, allKindsExit);
+
+ Assert.That(() => stateUsageAllKinds.ComputeDoAction(), Throws.TypeOf());
}
-
+
[Test]
- public void ComputeEntryAction_ThrowsNotSupportedException()
+ public void VerifyComputeEntryAction()
{
- Assert.That(() => ((IStateUsage)null).ComputeEntryAction(), Throws.TypeOf());
+ Assert.That(() => ((IStateUsage)null).ComputeEntryAction(), Throws.TypeOf());
+
+ // Empty: no StateSubactionMembership at all → null.
+ var emptyStateUsage = new StateUsage();
+
+ Assert.That(emptyStateUsage.ComputeEntryAction(), Is.Null);
+
+ // Wrong kind: one StateSubactionMembership of kind Do only → null (action is never accessed).
+ var stateUsageWithDo = new StateUsage();
+ var doAction = new ActionUsage();
+ stateUsageWithDo.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Do }, doAction);
+
+ Assert.That(stateUsageWithDo.ComputeEntryAction(), Is.Null);
+
+ // Matching kind: one StateSubactionMembership of kind Entry → NotSupportedException until
+ // StateSubactionMembershipExtensions.ComputeAction stub is resolved.
+ // For Later: depends on StateSubactionMembershipExtensions.ComputeAction stub
+ var stateUsageWithEntry = new StateUsage();
+ var entryAction = new ActionUsage();
+ stateUsageWithEntry.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Entry }, entryAction);
+
+ Assert.That(() => stateUsageWithEntry.ComputeEntryAction(), Throws.TypeOf());
+
+ // All three kinds present → first matching Entry membership's action is accessed → NotSupportedException.
+ // For Later: depends on StateSubactionMembershipExtensions.ComputeAction stub
+ var stateUsageAllKinds = new StateUsage();
+ var allKindsEntry = new ActionUsage();
+ var allKindsDo = new ActionUsage();
+ var allKindsExit = new ActionUsage();
+ stateUsageAllKinds.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Entry }, allKindsEntry);
+ stateUsageAllKinds.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Do }, allKindsDo);
+ stateUsageAllKinds.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Exit }, allKindsExit);
+
+ Assert.That(() => stateUsageAllKinds.ComputeEntryAction(), Throws.TypeOf());
}
-
+
[Test]
- public void ComputeExitAction_ThrowsNotSupportedException()
+ public void VerifyComputeExitAction()
{
- Assert.That(() => ((IStateUsage)null).ComputeExitAction(), Throws.TypeOf());
+ Assert.That(() => ((IStateUsage)null).ComputeExitAction(), Throws.TypeOf());
+
+ // Empty: no StateSubactionMembership at all → null.
+ var emptyStateUsage = new StateUsage();
+
+ Assert.That(emptyStateUsage.ComputeExitAction(), Is.Null);
+
+ // Wrong kind: one StateSubactionMembership of kind Do only → null (action is never accessed).
+ var stateUsageWithDo = new StateUsage();
+ var doAction = new ActionUsage();
+ stateUsageWithDo.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Do }, doAction);
+
+ Assert.That(stateUsageWithDo.ComputeExitAction(), Is.Null);
+
+ // Matching kind: one StateSubactionMembership of kind Exit → NotSupportedException until
+ // StateSubactionMembershipExtensions.ComputeAction stub is resolved.
+ // For Later: depends on StateSubactionMembershipExtensions.ComputeAction stub
+ var stateUsageWithExit = new StateUsage();
+ var exitAction = new ActionUsage();
+ stateUsageWithExit.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Exit }, exitAction);
+
+ Assert.That(() => stateUsageWithExit.ComputeExitAction(), Throws.TypeOf());
+
+ // All three kinds present → first matching Exit membership's action is accessed → NotSupportedException.
+ // For Later: depends on StateSubactionMembershipExtensions.ComputeAction stub
+ var stateUsageAllKinds = new StateUsage();
+ var allKindsEntry = new ActionUsage();
+ var allKindsDo = new ActionUsage();
+ var allKindsExit = new ActionUsage();
+ stateUsageAllKinds.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Entry }, allKindsEntry);
+ stateUsageAllKinds.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Do }, allKindsDo);
+ stateUsageAllKinds.AssignOwnership(new StateSubactionMembership { Kind = StateSubactionKind.Exit }, allKindsExit);
+
+ Assert.That(() => stateUsageAllKinds.ComputeExitAction(), Throws.TypeOf());
}
-
+
+ [Test]
+ public void VerifyComputeStateDefinition()
+ {
+ Assert.That(() => ((IStateUsage)null).ComputeStateDefinition(), Throws.TypeOf());
+
+ // Empty: no FeatureTyping in OwnedRelationship → empty list.
+ var emptyStateUsage = new StateUsage();
+
+ Assert.That(emptyStateUsage.ComputeStateDefinition(), Has.Count.EqualTo(0));
+
+ // One FeatureTyping whose Type is a StateDefinition (which implements IBehavior) → returned.
+ var stateUsageWithStateDefinition = new StateUsage();
+ var stateDefinition = new StateDefinition();
+ stateUsageWithStateDefinition.AssignOwnership(new FeatureTyping { Type = stateDefinition });
+
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(stateUsageWithStateDefinition.ComputeStateDefinition(), Has.Count.EqualTo(1));
+ Assert.That(stateUsageWithStateDefinition.ComputeStateDefinition(), Does.Contain(stateDefinition));
+ }
+
+ // One FeatureTyping whose Type is a plain Behavior (not IStateDefinition) → also returned (spec allows it).
+ var stateUsageWithBehavior = new StateUsage();
+ var plainBehavior = new Behavior();
+ stateUsageWithBehavior.AssignOwnership(new FeatureTyping { Type = plainBehavior });
+
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(stateUsageWithBehavior.ComputeStateDefinition(), Has.Count.EqualTo(1));
+ Assert.That(stateUsageWithBehavior.ComputeStateDefinition(), Does.Contain(plainBehavior));
+ }
+
+ // Mixed FeatureTypings: one IBehavior and one plain Classifier (not an IBehavior) → only the IBehavior returned.
+ var mixedStateUsage = new StateUsage();
+ var mixedBehavior = new StateDefinition();
+ var nonBehaviorType = new Classifier();
+ mixedStateUsage.AssignOwnership(new FeatureTyping { Type = mixedBehavior });
+ mixedStateUsage.AssignOwnership(new FeatureTyping { Type = nonBehaviorType });
+
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(mixedStateUsage.ComputeStateDefinition(), Has.Count.EqualTo(1));
+ Assert.That(mixedStateUsage.ComputeStateDefinition(), Does.Contain(mixedBehavior));
+ Assert.That(mixedStateUsage.ComputeStateDefinition(), Does.Not.Contain(nonBehaviorType));
+ }
+ }
+
[Test]
- public void ComputeStateDefinition_ThrowsNotSupportedException()
+ public void VerifyComputeIsSubstateUsageOperation()
{
- Assert.That(() => ((IStateUsage)null).ComputeStateDefinition(), Throws.TypeOf());
+ Assert.That(() => ((IStateUsage)null).ComputeIsSubstateUsageOperation(false), Throws.TypeOf());
+
+ // IsComposite = false → false regardless of other conditions.
+ var nonCompositeStateUsage = new StateUsage { IsComposite = false };
+
+ Assert.That(nonCompositeStateUsage.ComputeIsSubstateUsageOperation(false), Is.False);
+
+ // IsComposite = true, owningType = null (no owner) → false.
+ var orphanStateUsage = new StateUsage { IsComposite = true };
+
+ Assert.That(orphanStateUsage.ComputeIsSubstateUsageOperation(false), Is.False);
+
+ // owningType is a StateDefinition with IsParallel = true, isParallel = true,
+ // owned via plain FeatureMembership (not StateSubactionMembership) → true.
+ var parentStateDefinition = new StateDefinition { IsParallel = true };
+ var substateUnderDefinition = new StateUsage { IsComposite = true };
+ parentStateDefinition.AssignOwnership(new FeatureMembership(), substateUnderDefinition);
+
+ Assert.That(substateUnderDefinition.ComputeIsSubstateUsageOperation(true), Is.True);
+
+ // owningType is a StateDefinition with IsParallel = false, isParallel = true → false (mismatch).
+ var nonParallelDefinition = new StateDefinition { IsParallel = false };
+ var substateParallelMismatch = new StateUsage { IsComposite = true };
+ nonParallelDefinition.AssignOwnership(new FeatureMembership(), substateParallelMismatch);
+
+ Assert.That(substateParallelMismatch.ComputeIsSubstateUsageOperation(true), Is.False);
+
+ // owningType is a StateUsage with IsParallel = false, isParallel = false,
+ // owned via plain FeatureMembership → true.
+ var parentStateUsage = new StateUsage { IsParallel = false };
+ var substateUnderUsage = new StateUsage { IsComposite = true };
+ parentStateUsage.AssignOwnership(new FeatureMembership(), substateUnderUsage);
+
+ Assert.That(substateUnderUsage.ComputeIsSubstateUsageOperation(false), Is.True);
+
+ // All conditions met but owningFeatureMembership is a StateSubactionMembership → false.
+ var parallelDefinition = new StateDefinition { IsParallel = true };
+ var actionSubstate = new StateUsage { IsComposite = true };
+ var doMembership = new StateSubactionMembership { Kind = StateSubactionKind.Do };
+ parallelDefinition.AssignOwnership(doMembership, actionSubstate);
+
+ Assert.That(actionSubstate.ComputeIsSubstateUsageOperation(true), Is.False);
+
+ // owningType is some other type (not StateDefinition/StateUsage) → false.
+ var otherParent = new ActionDefinition();
+ var substateUnderOtherType = new StateUsage { IsComposite = true };
+ otherParent.AssignOwnership(new FeatureMembership(), substateUnderOtherType);
+
+ Assert.That(substateUnderOtherType.ComputeIsSubstateUsageOperation(false), Is.False);
}
}
}
diff --git a/SysML2.NET/Extend/StateUsageExtensions.cs b/SysML2.NET/Extend/StateUsageExtensions.cs
index 02a71c0f..7c71586a 100644
--- a/SysML2.NET/Extend/StateUsageExtensions.cs
+++ b/SysML2.NET/Extend/StateUsageExtensions.cs
@@ -22,40 +22,12 @@ namespace SysML2.NET.Core.POCO.Systems.States
{
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.Systems.Occurrences;
- using SysML2.NET.Core.POCO.Core.Classifiers;
using SysML2.NET.Core.POCO.Core.Features;
- using SysML2.NET.Core.POCO.Core.Types;
using SysML2.NET.Core.POCO.Kernel.Behaviors;
- using SysML2.NET.Core.POCO.Kernel.Classes;
- using SysML2.NET.Core.POCO.Kernel.Functions;
- 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.Actions;
- using SysML2.NET.Core.POCO.Systems.Allocations;
- using SysML2.NET.Core.POCO.Systems.AnalysisCases;
- using SysML2.NET.Core.POCO.Systems.Attributes;
- using SysML2.NET.Core.POCO.Systems.Calculations;
- using SysML2.NET.Core.POCO.Systems.Cases;
- using SysML2.NET.Core.POCO.Systems.Connections;
- using SysML2.NET.Core.POCO.Systems.Constraints;
- using SysML2.NET.Core.POCO.Systems.DefinitionAndUsage;
- using SysML2.NET.Core.POCO.Systems.Enumerations;
- using SysML2.NET.Core.POCO.Systems.Flows;
- using SysML2.NET.Core.POCO.Systems.Interfaces;
- using SysML2.NET.Core.POCO.Systems.Items;
- using SysML2.NET.Core.POCO.Systems.Metadata;
- using SysML2.NET.Core.POCO.Systems.Occurrences;
- using SysML2.NET.Core.POCO.Systems.Parts;
- using SysML2.NET.Core.POCO.Systems.Ports;
- using SysML2.NET.Core.POCO.Systems.Requirements;
- using SysML2.NET.Core.POCO.Systems.UseCases;
- using SysML2.NET.Core.POCO.Systems.VerificationCases;
- using SysML2.NET.Core.POCO.Systems.Views;
+ using SysML2.NET.Core.Systems.States;
///
/// The class provides extensions methods for
@@ -85,10 +57,17 @@ internal static class StateUsageExtensions
///
/// the computed result
///
- [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
internal static IActionUsage ComputeDoAction(this IStateUsage stateUsageSubject)
{
- throw new NotSupportedException("Create a GitHub issue when this method is required");
+ if (stateUsageSubject == null)
+ {
+ throw new ArgumentNullException(nameof(stateUsageSubject));
+ }
+
+ return stateUsageSubject.ownedMembership
+ .OfType()
+ .FirstOrDefault(membership => membership.Kind == StateSubactionKind.Do)
+ ?.action;
}
///
@@ -113,10 +92,17 @@ internal static IActionUsage ComputeDoAction(this IStateUsage stateUsageSubject)
///
/// the computed result
///
- [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
internal static IActionUsage ComputeEntryAction(this IStateUsage stateUsageSubject)
{
- throw new NotSupportedException("Create a GitHub issue when this method is required");
+ if (stateUsageSubject == null)
+ {
+ throw new ArgumentNullException(nameof(stateUsageSubject));
+ }
+
+ return stateUsageSubject.ownedMembership
+ .OfType()
+ .FirstOrDefault(membership => membership.Kind == StateSubactionKind.Entry)
+ ?.action;
}
///
@@ -141,10 +127,17 @@ internal static IActionUsage ComputeEntryAction(this IStateUsage stateUsageSubje
///
/// the computed result
///
- [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
internal static IActionUsage ComputeExitAction(this IStateUsage stateUsageSubject)
{
- throw new NotSupportedException("Create a GitHub issue when this method is required");
+ if (stateUsageSubject == null)
+ {
+ throw new ArgumentNullException(nameof(stateUsageSubject));
+ }
+
+ return stateUsageSubject.ownedMembership
+ .OfType()
+ .FirstOrDefault(membership => membership.Kind == StateSubactionKind.Exit)
+ ?.action;
}
///
@@ -156,10 +149,15 @@ internal static IActionUsage ComputeExitAction(this IStateUsage stateUsageSubjec
///
/// the computed result
///
- [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
internal static List ComputeStateDefinition(this IStateUsage stateUsageSubject)
{
- throw new NotSupportedException("Create a GitHub issue when this method is required");
+ return stateUsageSubject == null
+ ? throw new ArgumentNullException(nameof(stateUsageSubject))
+ : [
+ .. stateUsageSubject.OwnedRelationship.OfType()
+ .Select(featureTyping => featureTyping.Type)
+ .OfType()
+ ];
}
///
@@ -188,10 +186,18 @@ internal static List ComputeStateDefinition(this IStateUsage stateUsa
///
/// The expected
///
- [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
internal static bool ComputeIsSubstateUsageOperation(this IStateUsage stateUsageSubject, bool isParallel)
{
- throw new NotSupportedException("Create a GitHub issue when this method is required");
+ if (stateUsageSubject == null)
+ {
+ throw new ArgumentNullException(nameof(stateUsageSubject));
+ }
+
+ return stateUsageSubject.IsComposite
+ && stateUsageSubject.owningType != null
+ && (stateUsageSubject.owningType is IStateDefinition stateDefinition && stateDefinition.IsParallel == isParallel
+ || stateUsageSubject.owningType is IStateUsage owningStateUsage && owningStateUsage.IsParallel == isParallel)
+ && stateUsageSubject.owningFeatureMembership is not IStateSubactionMembership;
}
}
}