diff --git a/SysML2.NET.Tests/Extend/FeatureExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/FeatureExtensionsTestFixture.cs index 08b69bb1..2b928133 100644 --- a/SysML2.NET.Tests/Extend/FeatureExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/FeatureExtensionsTestFixture.cs @@ -1,128 +1,832 @@ -// ------------------------------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------- // -// +// // Copyright 2022-2026 Starion Group S.A. -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// +// // // ------------------------------------------------------------------------------------------------ namespace SysML2.NET.Tests.Extend { using System; - + using NUnit.Framework; - + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Core.Types; + using SysML2.NET.Core.POCO.Root.Namespaces; + using SysML2.NET.Extensions; + + using Type = SysML2.NET.Core.POCO.Core.Types.Type; [TestFixture] public class FeatureExtensionsTestFixture { [Test] - public void ComputeChainingFeature_ThrowsNotSupportedException() + public void VerifyComputeOwnedFeatureChaining() { - Assert.That(() => ((IFeature)null).ComputeChainingFeature(), Throws.TypeOf()); + Assert.That(() => ((IFeature)null).ComputeOwnedFeatureChaining(), Throws.TypeOf()); + + var feature = new Feature(); + + Assert.That(feature.ComputeOwnedFeatureChaining(), Has.Count.EqualTo(0)); + + var subsetting = new Subsetting(); + feature.AssignOwnership(subsetting, new Feature()); + + Assert.That(feature.ComputeOwnedFeatureChaining(), Has.Count.EqualTo(0)); + + var chainingTarget1 = new Feature(); + var chaining1 = new FeatureChaining { ChainingFeature = chainingTarget1 }; + feature.AssignOwnership(chaining1, new Feature()); + + var chainingTarget2 = new Feature(); + var chaining2 = new FeatureChaining { ChainingFeature = chainingTarget2 }; + feature.AssignOwnership(chaining2, new Feature()); + + var result = feature.ComputeOwnedFeatureChaining(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(result, Has.Count.EqualTo(2)); + Assert.That(result[0], Is.SameAs(chaining1)); + Assert.That(result[1], Is.SameAs(chaining2)); + } } - + [Test] - public void ComputeCrossFeature_ThrowsNotSupportedException() + public void VerifyComputeOwnedFeatureInverting() { - Assert.That(() => ((IFeature)null).ComputeCrossFeature(), Throws.TypeOf()); + Assert.That(() => ((IFeature)null).ComputeOwnedFeatureInverting(), Throws.TypeOf()); + + var feature = new Feature(); + + Assert.That(feature.ComputeOwnedFeatureInverting(), Has.Count.EqualTo(0)); + + var otherFeature = new Feature(); + var invertingPointingElsewhere = new FeatureInverting { FeatureInverted = otherFeature }; + feature.AssignOwnership(invertingPointingElsewhere, new Feature()); + + Assert.That(feature.ComputeOwnedFeatureInverting(), Has.Count.EqualTo(0)); + + var invertingPointingSelf = new FeatureInverting { FeatureInverted = feature }; + feature.AssignOwnership(invertingPointingSelf, new Feature()); + + var result = feature.ComputeOwnedFeatureInverting(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(result, Has.Count.EqualTo(1)); + Assert.That(result[0], Is.SameAs(invertingPointingSelf)); + } } - + [Test] - public void ComputeEndOwningType_ThrowsNotSupportedException() + public void VerifyComputeOwnedTypeFeaturing() { - Assert.That(() => ((IFeature)null).ComputeEndOwningType(), Throws.TypeOf()); + Assert.That(() => ((IFeature)null).ComputeOwnedTypeFeaturing(), Throws.TypeOf()); + + var feature = new Feature(); + + Assert.That(feature.ComputeOwnedTypeFeaturing(), Has.Count.EqualTo(0)); + + var otherFeature = new Feature(); + var typeFeaturingPointingElsewhere = new TypeFeaturing { FeatureOfType = otherFeature }; + feature.AssignOwnership(typeFeaturingPointingElsewhere, new Feature()); + + Assert.That(feature.ComputeOwnedTypeFeaturing(), Has.Count.EqualTo(0)); + + var featuringType = new Type(); + var typeFeaturingPointingSelf = new TypeFeaturing { FeatureOfType = feature, FeaturingType = featuringType }; + feature.AssignOwnership(typeFeaturingPointingSelf, new Feature()); + + var result = feature.ComputeOwnedTypeFeaturing(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(result, Has.Count.EqualTo(1)); + Assert.That(result[0], Is.SameAs(typeFeaturingPointingSelf)); + } } - + [Test] - public void ComputeFeatureTarget_ThrowsNotSupportedException() + public void VerifyComputeOwnedSubsetting() { - Assert.That(() => ((IFeature)null).ComputeFeatureTarget(), Throws.TypeOf()); + Assert.That(() => ((IFeature)null).ComputeOwnedSubsetting(), Throws.TypeOf()); + + var feature = new Feature(); + + Assert.That(feature.ComputeOwnedSubsetting(), Has.Count.EqualTo(0)); + + var specialization = new Specialization(); + feature.AssignOwnership(specialization, new Feature()); + + Assert.That(feature.ComputeOwnedSubsetting(), Has.Count.EqualTo(0)); + + var subsetting = new Subsetting(); + feature.AssignOwnership(subsetting, new Feature()); + + var redefinition = new Redefinition(); + feature.AssignOwnership(redefinition, new Feature()); + + var referenceSubsetting = new ReferenceSubsetting(); + feature.AssignOwnership(referenceSubsetting, new Feature()); + + var result = feature.ComputeOwnedSubsetting(); + + Assert.That(result, Has.Count.EqualTo(3)); } - + + [Test] + public void VerifyComputeOwnedRedefinition() + { + Assert.That(() => ((IFeature)null).ComputeOwnedRedefinition(), Throws.TypeOf()); + + var feature = new Feature(); + + Assert.That(feature.ComputeOwnedRedefinition(), Has.Count.EqualTo(0)); + + var subsetting = new Subsetting(); + feature.AssignOwnership(subsetting, new Feature()); + + Assert.That(feature.ComputeOwnedRedefinition(), Has.Count.EqualTo(0)); + + var redefinition1 = new Redefinition(); + feature.AssignOwnership(redefinition1, new Feature()); + + var redefinition2 = new Redefinition(); + feature.AssignOwnership(redefinition2, new Feature()); + + var result = feature.ComputeOwnedRedefinition(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(result, Has.Count.EqualTo(2)); + Assert.That(result, Does.Contain(redefinition1)); + Assert.That(result, Does.Contain(redefinition2)); + } + } + + [Test] + public void VerifyComputeOwnedReferenceSubsetting() + { + Assert.That(() => ((IFeature)null).ComputeOwnedReferenceSubsetting(), Throws.TypeOf()); + + var feature = new Feature(); + + Assert.That(feature.ComputeOwnedReferenceSubsetting(), Is.Null); + + var referenceSubsetting = new ReferenceSubsetting(); + feature.AssignOwnership(referenceSubsetting, new Feature()); + + Assert.That(feature.ComputeOwnedReferenceSubsetting(), Is.SameAs(referenceSubsetting)); + } + [Test] - public void ComputeFeaturingType_ThrowsNotSupportedException() + public void VerifyComputeOwnedCrossSubsetting() { - Assert.That(() => ((IFeature)null).ComputeFeaturingType(), Throws.TypeOf()); + Assert.That(() => ((IFeature)null).ComputeOwnedCrossSubsetting(), Throws.TypeOf()); + + var feature = new Feature(); + + Assert.That(feature.ComputeOwnedCrossSubsetting(), Is.Null); + + var crossSubsetting = new CrossSubsetting(); + feature.AssignOwnership(crossSubsetting, new Feature()); + + Assert.That(feature.ComputeOwnedCrossSubsetting(), Is.SameAs(crossSubsetting)); } - + [Test] - public void ComputeOwnedCrossSubsetting_ThrowsNotSupportedException() + public void VerifyComputeOwnedTyping() { - Assert.That(() => ((IFeature)null).ComputeOwnedCrossSubsetting(), Throws.TypeOf()); + Assert.That(() => ((IFeature)null).ComputeOwnedTyping(), Throws.TypeOf()); + + var feature = new Feature(); + + Assert.That(feature.ComputeOwnedTyping(), Has.Count.EqualTo(0)); + + var specialization = new Specialization(); + feature.AssignOwnership(specialization, new Feature()); + + Assert.That(feature.ComputeOwnedTyping(), Has.Count.EqualTo(0)); + + var type1 = new Type(); + var typing1 = new FeatureTyping { Type = type1 }; + feature.AssignOwnership(typing1, new Feature()); + + var type2 = new Type(); + var typing2 = new FeatureTyping { Type = type2 }; + feature.AssignOwnership(typing2, new Feature()); + + var result = feature.ComputeOwnedTyping(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(result, Has.Count.EqualTo(2)); + Assert.That(result[0], Is.SameAs(typing1)); + Assert.That(result[1], Is.SameAs(typing2)); + } } - + [Test] - public void ComputeOwnedFeatureChaining_ThrowsNotSupportedException() + public void VerifyComputeChainingFeature() { - Assert.That(() => ((IFeature)null).ComputeOwnedFeatureChaining(), Throws.TypeOf()); + Assert.That(() => ((IFeature)null).ComputeChainingFeature(), Throws.TypeOf()); + + var feature = new Feature(); + + Assert.That(feature.ComputeChainingFeature(), Has.Count.EqualTo(0)); + + var target1 = new Feature(); + var chaining1 = new FeatureChaining { ChainingFeature = target1 }; + feature.AssignOwnership(chaining1, new Feature()); + + var target2 = new Feature(); + var chaining2 = new FeatureChaining { ChainingFeature = target2 }; + feature.AssignOwnership(chaining2, new Feature()); + + var result = feature.ComputeChainingFeature(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(result, Has.Count.EqualTo(2)); + Assert.That(result[0], Is.SameAs(target1)); + Assert.That(result[1], Is.SameAs(target2)); + } } - + [Test] - public void ComputeOwnedFeatureInverting_ThrowsNotSupportedException() + public void VerifyComputeOwningFeatureMembership() { - Assert.That(() => ((IFeature)null).ComputeOwnedFeatureInverting(), Throws.TypeOf()); + Assert.That(() => ((IFeature)null).ComputeOwningFeatureMembership(), Throws.TypeOf()); + + var feature = new Feature(); + + Assert.That(feature.ComputeOwningFeatureMembership(), Is.Null); + + var ownerWithMembership = new Type(); + var membership = new OwningMembership(); + ownerWithMembership.AssignOwnership(membership, feature); + + Assert.That(feature.ComputeOwningFeatureMembership(), Is.Null); + + var feature2 = new Feature(); + var ownerWithFeatureMembership = new Type(); + var featureMembership = new FeatureMembership(); + ownerWithFeatureMembership.AssignOwnership(featureMembership, feature2); + + Assert.That(feature2.ComputeOwningFeatureMembership(), Is.SameAs(featureMembership)); } - + [Test] - public void ComputeOwnedRedefinition_ThrowsNotSupportedException() + public void VerifyComputeOwningType() { - Assert.That(() => ((IFeature)null).ComputeOwnedRedefinition(), Throws.TypeOf()); + Assert.That(() => ((IFeature)null).ComputeOwningType(), Throws.TypeOf()); + + var feature = new Feature(); + + Assert.That(feature.ComputeOwningType(), Is.Null); + + var ownerNamespace = new Type(); + var membership = new OwningMembership(); + ownerNamespace.AssignOwnership(membership, feature); + + Assert.That(feature.ComputeOwningType(), Is.Null); + + var feature2 = new Feature(); + var ownerType = new Type(); + var featureMembership = new FeatureMembership(); + ownerType.AssignOwnership(featureMembership, feature2); + + Assert.That(feature2.ComputeOwningType(), Is.SameAs(ownerType)); } - + [Test] - public void ComputeOwnedReferenceSubsetting_ThrowsNotSupportedException() + public void VerifyComputeFeatureTarget() { - Assert.That(() => ((IFeature)null).ComputeOwnedReferenceSubsetting(), Throws.TypeOf()); + Assert.That(() => ((IFeature)null).ComputeFeatureTarget(), Throws.TypeOf()); + + var feature = new Feature(); + + Assert.That(feature.ComputeFeatureTarget(), Is.SameAs(feature)); + + var target1 = new Feature(); + var chaining1 = new FeatureChaining { ChainingFeature = target1 }; + feature.AssignOwnership(chaining1, new Feature()); + + var lastTarget = new Feature(); + var chaining2 = new FeatureChaining { ChainingFeature = lastTarget }; + feature.AssignOwnership(chaining2, new Feature()); + + Assert.That(feature.ComputeFeatureTarget(), Is.SameAs(lastTarget)); } - + [Test] - public void ComputeOwnedSubsetting_ThrowsNotSupportedException() + public void VerifyComputeEndOwningType() { - Assert.That(() => ((IFeature)null).ComputeOwnedSubsetting(), Throws.TypeOf()); + Assert.That(() => ((IFeature)null).ComputeEndOwningType(), Throws.TypeOf()); + + var feature = new Feature(); + + Assert.That(feature.ComputeEndOwningType(), Is.Null); + + var feature2 = new Feature(); + var ownerType = new Type(); + var regularFeatureMembership = new FeatureMembership(); + ownerType.AssignOwnership(regularFeatureMembership, feature2); + + Assert.That(feature2.ComputeEndOwningType(), Is.Null); + + var feature3 = new Feature(); + var endOwnerType = new Type(); + var endFeatureMembership = new EndFeatureMembership(); + endOwnerType.AssignOwnership(endFeatureMembership, feature3); + + Assert.That(feature3.ComputeEndOwningType(), Is.SameAs(endOwnerType)); } - + [Test] - public void ComputeOwnedTypeFeaturing_ThrowsNotSupportedException() + public void VerifyComputeCrossFeature() { - Assert.That(() => ((IFeature)null).ComputeOwnedTypeFeaturing(), Throws.TypeOf()); + Assert.That(() => ((IFeature)null).ComputeCrossFeature(), Throws.TypeOf()); + + var feature = new Feature(); + + Assert.That(feature.ComputeCrossFeature(), Is.Null); + + var crossedFeature = new Feature(); + var crossSubsetting = new CrossSubsetting { CrossedFeature = crossedFeature }; + feature.AssignOwnership(crossSubsetting, new Feature()); + + Assert.That(feature.ComputeCrossFeature(), Is.Null); + + var chainingTarget1 = new Feature(); + var chaining1 = new FeatureChaining { ChainingFeature = chainingTarget1 }; + crossedFeature.AssignOwnership(chaining1, new Feature()); + + Assert.That(feature.ComputeCrossFeature(), Is.Null); + + var chainingTarget2 = new Feature(); + var chaining2 = new FeatureChaining { ChainingFeature = chainingTarget2 }; + crossedFeature.AssignOwnership(chaining2, new Feature()); + + Assert.That(feature.ComputeCrossFeature(), Is.SameAs(chainingTarget2)); } - + [Test] - public void ComputeOwnedTyping_ThrowsNotSupportedException() + public void VerifyComputeFeaturingType() { - Assert.That(() => ((IFeature)null).ComputeOwnedTyping(), Throws.TypeOf()); + Assert.That(() => ((IFeature)null).ComputeFeaturingType(), Throws.TypeOf()); + + var feature = new Feature(); + + Assert.That(feature.ComputeFeaturingType(), Has.Count.EqualTo(0)); + + var theType = new Type(); + var typeFeaturing = new TypeFeaturing { FeatureOfType = feature, FeaturingType = theType }; + feature.AssignOwnership(typeFeaturing, new Feature()); + + var result = feature.ComputeFeaturingType(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(result, Has.Count.EqualTo(1)); + Assert.That(result[0], Is.SameAs(theType)); + } + + var chainingTarget = new Feature(); + var chainingTargetType = new Type(); + var chainingTypeFeaturing = new TypeFeaturing { FeatureOfType = chainingTarget, FeaturingType = chainingTargetType }; + chainingTarget.AssignOwnership(chainingTypeFeaturing, new Feature()); + + var chaining = new FeatureChaining { ChainingFeature = chainingTarget }; + feature.AssignOwnership(chaining, new Feature()); + + var resultWithChaining = feature.ComputeFeaturingType(); + + Assert.That(resultWithChaining, Has.Count.EqualTo(2)); } - + [Test] - public void ComputeOwningFeatureMembership_ThrowsNotSupportedException() + public void VerifyComputeType() { - Assert.That(() => ((IFeature)null).ComputeOwningFeatureMembership(), Throws.TypeOf()); + Assert.That(() => ((IFeature)null).ComputeType(), Throws.TypeOf()); + + var feature = new Feature(); + + Assert.That(feature.ComputeType(), Has.Count.EqualTo(0)); + + var type1 = new Type(); + var typing1 = new FeatureTyping { Type = type1 }; + feature.AssignOwnership(typing1, new Feature()); + + var type2 = new Type(); + var typing2 = new FeatureTyping { Type = type2 }; + feature.AssignOwnership(typing2, new Feature()); + + var result = feature.ComputeType(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(result, Has.Count.EqualTo(2)); + Assert.That(result[0], Is.SameAs(type1)); + Assert.That(result[1], Is.SameAs(type2)); + } } - + [Test] - public void ComputeOwningType_ThrowsNotSupportedException() + public void VerifyComputeNamingFeatureOperation() { - Assert.That(() => ((IFeature)null).ComputeOwningType(), Throws.TypeOf()); + Assert.That(() => ((IFeature)null).ComputeNamingFeatureOperation(), Throws.TypeOf()); + + var feature = new Feature(); + + Assert.That(feature.ComputeNamingFeatureOperation(), Is.Null); + + var redefinedFeature = new Feature { DeclaredName = "redefined" }; + var redefinition = new Redefinition { RedefinedFeature = redefinedFeature }; + feature.AssignOwnership(redefinition, new Feature()); + + Assert.That(feature.ComputeNamingFeatureOperation(), Is.SameAs(redefinedFeature)); + + var redefinedFeature2 = new Feature { DeclaredName = "redefined2" }; + var redefinition2 = new Redefinition { RedefinedFeature = redefinedFeature2 }; + feature.AssignOwnership(redefinition2, new Feature()); + + Assert.That(feature.ComputeNamingFeatureOperation(), Is.SameAs(redefinedFeature)); } - + [Test] - public void ComputeType_ThrowsNotSupportedException() + public void VerifyComputeRedefinedEffectiveShortNameOperation() { - Assert.That(() => ((IFeature)null).ComputeType(), Throws.TypeOf()); + Assert.That(() => ((IFeature)null).ComputeRedefinedEffectiveShortNameOperation(), Throws.TypeOf()); + + var feature = new Feature(); + + Assert.That(feature.ComputeRedefinedEffectiveShortNameOperation(), Is.Null); + + var feature2 = new Feature { DeclaredName = "someName" }; + + Assert.That(feature2.ComputeRedefinedEffectiveShortNameOperation(), Is.Null); + + var feature3 = new Feature { DeclaredShortName = "sn" }; + + Assert.That(feature3.ComputeRedefinedEffectiveShortNameOperation(), Is.EqualTo("sn")); + + var featureWithRedefinition = new Feature(); + var namingFeature = new Feature { DeclaredShortName = "nfShort" }; + var redefinition = new Redefinition { RedefinedFeature = namingFeature }; + featureWithRedefinition.AssignOwnership(redefinition, new Feature()); + + Assert.That(featureWithRedefinition.ComputeRedefinedEffectiveShortNameOperation(), Is.EqualTo("nfShort")); + + var featureNoNaming = new Feature(); + var redefinitionNoTarget = new Redefinition { RedefinedFeature = new Feature() }; + featureNoNaming.AssignOwnership(redefinitionNoTarget, new Feature()); + + Assert.That(featureNoNaming.ComputeRedefinedEffectiveShortNameOperation(), Is.Null); + } + + [Test] + public void VerifyComputeRedefinedEffectiveNameOperation() + { + Assert.That(() => ((IFeature)null).ComputeRedefinedEffectiveNameOperation(), Throws.TypeOf()); + + var feature = new Feature(); + + Assert.That(feature.ComputeRedefinedEffectiveNameOperation(), Is.Null); + + var feature2 = new Feature { DeclaredShortName = "sn" }; + + Assert.That(feature2.ComputeRedefinedEffectiveNameOperation(), Is.Null); + + var feature3 = new Feature { DeclaredName = "theName" }; + + Assert.That(feature3.ComputeRedefinedEffectiveNameOperation(), Is.EqualTo("theName")); + + var featureWithRedefinition = new Feature(); + var namingFeature = new Feature { DeclaredName = "nfName" }; + var redefinition = new Redefinition { RedefinedFeature = namingFeature }; + featureWithRedefinition.AssignOwnership(redefinition, new Feature()); + + Assert.That(featureWithRedefinition.ComputeRedefinedEffectiveNameOperation(), Is.EqualTo("nfName")); + + var featureNoNaming = new Feature(); + var redefinitionNoTarget = new Redefinition { RedefinedFeature = new Feature() }; + featureNoNaming.AssignOwnership(redefinitionNoTarget, new Feature()); + + Assert.That(featureNoNaming.ComputeRedefinedEffectiveNameOperation(), Is.Null); + } + + [Test] + public void VerifyComputeIsOwnedCrossFeatureOperation() + { + Assert.That(() => ((IFeature)null).ComputeIsOwnedCrossFeatureOperation(), Throws.TypeOf()); + + var feature = new Feature(); + + Assert.That(feature.ComputeIsOwnedCrossFeatureOperation(), Is.False); + + var nonFeatureOwner = new Type(); + var membership = new OwningMembership(); + nonFeatureOwner.AssignOwnership(membership, feature); + + Assert.That(feature.ComputeIsOwnedCrossFeatureOperation(), Is.False); + + var endFeature = new Feature { IsEnd = true }; + var ownerType = new Type(); + var endMembership = new EndFeatureMembership(); + ownerType.AssignOwnership(endMembership, endFeature); + + var crossCandidate = new Feature(); + var innerMembership = new OwningMembership(); + endFeature.AssignOwnership(innerMembership, crossCandidate); + + Assert.That(crossCandidate.ComputeIsOwnedCrossFeatureOperation(), Is.True); + } + + [Test] + public void VerifyComputeRedefinesOperation() + { + Assert.That(() => ((IFeature)null).ComputeRedefinesOperation(null), Throws.TypeOf()); + + var feature = new Feature(); + + var targetFeature = new Feature(); + + Assert.That(feature.ComputeRedefinesOperation(targetFeature), Is.False); + + var otherFeature = new Feature(); + var redefinition = new Redefinition { RedefinedFeature = otherFeature }; + feature.AssignOwnership(redefinition, new Feature()); + + using (Assert.EnterMultipleScope()) + { + Assert.That(feature.ComputeRedefinesOperation(targetFeature), Is.False); + Assert.That(feature.ComputeRedefinesOperation(otherFeature), Is.True); + } + } + + [Test] + public void VerifyComputeDirectionForOperation() + { + Assert.That(() => ((IFeature)null).ComputeDirectionForOperation(null), Throws.TypeOf()); + + var feature = new Feature(); + + Assert.That(() => feature.ComputeDirectionForOperation(new Type()), Throws.TypeOf()); + } + + [Test] + public void VerifyComputeOwnedCrossFeatureOperation() + { + Assert.That(() => ((IFeature)null).ComputeOwnedCrossFeatureOperation(), Throws.TypeOf()); + + var feature = new Feature(); + + Assert.That(feature.ComputeOwnedCrossFeatureOperation(), Is.Null); + + var endFeatureNoOwner = new Feature { IsEnd = true }; + + Assert.That(endFeatureNoOwner.ComputeOwnedCrossFeatureOperation(), Is.Null); + + var endFeature = new Feature { IsEnd = true }; + var ownerType = new Type(); + var endMembership = new EndFeatureMembership(); + ownerType.AssignOwnership(endMembership, endFeature); + + Assert.That(endFeature.ComputeOwnedCrossFeatureOperation(), Is.Null); + + var multiplicityMember = new Multiplicity(); + var multMembership = new OwningMembership(); + endFeature.AssignOwnership(multMembership, multiplicityMember); + + Assert.That(endFeature.ComputeOwnedCrossFeatureOperation(), Is.Null); + + var featureMemberChild = new Feature(); + var featureMembership = new FeatureMembership(); + endFeature.AssignOwnership(featureMembership, featureMemberChild); + + Assert.That(endFeature.ComputeOwnedCrossFeatureOperation(), Is.Null); + + var plainFeature = new Feature(); + var plainMembership = new OwningMembership(); + endFeature.AssignOwnership(plainMembership, plainFeature); + + Assert.That(endFeature.ComputeOwnedCrossFeatureOperation(), Is.SameAs(plainFeature)); + } + + [Test] + public void VerifyComputeIsCartesianProductOperation() + { + Assert.That(() => ((IFeature)null).ComputeIsCartesianProductOperation(), Throws.TypeOf()); + + var feature = new Feature(); + + Assert.That(feature.ComputeIsCartesianProductOperation(), Is.False); + + var theType = new Type(); + var typing = new FeatureTyping { Type = theType }; + feature.AssignOwnership(typing, new Feature()); + + Assert.That(feature.ComputeIsCartesianProductOperation(), Is.False); + + var featuringType = new Type(); + var typeFeaturing = new TypeFeaturing { FeatureOfType = feature, FeaturingType = featuringType }; + feature.AssignOwnership(typeFeaturing, new Feature()); + + Assert.That(feature.ComputeIsCartesianProductOperation(), Is.True); + } + + [Test] + public void VerifyComputeAsCartesianProductOperation() + { + Assert.That(() => ((IFeature)null).ComputeAsCartesianProductOperation(), Throws.TypeOf()); + + var feature = new Feature(); + + var result = feature.ComputeAsCartesianProductOperation(); + + Assert.That(result, Has.Count.EqualTo(0)); + + var theType = new Type(); + var typing = new FeatureTyping { Type = theType }; + feature.AssignOwnership(typing, new Feature()); + + result = feature.ComputeAsCartesianProductOperation(); + + Assert.That(result, Has.Count.EqualTo(1)); + Assert.That(result[0], Is.SameAs(theType)); + + var featuringType = new Type(); + var typeFeaturing = new TypeFeaturing { FeatureOfType = feature, FeaturingType = featuringType }; + feature.AssignOwnership(typeFeaturing, new Feature()); + + result = feature.ComputeAsCartesianProductOperation(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(result, Has.Count.EqualTo(2)); + Assert.That(result[0], Is.SameAs(featuringType)); + Assert.That(result[1], Is.SameAs(theType)); + } + } + + [Test] + public void VerifyComputeIsFeaturingTypeOperation() + { + Assert.That(() => ((IFeature)null).ComputeIsFeaturingTypeOperation(null), Throws.TypeOf()); + + var feature = new Feature(); + + Assert.That(feature.ComputeIsFeaturingTypeOperation(new Type()), Is.False); + + var ownerType = new Type(); + var featureMembership = new FeatureMembership(); + ownerType.AssignOwnership(featureMembership, feature); + + using (Assert.EnterMultipleScope()) + { + Assert.That(feature.ComputeIsFeaturingTypeOperation(ownerType), Is.True); + Assert.That(feature.ComputeIsFeaturingTypeOperation(new Type()), Is.False); + } + } + + [Test] + public void VerifyComputeIsFeaturedWithinOperation() + { + Assert.That(() => ((IFeature)null).ComputeIsFeaturedWithinOperation(null), Throws.TypeOf()); + + // all body cases blocked by TypeExtensions.ComputeRedefinedVisibleMembershipsOperation + // (reached via ResolveGlobal path even for type=null and empty featuringTypes, + // because the impl resolves Base::Anything to check implicit featuring). + var feature = new Feature(); + + Assert.That(() => feature.ComputeIsFeaturedWithinOperation(null), Throws.TypeOf()); + } + + [Test] + public void VerifyComputeCanAccessOperation() + { + Assert.That(() => ((IFeature)null).ComputeCanAccessOperation(null), Throws.TypeOf()); + + // all body cases blocked by TypeExtensions.ComputeRedefinedVisibleMembershipsOperation + // (reached via IsFeaturedWithin → ResolveGlobal path even for empty featuringTypes). + var feature = new Feature(); + + Assert.That(() => feature.ComputeCanAccessOperation(new Feature()), Throws.TypeOf()); + } + + [Test] + public void VerifyComputeAllRedefinedFeaturesOperation() + { + Assert.That(() => ((IFeature)null).ComputeAllRedefinedFeaturesOperation(), Throws.TypeOf()); + + var feature = new Feature(); + + var result = feature.ComputeAllRedefinedFeaturesOperation(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(result, Has.Count.EqualTo(1)); + Assert.That(result[0], Is.SameAs(feature)); + } + + var redefinedB = new Feature(); + var redefinitionAB = new Redefinition { RedefinedFeature = redefinedB }; + feature.AssignOwnership(redefinitionAB, new Feature()); + + result = feature.ComputeAllRedefinedFeaturesOperation(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(result, Has.Count.EqualTo(2)); + Assert.That(result[0], Is.SameAs(feature)); + Assert.That(result[1], Is.SameAs(redefinedB)); + } + + var redefinedC = new Feature(); + var redefinitionBC = new Redefinition { RedefinedFeature = redefinedC }; + redefinedB.AssignOwnership(redefinitionBC, new Feature()); + + result = feature.ComputeAllRedefinedFeaturesOperation(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(result, Has.Count.EqualTo(3)); + Assert.That(result[0], Is.SameAs(feature)); + Assert.That(result[1], Is.SameAs(redefinedB)); + Assert.That(result[2], Is.SameAs(redefinedC)); + } + } + + [Test] + public void VerifyComputeRedefinedSupertypesOperation() + { + Assert.That(() => ((IFeature)null).ComputeRedefinedSupertypesOperation(false), Throws.TypeOf()); + + var feature = new Feature(); + + Assert.That(() => feature.ComputeRedefinedSupertypesOperation(false), Throws.TypeOf()); + } + + [Test] + public void VerifyComputeTypingFeaturesOperation() + { + Assert.That(() => ((IFeature)null).ComputeTypingFeaturesOperation(), Throws.TypeOf()); + + // body cases (non-conjugated subsetting filtering, CrossSubsetting exclusion, + // chainingFeature append) are blocked by TypeExtensions.ComputeIsConjugated stub + // which is called via featureSubject.isConjugated at the start of the implementation + var feature = new Feature(); + + Assert.That(() => feature.ComputeTypingFeaturesOperation(), Throws.TypeOf()); + } + + [Test] + public void VerifyComputeSubsetsChainOperation() + { + Assert.That(() => ((IFeature)null).ComputeSubsetsChainOperation(null, null), Throws.TypeOf()); + + var feature = new Feature(); + + Assert.That(() => feature.ComputeSubsetsChainOperation(new Feature(), new Feature()), Throws.TypeOf()); + } + + [Test] + public void VerifyComputeRedefinedIsCompatibleWithOperation() + { + Assert.That(() => ((IFeature)null).ComputeRedefinedIsCompatibleWithOperation(null), Throws.TypeOf()); + + var feature = new Feature(); + + Assert.That(() => feature.ComputeRedefinedIsCompatibleWithOperation(new Type()), Throws.TypeOf()); + } + + [Test] + public void VerifyComputeRedefinesFromLibraryOperation() + { + Assert.That(() => ((IFeature)null).ComputeRedefinesFromLibraryOperation(null), Throws.TypeOf()); + + // positive verification of library redefinition requires a loaded library model + // and is blocked by TypeExtensions.ComputeRedefinedVisibleMembershipsOperation stub + // (via ResolveGlobal → VisibleMemberships chain) + var feature = new Feature(); + + Assert.That(() => feature.ComputeRedefinesFromLibraryOperation("Some::Library::Feature"), Throws.TypeOf()); } } } diff --git a/SysML2.NET.Tests/TextualNotation/TextualNotationBuilderTestFixture.cs b/SysML2.NET.Tests/TextualNotation/TextualNotationBuilderTestFixture.cs index 060d0f8f..ba123ac2 100644 --- a/SysML2.NET.Tests/TextualNotation/TextualNotationBuilderTestFixture.cs +++ b/SysML2.NET.Tests/TextualNotation/TextualNotationBuilderTestFixture.cs @@ -89,7 +89,7 @@ public void Verify_that_textual_notation_is_produced_from_Quantities_root_namesp } catch (System.NotSupportedException notSupportedException) { - TestContext.WriteLine($"Builder stopped early due to unimplemented derived property: {notSupportedException.Message}"); + TestContext.WriteLine($"Builder stopped early due to unimplemented derived property: {notSupportedException.Message}, {notSupportedException.StackTrace}"); } var textualNotation = stringBuilder.ToString(); diff --git a/SysML2.NET/Extend/ElementExtensions.cs b/SysML2.NET/Extend/ElementExtensions.cs index 7c61229d..2bef7c8d 100644 --- a/SysML2.NET/Extend/ElementExtensions.cs +++ b/SysML2.NET/Extend/ElementExtensions.cs @@ -37,6 +37,13 @@ internal static class ElementExtensions /// /// Computes the derived property. /// + /// + /// OCL (KerML XMI): + /// + /// documentation = ownedElement->selectByKind(Documentation) + /// + /// The documentation of an Element is its ownedElements that are Documentation. + /// /// /// The subject /// @@ -51,6 +58,13 @@ internal static List ComputeDocumentation(this IElement elementS /// /// Computes the derived property. /// + /// + /// OCL (KerML XMI): + /// + /// isLibraryElement = libraryNamespace() <> null + /// + /// An Element isLibraryElement if libraryNamespace() is not null. + /// /// /// The subject /// @@ -65,6 +79,13 @@ internal static bool ComputeIsLibraryElement(this IElement elementSubject) /// /// Computes the derived property. /// + /// + /// OCL (KerML XMI): + /// + /// name = effectiveName() + /// + /// The name of an Element is given by the result of the effectiveName() operation. By default, it is the same as the declaredName, but this is overridden for certain kinds of Elements to compute a name even when the declaredName is null. + /// /// /// The subject /// @@ -79,6 +100,15 @@ internal static string ComputeName(this IElement elementSubject) /// /// Computes the derived property. /// + /// + /// OCL (KerML XMI): + /// + /// ownedAnnotation = ownedRelationship-> + /// selectByKind(Annotation)-> + /// select(a | a.annotatedElement = self) + /// + /// The ownedAnnotations of an Element are its ownedRelationships that are Annotations, for which the Element is the annotatedElement. + /// /// /// The subject /// @@ -93,6 +123,13 @@ internal static List ComputeOwnedAnnotation(this IElement elementSu /// /// Computes the derived property. /// + /// + /// OCL (KerML XMI): + /// + /// ownedElement = ownedRelationship.ownedRelatedElement + /// + /// The ownedElements of an Element are the ownedRelatedElements of its ownedRelationships. + /// /// /// The subject /// @@ -107,6 +144,13 @@ internal static List ComputeOwnedElement(this IElement elementSubject) /// /// Computes the derived property. /// + /// + /// OCL (KerML XMI): + /// + /// owner = owningRelationship.owningRelatedElement + /// + /// The owner of an Element is the owningRelatedElement of its owningRelationship. + /// /// /// The subject /// @@ -121,6 +165,10 @@ internal static IElement ComputeOwner(this IElement elementSubject) /// /// Computes the derived property. /// + /// + /// No explicit OCL derivation rule in XMI. Derived from UML association semantics: + /// The owningRelationship of this Element, if that Relationship is a Membership. Since owningMembership subsets owningRelationship with type OwningMembership, its value is the owningRelationship when that relationship is an OwningMembership, otherwise null. + /// /// /// The subject /// @@ -135,6 +183,16 @@ internal static IOwningMembership ComputeOwningMembership(this IElement elementS /// /// Computes the derived property. /// + /// + /// OCL (KerML XMI): + /// + /// owningNamespace = + /// if owningMembership = null then null + /// else owningMembership.membershipOwningNamespace + /// endif + /// + /// The owningNamespace of an Element is the membershipOwningNamespace of its owningMembership (if any). + /// /// /// The subject /// @@ -149,6 +207,22 @@ internal static INamespace ComputeOwningNamespace(this IElement elementSubject) /// /// Computes the derived property. /// + /// + /// OCL (KerML XMI): + /// + /// qualifiedName = + /// if owningNamespace = null then null + /// else if name <> null and + /// owningNamespace.ownedMember-> + /// select(m | m.name = name).indexOf(self) <> 1 then null + /// else if owningNamespace.owner = null then escapedName() + /// else if owningNamespace.qualifiedName = null or + /// escapedName() = null then null + /// else owningNamespace.qualifiedName + '::' + escapedName() + /// endif endif endif endif + /// + /// If this Element does not have an owningNamespace, then its qualifiedName is null. If the owningNamespace of this Element is a root Namespace, then the qualifiedName of the Element is the escaped name of the Element (if any). If the owningNamespace is non-null but not a root Namespace, then the qualifiedName of this Element is constructed from the qualifiedName of the owningNamespace and the escaped name of the Element, unless the qualifiedName of the owningNamespace is null or the escaped name is null, in which case the qualifiedName of this Element is also null. Further, if the owningNamespace has other ownedMembers with the same non-null name as this Element, and this Element is not the first, then the qualifiedName of this Element is null. + /// /// /// The subject /// @@ -196,6 +270,13 @@ internal static string ComputeQualifiedName(this IElement elementSubject) /// /// Computes the derived property. /// + /// + /// OCL (KerML XMI): + /// + /// shortName = effectiveShortName() + /// + /// The shortName of an Element is given by the result of the effectiveShortName() operation. By default, it is the same as the declaredShortName, but this is overridden for certain kinds of Elements to compute a shortName even when the declaredName is null. + /// /// /// The subject /// @@ -210,6 +291,13 @@ internal static string ComputeShortName(this IElement elementSubject) /// /// Computes the derived property. /// + /// + /// OCL (KerML XMI): + /// + /// textualRepresentation = ownedElement->selectByKind(TextualRepresentation) + /// + /// The textualRepresentations of an Element are its ownedElements that are TextualRepresentations. + /// /// /// The subject /// @@ -225,6 +313,13 @@ internal static List ComputeTextualRepresentation(this I /// Validates the constraint that if an Element has any ownedRelationships for which isImplied = true, /// then the Element must also have isImpliedIncluded = true. /// + /// + /// OCL (KerML XMI): + /// + /// ownedRelationship->exists(isImplied) implies isImpliedIncluded + /// + /// If an Element has any ownedRelationships for which isImplied = true, then the Element must also have isImpliedIncluded = true. (Note that an Element can have isImplied = true even if no ownedRelationships have isImplied = true, indicating the Element simply has no implied Relationships.) + /// /// /// The subject /// @@ -249,6 +344,10 @@ internal static bool ValidateIsImpliedIncluded(this IElement elementSubject) /// otherwise, represented as a restricted name according to the lexical structure of the KerML textual /// notation (i.e., surrounded by single quote characters and with special characters escaped). /// + /// + /// No explicit OCL derivation rule in XMI. Derived from UML association semantics: + /// 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, otherwise, represented as a restricted name according to the lexical structure of the KerML textual notation (i.e., surrounded by single quote characters and with special characters escaped). + /// /// /// The subject /// @@ -281,6 +380,13 @@ internal static string ComputeEscapedNameOperation(this IElement elementSubject) /// Return an effective shortName for this Element. By default this is the same as its /// declaredShortName. /// + /// + /// OCL (KerML XMI): + /// + /// declaredShortName + /// + /// Return an effective shortName for this Element. By default this is the same as its declaredShortName. (Note: this operation is redefined on Feature to also consider the naming feature when declaredShortName and declaredName are both null.) + /// /// /// The subject /// @@ -295,6 +401,13 @@ internal static string ComputeEffectiveShortNameOperation(this IElement elementS /// /// Return an effective name for this Element. By default this is the same as its declaredName. /// + /// + /// OCL (KerML XMI): + /// + /// declaredName + /// + /// Return an effective name for this Element. By default this is the same as its declaredName. (Note: this operation is redefined on Feature to also consider the naming feature when declaredShortName and declaredName are both null.) + /// /// /// The subject /// @@ -309,6 +422,14 @@ internal static string ComputeEffectiveNameOperation(this IElement elementSubjec /// /// By default, return the library Namespace of the owningRelationship of this Element, if it has one. /// + /// + /// OCL (KerML XMI): + /// + /// if owningRelationship <> null then owningRelationship.libraryNamespace() + /// else null endif + /// + /// By default, return the library Namespace of the owningRelationship of this Element, if it has one. (Note: this operation is redefined on Relationship to also check owningRelatedElement, and on LibraryPackage to return itself.) + /// /// /// The subject /// @@ -329,6 +450,18 @@ internal static INamespace ComputeLibraryNamespaceOperation(this IElement elemen /// empty string. (Note that this operation is overridden for Relationships /// to use owningRelatedElement when appropriate.) /// + /// + /// OCL (KerML XMI): + /// + /// if qualifiedName <> null then qualifiedName + /// else if owningRelationship <> null then + /// owningRelationship.path() + '/' + + /// owningRelationship.ownedRelatedElement->indexOf(self).toString() + /// else '' + /// endif endif + /// + /// Return a unique description of the location of this Element in the containment structure rooted in a root Namespace. If the Element has a non-null qualifiedName, then return that. Otherwise, if it has an owningRelationship, then return the string constructed by appending to the path of its owningRelationship the character / followed by the string representation of its position in the list of ownedRelatedElements of the owningRelationship (indexed starting at 1). Otherwise, return the empty string. (Note that this operation is overridden for Relationships to use owningRelatedElement when appropriate.) + /// /// /// The subject /// diff --git a/SysML2.NET/Extend/FeatureExtensions.cs b/SysML2.NET/Extend/FeatureExtensions.cs index 004d42de..29f32f3c 100644 --- a/SysML2.NET/Extend/FeatureExtensions.cs +++ b/SysML2.NET/Extend/FeatureExtensions.cs @@ -1,20 +1,20 @@ // ------------------------------------------------------------------------------------------------- // -// -// Copyright (C) 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 -// +// +// 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. -// +// // // ------------------------------------------------------------------------------------------------ @@ -22,264 +22,466 @@ namespace SysML2.NET.Core.POCO.Core.Features { using System; using System.Collections.Generic; + using System.Linq; using SysML2.NET.Core.Core.Types; using SysML2.NET.Core.POCO.Core.Types; - using SysML2.NET.Core.POCO.Root.Annotations; - using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Kernel.FeatureValues; + using SysML2.NET.Core.POCO.Kernel.Metadata; using SysML2.NET.Core.POCO.Root.Namespaces; /// - /// The class provides extensions methods for - /// the interface + /// The class provides extensions methods for + /// the interface /// internal static class FeatureExtensions { /// /// Computes the derived property. /// + /// + /// OCL (KerML XMI): + /// + /// chainingFeature = ownedFeatureChaining.chainingFeature + /// + /// The chainingFeatures of a Feature are the chainingFeatures of its ownedFeatureChainings. + /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeChainingFeature(this IFeature featureSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return featureSubject == null + ? throw new ArgumentNullException(nameof(featureSubject)) + : [..featureSubject.OwnedRelationship.OfType().Select(fc => fc.ChainingFeature)]; } /// /// Computes the derived property. /// + /// + /// OCL (KerML XMI): + /// + /// crossFeature = + /// if ownedCrossSubsetting = null then null + /// else + /// let chainingFeatures: Sequence(Feature) = + /// ownedCrossSubsetting.crossedFeature.chainingFeature in + /// if chainingFeatures->size() < 2 then null + /// else chainingFeatures->at(2) + /// endif + /// + /// The crossFeature of a Feature is the second chainingFeature of the crossedFeature of the ownedCrossSubsetting of the Feature, if any. + /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IFeature ComputeCrossFeature(this IFeature featureSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (featureSubject == null) + { + throw new ArgumentNullException(nameof(featureSubject)); + } + + var ownedCrossSubsetting = featureSubject.OwnedRelationship.OfType().FirstOrDefault(); + + var crossedFeature = ownedCrossSubsetting?.CrossedFeature; + + var chainingFeatures = crossedFeature?.chainingFeature; + return chainingFeatures?.Count >= 2 ? chainingFeatures[1] : null; } /// /// Computes the derived property. /// + /// + /// No explicit OCL derivation rule in XMI. Derived from UML association semantics: + /// endOwningType is the owningType but only when the owningFeatureMembership is an EndFeatureMembership. + /// Since endOwningType subsets owningType, it must equal owningType when the condition holds, and be null otherwise. + /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IType ComputeEndOwningType(this IFeature featureSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return featureSubject == null + ? throw new ArgumentNullException(nameof(featureSubject)) + : featureSubject.OwningRelationship is IEndFeatureMembership efm + ? efm.OwningRelatedElement as IType + : null; } /// /// Computes the derived property. /// + /// + /// OCL (KerML XMI): + /// + /// featureTarget = if chainingFeature->isEmpty() then self else chainingFeature->last() endif + /// + /// If a Feature has no chainingFeatures, then its featureTarget is the Feature itself, otherwise the featureTarget is the last of the chainingFeatures. + /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IFeature ComputeFeatureTarget(this IFeature featureSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (featureSubject == null) + { + throw new ArgumentNullException(nameof(featureSubject)); + } + + var chainingFeatures = featureSubject.OwnedRelationship.OfType().ToList(); + + return chainingFeatures.Count == 0 + ? featureSubject + : chainingFeatures[^1].ChainingFeature; } /// /// Computes the derived property. /// + /// + /// OCL (KerML XMI): + /// + /// featuringType = + /// let featuringTypes : OrderedSet(Type) = + /// featuring.type->asOrderedSet() in + /// if chainingFeature->isEmpty() then featuringTypes + /// else + /// featuringTypes-> + /// union(chainingFeature->first().featuringType)-> + /// asOrderedSet() + /// endif + /// + /// The featuringTypes of a Feature include the featuringTypes of all the typeFeaturings of the Feature. + /// If the Feature has chainingFeatures, then its featuringTypes also include the featuringTypes of the first chainingFeature. + /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeFeaturingType(this IFeature featureSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (featureSubject == null) + { + throw new ArgumentNullException(nameof(featureSubject)); + } + + var featuringTypes = featureSubject.OwnedRelationship + .OfType() + .Where(tf => tf.FeatureOfType == featureSubject) + .Select(tf => tf.FeaturingType) + .ToList(); + + var chainingFeatures = featureSubject.OwnedRelationship + .OfType() + .Select(fc => fc.ChainingFeature) + .ToList(); + + if (chainingFeatures.Count > 0) + { + featuringTypes = [..featuringTypes.Union(chainingFeatures[0].featuringType)]; + } + + return featuringTypes; } /// /// Computes the derived property. /// + /// + /// OCL (KerML XMI): + /// + /// ownedCrossSubsetting = + /// let crossSubsettings: Sequence(CrossSubsetting) = + /// ownedSubsetting->selectByKind(CrossSubsetting) in + /// if crossSubsettings->isEmpty() then null + /// else crossSubsettings->first() + /// endif + /// + /// The ownedCrossSubsetting of a Feature is the ownedSubsetting that is a CrossSubsetting, if any. + /// A Feature must have at most one (validated by validateFeatureOwnedCrossSubsetting). + /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static ICrossSubsetting ComputeOwnedCrossSubsetting(this IFeature featureSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return featureSubject == null + ? throw new ArgumentNullException(nameof(featureSubject)) + : featureSubject.OwnedRelationship.OfType().FirstOrDefault(); } /// /// Computes the derived property. /// + /// + /// OCL (KerML XMI): + /// + /// ownedFeatureChaining = ownedRelationship->selectByKind(FeatureChaining) + /// + /// The ownedFeatureChainings of a Feature are the ownedRelationships that are FeatureChainings. + /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeOwnedFeatureChaining(this IFeature featureSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return featureSubject == null + ? throw new ArgumentNullException(nameof(featureSubject)) + : [..featureSubject.OwnedRelationship.OfType()]; } /// /// Computes the derived property. /// + /// + /// OCL (KerML XMI): + /// + /// ownedFeatureInverting = ownedRelationship->selectByKind(FeatureInverting)-> + /// select(fi | fi.featureInverted = self) + /// + /// The ownedFeatureInvertings of a Feature are its ownedRelationships that are FeatureInvertings + /// and for which the Feature is the featureInverted. + /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeOwnedFeatureInverting(this IFeature featureSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return featureSubject == null + ? throw new ArgumentNullException(nameof(featureSubject)) + : [..featureSubject.OwnedRelationship.OfType().Where(fi => fi.FeatureInverted == featureSubject)]; } /// /// Computes the derived property. /// + /// + /// OCL (KerML XMI): + /// + /// ownedRedefinition = ownedSubsetting->selectByKind(Redefinition) + /// + /// The ownedRedefinitions of a Feature are its ownedSubsettings that are Redefinitions. + /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeOwnedRedefinition(this IFeature featureSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return featureSubject == null + ? throw new ArgumentNullException(nameof(featureSubject)) + : [..featureSubject.OwnedRelationship.OfType()]; } /// /// Computes the derived property. /// + /// + /// OCL (KerML XMI): + /// + /// ownedReferenceSubsetting = + /// let referenceSubsettings : OrderedSet(ReferenceSubsetting) = + /// ownedSubsetting->selectByKind(ReferenceSubsetting) in + /// if referenceSubsettings->isEmpty() then null + /// else referenceSubsettings->first() endif + /// + /// The ownedReferenceSubsetting of a Feature is the first ownedSubsetting that is a ReferenceSubsetting (if any). + /// A Feature must have at most one (validated by validateFeatureOwnedReferenceSubsetting). + /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IReferenceSubsetting ComputeOwnedReferenceSubsetting(this IFeature featureSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return featureSubject == null + ? throw new ArgumentNullException(nameof(featureSubject)) + : featureSubject.OwnedRelationship.OfType().FirstOrDefault(); } /// /// Computes the derived property. /// + /// + /// OCL (KerML XMI): + /// + /// ownedSubsetting = ownedSpecialization->selectByKind(Subsetting) + /// + /// The ownedSubsettings of a Feature are its ownedSpecializations that are Subsettings. + /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeOwnedSubsetting(this IFeature featureSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return featureSubject == null + ? throw new ArgumentNullException(nameof(featureSubject)) + : [..featureSubject.OwnedRelationship.OfType()]; } /// /// Computes the derived property. /// + /// + /// OCL (KerML XMI): + /// + /// ownedTypeFeaturing = ownedRelationship->selectByKind(TypeFeaturing)-> + /// select(tf | tf.featureOfType = self) + /// + /// The ownedTypeFeaturings of a Feature are its ownedRelationships that are TypeFeaturings + /// and which have the Feature as their featureOfType. + /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeOwnedTypeFeaturing(this IFeature featureSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return featureSubject == null + ? throw new ArgumentNullException(nameof(featureSubject)) + : [..featureSubject.OwnedRelationship.OfType().Where(tf => tf.FeatureOfType == featureSubject)]; } /// /// Computes the derived property. /// + /// + /// OCL (KerML XMI): + /// + /// ownedTyping = ownedGeneralization->selectByKind(FeatureTyping) + /// + /// The ownedTypings of a Feature are its ownedSpecializations that are FeatureTypings. + /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeOwnedTyping(this IFeature featureSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return featureSubject == null + ? throw new ArgumentNullException(nameof(featureSubject)) + : [..featureSubject.OwnedRelationship.OfType()]; } /// /// Computes the derived property. /// + /// + /// No explicit OCL derivation rule in XMI. Derived from UML association semantics: + /// owningFeatureMembership is the owningRelationship of this Element, if that Relationship is a FeatureMembership. + /// It subsets owningMembership (which is the owningRelationship if it is a Membership). + /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IFeatureMembership ComputeOwningFeatureMembership(this IFeature featureSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return featureSubject == null + ? throw new ArgumentNullException(nameof(featureSubject)) + : featureSubject.OwningRelationship as IFeatureMembership; } /// /// Computes the derived property. /// + /// + /// No explicit OCL derivation rule in XMI. Derived from UML association semantics: + /// owningType is the Type that is the owningType of the owningFeatureMembership of this Feature. + /// Navigate through owningFeatureMembership to its owningType. Subsets typeWithFeature, owningNamespace, featuringType. + /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IType ComputeOwningType(this IFeature featureSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return featureSubject == null + ? throw new ArgumentNullException(nameof(featureSubject)) + : (featureSubject.OwningRelationship as IFeatureMembership)?.OwningRelatedElement as IType; } /// /// Computes the derived property. /// + /// + /// OCL (KerML XMI): + /// + /// type = + /// let types : OrderedSet(Types) = OrderedSet{self}-> + /// -- Note: The closure operation automatically handles circular relationships. + /// closure(typingFeatures()).typing.type->asOrderedSet() in + /// types->reject(t1 | types->exist(t2 | t2 <> t1 and t2.specializes(t1))) + /// + /// The types of a Feature are the union of the types of its typings and the types of the Features it subsets, + /// with all redundant supertypes removed. If the Feature has chainingFeatures, then the union also includes the types of the last chainingFeature. + /// /// - /// The subject + /// The subject /// /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeType(this IFeature featureSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return featureSubject == null + ? throw new ArgumentNullException(nameof(featureSubject)) + : [..featureSubject.OwnedRelationship.OfType().Select(ft => ft.Type).Where(t => t != null)]; } /// /// Return the directionOf this Feature relative to the given type. /// + /// + /// OCL (KerML XMI): + /// + /// type.directionOf(self) + /// + /// Return the directionOf this Feature relative to the given type. + /// /// - /// The subject + /// The subject /// /// /// No documentation provided @@ -287,26 +489,55 @@ internal static List ComputeType(this IFeature featureSubject) /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static FeatureDirectionKind? ComputeDirectionForOperation(this IFeature featureSubject, IType type) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return featureSubject == null + ? throw new ArgumentNullException(nameof(featureSubject)) + : type?.DirectionOf(featureSubject); } /// /// If a Feature has no declaredShortName or declaredName, then its effective shortName is given by the /// effective shortName of the Feature returned by the namingFeature() operation, if any. /// + /// + /// OCL (KerML XMI): + /// + /// if declaredShortName <> null or declaredName <> null then + /// declaredShortName + /// else + /// let namingFeature : Feature = namingFeature() in + /// if namingFeature = null then + /// null + /// else + /// namingFeature.effectiveShortName() + /// endif + /// endif + /// + /// If a Feature has no declaredShortName or declaredName, then its effective shortName is given by the + /// effective shortName of the Feature returned by the namingFeature() operation, if any. + /// /// - /// The subject + /// The subject /// /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static string ComputeRedefinedEffectiveShortNameOperation(this IFeature featureSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (featureSubject == null) + { + throw new ArgumentNullException(nameof(featureSubject)); + } + + if (!string.IsNullOrWhiteSpace(featureSubject.DeclaredShortName) || !string.IsNullOrWhiteSpace(featureSubject.DeclaredName)) + { + return featureSubject.DeclaredShortName; + } + + var namingFeature = featureSubject.OwnedRelationship.OfType().FirstOrDefault()?.RedefinedFeature; + + return namingFeature?.EffectiveShortName(); } /// @@ -314,38 +545,89 @@ internal static string ComputeRedefinedEffectiveShortNameOperation(this IFeature /// effective name is given by the effective name of the Feature returned by the namingFeature() /// operation, if any. /// + /// + /// OCL (KerML XMI): + /// + /// if declaredShortName <> null or declaredName <> null then + /// declaredName + /// else + /// let namingFeature : Feature = namingFeature() in + /// if namingFeature = null then + /// null + /// else + /// namingFeature.effectiveName() + /// endif + /// endif + /// + /// If a Feature has no declaredName or declaredShortName, then its effective name is given by the + /// effective name of the Feature returned by the namingFeature() operation, if any. + /// /// - /// The subject + /// The subject /// /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static string ComputeRedefinedEffectiveNameOperation(this IFeature featureSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (featureSubject == null) + { + throw new ArgumentNullException(nameof(featureSubject)); + } + + if (!string.IsNullOrWhiteSpace(featureSubject.DeclaredShortName) || !string.IsNullOrWhiteSpace(featureSubject.DeclaredName)) + { + return featureSubject.DeclaredName; + } + + var namingFeature = featureSubject.OwnedRelationship.OfType().FirstOrDefault()?.RedefinedFeature; + + return namingFeature?.EffectiveName(); } /// /// By default, the naming Feature of a Feature is given by its first redefinedFeature of its first /// ownedRedefinition, if any. /// + /// + /// OCL (KerML XMI): + /// + /// if ownedRedefinition->isEmpty() then + /// null + /// else + /// ownedRedefinition->at(1).redefinedFeature + /// endif + /// + /// By default, the naming Feature of a Feature is given by its first redefinedFeature of its first ownedRedefinition, if any. + /// /// - /// The subject + /// The subject /// /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IFeature ComputeNamingFeatureOperation(this IFeature featureSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return featureSubject == null + ? throw new ArgumentNullException(nameof(featureSubject)) + : featureSubject.OwnedRelationship.OfType().FirstOrDefault()?.RedefinedFeature; } /// /// + /// + /// OCL (KerML XMI): + /// + /// let supertypes : OrderedSet(Type) = + /// self.oclAsType(Type).supertypes(excludeImplied) in + /// if featureTarget = self then supertypes + /// else supertypes->append(featureTarget) + /// endif + /// + /// If featureTarget is not self (i.e., this Feature has chainingFeatures), then the featureTarget is appended to the supertypes. + /// /// - /// The subject + /// The subject /// /// /// No documentation provided @@ -353,17 +635,55 @@ internal static IFeature ComputeNamingFeatureOperation(this IFeature featureSubj /// /// The expected collection of /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeRedefinedSupertypesOperation(this IFeature featureSubject, bool excludeImplied) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (featureSubject == null) + { + throw new ArgumentNullException(nameof(featureSubject)); + } + + // Inline Type-level supertypes to avoid recursion into the Feature override + List supertypes; + + if (featureSubject.isConjugated) + { + var originalType = featureSubject.ownedConjugator?.OriginalType; + supertypes = originalType != null ? [originalType] : []; + } + else + { + var specializations = featureSubject.OwnedRelationship.OfType(); + + if (excludeImplied) + { + specializations = specializations.Where(s => !s.IsImplied); + } + + supertypes = [..specializations.Select(s => s.General).Where(g => g != null)]; + } + + var target = featureSubject.featureTarget; + + if (target != featureSubject) + { + supertypes.Add(target); + } + + return supertypes; } /// /// Check whether this Feature directly redefines the given redefinedFeature. /// + /// + /// OCL (KerML XMI): + /// + /// ownedRedefinition.redefinedFeature->includes(redefinedFeature) + /// + /// Check whether this Feature directly redefines the given redefinedFeature. + /// /// - /// The subject + /// The subject /// /// /// No documentation provided @@ -371,18 +691,34 @@ internal static List ComputeRedefinedSupertypesOperation(this IFeature fe /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static bool ComputeRedefinesOperation(this IFeature featureSubject, IFeature redefinedFeature) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (featureSubject == null) + { + throw new ArgumentNullException(nameof(featureSubject)); + } + + return featureSubject.OwnedRelationship + .OfType() + .Any(r => r.RedefinedFeature == redefinedFeature); } /// /// Check whether this Feature directly redefines the named library Feature. libraryFeatureName must /// conform to the syntax of a KerML qualified name and must resolve to a Feature in global scope. /// + /// + /// OCL (KerML XMI): + /// + /// let mem: Membership = resolveGlobal(libraryFeatureName) in + /// mem <> null and mem.memberElement.oclIsKindOf(Feature) and + /// redefines(mem.memberElement.oclAsType(Feature)) + /// + /// Check whether this Feature directly redefines the named library Feature. libraryFeatureName must + /// conform to the syntax of a KerML qualified name and must resolve to a Feature in global scope. + /// /// - /// The subject + /// The subject /// /// /// No documentation provided @@ -390,18 +726,39 @@ internal static bool ComputeRedefinesOperation(this IFeature featureSubject, IFe /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static bool ComputeRedefinesFromLibraryOperation(this IFeature featureSubject, string libraryFeatureName) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (featureSubject == null) + { + throw new ArgumentNullException(nameof(featureSubject)); + } + + var membership = featureSubject.ResolveGlobal(libraryFeatureName); + + return membership?.MemberElement is IFeature libraryFeature + && featureSubject.OwnedRelationship + .OfType() + .Any(r => r.RedefinedFeature == libraryFeature); } /// /// Check whether this Feature directly or indirectly specializes a Feature whose last two /// chainingFeatures are the given Features first and second. /// + /// + /// OCL (KerML XMI): + /// + /// allSuperTypes()->selectAsKind(Feature)-> + /// exists(f | let n: Integer = f.chainingFeature->size() in + /// n >= 2 and + /// f.chainingFeature->at(n-1) = first and + /// f.chainingFeature->at(n) = second) + /// + /// Check whether this Feature directly or indirectly specializes a Feature whose last two chainingFeatures + /// are the given Features first and second. + /// /// - /// The subject + /// The subject /// /// /// No documentation provided @@ -412,22 +769,92 @@ internal static bool ComputeRedefinesFromLibraryOperation(this IFeature featureS /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static bool ComputeSubsetsChainOperation(this IFeature featureSubject, IFeature first, IFeature second) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (featureSubject == null) + { + throw new ArgumentNullException(nameof(featureSubject)); + } + + // Inline AllSupertypes: BFS transitive closure of supertypes (bypassing the stub) + var visited = new HashSet(); + var queue = new Queue(); + visited.Add(featureSubject); + queue.Enqueue(featureSubject); + + while (queue.Count > 0) + { + var current = queue.Dequeue(); + + // Inline supertypes: general Types from ownedSpecializations + IEnumerable supertypes; + + if (current.isConjugated) + { + var originalType = current.ownedConjugator?.OriginalType; + supertypes = originalType != null ? [originalType] : []; + } + else + { + supertypes = current.OwnedRelationship + .OfType() + .Select(s => s.General) + .Where(g => g != null); + } + + foreach (var supertype in supertypes) + { + if (visited.Add(supertype)) + { + queue.Enqueue(supertype); + } + } + } + + return visited + .OfType() + .Any(f => + { + var chain = f.chainingFeature; + var chainCount = chain.Count; + return chainCount >= 2 + && chain[chainCount - 2] == first + && chain[chainCount - 1] == second; + }); } /// /// A Feature is compatible with an otherType if it either directly or indirectly specializes the - /// otherType or if the otherType is also a Feature and all of the following are true. - ///
  1. Neither this Feature or the otherType have any - /// ownedFeatures.
  2. This Feature directly or indirectly redefines a - /// Feature that is also directly or indirectly redefined by the otherType.
  3. - ///
  4. This Feature can access the otherType.
+ /// otherType or if the otherType is also a Feature and all of the following are true. + ///
    + ///
  1. + /// Neither this Feature or the otherType have any + /// ownedFeatures. + ///
  2. + ///
  3. + /// This Feature directly or indirectly redefines a + /// Feature that is also directly or indirectly redefined by the otherType. + ///
  4. + ///
  5. This Feature can access the otherType.
  6. + ///
///
+ /// + /// OCL (KerML XMI): + /// + /// specializes(otherType) or + /// supertype.oclIsKindOf(Feature) and + /// ownedFeature->isEmpty() and + /// otherType.ownedFeature->isEmpty() and + /// ownedRedefinitions.allRedefinedFeatures()->exists(f | + /// otherType.oclAsType(Feature).allRedefinedFeatures()->includes(f)) and + /// canAccess(otherType.oclAsType(Feature)) + /// + /// A Feature is compatible with an otherType if it either directly or indirectly specializes the + /// otherType or if the otherType is also a Feature and all of the following are true: (1) Neither has + /// ownedFeatures. (2) They share a common allRedefinedFeature. (3) This Feature can access the otherType. + /// /// - /// The subject + /// The subject /// /// /// No documentation provided @@ -435,32 +862,110 @@ internal static bool ComputeSubsetsChainOperation(this IFeature featureSubject, /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static bool ComputeRedefinedIsCompatibleWithOperation(this IFeature featureSubject, IType otherType) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (featureSubject == null) + { + throw new ArgumentNullException(nameof(featureSubject)); + } + + if (featureSubject.Specializes(otherType)) + { + return true; + } + + if (otherType is not IFeature otherFeature) + { + return false; + } + + if (featureSubject.ownedFeature.Count != 0 || otherType.ownedFeature.Count != 0) + { + return false; + } + + var selfRedefined = new HashSet( + featureSubject.OwnedRelationship + .OfType() + .Where(r => r.RedefinedFeature != null) + .SelectMany(r => r.RedefinedFeature.AllRedefinedFeatures())); + + var otherRedefined = otherFeature.AllRedefinedFeatures(); + + var hasCommonRedefinition = otherRedefined.Any(selfRedefined.Contains); + + return hasCommonRedefinition && featureSubject.CanAccess(otherFeature); } /// /// Return the Features used to determine the types of this Feature (other than this Feature itself). If /// this Feature is not conjugated, then the typingFeatures consist of all subsetted Features, except /// from CrossSubsetting, and the last chainingFeature (if any). If this Feature is conjugated, then the - /// typingFeatures are only its originalType (if the originalType is a Feature). - /// Note. CrossSubsetting is excluded from the determination of the type of a + /// typingFeatures are only its originalType (if the originalType is a Feature). + /// Note. CrossSubsetting is excluded from the determination of the type of a /// Feature in order to avoid circularity in the construction of implied CrossSubsetting relationships. /// The validateFeatureCrossFeatureType requires that the crossFeature of a Feature have the same type /// as the Feature. /// + /// + /// OCL (KerML XMI): + /// + /// if not isConjugated then + /// let subsettedFeatures : OrderedSet(Feature) = + /// subsetting->reject(s | s.oclIsKindOf(CrossSubsetting)).subsettedFeatures in + /// if chainingFeature->isEmpty() or + /// subsettedFeature->includes(chainingFeature->last()) + /// then subsettedFeatures + /// else subsettedFeatures->append(chainingFeature->last()) + /// endif + /// else if conjugator.originalType.oclIsKindOf(Feature) then + /// OrderedSet{conjugator.originalType.oclAsType(Feature)} + /// else OrderedSet{} + /// endif endif + /// + /// Return the Features used to determine the types of this Feature (other than this Feature itself). + /// /// - /// The subject + /// The subject /// /// /// The expected collection of /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeTypingFeaturesOperation(this IFeature featureSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (featureSubject == null) + { + throw new ArgumentNullException(nameof(featureSubject)); + } + + if (!featureSubject.isConjugated) + { + var subsettedFeatures = featureSubject.OwnedRelationship + .OfType() + .Where(s => s is not ICrossSubsetting) + .Select(s => s.SubsettedFeature) + .Where(f => f != null) + .Distinct() + .ToList(); + + var chainingFeatures = featureSubject.chainingFeature; + + if (chainingFeatures.Count > 0 && !subsettedFeatures.Contains(chainingFeatures[^1])) + { + subsettedFeatures.Add(chainingFeatures[^1]); + } + + return subsettedFeatures; + } + + var conjugator = featureSubject.ownedConjugator; + + if (conjugator?.OriginalType is IFeature originalFeature) + { + return [originalFeature]; + } + + return []; } /// @@ -468,46 +973,112 @@ internal static List ComputeTypingFeaturesOperation(this IFeature feat /// represented by this Feature. (If isCartesianProduct is not true, the operation will still return a /// valid value, it will just not represent anything useful.) /// + /// + /// OCL (KerML XMI): + /// + /// featuringType->select(t | t.owner <> self)-> + /// union(featuringType->select(t | t.owner = self)-> + /// selectByKind(Feature).asCartesianProduct())-> + /// union(type) + /// + /// If isCartesianProduct is true, then return the list of Types whose Cartesian product can be + /// represented by this Feature. (If isCartesianProduct is not true, the operation will still return a + /// valid value, it will just not represent anything useful.) + /// /// - /// The subject + /// The subject /// /// /// The expected collection of /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeAsCartesianProductOperation(this IFeature featureSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (featureSubject == null) + { + throw new ArgumentNullException(nameof(featureSubject)); + } + + var notOwnedBySelf = featureSubject.featuringType + .Where(t => t.owner != featureSubject); + + var ownedBySelf = featureSubject.featuringType + .Where(t => t.owner == featureSubject) + .OfType() + .SelectMany(f => f.AsCartesianProduct()); + + return [..notOwnedBySelf.Concat(ownedBySelf).Concat(featureSubject.type)]; } /// /// Check whether this Feature can be used to represent a Cartesian product of Types. /// + /// + /// OCL (KerML XMI): + /// + /// type->size() = 1 and + /// featuringType.size() = 1 and + /// (featuringType.first().owner = self implies + /// featuringType.first().oclIsKindOf(Feature) and + /// featuringType.first().oclAsType(Feature).isCartesianProduct()) + /// + /// Check whether this Feature can be used to represent a Cartesian product of Types. + /// /// - /// The subject + /// The subject /// /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static bool ComputeIsCartesianProductOperation(this IFeature featureSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (featureSubject == null) + { + throw new ArgumentNullException(nameof(featureSubject)); + } + + if (featureSubject.type.Count != 1 || featureSubject.featuringType.Count != 1) + { + return false; + } + + var firstFeaturingType = featureSubject.featuringType[0]; + + if (firstFeaturingType.owner != featureSubject) + { + return true; + } + + return firstFeaturingType is IFeature featuringFeature + && featuringFeature.IsCartesianProduct(); } /// /// Return whether this Feature is an owned cross Feature of an end Feature. /// + /// + /// OCL (KerML XMI): + /// + /// owningNamespace <> null and + /// owningNamespace.oclIsKindOf(Feature) and + /// owningNamespace.oclAsType(Feature).ownedCrossFeature() = self + /// + /// Return whether this Feature is an owned cross Feature of an end Feature. + /// /// - /// The subject + /// The subject /// /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static bool ComputeIsOwnedCrossFeatureOperation(this IFeature featureSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (featureSubject == null) + { + throw new ArgumentNullException(nameof(featureSubject)); + } + + return featureSubject.owningNamespace is IFeature owningFeature + && owningFeature.OwnedCrossFeature() == featureSubject; } /// @@ -515,31 +1086,106 @@ internal static bool ComputeIsOwnedCrossFeatureOperation(this IFeature featureSu /// Feature that is a Feature, but not a Multiplicity or a MetadataFeature, and whose owningMembership /// is not a FeatureMembership. If this exists, it is the crossFeature of the end Feature. /// + /// + /// OCL (KerML XMI): + /// + /// if not isEnd or owningType = null then null + /// else + /// let ownedMemberFeatures: Sequence(Feature) = + /// ownedMember->selectByKind(Feature)-> + /// reject(oclIsKindOf(Multiplicity) or + /// oclIsKindOf(MetadataFeature) or + /// oclIsKindOf(FeatureValue))-> + /// reject(owningMembership.oclIsKindOf(FeatureMembership)) in + /// if ownedMemberFeatures.isEmpty() then null + /// else ownedMemberFeatures->first() + /// endif + /// + /// If this Feature is an end Feature of its owningType, then return the first ownedMember of the + /// Feature that is a Feature, but not a Multiplicity or a MetadataFeature, and whose owningMembership + /// is not a FeatureMembership. If this exists, it is the crossFeature of the end Feature. + /// /// - /// The subject + /// The subject /// /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IFeature ComputeOwnedCrossFeatureOperation(this IFeature featureSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (featureSubject == null) + { + throw new ArgumentNullException(nameof(featureSubject)); + } + + if (!featureSubject.IsEnd || featureSubject.owningType == null) + { + return null; + } + + return featureSubject.OwnedRelationship + .OfType() + .Where(om => om is not IFeatureMembership) + .SelectMany(om => om.OwnedRelatedElement) + .OfType() + .Where(f => f is not IMultiplicity + && f is not IMetadataFeature + && f is not IFeatureValue) + .FirstOrDefault(); } /// /// Return this Feature and all the Features that are directly or indirectly Redefined by this Feature. /// + /// + /// OCL (KerML XMI): + /// + /// ownedRedefinition.redefinedFeature-> + /// closure(ownedRedefinition.redefinedFeature)-> + /// asOrderedSet()->prepend(self) + /// + /// Return this Feature and all the Features that are directly or indirectly Redefined by this Feature. + /// /// - /// The subject + /// The subject /// /// /// The expected collection of /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeAllRedefinedFeaturesOperation(this IFeature featureSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (featureSubject == null) + { + throw new ArgumentNullException(nameof(featureSubject)); + } + + var result = new List { featureSubject }; + var visited = new HashSet { featureSubject }; + var queue = new Queue(); + + foreach (var redefinition in featureSubject.OwnedRelationship.OfType()) + { + if (redefinition.RedefinedFeature != null && visited.Add(redefinition.RedefinedFeature)) + { + queue.Enqueue(redefinition.RedefinedFeature); + } + } + + while (queue.Count > 0) + { + var current = queue.Dequeue(); + result.Add(current); + + foreach (var redefinition in current.OwnedRelationship.OfType()) + { + if (redefinition.RedefinedFeature != null && visited.Add(redefinition.RedefinedFeature)) + { + queue.Enqueue(redefinition.RedefinedFeature); + } + } + } + + return result; } /// @@ -549,8 +1195,23 @@ internal static List ComputeAllRedefinedFeaturesOperation(this IFeatur /// is a feature chain whose first chainingFeature has isVariable = true, then also consider it to be /// featured within the owningType of its first chainingFeature. /// + /// + /// OCL (KerML XMI): + /// + /// if type = null then + /// featuringType->forAll(f | f = resolveGlobal('Base::Anything').memberElement) + /// else + /// featuringType->forAll(f | type.isCompatibleWith(f)) or + /// isVariable and type.specializes(owningType) or + /// chainingFeature->notEmpty() and chainingFeature->first().isVariable and + /// type.specializes(chainingFeature->first().owningType) + /// endif + /// + /// Return if the featuringTypes of this Feature are compatible with the given type. If type is null, + /// then check if this Feature is explicitly or implicitly featured by Base::Anything. + /// /// - /// The subject + /// The subject /// /// /// No documentation provided @@ -558,18 +1219,71 @@ internal static List ComputeAllRedefinedFeaturesOperation(this IFeatur /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static bool ComputeIsFeaturedWithinOperation(this IFeature featureSubject, IType type) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (featureSubject == null) + { + throw new ArgumentNullException(nameof(featureSubject)); + } + + if (type == null) + { + var anythingMembership = featureSubject.ResolveGlobal("Base::Anything"); + var anythingElement = anythingMembership?.MemberElement; + + return featureSubject.featuringType.Count == 0 || featureSubject.featuringType.All(f => f == anythingElement); + } + + if (featureSubject.featuringType.All(f => type.IsCompatibleWith(f))) + { + return true; + } + + if (featureSubject.IsVariable && featureSubject.owningType != null && type.Specializes(featureSubject.owningType)) + { + return true; + } + + var chainingFeatures = featureSubject.chainingFeature; + + if (chainingFeatures.Count > 0) + { + var firstChaining = chainingFeatures[0]; + + if (firstChaining.IsVariable && firstChaining.owningType != null && type.Specializes(firstChaining.owningType)) + { + return true; + } + } + + return false; } /// /// A Feature can access another feature if the other feature is featured within one of the direct or /// indirect featuringTypes of this Feature. /// + /// + /// OCL (KerML XMI): + /// + /// let anythingType: Element = + /// subsettingFeature.resolveGlobal('Base::Anything').memberElement in + /// let allFeaturingTypes : Sequence(Type) = + /// featuringTypes->closure(t | + /// if not t.oclIsKindOf(Feature) then Sequence{} + /// else + /// let featuringTypes : OrderedSet(Type) = t.oclAsType(Feature).featuringType in + /// if featuringTypes->isEmpty() then Sequence{anythingType} + /// else featuringTypes + /// endif + /// endif) in + /// allFeaturingTypes->exists(t | feature.isFeaturedWithin(t)) + /// + /// A Feature can access another feature if the other feature is featured within one of the direct or + /// indirect featuringTypes of this Feature. + /// /// - /// The subject + /// The subject /// /// /// No documentation provided @@ -577,10 +1291,47 @@ internal static bool ComputeIsFeaturedWithinOperation(this IFeature featureSubje /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static bool ComputeCanAccessOperation(this IFeature featureSubject, IFeature feature) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (featureSubject == null) + { + throw new ArgumentNullException(nameof(featureSubject)); + } + + var anythingMembership = featureSubject.ResolveGlobal("Base::Anything"); + var anythingType = anythingMembership?.MemberElement as IType; + + var visited = new HashSet(); + var queue = new Queue(featureSubject.featuringType); + + while (queue.Count > 0) + { + var current = queue.Dequeue(); + + if (!visited.Add(current)) + { + continue; + } + + if (current is IFeature currentFeature) + { + var innerFeaturingTypes = currentFeature.featuringType; + + if (innerFeaturingTypes.Count == 0 && anythingType != null) + { + queue.Enqueue(anythingType); + } + else + { + foreach (var innerType in innerFeaturingTypes) + { + queue.Enqueue(innerType); + } + } + } + } + + return visited.Any(feature.IsFeaturedWithin); } /// @@ -589,8 +1340,26 @@ internal static bool ComputeCanAccessOperation(this IFeature featureSubject, IFe /// true, then return true if the type is a Feature representing the snapshots of the owningType of this /// Feature. /// + /// + /// OCL (KerML XMI): + /// + /// owningType <> null and + /// if not isVariable then type = owningType + /// else if owningType = resolveGlobal('Occurrences::Occurrence').memberElement then + /// type = resolveGlobal('Occurrences::Occurrence::snapshots').memberElement + /// else + /// type.oclIsKindOf(Feature) and + /// let feature : Feature = type.oclAsType(Feature) in + /// feature.featuringType->includes(owningType) and + /// feature.redefinesFromLibrary('Occurrences::Occurrence::snapshots') + /// endif + /// + /// Return whether the given type must be a featuringType of this Feature. If this Feature has + /// isVariable = false, then return true if the type is the owningType of the Feature. If isVariable = + /// true, then return true if the type is a Feature representing the snapshots of the owningType of this Feature. + /// /// - /// The subject + /// The subject /// /// /// No documentation provided @@ -598,10 +1367,34 @@ internal static bool ComputeCanAccessOperation(this IFeature featureSubject, IFe /// /// The expected /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static bool ComputeIsFeaturingTypeOperation(this IFeature featureSubject, IType type) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (featureSubject == null) + { + throw new ArgumentNullException(nameof(featureSubject)); + } + + if (featureSubject.owningType == null) + { + return false; + } + + if (!featureSubject.IsVariable) + { + return type == featureSubject.owningType; + } + + var occurrenceMembership = featureSubject.ResolveGlobal("Occurrences::Occurrence"); + + if (occurrenceMembership?.MemberElement == featureSubject.owningType) + { + var snapshotsMembership = featureSubject.ResolveGlobal("Occurrences::Occurrence::snapshots"); + return snapshotsMembership?.MemberElement == type; + } + + return type is IFeature typeFeature + && typeFeature.featuringType.Contains(featureSubject.owningType) + && typeFeature.RedefinesFromLibrary("Occurrences::Occurrence::snapshots"); } } } diff --git a/SysML2.NET/Extend/RelationshipExtensions.cs b/SysML2.NET/Extend/RelationshipExtensions.cs index 36a804f7..d83fcab3 100644 --- a/SysML2.NET/Extend/RelationshipExtensions.cs +++ b/SysML2.NET/Extend/RelationshipExtensions.cs @@ -36,6 +36,13 @@ internal static class RelationshipExtensions /// /// Computes the derived property. /// + /// + /// OCL (KerML XMI): + /// + /// relatedElement = source->union(target) + /// + /// The relatedElements of a Relationship consist of all of its source Elements followed by all of its target Elements. + /// /// /// The subject /// @@ -51,6 +58,15 @@ internal static List ComputeRelatedElement(this IRelationship relation /// Return whether this Relationship has either an owningRelatedElement or owningRelationship that is a /// library element. /// + /// + /// OCL (KerML XMI): + /// + /// if owningRelatedElement <> null then owningRelatedElement.libraryNamespace() + /// else if owningRelationship <> null then owningRelationship.libraryNamespace() + /// else null endif endif + /// + /// Return whether this Relationship has either an owningRelatedElement or owningRelationship that is a library element. Unlike Element::libraryNamespace which only checks owningRelationship, the Relationship override first checks owningRelatedElement (since a Relationship may be owned by a non-Relationship Element via the owningRelatedElement/ownedRelationship association rather than via owningRelationship). + /// /// /// The subject /// @@ -73,6 +89,17 @@ internal static INamespace ComputeRedefinedLibraryNamespaceOperation(this IRelat /// owningRelatedElement. Otherwise, return the path of the Relationship as specified for an Element in /// general. /// + /// + /// OCL (KerML XMI): + /// + /// if owningRelationship = null and owningRelatedElement <> null then + /// owningRelatedElement.path() + '/' + + /// owningRelatedElement.ownedRelationship->indexOf(self).toString() + /// else self.oclAsType(Element).path() + /// endif + /// + /// If the owningRelationship of the Relationship is null but its owningRelatedElement is non-null, construct the path using the position of the Relationship in the list of ownedRelationships of its owningRelatedElement. Otherwise, return the path of the Relationship as specified for an Element in general. + /// /// /// The subject ///