diff --git a/.editorconfig b/.editorconfig index 20935edd..86fce5ad 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,4 +6,6 @@ dotnet_diagnostic.S3776.severity = none # Disable CS1570/CS1584/CS1658 for all codegen dotnet_diagnostic.CS1570.severity = none dotnet_diagnostic.CS1584.severity = none -dotnet_diagnostic.CS1658.severity = none \ No newline at end of file +dotnet_diagnostic.CS1658.severity = none +# Disable CA1815: generated value types carry data only; equality semantics are not part of their contract +dotnet_diagnostic.CA1815.severity = none \ No newline at end of file diff --git a/SysML2.NET.CodeGenerator.Tests/Generators/UmlHandleBarsGenerators/ImpliedGuardParserTestFixture.cs b/SysML2.NET.CodeGenerator.Tests/Generators/UmlHandleBarsGenerators/ImpliedGuardParserTestFixture.cs new file mode 100644 index 00000000..715eb53f --- /dev/null +++ b/SysML2.NET.CodeGenerator.Tests/Generators/UmlHandleBarsGenerators/ImpliedGuardParserTestFixture.cs @@ -0,0 +1,195 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.CodeGenerator.Tests.Generators.UmlHandleBarsGenerators +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using NUnit.Framework; + + using SysML2.NET.CodeGenerator.Extensions; + + using uml4net.Classification; + using uml4net.SimpleClassifiers; + using uml4net.StructuredClassifiers; + + [TestFixture] + public class ImpliedGuardParserTestFixture + { + /// + /// The metaclass names an owning-Type kind test over two alternatives yields. + /// + private static readonly string[] ExpectedPartTypeNames = ["PartDefinition", "PartUsage"]; + + /// + /// The metaclass name an owned-typing kind test yields. + /// + private static readonly string[] ExpectedDataTypeNames = ["DataType"]; + + [Test] + public void VerifyParse() + { + using (Assert.EnterMultipleScope()) + { + var owningTypeKind = ImpliedGuardParser.Parse("isComposite and owningType <> null and (owningType.oclIsKindOf(PartDefinition) or owningType.oclIsKindOf(PartUsage))"); + Assert.That(owningTypeKind.Shape, Is.EqualTo(ImpliedGuardShape.OwningTypeKind)); + Assert.That(owningTypeKind.RequiresComposite, Is.True); + Assert.That(owningTypeKind.TypeNames, Is.EqualTo(ExpectedPartTypeNames)); + + var withoutComposite = ImpliedGuardParser.Parse("owningType <> null and (owningType.oclIsKindOf(ViewDefinition) or owningType.oclIsKindOf(ViewUsage))"); + Assert.That(withoutComposite.Shape, Is.EqualTo(ImpliedGuardShape.OwningTypeKind)); + Assert.That(withoutComposite.RequiresComposite, Is.False); + + var operationCall = ImpliedGuardParser.Parse("isSubactionUsage()"); + Assert.That(operationCall.Shape, Is.EqualTo(ImpliedGuardShape.OperationCall)); + Assert.That(operationCall.MemberName, Is.EqualTo("isSubactionUsage")); + Assert.That(operationCall.IsNegated, Is.False); + + var negated = ImpliedGuardParser.Parse("not isTriggerAction()"); + Assert.That(negated.Shape, Is.EqualTo(ImpliedGuardShape.OperationCall)); + Assert.That(negated.IsNegated, Is.True); + + var withArgument = ImpliedGuardParser.Parse("isSubstateUsage(true)"); + Assert.That(withArgument.Shape, Is.EqualTo(ImpliedGuardShape.OperationCall)); + Assert.That(withArgument.Literal, Is.EqualTo("true")); + + var endCount = ImpliedGuardParser.Parse("ownedEndFeature->size() = 2"); + Assert.That(endCount.Shape, Is.EqualTo(ImpliedGuardShape.OwnedEndFeatureCount)); + Assert.That(endCount.Literal, Is.EqualTo("2")); + + var notEmpty = ImpliedGuardParser.Parse("ownedEndFeatures->notEmpty()"); + Assert.That(notEmpty.Shape, Is.EqualTo(ImpliedGuardShape.OwnedEndFeatureCount)); + Assert.That(notEmpty.Literal, Is.Null); + + var ownedTyping = ImpliedGuardParser.Parse("ownedTyping.type->exists(selectByKind(DataType))"); + Assert.That(ownedTyping.Shape, Is.EqualTo(ImpliedGuardShape.OwnedTypingKind)); + Assert.That(ownedTyping.TypeNames, Is.EqualTo(ExpectedDataTypeNames)); + + var membership = ImpliedGuardParser.Parse("owningFeatureMembership <> null and owningFeatureMembership.oclIsKindOf(StakeholderMembership)"); + Assert.That(membership.Shape, Is.EqualTo(ImpliedGuardShape.OwningFeatureMembershipKind)); + + var enumeration = ImpliedGuardParser.Parse("portionKind = PortionKind::timeslice"); + Assert.That(enumeration.Shape, Is.EqualTo(ImpliedGuardShape.EnumerationComparison)); + Assert.That(enumeration.MemberName, Is.EqualTo("portionKind")); + Assert.That(enumeration.Literal, Is.EqualTo("timeslice")); + + var booleanProperty = ImpliedGuardParser.Parse("isIndividual"); + Assert.That(booleanProperty.Shape, Is.EqualTo(ImpliedGuardShape.BooleanProperty)); + + // Multi-line OCL from the XMI must normalise before matching. + var multiLine = ImpliedGuardParser.Parse("owningType <> null and\n (owningType.oclIsKindOf(Behavior) or\n owningType.oclIsKindOf(Step))"); + Assert.That(multiLine.Shape, Is.EqualTo(ImpliedGuardShape.OwningTypeKind)); + } + } + + [Test] + public void VerifyParseRejectsWhatItCannotTranslate() + { + using (Assert.EnterMultipleScope()) + { + // A nested oclAsType navigation is beyond the recognised shapes and must NOT be approximated. + var nested = ImpliedGuardParser.Parse("isComposite and owningType <> null and (owningType.oclIsKindOf(Structure) or owningType.oclIsKindOf(Feature) and owningType.oclAsType(Feature).type->exists(oclIsKindOf(Structure)))"); + Assert.That(nested.Shape, Is.EqualTo(ImpliedGuardShape.RequiresHandCoding)); + + // An extra conjunct beyond the recognised owner-kind shape likewise falls back. + var extraConjunct = ImpliedGuardParser.Parse("isComposite and owningType <> null and (owningType.oclIsKindOf(StateDefinition) or owningType.oclIsKindOf(StateUsage)) and source <> null and source.oclIsKindOf(StateUsage)"); + Assert.That(extraConjunct.Shape, Is.EqualTo(ImpliedGuardShape.RequiresHandCoding)); + + Assert.That(ImpliedGuardParser.Parse(null).Shape, Is.EqualTo(ImpliedGuardShape.RequiresHandCoding)); + Assert.That(ImpliedGuardParser.Parse(string.Empty).Shape, Is.EqualTo(ImpliedGuardShape.RequiresHandCoding)); + Assert.That(ImpliedGuardParser.Parse(" ").Shape, Is.EqualTo(ImpliedGuardShape.RequiresHandCoding)); + } + } + + /// + /// Pins how much of the REAL constraint set the parser covers, so a regression in the patterns shows + /// up as a coverage drop rather than silently shifting guards into hand-coding. + /// + [Test] + public void VerifyCoverageOfTheActualConstraintSet() + { + var guarded = GeneratorSetupFixture.XmiReaderResult + .QueryImpliedRelationshipRules() + .Where(rule => rule.Form == ImpliedRuleForm.GuardedLibrarySpecialization) + .ToList(); + + var parsed = guarded + .Select(rule => ImpliedGuardParser.Parse(rule.GuardExpression)) + .ToList(); + + var translatable = parsed.Count(expression => expression.Shape != ImpliedGuardShape.RequiresHandCoding); + + using (Assert.EnterMultipleScope()) + { + Assert.That(guarded, Has.Count.EqualTo(63), "The number of guarded constraints in the abstract syntax changed."); + Assert.That(translatable, Is.EqualTo(46), "Guard-shape coverage changed; re-check the patterns against the OCL."); + } + } + + /// + /// Emits a predicate for every translatable guard in the real constraint set, so a shape that parses + /// but cannot be rendered — an unknown metaclass, say — is caught here rather than as a compile + /// failure in the generated assembly. + /// + [Test] + public void VerifyEveryTranslatableGuardEmitsAPredicate() + { + var interfaceFqnByName = QueryInterfaceFqnByName(); + var enumerationFqnByName = QueryEnumerationFqnByName(); + + var unrenderable = GeneratorSetupFixture.XmiReaderResult + .QueryImpliedRelationshipRules() + .Where(rule => rule.Form == ImpliedRuleForm.GuardedLibrarySpecialization) + .Select(rule => new + { + rule.ConstraintName, + rule.MetaclassName, + Expression = ImpliedGuardParser.Parse(rule.GuardExpression) + }) + .Where(candidate => candidate.Expression.Shape != ImpliedGuardShape.RequiresHandCoding) + .Where(candidate => !interfaceFqnByName.TryGetValue(candidate.MetaclassName, out var declaringFqn) + || ImpliedGuardEmitter.Emit(candidate.Expression, declaringFqn, interfaceFqnByName, enumerationFqnByName) == null) + .Select(candidate => candidate.ConstraintName) + .ToList(); + + Assert.That(unrenderable, Is.Empty, $"These guards parse but emit no predicate: {string.Join(", ", unrenderable)}"); + } + + private static Dictionary QueryInterfaceFqnByName() + { + return GeneratorSetupFixture.XmiReaderResult + .QueryContainedAndImported("SysML") + .SelectMany(package => package.PackagedElement.OfType()) + .GroupBy(umlClass => umlClass.Name, StringComparer.Ordinal) + .ToDictionary(group => group.Key, group => group.First().QueryFullyQualifiedTypeName(), StringComparer.Ordinal); + } + + private static Dictionary QueryEnumerationFqnByName() + { + return GeneratorSetupFixture.XmiReaderResult + .QueryContainedAndImported("SysML") + .SelectMany(package => package.PackagedElement.OfType()) + .GroupBy(enumeration => enumeration.Name, StringComparer.Ordinal) + .ToDictionary(group => group.Key, group => group.First().QueryFullyQualifiedTypeName(), StringComparer.Ordinal); + } + } +} diff --git a/SysML2.NET.CodeGenerator.Tests/Generators/UmlHandleBarsGenerators/ImpliedRelationshipExtensionsTestFixture.cs b/SysML2.NET.CodeGenerator.Tests/Generators/UmlHandleBarsGenerators/ImpliedRelationshipExtensionsTestFixture.cs new file mode 100644 index 00000000..0070af2a --- /dev/null +++ b/SysML2.NET.CodeGenerator.Tests/Generators/UmlHandleBarsGenerators/ImpliedRelationshipExtensionsTestFixture.cs @@ -0,0 +1,89 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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.CodeGenerator.Tests.Generators.UmlHandleBarsGenerators +{ + using System.Linq; + + using NUnit.Framework; + + using SysML2.NET.CodeGenerator.Extensions; + + [TestFixture] + public class ImpliedRelationshipExtensionsTestFixture + { + [Test] + public void VerifyQueryImpliedRelationshipRules() + { + var rules = GeneratorSetupFixture.XmiReaderResult.QueryImpliedRelationshipRules(); + + using (Assert.EnterMultipleScope()) + { + // Only `check` rules are semantic constraints. `derive` rules are derivations and `validate` + // rules are validation constraints (KerML §8.3.1); neither implies a Relationship, and both + // carry the same category keywords in their names, so they must not leak in. + Assert.That(rules.Select(rule => rule.ConstraintName), Is.All.StartWith("check")); + + // Category totals, cross-checked against an independent scan of the raw XMI. + Assert.That(rules.Count(rule => rule.Category == ImpliedConstraintCategory.Specialization), Is.EqualTo(175)); + Assert.That(rules.Count(rule => rule.Category == ImpliedConstraintCategory.Redefinition), Is.EqualTo(15)); + Assert.That(rules.Count(rule => rule.Category == ImpliedConstraintCategory.TypeFeaturing), Is.EqualTo(7)); + Assert.That(rules.Count(rule => rule.Category == ImpliedConstraintCategory.BindingConnector), Is.EqualTo(11)); + + // Form split — this is what decides how much is generated versus hand-written. + Assert.That(rules.Count(rule => rule.Form == ImpliedRuleForm.UnconditionalLibrarySpecialization), Is.EqualTo(85)); + Assert.That(rules.Count(rule => rule.Form == ImpliedRuleForm.GuardedLibrarySpecialization), Is.EqualTo(63)); + Assert.That(rules.Count(rule => rule.Form == ImpliedRuleForm.SpecificationTbd), Is.EqualTo(2)); + } + + // A representative unconditional rule: the whole body is the library target. + var portUsage = rules.Single(rule => rule.ConstraintName == "checkPortUsageSpecialization"); + + using (Assert.EnterMultipleScope()) + { + Assert.That(portUsage.MetaclassName, Is.EqualTo("PortUsage")); + Assert.That(portUsage.Form, Is.EqualTo(ImpliedRuleForm.UnconditionalLibrarySpecialization)); + Assert.That(portUsage.TargetLibraryName, Is.EqualTo("Ports::ports")); + Assert.That(portUsage.GuardExpression, Is.Null); + } + + // A representative guarded rule: the target is still extracted mechanically, and the guard is + // captured verbatim for a hand-written predicate. + var subport = rules.Single(rule => rule.ConstraintName == "checkPortUsageSubportSpecialization"); + + using (Assert.EnterMultipleScope()) + { + Assert.That(subport.Form, Is.EqualTo(ImpliedRuleForm.GuardedLibrarySpecialization)); + Assert.That(subport.TargetLibraryName, Is.EqualTo("Ports::Port::subports")); + Assert.That(subport.GuardExpression, Is.Not.Empty); + Assert.That(subport.GuardExpression, Does.Not.Contain("specializesFromLibrary")); + } + + // Categories 2-4 relate user-model elements, so they never carry a library target. + Assert.That( + rules.Where(rule => rule.Category != ImpliedConstraintCategory.Specialization).Select(rule => rule.TargetLibraryName), + Is.All.Null); + + // Every rule carries the OCL it was classified from, so a hand-coded arm can be checked + // against the source of truth without re-reading the XMI. + Assert.That(rules.Select(rule => rule.Ocl), Is.All.Not.Null); + } + } +} diff --git a/SysML2.NET.CodeGenerator.Tests/Generators/UmlHandleBarsGenerators/UmlCoreImpliedRelationshipGeneratorTestFixture.cs b/SysML2.NET.CodeGenerator.Tests/Generators/UmlHandleBarsGenerators/UmlCoreImpliedRelationshipGeneratorTestFixture.cs new file mode 100644 index 00000000..ce778b8b --- /dev/null +++ b/SysML2.NET.CodeGenerator.Tests/Generators/UmlHandleBarsGenerators/UmlCoreImpliedRelationshipGeneratorTestFixture.cs @@ -0,0 +1,88 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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.CodeGenerator.Tests.Generators.UmlHandleBarsGenerators +{ + using System.IO; + using System.Threading.Tasks; + + using NUnit.Framework; + + using SysML2.NET.CodeGenerator.Generators.UmlHandleBarsGenerators; + + [TestFixture] + public class UmlCoreImpliedRelationshipGeneratorTestFixture + { + private DirectoryInfo outputDirectory; + private UmlCoreImpliedRelationshipGenerator generator; + + [OneTimeSetUp] + public void OneTimeSetup() + { + var directoryInfo = new DirectoryInfo(TestContext.CurrentContext.TestDirectory); + + var path = Path.Combine("UML", "_SysML2.NET.Semantics.AutoGenImplied"); + + this.outputDirectory = directoryInfo.CreateSubdirectory(path); + this.generator = new UmlCoreImpliedRelationshipGenerator(); + } + + [Test] + public async Task VerifyImpliedRelationshipTableIsGenerated() + { + await Assert.ThatAsync( + () => this.generator.GenerateAsync(GeneratorSetupFixture.XmiReaderResult, this.outputDirectory), + Throws.Nothing); + } + + [Test] + public async Task VerifyGeneratedTableCarriesTheExtractedRules() + { + var generatedCode = await this.generator.GenerateImpliedRelationshipTable( + GeneratorSetupFixture.XmiReaderResult, + this.outputDirectory); + + using (Assert.EnterMultipleScope()) + { + // An unconditional rule declared on the metaclass itself. + Assert.That(generatedCode, Does.Contain(@"new(""checkPortUsageSpecialization"", ""Ports::ports"", ""PortUsage"", false)")); + + // A guarded rule keeps its target but is flagged so the caller consults the hand-written + // predicate before applying it. + Assert.That(generatedCode, Does.Contain(@"new(""checkPortUsageSubportSpecialization"", ""Ports::Port::subports"", ""PortUsage"", true)")); + + // Constraints are flattened DOWN the metaclass hierarchy: PartUsage declares none of these, + // it inherits them, and the generated arm must still carry them. + Assert.That(generatedCode, Does.Contain("IPartUsageRules").Or.Contain("PartUsageRules")); + Assert.That(generatedCode, Does.Contain(@"""checkFeatureSpecialization"", ""Base::things"", ""Feature""")); + + // The manifest must account for every constraint that could not be generated, including the + // two whose specification body the OMG left as TBD. + Assert.That(generatedCode, Does.Contain("checkInvocationExpressionDefaultValueBindingConnector")); + Assert.That(generatedCode, Does.Contain("specification body is TBD")); + + // The hand-maintained half of the table — the part the OCL cannot supply — must survive + // into the generated file. + Assert.That(generatedCode, Does.Contain("SubclassificationMetaclasses")); + Assert.That(generatedCode, Does.Contain(@"""PartDefinition""")); + } + } + } +} diff --git a/SysML2.NET.CodeGenerator/Extensions/ImpliedGuardEmitter.cs b/SysML2.NET.CodeGenerator/Extensions/ImpliedGuardEmitter.cs new file mode 100644 index 00000000..67d69148 --- /dev/null +++ b/SysML2.NET.CodeGenerator/Extensions/ImpliedGuardEmitter.cs @@ -0,0 +1,209 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.CodeGenerator.Extensions +{ + using System; + using System.Collections.Generic; + using System.Linq; + + /// + /// Translates a parsed guard expression into the C# predicate the generated guard evaluates. + /// + /// + /// The emitted predicate is a single boolean expression over a parameter named element. A shape + /// the emitter cannot render — because a referenced metaclass is unknown, for instance — yields + /// null, which keeps the constraint in the hand-coded set rather than emitting something that + /// does not compile or, worse, compiles and is wrong. + /// + public static class ImpliedGuardEmitter + { + /// + /// Emits the C# predicate for a parsed guard expression. + /// + /// The parsed guard expression. + /// The fully qualified interface of the declaring metaclass. + /// The fully qualified interface of every known metaclass, by name. + /// The fully qualified name of every known enumeration, by name. + /// The predicate over element, or null when it cannot be rendered. + /// Thrown when is null. + public static string Emit(ImpliedGuardExpression expression, string declaringInterfaceFqn, IReadOnlyDictionary interfaceFqnByName, IReadOnlyDictionary enumerationFqnByName) + { + ArgumentNullException.ThrowIfNull(expression); + + if (string.IsNullOrWhiteSpace(declaringInterfaceFqn)) + { + return null; + } + + var subject = $"element is {declaringInterfaceFqn} guardSubject"; + + return expression.Shape switch + { + ImpliedGuardShape.BooleanProperty => $"element is {declaringInterfaceFqn} {{ {PascalCase(expression.MemberName)}: true }}", + ImpliedGuardShape.OperationCall => EmitOperationCall(expression, subject), + ImpliedGuardShape.OwningTypeKind => EmitOwningTypeKind(expression, declaringInterfaceFqn, interfaceFqnByName), + ImpliedGuardShape.OwnedEndFeatureCount => EmitOwnedEndFeatureCount(expression, subject), + ImpliedGuardShape.OwnedTypingKind => EmitOwnedTypingKind(expression, subject, interfaceFqnByName), + ImpliedGuardShape.OwningFeatureMembershipKind => EmitOwningFeatureMembershipKind(expression, declaringInterfaceFqn, interfaceFqnByName), + ImpliedGuardShape.EnumerationComparison => EmitEnumerationComparison(expression, declaringInterfaceFqn, enumerationFqnByName), + _ => null + }; + } + + /// + /// Emits a boolean operation call, honouring negation and an optional boolean argument. + /// + /// The parsed guard expression. + /// The type-pattern prefix binding guardSubject. + /// The predicate. + private static string EmitOperationCall(ImpliedGuardExpression expression, string subject) + { + var argument = expression.Literal ?? string.Empty; + var call = $"guardSubject.{PascalCase(expression.MemberName)}({argument})"; + + return $"{subject} && {(expression.IsNegated ? "!" : string.Empty)}{call}"; + } + + /// + /// Emits an owning-Type kind test, optionally conjoined with the composite flag. + /// + /// The parsed guard expression. + /// The fully qualified interface of the declaring metaclass. + /// The fully qualified interface of every known metaclass, by name. + /// The predicate, or null when a metaclass is unknown. + /// + /// Emitted as one merged property pattern rather than a chain of conjuncts, so the whole condition + /// reads as a single shape test. + /// + private static string EmitOwningTypeKind(ImpliedGuardExpression expression, string declaringInterfaceFqn, IReadOnlyDictionary interfaceFqnByName) + { + if (!TryQueryInterfaces(expression.TypeNames, interfaceFqnByName, out var alternatives)) + { + return null; + } + + var composite = expression.RequiresComposite ? "IsComposite: true, " : string.Empty; + + return $"element is {declaringInterfaceFqn} {{ {composite}owningType: {string.Join(" or ", alternatives)} }}"; + } + + /// + /// Emits an owned-end-Feature cardinality test. + /// + /// The parsed guard expression. + /// The type-pattern prefix binding guardSubject. + /// The predicate. + /// + /// The abstract syntax spells the property both ownedEndFeature and ownedEndFeatures; + /// only the singular exists, so the emitted code always uses it. + /// + private static string EmitOwnedEndFeatureCount(ImpliedGuardExpression expression, string subject) + { + var comparison = expression.Literal == null + ? "Count > 0" + : $"Count == {expression.Literal}"; + + return $"{subject} && ((SysML2.NET.Core.POCO.Core.Types.IType)guardSubject).ownedEndFeature.{comparison}"; + } + + /// + /// Emits an owned-typing kind test. + /// + /// The parsed guard expression. + /// The type-pattern prefix binding guardSubject. + /// The fully qualified interface of every known metaclass, by name. + /// The predicate, or null when the metaclass is unknown. + private static string EmitOwnedTypingKind(ImpliedGuardExpression expression, string subject, IReadOnlyDictionary interfaceFqnByName) + { + return TryQueryInterfaces(expression.TypeNames, interfaceFqnByName, out var alternatives) + ? $"{subject} && guardSubject.ownedTyping.Any(featureTyping => featureTyping.Type is {alternatives[0]})" + : null; + } + + /// + /// Emits an owning-FeatureMembership kind test. + /// + /// The parsed guard expression. + /// The fully qualified interface of the declaring metaclass. + /// The fully qualified interface of every known metaclass, by name. + /// The predicate, or null when the metaclass is unknown. + private static string EmitOwningFeatureMembershipKind(ImpliedGuardExpression expression, string declaringInterfaceFqn, IReadOnlyDictionary interfaceFqnByName) + { + return TryQueryInterfaces(expression.TypeNames, interfaceFqnByName, out var alternatives) + ? $"element is {declaringInterfaceFqn} {{ owningFeatureMembership: {alternatives[0]} }}" + : null; + } + + /// + /// Emits an enumeration-literal comparison. + /// + /// The parsed guard expression. + /// The fully qualified interface of the declaring metaclass. + /// The fully qualified name of every known enumeration, by name. + /// The predicate, or null when the enumeration is unknown. + private static string EmitEnumerationComparison(ImpliedGuardExpression expression, string declaringInterfaceFqn, IReadOnlyDictionary enumerationFqnByName) + { + return enumerationFqnByName.TryGetValue(expression.TypeNames[0], out var enumerationFqn) + ? $"element is {declaringInterfaceFqn} {{ {PascalCase(expression.MemberName)}: {enumerationFqn}.{PascalCase(expression.Literal)} }}" + : null; + } + + /// + /// Resolves metaclass names to their fully qualified interfaces. + /// + /// The metaclass names to resolve. + /// The fully qualified interface of every known metaclass, by name. + /// The resolved interfaces, when every name resolved and at least one was given. + /// when the guard can be emitted from these names. + /// + /// A Try pattern rather than a nullable collection: "a metaclass name is unknown" is an OUTCOME — + /// the guard then falls back to hand-coding — not an empty result, and the two must not be + /// conflated by a caller that iterates what it gets back. + /// + private static bool TryQueryInterfaces(IReadOnlyList typeNames, IReadOnlyDictionary interfaceFqnByName, out List interfaces) + { + var resolved = new List(); + + foreach (var typeName in typeNames) + { + if (!interfaceFqnByName.TryGetValue(typeName, out var interfaceFqn)) + { + interfaces = null; + + return false; + } + + resolved.Add(interfaceFqn); + } + + interfaces = resolved; + + return resolved.Count != 0; + } + + /// + /// Upper-cases the first character, turning an OCL member name into its C# counterpart. + /// + /// The OCL member name. + /// The C# member name. + private static string PascalCase(string name) => string.IsNullOrEmpty(name) ? name : char.ToUpperInvariant(name[0]) + name[1..]; + } +} diff --git a/SysML2.NET.CodeGenerator/Extensions/ImpliedGuardExpression.cs b/SysML2.NET.CodeGenerator/Extensions/ImpliedGuardExpression.cs new file mode 100644 index 00000000..1e1e697a --- /dev/null +++ b/SysML2.NET.CodeGenerator/Extensions/ImpliedGuardExpression.cs @@ -0,0 +1,68 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.CodeGenerator.Extensions +{ + using System.Collections.Generic; + + /// + /// A guard expression parsed into the operands a C# predicate needs. + /// + public class ImpliedGuardExpression + { + /// + /// Gets the recognised shape, or . + /// + public ImpliedGuardShape Shape { get; init; } + + /// + /// Gets the OCL the expression was parsed from, retained for the generated doc comment. + /// + public string Ocl { get; init; } + + /// + /// Gets the property or operation name the shape tests, e.g. isComposite or + /// isSubactionUsage. + /// + public string MemberName { get; init; } + + /// + /// Gets the metaclass names the shape tests against, e.g. PartDefinition and + /// PartUsage. + /// + public IReadOnlyList TypeNames { get; init; } = []; + + /// + /// Gets a value indicating whether the expression is negated, as in not isTriggerAction(). + /// + public bool IsNegated { get; init; } + + /// + /// Gets a value indicating whether the shape is additionally conjoined with isComposite. + /// + public bool RequiresComposite { get; init; } + + /// + /// Gets the literal the shape compares against — an enumeration literal, a boolean argument, or a + /// cardinality — or null when the shape has none. + /// + public string Literal { get; init; } + } +} diff --git a/SysML2.NET.CodeGenerator/Extensions/ImpliedGuardParser.cs b/SysML2.NET.CodeGenerator/Extensions/ImpliedGuardParser.cs new file mode 100644 index 00000000..a285b1ac --- /dev/null +++ b/SysML2.NET.CodeGenerator/Extensions/ImpliedGuardParser.cs @@ -0,0 +1,206 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.CodeGenerator.Extensions +{ + using System.Text.RegularExpressions; + + /// + /// Parses the guard expression of a semantic constraint into one of the mechanically translatable + /// shapes. + /// + /// + /// The parser is deliberately strict: an expression it does not recognise EXACTLY is reported as + /// rather than approximated. A guard that silently + /// mistranslates would inject Specializations a model does not require, corrupting every inheritance + /// result computed from it. + /// + public static partial class ImpliedGuardParser + { + /// + /// Upper bound on a single match, guarding against catastrophic backtracking. + /// + private const int MatchTimeoutMilliseconds = 1000; + + /// + /// The capture group holding the boolean argument of an operation call. + /// + private const string LiteralGroup = "literal"; + + /// + /// Matches a bare boolean property, e.g. isIndividual. + /// + private static readonly Regex BooleanPropertyPattern = + new(@"^(?is[A-Za-z]+)$", RegexOptions.Compiled, System.TimeSpan.FromMilliseconds(MatchTimeoutMilliseconds)); + + /// + /// Matches a boolean operation call, optionally negated and optionally with a boolean argument. + /// + private static readonly Regex OperationCallPattern = + new(@"^(?not\s+)?(?is[A-Za-z]+)\((?true|false)?\)$", RegexOptions.Compiled, System.TimeSpan.FromMilliseconds(MatchTimeoutMilliseconds)); + + /// + /// Matches an owning-Type kind test over two alternatives, optionally conjoined with isComposite. + /// + private static readonly Regex OwningTypeKindPattern = + new(@"^(?isComposite\s+and\s+)?owningType\s*<>\s*null\s+and\s*\(\s*owningType\.oclIsKindOf\((?[A-Za-z]+)\)\s+or\s+owningType\.oclIsKindOf\((?[A-Za-z]+)\)\s*\)$", RegexOptions.Compiled, System.TimeSpan.FromMilliseconds(MatchTimeoutMilliseconds)); + + /// + /// Matches an owned-end-Feature cardinality test. + /// + private static readonly Regex OwnedEndFeatureCountPattern = + new(@"^ownedEndFeatures?->(?:size\(\)\s*=\s*(?[0-9]+)|(?notEmpty\(\)))$", RegexOptions.Compiled, System.TimeSpan.FromMilliseconds(MatchTimeoutMilliseconds)); + + /// + /// Matches an owned-typing kind test. + /// + private static readonly Regex OwnedTypingKindPattern = + new(@"^ownedTyping\.type->exists\(selectByKind\((?[A-Za-z]+)\)\)$", RegexOptions.Compiled, System.TimeSpan.FromMilliseconds(MatchTimeoutMilliseconds)); + + /// + /// Matches an owning-FeatureMembership kind test. + /// + private static readonly Regex OwningFeatureMembershipKindPattern = + new(@"^owningFeatureMembership\s*<>\s*null\s+and\s+owningFeatureMembership\.oclIsKindOf\((?[A-Za-z]+)\)$", RegexOptions.Compiled, System.TimeSpan.FromMilliseconds(MatchTimeoutMilliseconds)); + + /// + /// Matches an enumeration-literal comparison. + /// + private static readonly Regex EnumerationComparisonPattern = + new(@"^(?[a-z][A-Za-z]*)\s*=\s*(?[A-Za-z]+)::(?[a-zA-Z]+)$", RegexOptions.Compiled, System.TimeSpan.FromMilliseconds(MatchTimeoutMilliseconds)); + + /// + /// Parses a guard expression into the operands a C# predicate needs. + /// + /// The guard OCL, i.e. the antecedent of the implies. + /// The parsed expression; its shape is RequiresHandCoding when unrecognised. + public static ImpliedGuardExpression Parse(string guardOcl) + { + var normalised = Normalise(guardOcl); + + if (string.IsNullOrWhiteSpace(normalised)) + { + return new ImpliedGuardExpression { Shape = ImpliedGuardShape.RequiresHandCoding, Ocl = guardOcl }; + } + + var owningTypeKind = OwningTypeKindPattern.Match(normalised); + + if (owningTypeKind.Success) + { + return new ImpliedGuardExpression + { + Shape = ImpliedGuardShape.OwningTypeKind, + Ocl = normalised, + TypeNames = [owningTypeKind.Groups["first"].Value, owningTypeKind.Groups["second"].Value], + RequiresComposite = owningTypeKind.Groups["composite"].Success + }; + } + + var operationCall = OperationCallPattern.Match(normalised); + + if (operationCall.Success) + { + return new ImpliedGuardExpression + { + Shape = ImpliedGuardShape.OperationCall, + Ocl = normalised, + MemberName = operationCall.Groups["member"].Value, + IsNegated = operationCall.Groups["not"].Success, + Literal = operationCall.Groups[LiteralGroup].Success ? operationCall.Groups[LiteralGroup].Value : null + }; + } + + var ownedEndFeatureCount = OwnedEndFeatureCountPattern.Match(normalised); + + if (ownedEndFeatureCount.Success) + { + return new ImpliedGuardExpression + { + Shape = ImpliedGuardShape.OwnedEndFeatureCount, + Ocl = normalised, + Literal = ownedEndFeatureCount.Groups["notEmpty"].Success ? null : ownedEndFeatureCount.Groups[LiteralGroup].Value + }; + } + + var ownedTypingKind = OwnedTypingKindPattern.Match(normalised); + + if (ownedTypingKind.Success) + { + return new ImpliedGuardExpression + { + Shape = ImpliedGuardShape.OwnedTypingKind, + Ocl = normalised, + TypeNames = [ownedTypingKind.Groups["first"].Value] + }; + } + + var owningFeatureMembershipKind = OwningFeatureMembershipKindPattern.Match(normalised); + + if (owningFeatureMembershipKind.Success) + { + return new ImpliedGuardExpression + { + Shape = ImpliedGuardShape.OwningFeatureMembershipKind, + Ocl = normalised, + TypeNames = [owningFeatureMembershipKind.Groups["first"].Value] + }; + } + + var enumerationComparison = EnumerationComparisonPattern.Match(normalised); + + if (enumerationComparison.Success) + { + return new ImpliedGuardExpression + { + Shape = ImpliedGuardShape.EnumerationComparison, + Ocl = normalised, + MemberName = enumerationComparison.Groups["member"].Value, + TypeNames = [enumerationComparison.Groups["enumeration"].Value], + Literal = enumerationComparison.Groups[LiteralGroup].Value + }; + } + + var booleanProperty = BooleanPropertyPattern.Match(normalised); + + return booleanProperty.Success + ? new ImpliedGuardExpression + { + Shape = ImpliedGuardShape.BooleanProperty, + Ocl = normalised, + MemberName = booleanProperty.Groups["member"].Value + } + : new ImpliedGuardExpression { Shape = ImpliedGuardShape.RequiresHandCoding, Ocl = normalised }; + } + + /// + /// Collapses the whitespace an XMI body carries across lines into single spaces. + /// + /// The raw guard OCL. + /// The single-line form, or null when the input is null. + private static string Normalise(string guardOcl) => guardOcl == null ? null : WhitespaceRunPattern().Replace(guardOcl, " ").Trim(); + + /// + /// Matches a run of whitespace, including the line breaks an XMI body carries. + /// + /// The source-generated pattern. + [GeneratedRegex(@"\s+", RegexOptions.None, MatchTimeoutMilliseconds)] + private static partial Regex WhitespaceRunPattern(); + } +} diff --git a/SysML2.NET.CodeGenerator/Extensions/ImpliedGuardShape.cs b/SysML2.NET.CodeGenerator/Extensions/ImpliedGuardShape.cs new file mode 100644 index 00000000..d33e5fa0 --- /dev/null +++ b/SysML2.NET.CodeGenerator/Extensions/ImpliedGuardShape.cs @@ -0,0 +1,78 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.CodeGenerator.Extensions +{ + /// + /// The mechanically translatable shapes a semantic-constraint guard expression can take. + /// + /// + /// A guard is the antecedent of an <guard> implies specializesFromLibrary('…') constraint. + /// These shapes cover the majority of them; anything else is reported as + /// so it is never silently mistranslated. + /// + public enum ImpliedGuardShape + { + /// + /// The guard is not one of the recognised shapes and must be written by hand. + /// + RequiresHandCoding, + + /// + /// A bare boolean property, e.g. isIndividual. + /// + BooleanProperty, + + /// + /// A boolean operation call, optionally negated, e.g. isSubactionUsage(), + /// not isTriggerAction(), isSubstateUsage(true). + /// + OperationCall, + + /// + /// An owning-Type kind test over two alternatives, optionally conjoined with isComposite, + /// e.g. owningType <> null and (owningType.oclIsKindOf(PartDefinition) or + /// owningType.oclIsKindOf(PartUsage)). + /// + OwningTypeKind, + + /// + /// An owned-end-Feature cardinality test, e.g. ownedEndFeature->size() = 2 or + /// ownedEndFeatures->notEmpty(). + /// + OwnedEndFeatureCount, + + /// + /// An owned-typing kind test, e.g. ownedTyping.type->exists(selectByKind(DataType)). + /// + OwnedTypingKind, + + /// + /// An owning-FeatureMembership kind test, e.g. owningFeatureMembership <> null and + /// owningFeatureMembership.oclIsKindOf(StakeholderMembership). + /// + OwningFeatureMembershipKind, + + /// + /// An enumeration-literal comparison, e.g. portionKind = PortionKind::timeslice. + /// + EnumerationComparison + } +} diff --git a/SysML2.NET.CodeGenerator/Extensions/ImpliedRelationshipExtensions.cs b/SysML2.NET.CodeGenerator/Extensions/ImpliedRelationshipExtensions.cs new file mode 100644 index 00000000..703c14d2 --- /dev/null +++ b/SysML2.NET.CodeGenerator/Extensions/ImpliedRelationshipExtensions.cs @@ -0,0 +1,236 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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.CodeGenerator.Extensions +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text.RegularExpressions; + + using uml4net.CommonStructure; + using uml4net.StructuredClassifiers; + using uml4net.Values; + using uml4net.xmi.Readers; + + /// + /// Extracts the KerML/SysML semantic constraints that a tool may satisfy by inserting implied + /// Relationships, as described in KerML 1.0 §8.4.2. + /// + /// + /// §8.4.2 names four categories of semantic constraint and fixes a naming convention for each — the + /// constraint name always contains the word Specialization, Redefinition, + /// TypeFeaturing or BindingConnector. Only the check-prefixed rules are semantic + /// constraints; derive rules are derivations and validate rules are validation constraints + /// (§8.3.1), and neither implies a Relationship. + /// + /// The normative catalogue of what to insert for each constraint is the set of tables in KerML §8.4.3.1.1 + /// (Tables 8, 9), §8.4.4.1 (Tables 10, 11) and SysML §Tables 31-33. This extractor reads the machine-readable + /// XMI as a proxy for those tables. Where the two disagree, the tables win — reconciling them is a + /// deliberate follow-up, and exists so that no constraint + /// is silently dropped in the meantime. + /// + /// + public static partial class ImpliedRelationshipExtensions + { + /// + /// Upper bound on a single match, so a pathological OCL body cannot stall generation. + /// + private const int MatchTimeoutMilliseconds = 2000; + + /// + /// Matches an OCL body that is nothing but a library specialization, e.g. + /// specializesFromLibrary('Ports::ports'). + /// + /// The source-generated pattern. + [GeneratedRegex(@"^specializesFromLibrary\('(?[^']+)'\)$", RegexOptions.None, MatchTimeoutMilliseconds)] + private static partial Regex UnconditionalLibraryPattern(); + + /// + /// Matches an OCL body of the form <guard> implies specializesFromLibrary('X::y'). The + /// guard is captured verbatim; translating it into a C# predicate is hand-work, but the TARGET is + /// still extracted mechanically. + /// + /// The source-generated pattern. + [GeneratedRegex(@"^(?.+?)\bimplies\b\s*specializesFromLibrary\('(?[^']+)'\)$", RegexOptions.None, MatchTimeoutMilliseconds)] + private static partial Regex GuardedLibraryPattern(); + + /// + /// The four category keywords of KerML §8.4.2, in the order the specification lists them. + /// + private static readonly (string Keyword, ImpliedConstraintCategory Category)[] CategoryKeywords = + [ + ("Specialization", ImpliedConstraintCategory.Specialization), + ("Redefinition", ImpliedConstraintCategory.Redefinition), + ("TypeFeaturing", ImpliedConstraintCategory.TypeFeaturing), + ("BindingConnector", ImpliedConstraintCategory.BindingConnector) + ]; + + /// + /// Extracts every semantic constraint that may be satisfied by an implied Relationship, from + /// every reachable from the merged model. + /// + /// + /// The holding the merged KerML + SysML model + /// + /// + /// The extracted rules, ordered by metaclass then constraint name + /// + public static IReadOnlyList QueryImpliedRelationshipRules(this XmiReaderResult xmiReaderResult) + { + ArgumentNullException.ThrowIfNull(xmiReaderResult); + + var rules = new List(); + + foreach (var umlClass in xmiReaderResult.QueryContainedAndImported("SysML").SelectMany(package => package.PackagedElement.OfType())) + { + rules.AddRange(umlClass.OwnedRule + .Where(IsSemanticConstraint) + .Select(rule => CreateRule(umlClass, rule)) + .Where(rule => rule != null)); + } + + return + [ + ..rules + .OrderBy(rule => rule.MetaclassName, StringComparer.Ordinal) + .ThenBy(rule => rule.ConstraintName, StringComparer.Ordinal) + ]; + } + + /// + /// Determines whether a constraint is one of the §8.4.2 semantic constraints — a check rule + /// whose name carries one of the four category keywords. + /// + /// + /// The to test + /// + /// + /// True when the constraint may imply a Relationship + /// + private static bool IsSemanticConstraint(IConstraint constraint) + { + return !string.IsNullOrWhiteSpace(constraint.Name) + && constraint.Name.StartsWith("check", StringComparison.Ordinal) + && CategoryKeywords.Any(candidate => constraint.Name.Contains(candidate.Keyword, StringComparison.Ordinal)); + } + + /// + /// Projects a single constraint into an , classifying how far the + /// OCL body can be turned into generated code. + /// + /// + /// The the constraint is declared on + /// + /// + /// The to project + /// + /// + /// The rule, or null when the constraint carries no OCL body at all + /// + private static ImpliedRelationshipRule CreateRule(IClass umlClass, IConstraint constraint) + { + var ocl = QueryOclBody(constraint); + + if (ocl == null) + { + return null; + } + + var category = CategoryKeywords.First(candidate => constraint.Name.Contains(candidate.Keyword, StringComparison.Ordinal)).Category; + + var (form, target, guard) = ClassifyOcl(ocl, category); + + return new ImpliedRelationshipRule + { + ConstraintName = constraint.Name, + MetaclassName = umlClass.Name, + Category = category, + Form = form, + TargetLibraryName = target, + GuardExpression = guard, + Ocl = ocl + }; + } + + /// + /// Classifies an OCL body into the form that decides how much of the rule can be generated. + /// + /// + /// The normalised OCL body + /// + /// + /// The §8.4.2 category the constraint belongs to + /// + /// + /// The form, the library target when there is one, and the guard when there is one + /// + private static (ImpliedRuleForm Form, string Target, string Guard) ClassifyOcl(string ocl, ImpliedConstraintCategory category) + { + if (string.Equals(ocl, "TBD", StringComparison.OrdinalIgnoreCase)) + { + return (ImpliedRuleForm.SpecificationTbd, null, null); + } + + // Only specialization constraints target the model libraries by qualified name; redefinition, + // type-featuring and binding-connector constraints relate user-model elements to each other and + // have no mechanically extractable target (§8.4.2 categories 2-4). + if (category != ImpliedConstraintCategory.Specialization) + { + return (ImpliedRuleForm.RequiresHandCoding, null, null); + } + + var unconditional = UnconditionalLibraryPattern().Match(ocl); + + if (unconditional.Success) + { + return (ImpliedRuleForm.UnconditionalLibrarySpecialization, unconditional.Groups["target"].Value, null); + } + + var guarded = GuardedLibraryPattern().Match(ocl); + + return guarded.Success + ? (ImpliedRuleForm.GuardedLibrarySpecialization, guarded.Groups["target"].Value, guarded.Groups["guard"].Value.Trim()) + : (ImpliedRuleForm.RequiresHandCoding, null, null); + } + + /// + /// Returns the constraint's OCL body as a single whitespace-normalised line, or null when the + /// constraint carries no non-blank body. + /// + /// + /// The to read + /// + /// + /// The normalised OCL, or null + /// + private static string QueryOclBody(IConstraint constraint) + { + var opaqueExpression = constraint.Specification?.OfType().FirstOrDefault(); + + var body = opaqueExpression?.Body?.FirstOrDefault(candidate => !string.IsNullOrWhiteSpace(candidate)); + + // Corrected before classification, so the target, the guard and the recorded OCL all agree. + return body == null + ? null + : OclErrata.Apply(string.Join(' ', body.Split((char[])null, StringSplitOptions.RemoveEmptyEntries))); + } + } +} diff --git a/SysML2.NET.CodeGenerator/Extensions/ImpliedRelationshipRule.cs b/SysML2.NET.CodeGenerator/Extensions/ImpliedRelationshipRule.cs new file mode 100644 index 00000000..ee11a0a7 --- /dev/null +++ b/SysML2.NET.CodeGenerator/Extensions/ImpliedRelationshipRule.cs @@ -0,0 +1,118 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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.CodeGenerator.Extensions +{ + /// + /// The category of semantic constraint, per KerML 1.0 §8.4.2. + /// + public enum ImpliedConstraintCategory + { + /// + /// Requires a Type to directly or indirectly specialize a base Type, normally from a model library. + /// + Specialization, + + /// + /// Requires a Redefinition between two Features of a user model. + /// + Redefinition, + + /// + /// Requires a TypeFeaturing between a Feature and a Type of a user model. + /// + TypeFeaturing, + + /// + /// Requires a BindingConnector to exist between two Features of a user model. + /// + BindingConnector + } + + /// + /// How much of a semantic constraint can be turned into generated code. + /// + public enum ImpliedRuleForm + { + /// + /// The whole OCL body is specializesFromLibrary('X::y') — fully generatable. + /// + UnconditionalLibrarySpecialization, + + /// + /// The OCL body is <guard> implies specializesFromLibrary('X::y') — the target is + /// generatable, the guard needs a hand-written predicate. + /// + GuardedLibrarySpecialization, + + /// + /// The constraint relates user-model elements, or its OCL is not in a mechanically extractable + /// shape; the whole rule needs hand-coding. + /// + RequiresHandCoding, + + /// + /// The specification itself leaves the OCL body as TBD, so there is nothing to implement. + /// + SpecificationTbd + } + + /// + /// A single semantic constraint that may be satisfied by inserting an implied Relationship. + /// + public sealed class ImpliedRelationshipRule + { + /// + /// Gets the name of the constraint as declared in the XMI, e.g. checkPortUsageSpecialization. + /// + public string ConstraintName { get; init; } + + /// + /// Gets the name of the metaclass the constraint is declared on. + /// + public string MetaclassName { get; init; } + + /// + /// Gets the §8.4.2 category of the constraint. + /// + public ImpliedConstraintCategory Category { get; init; } + + /// + /// Gets how much of the constraint can be generated. + /// + public ImpliedRuleForm Form { get; init; } + + /// + /// Gets the qualified name of the library Type that must be specialized, or null when the constraint + /// does not target a library element. + /// + public string TargetLibraryName { get; init; } + + /// + /// Gets the OCL guard that gates the specialization, or null when the constraint is unconditional. + /// + public string GuardExpression { get; init; } + + /// + /// Gets the whitespace-normalised OCL body the rule was extracted from. + /// + public string Ocl { get; init; } + } +} diff --git a/SysML2.NET.CodeGenerator/Extensions/OclErrata.cs b/SysML2.NET.CodeGenerator/Extensions/OclErrata.cs new file mode 100644 index 00000000..ea4c5b3d --- /dev/null +++ b/SysML2.NET.CodeGenerator/Extensions/OclErrata.cs @@ -0,0 +1,114 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.CodeGenerator.Extensions +{ + using System; + using System.Collections.Generic; + using System.Linq; + + /// + /// Corrects known defects in the OCL bodies carried by the UML XMI, at generation time. + /// + /// + /// The XMI files under Resources/ are OMG source and are never edited, and the generated output + /// is never hand-edited either — so a defect in a normative OCL body can only be corrected here, on the + /// way from the one to the other. + /// Every entry below is a library qualified name that no Type in the Kernel Semantic Library or + /// the Systems Library declares, so the constraint that carries it cannot be satisfied by any model. + /// Each is evidenced by the same XMI spelling the name correctly elsewhere, by the library declaring + /// the corrected name, or both. Nothing here reinterprets what a constraint MEANS: an erratum only + /// repairs a name that is demonstrably a typo. + /// These corrections are expected to become unnecessary as OMG publishes fixes. On a new XMI + /// release, run the generator and prune whatever reports — an entry + /// that no longer matches has been fixed upstream. `ImpliedRelationshipTargetsTestFixture` fails if a + /// target stops resolving, so a regression cannot pass unnoticed. + /// + public static class OclErrata + { + /// + /// The corrections applied to OCL bodies, keyed by the exact quoted literal they replace. + /// + /// + /// Matching includes the surrounding single quotes, so a correction cannot partially match a + /// longer name — 'Items::Item::subitem' does not match 'Items::Item::subitems' — and + /// re-applying a correction to already-corrected text is a no-op. + /// + private static readonly OclErratum[] Entries = + [ + new("'Action::Action::controls'", "'Actions::Action::controls'", + "The package is 'Actions'; the same XMI uses 'Actions::Action::…' in every other Action constraint."), + new("'Actions::Action::join'", "'Actions::Action::joins'", + "The Systems Library declares 'joins'; no Feature named 'join' exists."), + new("'Items::Item::subitem'", "'Items::Item::subitems'", + "The Systems Library declares 'subitems'; no Feature named 'subitem' exists."), + new("'Objects::Object::ownedPerformance'", "'Objects::Object::ownedPerformances'", + "The Kernel Semantic Library declares 'ownedPerformances'; no Feature named 'ownedPerformance' exists."), + new("'Occurrence::Occurrence::portions'", "'Occurrences::Occurrence::portions'", + "The package is 'Occurrences'; the same XMI uses 'Occurrences::Occurrence::…' for snapshots, timeSlices and timeEnclosedOccurrences."), + new("'Occurrence::Occurrence::suboccurrences'", "'Occurrences::Occurrence::suboccurrences'", + "The package is 'Occurrences'; the same XMI spells this exact name correctly in other constraints."), + new("'Performances::Performance::enclosedPerformance'", "'Performances::Performance::enclosedPerformances'", + "The Kernel Semantic Library declares 'enclosedPerformances'; no Feature named 'enclosedPerformance' exists."), + new("'Performances::Performance::subperformance'", "'Performances::Performance::subperformances'", + "The Kernel Semantic Library declares 'subperformances'; no Feature named 'subperformance' exists.") + ]; + + /// + /// The corrections that have matched at least one OCL body during this generator run. + /// + private static readonly HashSet AppliedOriginals = []; + + /// + /// Applies every known correction to an OCL body. + /// + /// The OCL body read from the XMI, which may be null. + /// The corrected OCL body, or unchanged when nothing applies. + public static string Apply(string ocl) + { + if (string.IsNullOrWhiteSpace(ocl)) + { + return ocl; + } + + return Entries + .Where(erratum => ocl.Contains(erratum.Original, StringComparison.Ordinal)) + .Aggregate(ocl, (corrected, erratum) => + { + AppliedOriginals.Add(erratum.Original); + + return corrected.Replace(erratum.Original, erratum.Replacement); + }); + } + + /// + /// Returns the corrections that matched no OCL body during this generator run. + /// + /// The stale entries, which should be pruned from . + /// + /// Only meaningful once every constraint has been read. A stale entry means the XMI no longer + /// carries the defect — either OMG fixed it, or the constraint was removed. + /// + public static IReadOnlyList QueryUnappliedErrata() + { + return [..Entries.Where(erratum => !AppliedOriginals.Contains(erratum.Original))]; + } + } +} diff --git a/SysML2.NET.CodeGenerator/Extensions/OclErratum.cs b/SysML2.NET.CodeGenerator/Extensions/OclErratum.cs new file mode 100644 index 00000000..db6a1553 --- /dev/null +++ b/SysML2.NET.CodeGenerator/Extensions/OclErratum.cs @@ -0,0 +1,74 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.CodeGenerator.Extensions +{ + using System; + + /// + /// A single correction applied to an OCL body carried by the UML XMI. + /// + public sealed class OclErratum + { + /// + /// Initializes a new instance of the class. + /// + /// The exact text the XMI carries, including any surrounding quotes. + /// The text it is corrected to. + /// The evidence that the original is a defect rather than intent. + /// Thrown when any argument is null or whitespace. + public OclErratum(string original, string replacement, string justification) + { + if (string.IsNullOrWhiteSpace(original)) + { + throw new ArgumentException("The original text is required.", nameof(original)); + } + + if (string.IsNullOrWhiteSpace(replacement)) + { + throw new ArgumentException("The replacement text is required.", nameof(replacement)); + } + + if (string.IsNullOrWhiteSpace(justification)) + { + throw new ArgumentException("A justification is required so the correction can be audited.", nameof(justification)); + } + + this.Original = original; + this.Replacement = replacement; + this.Justification = justification; + } + + /// + /// Gets the exact text the XMI carries. + /// + public string Original { get; } + + /// + /// Gets the text it is corrected to. + /// + public string Replacement { get; } + + /// + /// Gets the evidence that the original is a defect rather than intent. + /// + public string Justification { get; } + } +} diff --git a/SysML2.NET.CodeGenerator/Generators/UmlHandleBarsGenerators/UmlCoreImpliedRelationshipGenerator.cs b/SysML2.NET.CodeGenerator/Generators/UmlHandleBarsGenerators/UmlCoreImpliedRelationshipGenerator.cs new file mode 100644 index 00000000..59cde8dc --- /dev/null +++ b/SysML2.NET.CodeGenerator/Generators/UmlHandleBarsGenerators/UmlCoreImpliedRelationshipGenerator.cs @@ -0,0 +1,480 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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.CodeGenerator.Generators.UmlHandleBarsGenerators +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Threading.Tasks; + + using SysML2.NET.CodeGenerator.Extensions; + + using uml4net.Classification; + using uml4net.Extensions; + using uml4net.HandleBars; + using uml4net.SimpleClassifiers; + using uml4net.StructuredClassifiers; + using uml4net.xmi.Readers; + + using ClassHelper = SysML2.NET.CodeGenerator.HandleBarHelpers.ClassHelper; + using NamedElementHelper = SysML2.NET.CodeGenerator.HandleBarHelpers.NamedElementHelper; + using PropertyHelper = SysML2.NET.CodeGenerator.HandleBarHelpers.PropertyHelper; + + /// + /// Generates the table of implied library Specializations that KerML 1.0 §8.4.2 allows a tool to + /// insert in order to satisfy the specialization constraints of the abstract syntax. + /// + /// + /// Only the machine-readable half of each constraint is generated: the constrained metaclass and the + /// qualified name of the library Type, both taken from the constraint's OCL body. The half that the OCL + /// does NOT carry — whether the implied Relationship is a Subclassification or a + /// Subsetting — lives only in the specification tables (KerML Tables 8 and 10, SysML Tables + /// 31-33) and is hard-coded in the Handlebars template. + /// + /// Constraints whose OCL is not a bare or guarded specializesFromLibrary call are NOT generated; + /// they are emitted into a manifest on the generated class so that no semantic constraint is silently + /// dropped while the hand-coded arms are still outstanding. + /// + /// + public class UmlCoreImpliedRelationshipGenerator : UmlHandleBarsGenerator + { + /// + /// The name of the Handlebars template that emits the table. + /// + private const string ImpliedRelationshipTemplateName = "core-implied-relationship-table-template"; + + /// + /// The name of the file to write into the output directory. + /// + private const string OutputFileName = "ImpliedRelationshipTable.cs"; + + /// + /// The name of the template rendering the generated guards. + /// + private const string ImpliedGuardsTemplateName = "core-implied-guards-template"; + + /// + /// The name of the file the generated guards are written to. + /// + private const string GuardsOutputFileName = "GeneratedImpliedRuleGuards.cs"; + + /// + /// Generates the file in the supplied . + /// + /// + /// The with the loaded UML model + /// + /// + /// The target directory + /// + /// + /// An awaitable + /// + public override async Task GenerateAsync(XmiReaderResult xmiReaderResult, DirectoryInfo outputDirectory) + { + await this.GenerateImpliedRelationshipTable(xmiReaderResult, outputDirectory); + await this.GenerateImpliedRuleGuards(xmiReaderResult, outputDirectory); + } + + /// + /// Generates the file in the supplied . + /// + /// The carrying the abstract syntax. + /// The directory the file is written to. + /// The rendered content. + /// Thrown when either argument is null. + public async Task GenerateImpliedRuleGuards(XmiReaderResult xmiReaderResult, DirectoryInfo outputDirectory) + { + ArgumentNullException.ThrowIfNull(xmiReaderResult); + ArgumentNullException.ThrowIfNull(outputDirectory); + + var payload = QueryImpliedRelationshipPayload(xmiReaderResult); + + var template = this.Templates[ImpliedGuardsTemplateName]; + var rendered = template(payload); + rendered = this.CodeCleanup(rendered); + + await WriteAsync(rendered, outputDirectory, GuardsOutputFileName); + + return rendered; + } + + /// + /// Renders the table, writes it to and returns the generated + /// source for assertion in expected-output tests. + /// + /// + /// The with the loaded UML model + /// + /// + /// The target directory + /// + /// + /// The generated C# source, after CodeCleanup + /// + public async Task GenerateImpliedRelationshipTable(XmiReaderResult xmiReaderResult, DirectoryInfo outputDirectory) + { + ArgumentNullException.ThrowIfNull(xmiReaderResult); + ArgumentNullException.ThrowIfNull(outputDirectory); + + var payload = QueryImpliedRelationshipPayload(xmiReaderResult); + + var template = this.Templates[ImpliedRelationshipTemplateName]; + var rendered = template(payload); + rendered = this.CodeCleanup(rendered); + + await WriteAsync(rendered, outputDirectory, OutputFileName); + + return rendered; + } + + /// + /// Register the custom Handlebars helpers used by the template. + /// + protected override void RegisterHelpers() + { + this.Handlebars.RegisterStringHelper(); + this.Handlebars.RegisterPropertyHelper(); + this.Handlebars.RegisterClassHelper(); + NamedElementHelper.RegisterNamedElementHelper(this.Handlebars); + PropertyHelper.RegisterPropertyHelper(this.Handlebars); + ClassHelper.RegisterClassHelper(this.Handlebars); + } + + /// + /// Register the code template. + /// + protected override void RegisterTemplates() + { + this.RegisterTemplate(ImpliedRelationshipTemplateName); + this.RegisterTemplate(ImpliedGuardsTemplateName); + } + + /// + /// Builds the payload: one entry per metaclass that carries at least one generatable specialization + /// constraint, with the constraints of its supertypes FLATTENED IN, plus the manifest of constraints + /// that still need hand-coding. + /// + /// + /// Flattening at generation time is deliberate: a PartUsage is subject to the specialization + /// constraints of OccurrenceUsage, Usage, Feature and Type as well as its + /// own, and resolving that at run time would mean walking the metaclass hierarchy on every query. + /// Note that this does NOT pre-apply the §8.4.2 redundancy rules — rule 1 asks whether one implied + /// target is a subtype of another, and the targets are library elements that are not present in + /// these XMI files, so that reduction can only happen at run time. + /// + /// + /// The with the loaded UML model + /// + /// + /// The payload consumed by the template + /// + private static ImpliedRelationshipPayload QueryImpliedRelationshipPayload(XmiReaderResult xmiReaderResult) + { + var rules = xmiReaderResult.QueryImpliedRelationshipRules(); + + // Every constraint has now been read, so a correction that matched nothing is stale: the XMI no + // longer carries the defect it repairs. Reported rather than thrown, since a fix upstream must + // not break generation. + foreach (var stale in OclErrata.QueryUnappliedErrata()) + { + Console.WriteLine($"[OclErrata] STALE — {stale.Original} matched no OCL body and should be pruned. Recorded reason: {stale.Justification}"); + } + + var generatable = rules + .Where(rule => rule.Form is ImpliedRuleForm.UnconditionalLibrarySpecialization or ImpliedRuleForm.GuardedLibrarySpecialization) + .ToLookup(rule => rule.MetaclassName, StringComparer.Ordinal); + + var classesByName = xmiReaderResult.QueryContainedAndImported("SysML") + .SelectMany(package => package.PackagedElement.OfType()) + .GroupBy(umlClass => umlClass.Name, StringComparer.Ordinal) + .ToDictionary(group => group.Key, group => group.First(), StringComparer.Ordinal); + + var metaclasses = classesByName.Values + .Select(umlClass => CreateMetaclassRules(umlClass, generatable)) + .Where(metaclass => metaclass.Rules.Count > 0) + .OrderByDescending(metaclass => metaclass.InheritanceDepth) + .ThenBy(metaclass => metaclass.MetaclassName, StringComparer.Ordinal) + .ToList(); + + var notCovered = rules + .Where(rule => rule.Form is ImpliedRuleForm.RequiresHandCoding or ImpliedRuleForm.SpecificationTbd) + .Select(rule => new NotCoveredConstraint + { + ConstraintName = rule.ConstraintName, + MetaclassName = rule.MetaclassName, + Category = rule.Category.ToString(), + Reason = rule.Form == ImpliedRuleForm.SpecificationTbd ? "specification body is TBD" : "OCL is not a specializesFromLibrary call" + }) + .ToList(); + + var allConstraintNames = rules + .Select(rule => rule.ConstraintName) + .Distinct(StringComparer.Ordinal) + .OrderBy(constraintName => constraintName, StringComparer.Ordinal) + .ToList(); + + var interfaceFqnByName = classesByName.ToDictionary(entry => entry.Key, entry => entry.Value.QueryFullyQualifiedTypeName(), StringComparer.Ordinal); + + var enumerationFqnByName = xmiReaderResult.QueryContainedAndImported("SysML") + .SelectMany(package => package.PackagedElement.OfType()) + .GroupBy(enumeration => enumeration.Name, StringComparer.Ordinal) + .ToDictionary(group => group.Key, group => group.First().QueryFullyQualifiedTypeName(), StringComparer.Ordinal); + + var guards = rules + .Where(rule => rule.Form == ImpliedRuleForm.GuardedLibrarySpecialization) + .Select(rule => new + { + rule.ConstraintName, + Expression = ImpliedGuardParser.Parse(rule.GuardExpression), + DeclaringInterfaceFqn = interfaceFqnByName.TryGetValue(rule.MetaclassName, out var declaringFqn) ? declaringFqn : null + }) + .Select(candidate => new ImpliedGuardPayload + { + ConstraintName = candidate.ConstraintName, + Ocl = candidate.Expression.Ocl, + Predicate = ImpliedGuardEmitter.Emit(candidate.Expression, candidate.DeclaringInterfaceFqn, interfaceFqnByName, enumerationFqnByName) + }) + .Where(guard => guard.Predicate != null) + .GroupBy(guard => guard.ConstraintName, StringComparer.Ordinal) + .Select(group => group.First()) + .OrderBy(guard => guard.ConstraintName, StringComparer.Ordinal) + .ToList(); + + var conditionalConstraintNames = rules + .Where(rule => rule.Form == ImpliedRuleForm.GuardedLibrarySpecialization) + .Select(rule => rule.ConstraintName) + .Distinct(StringComparer.Ordinal) + .OrderBy(constraintName => constraintName, StringComparer.Ordinal) + .ToList(); + + var allLibraryTargets = rules + .Where(rule => !string.IsNullOrWhiteSpace(rule.TargetLibraryName)) + .Select(rule => rule.TargetLibraryName) + .Distinct(StringComparer.Ordinal) + .OrderBy(libraryTarget => libraryTarget, StringComparer.Ordinal) + .ToList(); + + return new ImpliedRelationshipPayload + { + Metaclasses = metaclasses, + NotCovered = notCovered, + AllConstraintNames = allConstraintNames, + ConditionalConstraintNames = conditionalConstraintNames, + AllLibraryTargets = allLibraryTargets, + Guards = guards + }; + } + + /// + /// Collects the generatable specialization constraints that apply to a metaclass — its own plus every + /// one inherited from a general classifier — ordered most-general first so the emitted array reads + /// from the root of the hierarchy downwards. + /// + /// + /// The metaclass to project + /// + /// + /// The generatable rules, keyed by declaring metaclass name + /// + /// + /// The metaclass entry, possibly with an empty rule list + /// + private static ImpliedMetaclassRules CreateMetaclassRules(IClass umlClass, ILookup generatable) + { + var generalClassifiers = umlClass.QueryAllGeneralClassifiers().ToList(); + + var applicable = generalClassifiers + .Select(general => general.Name) + .Append(umlClass.Name) + .Distinct(StringComparer.Ordinal) + .SelectMany(name => generatable[name]) + .OrderBy(rule => rule.ConstraintName, StringComparer.Ordinal) + .Select(rule => new ImpliedLibraryRule + { + ConstraintName = rule.ConstraintName, + DeclaringMetaclassName = rule.MetaclassName, + TargetLibraryName = rule.TargetLibraryName, + RequiresGuard = rule.Form == ImpliedRuleForm.GuardedLibrarySpecialization + }) + .ToList(); + + return new ImpliedMetaclassRules + { + MetaclassName = umlClass.Name, + InterfaceFqn = umlClass.QueryFullyQualifiedTypeName(), + InheritanceDepth = generalClassifiers.Count, + IsAbstract = umlClass.IsAbstract, + Rules = applicable + }; + } + } + + /// + /// The payload consumed by the implied-relationship-table template. + /// + public class ImpliedRelationshipPayload + { + /// + /// Gets the metaclasses carrying at least one generatable specialization constraint, ordered + /// most-derived first so the emitted switch matches the narrowest interface first. + /// + public IReadOnlyList Metaclasses { get; init; } + + /// + /// Gets the semantic constraints that could not be generated, emitted as a manifest so that none is + /// silently dropped. + /// + public IReadOnlyList NotCovered { get; init; } + + /// + /// Gets the names of every semantic constraint found in the model, covered or not, so a consumer can + /// report what it does not compute without hard-coding a list. + /// + public IReadOnlyList AllConstraintNames { get; init; } + + /// + /// Gets the names of the constraints whose application is conditional, i.e. every row that requires + /// a guard. + /// + public IReadOnlyList ConditionalConstraintNames { get; init; } + + /// + /// Gets the qualified name of every library Type targeted by a row, without duplicates, so that + /// resolution can be asserted for the whole table. + /// + public IReadOnlyList AllLibraryTargets { get; init; } + + /// + /// Gets the conditional constraints whose guard OCL was mechanically translated into a predicate. + /// + public IReadOnlyList Guards { get; init; } + } + + /// + /// One conditional constraint whose guard was translated into a C# predicate. + /// + public class ImpliedGuardPayload + { + /// + /// Gets the constraint the guard decides. + /// + public string ConstraintName { get; init; } + + /// + /// Gets the guard OCL, emitted as the generated member's doc comment. + /// + public string Ocl { get; init; } + + /// + /// Gets the C# boolean expression over a parameter named element. + /// + public string Predicate { get; init; } + } + + /// + /// The generatable specialization constraints that apply to one metaclass. + /// + public class ImpliedMetaclassRules + { + /// + /// Gets the metaclass name, e.g. PartUsage. + /// + public string MetaclassName { get; init; } + + /// + /// Gets the fully qualified POCO interface name, e.g. + /// SysML2.NET.Core.POCO.Systems.Parts.IPartUsage. + /// + public string InterfaceFqn { get; init; } + + /// + /// Gets the number of general classifiers, used to order the emitted switch most-derived first. + /// + public int InheritanceDepth { get; init; } + + /// + /// Gets a value indicating whether the metaclass is abstract. + /// + public bool IsAbstract { get; init; } + + /// + /// Gets the applicable rules, own and inherited, ordered by constraint name. + /// + public IReadOnlyList Rules { get; init; } + } + + /// + /// A single implied library specialization. + /// + public class ImpliedLibraryRule + { + /// + /// Gets the name of the constraint the rule was extracted from. + /// + public string ConstraintName { get; init; } + + /// + /// Gets the metaclass the constraint is declared on, which may be a supertype of the metaclass the + /// rule is emitted for. The relationship KIND is decided from this name, not from the inheriting + /// metaclass. + /// + public string DeclaringMetaclassName { get; init; } + + /// + /// Gets the qualified name of the library Type that must be specialized. + /// + public string TargetLibraryName { get; init; } + + /// + /// Gets a value indicating whether the constraint's OCL guards the specialization, in which case the + /// rule only applies when a hand-written predicate says so. + /// + public bool RequiresGuard { get; init; } + } + + /// + /// A semantic constraint that the generator could not turn into a table row. + /// + public class NotCoveredConstraint + { + /// + /// Gets the name of the constraint. + /// + public string ConstraintName { get; init; } + + /// + /// Gets the metaclass the constraint is declared on. + /// + public string MetaclassName { get; init; } + + /// + /// Gets the §8.4.2 category of the constraint. + /// + public string Category { get; init; } + + /// + /// Gets why the constraint could not be generated. + /// + public string Reason { get; init; } + } +} diff --git a/SysML2.NET.CodeGenerator/SysML2.NET.CodeGenerator.csproj b/SysML2.NET.CodeGenerator/SysML2.NET.CodeGenerator.csproj index affa35f1..0078c0fa 100644 --- a/SysML2.NET.CodeGenerator/SysML2.NET.CodeGenerator.csproj +++ b/SysML2.NET.CodeGenerator/SysML2.NET.CodeGenerator.csproj @@ -235,6 +235,12 @@ Always + + Always + + + Always + Always diff --git a/SysML2.NET.CodeGenerator/Templates/Uml/core-implied-guards-template.hbs b/SysML2.NET.CodeGenerator/Templates/Uml/core-implied-guards-template.hbs new file mode 100644 index 00000000..44e7193e --- /dev/null +++ b/SysML2.NET.CodeGenerator/Templates/Uml/core-implied-guards-template.hbs @@ -0,0 +1,50 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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. +// +// +// ------------------------------------------------------------------------------------------------ + +// ------------------------------------------------------------------------------------------------ +// --------THIS IS AN AUTOMATICALLY GENERATED FILE. ANY MANUAL CHANGES WILL BE OVERWRITTEN!-------- +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Semantics.Implied +{ + using System.Collections.Generic; + using System.Linq; + + /// + /// The guards whose OCL was mechanically translated from the abstract syntax. + /// + /// + /// A conditional semantic constraint absent from this set has a guard expression outside the + /// translatable shapes and must be supplied by a hand-written . + /// + public static class GeneratedImpliedRuleGuards + { + /// + /// The generated guards, ordered by constraint name. + /// + public static IReadOnlyList All { get; } = + [ +{{#each Guards as | guard |}} + // {{{guard.Ocl}}} + new GeneratedRuleGuard("{{guard.ConstraintName}}", element => {{{guard.Predicate}}}), +{{/each}} + ]; + } +} diff --git a/SysML2.NET.CodeGenerator/Templates/Uml/core-implied-relationship-table-template.hbs b/SysML2.NET.CodeGenerator/Templates/Uml/core-implied-relationship-table-template.hbs new file mode 100644 index 00000000..dbf385db --- /dev/null +++ b/SysML2.NET.CodeGenerator/Templates/Uml/core-implied-relationship-table-template.hbs @@ -0,0 +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. +// +// +// ------------------------------------------------------------------------------------------------ + +// ------------------------------------------------------------------------------------------------ +// --------THIS IS AN AUTOMATICALLY GENERATED FILE. ANY MANUAL CHANGES WILL BE OVERWRITTEN!-------- +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Semantics.Implied +{ + using System.Collections.Generic; + + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// The kind of Relationship that is implied to satisfy a semantic constraint. + /// + public enum ImpliedRelationshipKind + { + /// + /// A Subclassification, implied for a Classifier. + /// + Subclassification, + + /// + /// A Subsetting, implied for a Feature. + /// + Subsetting + } + + /// + /// A single implied library Specialization, as required by one semantic constraint of the + /// KerML/SysML abstract syntax. + /// + public readonly struct ImpliedLibrarySpecialization + { + /// + /// Initializes a new instance of the struct. + /// + /// + /// The name of the semantic constraint the rule was extracted from + /// + /// + /// The qualified name of the library Type that must be specialized + /// + /// + /// The metaclass the constraint is declared on, which decides + /// + /// + /// Whether the constraint's OCL guards the specialization + /// + public ImpliedLibrarySpecialization(string constraintName, string targetLibraryName, string declaringMetaclassName, bool requiresGuard) + { + this.ConstraintName = constraintName; + this.TargetLibraryName = targetLibraryName; + this.DeclaringMetaclassName = declaringMetaclassName; + this.RequiresGuard = requiresGuard; + } + + /// + /// Gets the name of the semantic constraint the rule was extracted from. + /// + public string ConstraintName { get; } + + /// + /// Gets the qualified name of the library Type that must be specialized. + /// + public string TargetLibraryName { get; } + + /// + /// Gets the metaclass the constraint is declared on. The Relationship kind is decided from this + /// name, not from the metaclass that inherits the constraint. + /// + public string DeclaringMetaclassName { get; } + + /// + /// Gets a value indicating whether the constraint's OCL guards the specialization, in which case the + /// rule applies only when the hand-written predicate for says so. + /// + public bool RequiresGuard { get; } + + /// + /// Gets the kind of Relationship to imply, decided by whether the declaring metaclass is a + /// Classifier or a Feature. + /// + public ImpliedRelationshipKind Kind => + ImpliedRelationshipTable.SubclassificationMetaclasses.Contains(this.DeclaringMetaclassName) + ? ImpliedRelationshipKind.Subclassification + : ImpliedRelationshipKind.Subsetting; + } + + /// + /// The table of implied library Specializations that KerML 1.0 §8.4.2 allows a tool to insert to + /// satisfy the specialization constraints of the abstract syntax. + /// + /// + /// + /// The metaclass and the library target of each row come from the constraint's OCL body in the UML XMI. + /// The does NOT: the OCL says only what to specialize, never + /// whether the implied Relationship is a Subclassification or a Subsetting. That is stated only in the + /// specification tables — KerML 1.0 Table 8 (§8.4.3.1.1) and Table 10 (§8.4.4.1), and SysML 2.0 + /// Tables 31-33 — so below is transcribed by hand from those + /// tables and lives in the Handlebars template, not in the generator. + /// + /// + /// Rows are NOT reduced against the §8.4.2 redundancy rules. Rule 1 suppresses an implied Specialization + /// whose general Type is a supertype of another applicable one, and deciding that needs the library + /// Types resolved — they are not present in the metamodel XMI. The reduction therefore belongs to the + /// caller, which must also apply rule 2 (de-duplicate identical targets). Neither rule applies to + /// Redefinitions. + /// + /// + public static class ImpliedRelationshipTable + { + /// + /// The metaclasses whose implied Specialization is a Subclassification rather than a + /// Subsetting — that is, the Classifiers. + /// + /// + /// HAND-MAINTAINED. This is the one part of the table that cannot be derived from the OCL; it is + /// transcribed from KerML Table 8 / Table 10 and SysML Tables 31-33. KerML Table 8 note 1 is the + /// reason Type is absent: checkTypeSpecialization applies to every Type, but the + /// Subclassification is only implied for Classifiers. Anything not listed here is a Feature and + /// implies a Subsetting. + /// + internal static readonly HashSet SubclassificationMetaclasses = + [ + "ActionDefinition", + "AllocationDefinition", + "AnalysisCaseDefinition", + "Association", + "AssociationStructure", + "Behavior", + "CalculationDefinition", + "CaseDefinition", + "Class", + "ConcernDefinition", + "ConnectionDefinition", + "ConstraintDefinition", + "DataType", + "FlowDefinition", + "Function", + "InterfaceDefinition", + "ItemDefinition", + "Metaclass", + "MetadataDefinition", + "OccurrenceDefinition", + "PartDefinition", + "PortDefinition", + "Predicate", + "RenderingDefinition", + "RequirementDefinition", + "StateDefinition", + "Structure", + "UseCaseDefinition", + "VerificationCaseDefinition", + "ViewDefinition", + "ViewpointDefinition" + ]; + + /// + /// The semantic constraints that are NOT represented in this table, with the reason. Emitted so that + /// no constraint of KerML §8.4.2 is silently dropped while its hand-coded arm is outstanding. + /// + public static IReadOnlyList NotCovered { get; } = + [ +{{#each NotCovered as | constraint |}} + "{{constraint.MetaclassName}}.{{constraint.ConstraintName}} ({{constraint.Category}}) - {{constraint.Reason}}", +{{/each}} + ]; + + /// + /// The name of every semantic constraint declared in the abstract syntax, covered or not. + /// + public static IReadOnlyList AllConstraintNames { get; } = + [ +{{#each AllConstraintNames as | constraintName |}} + "{{constraintName}}", +{{/each}} + ]; + + /// + /// The name of every constraint whose application is conditional, i.e. every row that requires a + /// guard before its implied Relationship may be included. + /// + public static IReadOnlyList AllConditionalConstraintNames { get; } = + [ +{{#each ConditionalConstraintNames as | constraintName |}} + "{{constraintName}}", +{{/each}} + ]; + + /// + /// The qualified name of every library Type targeted by a row of this table, without duplicates. + /// + /// + /// Every name here must resolve against a full model-library load, or the constraint that carries + /// it can never be satisfied. Exposed so that resolution can be asserted for the whole table rather + /// than only for the rows a given corpus happens to exercise. + /// + public static IReadOnlyList AllLibraryTargets { get; } = + [ +{{#each AllLibraryTargets as | libraryTarget |}} + "{{libraryTarget}}", +{{/each}} + ]; + + /// + /// Returns the implied library Specializations that apply to the supplied element, including + /// those inherited from its supertypes in the metamodel. + /// + /// + /// The to query + /// + /// + /// The applicable rules, or an empty list when the metaclass carries none + /// + public static IReadOnlyList QueryImpliedLibrarySpecializations(IElement element) + { + return element switch + { +{{#each Metaclasses as | metaclass |}} + {{metaclass.InterfaceFqn}} => {{metaclass.MetaclassName}}Rules, +{{/each}} + _ => [] + }; + } + +{{#each Metaclasses as | metaclass |}} + /// + /// The implied library Specializations applying to {{metaclass.MetaclassName}}, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] {{metaclass.MetaclassName}}Rules = + [ +{{#each metaclass.Rules as | rule |}} + new("{{rule.ConstraintName}}", "{{rule.TargetLibraryName}}", "{{rule.DeclaringMetaclassName}}", {{#if rule.RequiresGuard}}true{{else}}false{{/if}}), +{{/each}} + ]; + +{{/each}} + } +} diff --git a/SysML2.NET.Semantics.Tests/Implied/Guards/ImpliedRuleGuardTestFixture.cs b/SysML2.NET.Semantics.Tests/Implied/Guards/ImpliedRuleGuardTestFixture.cs new file mode 100644 index 00000000..0182dc59 --- /dev/null +++ b/SysML2.NET.Semantics.Tests/Implied/Guards/ImpliedRuleGuardTestFixture.cs @@ -0,0 +1,326 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Tests.Implied.Guards +{ + using System; + using System.Linq; + + using NUnit.Framework; + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Core.Types; + using SysML2.NET.Core.POCO.Kernel.Associations; + using SysML2.NET.Core.POCO.Kernel.Classes; + using SysML2.NET.Core.POCO.Kernel.Connectors; + using SysML2.NET.Core.POCO.Kernel.DataTypes; + using SysML2.NET.Core.POCO.Kernel.Structures; + using SysML2.NET.Core.POCO.Systems.Occurrences; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Systems.Actions; + using SysML2.NET.Core.POCO.Systems.Connections; + using SysML2.NET.Core.POCO.Systems.Parts; + using SysML2.NET.Semantics.Implied; + using SysML2.NET.Semantics.Implied.Guards; + + [TestFixture] + public class ImpliedRuleGuardTestFixture + { + [Test] + public void VerifyFeatureDataValueSpecializationGuard() + { + var guard = Generated("checkFeatureDataValueSpecialization"); + + var typedByDataType = new Feature { Id = Guid.NewGuid() }; + Type(typedByDataType, new DataType { Id = Guid.NewGuid(), DeclaredName = "Real" }); + + var typedByClass = new Feature { Id = Guid.NewGuid() }; + Type(typedByClass, new Class { Id = Guid.NewGuid(), DeclaredName = "Widget" }); + + using (Assert.EnterMultipleScope()) + { + Assert.That(guard.ConstraintName, Is.EqualTo("checkFeatureDataValueSpecialization")); + Assert.That(guard.Applies(typedByDataType), Is.True); + Assert.That(guard.Applies(typedByClass), Is.False); + Assert.That(guard.Applies(new Feature { Id = Guid.NewGuid() }), Is.False); + Assert.That(guard.Applies(new Class { Id = Guid.NewGuid() }), Is.False); + Assert.That(() => guard.Applies(null), Throws.TypeOf()); + } + } + + [Test] + public void VerifyConnectionUsageBinarySpecializationGuard() + { + var guard = Generated("checkConnectionUsageBinarySpecialization"); + + var binary = new ConnectionUsage { Id = Guid.NewGuid() }; + AddEnds(binary, 2); + + var nary = new ConnectionUsage { Id = Guid.NewGuid() }; + AddEnds(nary, 3); + + var unary = new ConnectionUsage { Id = Guid.NewGuid() }; + AddEnds(unary, 1); + + using (Assert.EnterMultipleScope()) + { + Assert.That(guard.ConstraintName, Is.EqualTo("checkConnectionUsageBinarySpecialization")); + Assert.That(guard.Applies(binary), Is.True); + + // The OCL is an EXACT count, so an n-ary connection must decline, not merely a unary one. + Assert.That(guard.Applies(nary), Is.False); + Assert.That(guard.Applies(unary), Is.False); + Assert.That(guard.Applies(new ConnectionUsage { Id = Guid.NewGuid() }), Is.False); + Assert.That(guard.Applies(new Feature { Id = Guid.NewGuid() }), Is.False); + Assert.That(() => guard.Applies(null), Throws.TypeOf()); + } + } + + [Test] + public void VerifyActionUsageOwnedActionSpecializationGuard() + { + var guard = Generated("checkActionUsageOwnedActionSpecialization"); + + var ownedByPartUsage = CreateActionUsage(true, new PartUsage { Id = Guid.NewGuid() }); + var ownedByPartDefinition = CreateActionUsage(true, new PartDefinition { Id = Guid.NewGuid() }); + var notComposite = CreateActionUsage(false, new PartUsage { Id = Guid.NewGuid() }); + var ownedByNonPart = CreateActionUsage(true, new ActionUsage { Id = Guid.NewGuid() }); + + using (Assert.EnterMultipleScope()) + { + Assert.That(guard.ConstraintName, Is.EqualTo("checkActionUsageOwnedActionSpecialization")); + Assert.That(guard.Applies(ownedByPartUsage), Is.True); + Assert.That(guard.Applies(ownedByPartDefinition), Is.True); + + // Both conjuncts matter: composite alone, or a part owner alone, is not enough. + Assert.That(guard.Applies(notComposite), Is.False); + Assert.That(guard.Applies(ownedByNonPart), Is.False); + + // owningType is null when the ActionUsage is not owned by a Type at all. + Assert.That(guard.Applies(new ActionUsage { Id = Guid.NewGuid(), IsComposite = true }), Is.False); + Assert.That(guard.Applies(new Feature { Id = Guid.NewGuid() }), Is.False); + Assert.That(() => guard.Applies(null), Throws.TypeOf()); + } + } + + [Test] + public void VerifyFeatureEndSpecializationGuard() + { + var guard = new FeatureEndSpecializationGuard(); + + var associationEnd = CreateEnd(true, new Association { Id = Guid.NewGuid() }); + var connectorEnd = CreateEnd(true, new Connector { Id = Guid.NewGuid() }); + var notAnEnd = CreateEnd(false, new Association { Id = Guid.NewGuid() }); + var ownedByPlainType = CreateEnd(true, new Class { Id = Guid.NewGuid() }); + + using (Assert.EnterMultipleScope()) + { + Assert.That(guard.ConstraintName, Is.EqualTo("checkFeatureEndSpecialization")); + Assert.That(guard.Applies(associationEnd), Is.True); + Assert.That(guard.Applies(connectorEnd), Is.True); + + // Both conjuncts matter: an end owned by a plain Type, or a non-end owned by an Association. + Assert.That(guard.Applies(notAnEnd), Is.False); + Assert.That(guard.Applies(ownedByPlainType), Is.False); + Assert.That(guard.Applies(new Feature { Id = Guid.NewGuid(), IsEnd = true }), Is.False); + Assert.That(() => guard.Applies(null), Throws.TypeOf()); + } + } + + [Test] + public void VerifyConnectorBinaryObjectSpecializationGuard() + { + var guard = new ConnectorBinaryObjectSpecializationGuard(); + + var binaryStructure = CreateConnector(2, new AssociationStructure { Id = Guid.NewGuid() }); + var binaryPlainAssociation = CreateConnector(2, new Association { Id = Guid.NewGuid() }); + var naryStructure = CreateConnector(3, new AssociationStructure { Id = Guid.NewGuid() }); + + using (Assert.EnterMultipleScope()) + { + Assert.That(guard.ConstraintName, Is.EqualTo("checkConnectorBinaryObjectSpecialization")); + Assert.That(guard.Applies(binaryStructure), Is.True); + + // A binary Connector typed by a plain Association carries a DIFFERENT library Specialization. + Assert.That(guard.Applies(binaryPlainAssociation), Is.False); + Assert.That(guard.Applies(naryStructure), Is.False); + Assert.That(guard.Applies(new Feature { Id = Guid.NewGuid() }), Is.False); + Assert.That(() => guard.Applies(null), Throws.TypeOf()); + } + } + + [Test] + public void VerifyOccurrenceDefinitionIndividualSpecializationGuard() + { + var guard = Generated("checkOccurrenceDefinitionIndividualSpecialization"); + + using (Assert.EnterMultipleScope()) + { + Assert.That(guard.ConstraintName, Is.EqualTo("checkOccurrenceDefinitionIndividualSpecialization")); + Assert.That(guard.Applies(new OccurrenceDefinition { Id = Guid.NewGuid(), IsIndividual = true }), Is.True); + Assert.That(guard.Applies(new OccurrenceDefinition { Id = Guid.NewGuid(), IsIndividual = false }), Is.False); + Assert.That(guard.Applies(new Feature { Id = Guid.NewGuid() }), Is.False); + Assert.That(() => guard.Applies(null), Throws.TypeOf()); + } + } + + + [Test] + public void VerifyConnectorBinarySpecializationGuard() + { + var guard = new ConnectorBinarySpecializationGuard(); + + var binary = CreateConnector(2, new Association { Id = Guid.NewGuid() }); + var nary = CreateConnector(3, new Association { Id = Guid.NewGuid() }); + + using (Assert.EnterMultipleScope()) + { + Assert.That(guard.ConstraintName, Is.EqualTo("checkConnectorBinarySpecialization")); + Assert.That(guard.Applies(binary), Is.True); + Assert.That(guard.Applies(nary), Is.False); + Assert.That(guard.Applies(new Connector { Id = Guid.NewGuid() }), Is.False); + + // A non-Connector must decline — this also disproves the CA1508 claim that the merged + // pattern is "always true". + Assert.That(guard.Applies(new Feature { Id = Guid.NewGuid() }), Is.False); + Assert.That(() => guard.Applies(null), Throws.TypeOf()); + } + } + + [Test] + public void VerifyConnectorObjectSpecializationGuard() + { + var guard = new ConnectorObjectSpecializationGuard(); + + var structureTyped = CreateConnector(3, new AssociationStructure { Id = Guid.NewGuid() }); + var plainAssociation = CreateConnector(3, new Association { Id = Guid.NewGuid() }); + + using (Assert.EnterMultipleScope()) + { + Assert.That(guard.ConstraintName, Is.EqualTo("checkConnectorObjectSpecialization")); + + // Unlike its binary counterpart the end count is irrelevant here. + Assert.That(guard.Applies(structureTyped), Is.True); + Assert.That(guard.Applies(plainAssociation), Is.False); + Assert.That(guard.Applies(new Connector { Id = Guid.NewGuid() }), Is.False); + Assert.That(guard.Applies(new Feature { Id = Guid.NewGuid() }), Is.False); + Assert.That(() => guard.Applies(null), Throws.TypeOf()); + } + } + + [Test] + public void VerifyFeaturePortionSpecializationGuard() + { + var guard = new FeaturePortionSpecializationGuard(); + + var portionOwnedByClass = CreatePortion(true, new Class { Id = Guid.NewGuid() }, typedByClass: true); + var notAPortion = CreatePortion(false, new Class { Id = Guid.NewGuid() }, typedByClass: true); + var notClassTyped = CreatePortion(true, new Class { Id = Guid.NewGuid() }, typedByClass: false); + + using (Assert.EnterMultipleScope()) + { + Assert.That(guard.ConstraintName, Is.EqualTo("checkFeaturePortionSpecialization")); + Assert.That(guard.Applies(portionOwnedByClass), Is.True); + + // Every conjunct of the constraint is load-bearing. + Assert.That(guard.Applies(notAPortion), Is.False); + Assert.That(guard.Applies(notClassTyped), Is.False); + Assert.That(guard.Applies(new Feature { Id = Guid.NewGuid(), IsPortion = true }), Is.False); + Assert.That(() => guard.Applies(null), Throws.TypeOf()); + } + } + + private static Feature CreatePortion(bool isPortion, IElement owner, bool typedByClass) + { + var portion = new Feature { Id = Guid.NewGuid(), IsPortion = isPortion }; + + if (typedByClass) + { + Type(portion, new Class { Id = Guid.NewGuid() }); + } + + var membership = new FeatureMembership { Id = Guid.NewGuid() }; + ((IContainedRelationship)membership).OwnedRelatedElement.Add(portion); + ((IContainedElement)owner).OwnedRelationship.Add(membership); + + return portion; + } + + private static IImpliedRuleGuard Generated(string constraintName) + { + var guard = GeneratedImpliedRuleGuards.All.SingleOrDefault(candidate => candidate.ConstraintName == constraintName); + + Assert.That(guard, Is.Not.Null, $"'{constraintName}' is expected to be generated from its guard OCL."); + + return guard; + } + + private static Feature CreateEnd(bool isEnd, IElement owner) + { + var end = new Feature { Id = Guid.NewGuid(), IsEnd = isEnd }; + + var membership = new FeatureMembership { Id = Guid.NewGuid() }; + ((IContainedRelationship)membership).OwnedRelatedElement.Add(end); + ((IContainedElement)owner).OwnedRelationship.Add(membership); + + return end; + } + + private static Connector CreateConnector(int endCount, IElement association) + { + var connector = new Connector { Id = Guid.NewGuid() }; + + AddEnds(connector, endCount); + + var featureTyping = new FeatureTyping { Id = Guid.NewGuid(), TypedFeature = connector, Type = (IType)association }; + ((IContainedElement)connector).OwnedRelationship.Add(featureTyping); + + return connector; + } + + private static ActionUsage CreateActionUsage(bool isComposite, IElement owner) + { + var actionUsage = new ActionUsage { Id = Guid.NewGuid(), IsComposite = isComposite }; + + var membership = new FeatureMembership { Id = Guid.NewGuid() }; + ((IContainedRelationship)membership).OwnedRelatedElement.Add(actionUsage); + ((IContainedElement)owner).OwnedRelationship.Add(membership); + + return actionUsage; + } + + private static void Type(IFeature feature, IType type) + { + var featureTyping = new FeatureTyping { Id = Guid.NewGuid(), TypedFeature = feature, Type = type }; + ((IContainedElement)feature).OwnedRelationship.Add(featureTyping); + } + + private static void AddEnds(IElement owner, int count) + { + for (var endIndex = 0; endIndex < count; endIndex++) + { + var end = new Feature { Id = Guid.NewGuid(), IsEnd = true }; + var membership = new EndFeatureMembership { Id = Guid.NewGuid() }; + ((IContainedRelationship)membership).OwnedRelatedElement.Add(end); + ((IContainedElement)owner).OwnedRelationship.Add(membership); + } + } + } +} diff --git a/SysML2.NET.Semantics.Tests/Implied/ImpliedRelationshipFactoryTestFixture.cs b/SysML2.NET.Semantics.Tests/Implied/ImpliedRelationshipFactoryTestFixture.cs new file mode 100644 index 00000000..66528789 --- /dev/null +++ b/SysML2.NET.Semantics.Tests/Implied/ImpliedRelationshipFactoryTestFixture.cs @@ -0,0 +1,112 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Tests.Implied +{ + 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.Semantics.Implied; + + [TestFixture] + public class ImpliedRelationshipFactoryTestFixture + { + private ImpliedRelationshipFactory factory; + + [SetUp] + public void SetUp() + { + this.factory = new ImpliedRelationshipFactory(); + } + + [Test] + public void VerifyCreateImpliedSubclassification() + { + var specific = new Classifier { Id = Guid.NewGuid(), DeclaredName = "Specific" }; + var general = new Classifier { Id = Guid.NewGuid(), DeclaredName = "General" }; + + var subclassification = this.factory.CreateImpliedSubclassification(specific, general); + + using (Assert.EnterMultipleScope()) + { + Assert.That(subclassification.IsImplied, Is.True); + Assert.That(subclassification.Subclassifier, Is.SameAs(specific)); + Assert.That(subclassification.Superclassifier, Is.SameAs(general)); + Assert.That(subclassification.Id, Is.Not.EqualTo(Guid.Empty)); + Assert.That((subclassification).General, Is.SameAs(general)); + Assert.That((subclassification).Specific, Is.SameAs(specific)); + + // The product must stay detached: attaching it would oblige the Element to declare + // isImpliedIncluded, which cannot be honoured while constraints remain uncovered. + Assert.That(specific.OwnedRelationship, Is.Empty); + Assert.That(general.OwnedRelationship, Is.Empty); + + Assert.That(() => this.factory.CreateImpliedSubclassification(null, general), Throws.TypeOf()); + Assert.That(() => this.factory.CreateImpliedSubclassification(specific, null), Throws.TypeOf()); + } + } + + [Test] + public void VerifyCreateImpliedSubsetting() + { + var specific = new Feature { Id = Guid.NewGuid(), DeclaredName = "specific" }; + var general = new Feature { Id = Guid.NewGuid(), DeclaredName = "general" }; + + var subsetting = this.factory.CreateImpliedSubsetting(specific, general); + + using (Assert.EnterMultipleScope()) + { + Assert.That(subsetting.IsImplied, Is.True); + Assert.That(subsetting.SubsettingFeature, Is.SameAs(specific)); + Assert.That(subsetting.SubsettedFeature, Is.SameAs(general)); + Assert.That(subsetting.Id, Is.Not.EqualTo(Guid.Empty)); + Assert.That(specific.OwnedRelationship, Is.Empty); + + Assert.That(() => this.factory.CreateImpliedSubsetting(null, general), Throws.TypeOf()); + Assert.That(() => this.factory.CreateImpliedSubsetting(specific, null), Throws.TypeOf()); + } + } + + [Test] + public void VerifyCreateImpliedRedefinition() + { + var specific = new Feature { Id = Guid.NewGuid(), DeclaredName = "specific" }; + var general = new Feature { Id = Guid.NewGuid(), DeclaredName = "general" }; + + var redefinition = this.factory.CreateImpliedRedefinition(specific, general); + + using (Assert.EnterMultipleScope()) + { + Assert.That(redefinition.IsImplied, Is.True); + Assert.That(redefinition.RedefiningFeature, Is.SameAs(specific)); + Assert.That(redefinition.RedefinedFeature, Is.SameAs(general)); + Assert.That(redefinition.Id, Is.Not.EqualTo(Guid.Empty)); + Assert.That(specific.OwnedRelationship, Is.Empty); + + Assert.That(() => this.factory.CreateImpliedRedefinition(null, general), Throws.TypeOf()); + Assert.That(() => this.factory.CreateImpliedRedefinition(specific, null), Throws.TypeOf()); + } + } + } +} diff --git a/SysML2.NET.Semantics.Tests/Implied/ImpliedRelationshipProviderTestFixture.cs b/SysML2.NET.Semantics.Tests/Implied/ImpliedRelationshipProviderTestFixture.cs new file mode 100644 index 00000000..76be3f97 --- /dev/null +++ b/SysML2.NET.Semantics.Tests/Implied/ImpliedRelationshipProviderTestFixture.cs @@ -0,0 +1,193 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Tests.Implied +{ + using System; + using System.Linq; + + using NUnit.Framework; + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Core.Types; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Root.Namespaces; + using SysML2.NET.Core.POCO.Systems.DefinitionAndUsage; + using SysML2.NET.Core.POCO.Systems.Parts; + using SysML2.NET.Semantics.Implied; + using SysML2.NET.Semantics.Implied.Rules; + + [TestFixture] + public class ImpliedRelationshipProviderTestFixture + { + private ImpliedRelationshipFactory factory; + + private PartUsage variation; + + private PartUsage variant; + + [SetUp] + public void SetUp() + { + this.factory = new ImpliedRelationshipFactory(); + + this.variation = new PartUsage { Id = Guid.NewGuid(), DeclaredName = "p", IsVariation = true }; + this.variant = new PartUsage { Id = Guid.NewGuid(), DeclaredName = "p1" }; + + var membership = new VariantMembership { Id = Guid.NewGuid() }; + ((IContainedRelationship)membership).OwnedRelatedElement.Add(this.variant); + ((IContainedElement)this.variation).OwnedRelationship.Add(membership); + } + + [Test] + public void VerifyGetImpliedSpecializations() + { + var provider = this.CreateProvider(); + + var specializations = provider.GetImpliedSpecializations(this.variant); + + using (Assert.EnterMultipleScope()) + { + Assert.That(specializations, Has.Count.EqualTo(1)); + Assert.That(specializations[0], Is.InstanceOf()); + Assert.That(specializations[0].General, Is.SameAs(this.variation)); + + // Library specializations are off by default, so nothing table-driven contributes. + Assert.That(specializations.All(specialization => specialization.IsImplied), Is.True); + + // The model must be untouched: no implied Relationship is attached anywhere. + Assert.That(this.variant.OwnedRelationship, Is.Empty); + Assert.That(this.variation.OwnedRelationship.OfType(), Is.Empty); + Assert.That(this.variant.IsImpliedIncluded, Is.False); + + // Memoised: the same instances come back on a second call. + Assert.That(provider.GetImpliedSpecializations(this.variant), Is.SameAs(specializations)); + + Assert.That(() => provider.GetImpliedSpecializations(null), Throws.TypeOf()); + } + } + + [Test] + public void VerifyGetImpliedRelationships() + { + var provider = this.CreateProvider(); + + var relationships = provider.GetImpliedRelationships(this.variant); + + using (Assert.EnterMultipleScope()) + { + // The Subsetting is reported once, through the Specialization arm, not twice. + Assert.That(relationships, Has.Count.EqualTo(1)); + Assert.That(relationships[0], Is.InstanceOf()); + + Assert.That(provider.GetImpliedRelationships(this.variation), Is.Empty); + Assert.That(() => provider.GetImpliedRelationships(null), Throws.TypeOf()); + } + } + + [Test] + public void VerifyGetImpliedRedefinitions() + { + var provider = this.CreateProvider(); + + using (Assert.EnterMultipleScope()) + { + // No Redefinition rule is registered yet, so none is produced. + Assert.That(provider.GetImpliedRedefinitions(this.variant), Is.Empty); + Assert.That(() => provider.GetImpliedRedefinitions(null), Throws.TypeOf()); + } + } + + [Test] + public void VerifyNotCoveredConstraints() + { + var withRules = this.CreateProvider(); + var withoutRules = new ImpliedRelationshipProvider( + OwnershipTreeLibraryTypeIndex.Build([]), + new ImpliedRuleGuardRegistry([]), + this.factory, + new ImpliedSpecializationReducer(), + new ImpliedRelationshipOptions(), + []); + + using (Assert.EnterMultipleScope()) + { + // A registered rule removes its constraint from the manifest. + Assert.That(withRules.NotCoveredConstraints.Any(entry => entry.Contains("checkUsageVariationUsageSpecialization")), Is.False); + Assert.That(withoutRules.NotCoveredConstraints.Any(entry => entry.Contains("checkUsageVariationUsageSpecialization")), Is.True); + + Assert.That(withRules.IsConstraintCovered("checkUsageVariationUsageSpecialization"), Is.True); + Assert.That(withoutRules.IsConstraintCovered("checkUsageVariationUsageSpecialization"), Is.False); + + // With library specializations off, everything table-driven is honestly reported as uncovered. + Assert.That(withRules.NotCoveredConstraints, Has.Count.GreaterThan(ImpliedRelationshipTable.NotCovered.Count)); + Assert.That(withRules.IsConstraintCovered("checkPortUsageSpecialization"), Is.False); + Assert.That(withRules.IsConstraintCovered(null), Is.False); + } + } + + [Test] + public void VerifyGetImpliedSpecializationsThrowsWhenAGuardIsMissing() + { + // Enabling the library-specialization rules exercises the 63 conditional rows. None has a guard + // registered yet, so the first one reached must fail loudly rather than be applied as if it were + // unconditional — applying it would inject Specializations the model does not require. + var provider = this.CreateProvider(new ImpliedRelationshipOptions { EnableLibrarySpecializations = true }); + + Assert.That(() => provider.GetImpliedSpecializations(this.variant), Throws.TypeOf()); + } + + [Test] + public void VerifyGetImpliedSpecializationsThrowsWhenALibraryTypeIsMissing() + { + // A Classifier's constraints target library Types (Occurrences::Occurrence and friends). With an + // empty index — the shape produced by loading only the libraries a model happens to import — + // resolution must fail loudly rather than silently omit the Specialization. + var provider = this.CreateProvider(new ImpliedRelationshipOptions { EnableLibrarySpecializations = true }); + + var classifier = new PartDefinition { Id = Guid.NewGuid(), DeclaredName = "P" }; + + Assert.That(() => provider.GetImpliedSpecializations(classifier), Throws.TypeOf() + .Or.TypeOf()); + } + + private ImpliedRelationshipProvider CreateProvider(ImpliedRelationshipOptions options) + { + return new ImpliedRelationshipProvider( + OwnershipTreeLibraryTypeIndex.Build([]), + new ImpliedRuleGuardRegistry([]), + this.factory, + new ImpliedSpecializationReducer(), + options, + [new VariationUsageSpecializationRule(this.factory), new VariationDefinitionSpecializationRule(this.factory)]); + } + + private ImpliedRelationshipProvider CreateProvider() + { + return new ImpliedRelationshipProvider( + OwnershipTreeLibraryTypeIndex.Build([]), + new ImpliedRuleGuardRegistry([]), + this.factory, + new ImpliedSpecializationReducer(), + new ImpliedRelationshipOptions(), + [new VariationUsageSpecializationRule(this.factory), new VariationDefinitionSpecializationRule(this.factory)]); + } + } +} diff --git a/SysML2.NET.Semantics.Tests/Implied/ImpliedRuleGuardRegistryTestFixture.cs b/SysML2.NET.Semantics.Tests/Implied/ImpliedRuleGuardRegistryTestFixture.cs new file mode 100644 index 00000000..b660bd31 --- /dev/null +++ b/SysML2.NET.Semantics.Tests/Implied/ImpliedRuleGuardRegistryTestFixture.cs @@ -0,0 +1,129 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Tests.Implied +{ + using System; + using System.Linq; + + using NUnit.Framework; + + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Semantics.Implied; + using SysML2.NET.Semantics.Implied.Guards; + + [TestFixture] + public class ImpliedRuleGuardRegistryTestFixture + { + [Test] + public void VerifyGetGuard() + { + var guard = new StubGuard("checkPortUsageSubportSpecialization", true); + var registry = new ImpliedRuleGuardRegistry([guard]); + + using (Assert.EnterMultipleScope()) + { + Assert.That(registry.GetGuard("checkPortUsageSubportSpecialization"), Is.SameAs(guard)); + Assert.That(() => registry.GetGuard("checkAbsentConstraint"), Throws.TypeOf()); + Assert.That(() => registry.GetGuard(null), Throws.TypeOf()); + } + } + + [Test] + public void VerifyHasGuard() + { + var registry = new ImpliedRuleGuardRegistry([new StubGuard("checkPortUsageSubportSpecialization", true)]); + + using (Assert.EnterMultipleScope()) + { + Assert.That(registry.HasGuard("checkPortUsageSubportSpecialization"), Is.True); + Assert.That(registry.HasGuard("checkAbsentConstraint"), Is.False); + Assert.That(registry.HasGuard(null), Is.False); + } + } + + [Test] + public void VerifyConstructor() + { + using (Assert.EnterMultipleScope()) + { + Assert.That(() => new ImpliedRuleGuardRegistry(null), Throws.TypeOf()); + Assert.That(() => new ImpliedRuleGuardRegistry([]), Throws.Nothing); + + // Two guards deciding the same constraint is a wiring error, not a last-one-wins. + Assert.That( + () => new ImpliedRuleGuardRegistry([new StubGuard("checkDuplicate", true), new StubGuard("checkDuplicate", false)]), + Throws.TypeOf()); + } + } + + /// + /// Phase-2 exit criterion: every conditional row in the generated table must have a guard, whether + /// generated from its OCL or hand written. A row without one makes the provider throw rather than + /// silently apply the constraint unconditionally. + /// + [Test] + public void VerifyEveryConditionalConstraintHasAGuard() + { + var registry = new ImpliedRuleGuardRegistry( + [ + ..GeneratedImpliedRuleGuards.All, + new AcceptActionUsageSubactionSpecializationGuard(), + new AssociationBinarySpecializationGuard(), + new AssociationStructureBinarySpecializationGuard(), + new ConnectorBinaryObjectSpecializationGuard(), + new ConnectorBinarySpecializationGuard(), + new ConnectorObjectSpecializationGuard(), + new FeatureEndSpecializationGuard(), + new FeaturePortionSpecializationGuard(), + new FeatureSubobjectSpecializationGuard(), + new FeatureSuboccurrenceSpecializationGuard(), + new FlowDefinitionBinarySpecializationGuard(), + new IncludeUseCaseUsageSpecializationGuard(), + new OccurrenceUsageSuboccurrenceSpecializationGuard(), + new StepOwnedPerformanceSpecializationGuard(), + new StepSubperformanceSpecializationGuard(), + new TransitionUsageActionSpecializationGuard(), + new TransitionUsageStateSpecializationGuard() + ]); + + var unguarded = ImpliedRelationshipTable.AllConditionalConstraintNames + .Where(constraintName => !registry.HasGuard(constraintName)) + .ToList(); + + Assert.That(unguarded, Is.Empty, $"These conditional constraints have no guard: {string.Join(", ", unguarded)}"); + } + + private sealed class StubGuard : IImpliedRuleGuard + { + private readonly bool applies; + + public StubGuard(string constraintName, bool applies) + { + this.ConstraintName = constraintName; + this.applies = applies; + } + + public string ConstraintName { get; } + + public bool Applies(IElement element) => this.applies; + } + } +} diff --git a/SysML2.NET.Semantics.Tests/Implied/ImpliedSpecializationReducerTestFixture.cs b/SysML2.NET.Semantics.Tests/Implied/ImpliedSpecializationReducerTestFixture.cs new file mode 100644 index 00000000..c8810ef5 --- /dev/null +++ b/SysML2.NET.Semantics.Tests/Implied/ImpliedSpecializationReducerTestFixture.cs @@ -0,0 +1,121 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Tests.Implied +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using NUnit.Framework; + + using SysML2.NET.Core.POCO.Core.Classifiers; + using SysML2.NET.Core.POCO.Core.Types; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Semantics.Implied; + + [TestFixture] + public class ImpliedSpecializationReducerTestFixture + { + private ImpliedSpecializationReducer reducer; + + private ImpliedRelationshipFactory factory; + + private Classifier anything; + + private Classifier occurrence; + + private Classifier subject; + + [SetUp] + public void SetUp() + { + this.reducer = new ImpliedSpecializationReducer(); + this.factory = new ImpliedRelationshipFactory(); + + this.anything = new Classifier { Id = Guid.NewGuid(), DeclaredName = "Anything" }; + this.occurrence = new Classifier { Id = Guid.NewGuid(), DeclaredName = "Occurrence" }; + this.subject = new Classifier { Id = Guid.NewGuid(), DeclaredName = "Subject" }; + + // Occurrence specializes Anything, so Occurrence is a strict subtype of Anything. + Specialize(this.occurrence, this.anything); + } + + [Test] + public void VerifyReduce() + { + var impliedAnything = this.factory.CreateImpliedSubclassification(this.subject, this.anything); + var impliedOccurrence = this.factory.CreateImpliedSubclassification(this.subject, this.occurrence); + var duplicateOccurrence = this.factory.CreateImpliedSubclassification(this.subject, this.occurrence); + + using (Assert.EnterMultipleScope()) + { + // Rule 1 within the implied set: Occurrence is a strict subtype of Anything, so specializing + // Anything as well is redundant. + var reduced = this.reducer.Reduce(this.subject, [impliedAnything, impliedOccurrence]); + Assert.That(reduced.Select(specialization => specialization.General), Is.EqualTo(new IType[] { this.occurrence })); + + // Rule 2: two candidates with the same general Type collapse to one. + var deduplicated = this.reducer.Reduce(this.subject, [impliedOccurrence, duplicateOccurrence]); + Assert.That(deduplicated, Has.Count.EqualTo(1)); + Assert.That(deduplicated[0], Is.SameAs(impliedOccurrence)); + + // A single candidate with no competitor survives — the self-comparison must not drop it. + var single = this.reducer.Reduce(this.subject, [impliedOccurrence]); + Assert.That(single, Has.Count.EqualTo(1)); + + Assert.That(this.reducer.Reduce(this.subject, []), Is.Empty); + Assert.That(() => this.reducer.Reduce(null, []), Throws.TypeOf()); + Assert.That(() => this.reducer.Reduce(this.subject, null), Throws.TypeOf()); + } + } + + [Test] + public void VerifyReduceAgainstDeclaredSpecializations() + { + var declaring = new Classifier { Id = Guid.NewGuid(), DeclaredName = "Declaring" }; + Specialize(declaring, this.occurrence); + + var impliedAnything = this.factory.CreateImpliedSubclassification(declaring, this.anything); + var impliedOccurrence = this.factory.CreateImpliedSubclassification(declaring, this.occurrence); + + using (Assert.EnterMultipleScope()) + { + // The declared Specialization to Occurrence already satisfies the looser Anything constraint. + Assert.That(this.reducer.Reduce(declaring, [impliedAnything]), Is.Empty); + + // ... and it makes an implied Specialization with the SAME general Type redundant too. + Assert.That(this.reducer.Reduce(declaring, [impliedOccurrence]), Is.Empty); + } + } + + private static void Specialize(IClassifier specific, IClassifier general) + { + var subclassification = new Subclassification + { + Id = Guid.NewGuid(), + Subclassifier = specific, + Superclassifier = general + }; + + ((IContainedElement)specific).OwnedRelationship.Add(subclassification); + } + } +} diff --git a/SysML2.NET.Semantics.Tests/Implied/OwnershipTreeLibraryTypeIndexTestFixture.cs b/SysML2.NET.Semantics.Tests/Implied/OwnershipTreeLibraryTypeIndexTestFixture.cs new file mode 100644 index 00000000..016bbc11 --- /dev/null +++ b/SysML2.NET.Semantics.Tests/Implied/OwnershipTreeLibraryTypeIndexTestFixture.cs @@ -0,0 +1,113 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Tests.Implied +{ + using System; + using System.Collections.Generic; + + using NUnit.Framework; + + using SysML2.NET.Core.POCO.Core.Classifiers; + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Kernel.Packages; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Root.Namespaces; + using SysML2.NET.Semantics.Implied; + + [TestFixture] + public class OwnershipTreeLibraryTypeIndexTestFixture + { + private Package libraryPackage; + + private Classifier occurrence; + + private Feature suboccurrences; + + [SetUp] + public void SetUp() + { + this.libraryPackage = new Package { Id = Guid.NewGuid(), DeclaredName = "Occurrences" }; + this.occurrence = new Classifier { Id = Guid.NewGuid(), DeclaredName = "Occurrence" }; + this.suboccurrences = new Feature { Id = Guid.NewGuid(), DeclaredName = "suboccurrences" }; + + Own(this.libraryPackage, this.occurrence); + Own(this.occurrence, this.suboccurrences); + } + + [Test] + public void VerifyBuild() + { + var index = OwnershipTreeLibraryTypeIndex.Build([this.libraryPackage]); + + using (Assert.EnterMultipleScope()) + { + Assert.That(index.TryGetType("Occurrences::Occurrence", out var resolvedOccurrence), Is.True); + Assert.That(resolvedOccurrence, Is.SameAs(this.occurrence)); + + Assert.That(index.TryGetType("Occurrences::Occurrence::suboccurrences", out var resolvedFeature), Is.True); + Assert.That(resolvedFeature, Is.SameAs(this.suboccurrences)); + + Assert.That(index.TryGetType("Occurrences::Absent", out var missing), Is.False); + Assert.That(missing, Is.Null); + + Assert.That(index.TryGetType(null, out _), Is.False); + Assert.That(index.TryGetType(string.Empty, out _), Is.False); + Assert.That(index.TryGetType(" ", out _), Is.False); + + // The Package itself is a Namespace, not a Type, so it is walked but not indexed. + Assert.That(index.TryGetType("Occurrences", out _), Is.False); + } + } + + [Test] + public void VerifyBuildWithNullAndEmptyInput() + { + using (Assert.EnterMultipleScope()) + { + Assert.That(() => OwnershipTreeLibraryTypeIndex.Build(null), Throws.TypeOf()); + Assert.That(OwnershipTreeLibraryTypeIndex.Build([]).Count, Is.Zero); + Assert.That(OwnershipTreeLibraryTypeIndex.Build([null]).Count, Is.Zero); + } + } + + [Test] + public void VerifyBuildWithCyclicOwnership() + { + var cyclic = new Package { Id = Guid.NewGuid(), DeclaredName = "Cyclic" }; + var inner = new Classifier { Id = Guid.NewGuid(), DeclaredName = "Inner" }; + + Own(cyclic, inner); + Own(inner, cyclic); + + var index = OwnershipTreeLibraryTypeIndex.Build([cyclic]); + + Assert.That(index.TryGetType("Cyclic::Inner", out var resolved), Is.True); + Assert.That(resolved, Is.SameAs(inner)); + } + + private static void Own(INamespace owner, IElement ownedElement) + { + var membership = new OwningMembership { Id = Guid.NewGuid() }; + ((IContainedRelationship)membership).OwnedRelatedElement.Add(ownedElement); + ((IContainedElement)owner).OwnedRelationship.Add(membership); + } + } +} diff --git a/SysML2.NET.Semantics.Tests/Implied/Rules/ChainSubsettingRuleTestFixture.cs b/SysML2.NET.Semantics.Tests/Implied/Rules/ChainSubsettingRuleTestFixture.cs new file mode 100644 index 00000000..fcfeccb1 --- /dev/null +++ b/SysML2.NET.Semantics.Tests/Implied/Rules/ChainSubsettingRuleTestFixture.cs @@ -0,0 +1,258 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Tests.Implied.Rules +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using NUnit.Framework; + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Core.Types; + using SysML2.NET.Core.POCO.Kernel.Connectors; + using SysML2.NET.Core.Core.Types; + using SysML2.NET.Core.Systems.States; + using SysML2.NET.Core.POCO.Kernel.Expressions; + using SysML2.NET.Core.POCO.Kernel.Functions; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Systems.DefinitionAndUsage; + using SysML2.NET.Core.POCO.Systems.Actions; + using SysML2.NET.Core.POCO.Systems.States; + using SysML2.NET.Extensions; + using SysML2.NET.Semantics.Implied; + using SysML2.NET.Semantics.Implied.Rules; + + [TestFixture] + public class ChainSubsettingRuleTestFixture + { + private const string OutgoingLink = "ControlPerformances::DecisionPerformance::outgoingHBLink"; + + private const string IncomingLink = "ControlPerformances::MergePerformance::incomingHBLink"; + + private ImpliedRelationshipFactory factory; + + private ILibraryTypeIndex libraryTypeIndex; + + private Dictionary libraryFeatures; + + [SetUp] + public void SetUp() + { + this.factory = new ImpliedRelationshipFactory(); + + this.libraryFeatures = new Dictionary + { + [OutgoingLink] = new Feature { Id = Guid.NewGuid(), DeclaredName = "outgoingHBLink" }, + [IncomingLink] = new Feature { Id = Guid.NewGuid(), DeclaredName = "incomingHBLink" } + }; + + this.libraryTypeIndex = new StubIndex(this.libraryFeatures); + } + + [Test] + public void VerifyCreateImpliedFeatureChain() + { + var first = new Feature { Id = Guid.NewGuid() }; + var second = new Feature { Id = Guid.NewGuid() }; + + var chain = this.factory.CreateImpliedFeatureChain(first, second); + + using (Assert.EnterMultipleScope()) + { + // The chain's whole meaning is the ORDER of its chainingFeatures. + Assert.That(chain.chainingFeature, Is.EqualTo([first, second])); + Assert.That(chain.ownedFeatureChaining.All(chaining => chaining.IsImplied), Is.True); + + Assert.That(() => this.factory.CreateImpliedFeatureChain(null, second), Throws.TypeOf()); + Assert.That(() => this.factory.CreateImpliedFeatureChain(first, null), Throws.TypeOf()); + } + } + + [Test] + public void VerifyDecisionNodeOutgoingSuccessionSpecializationRule() + { + var rule = new DecisionNodeOutgoingSuccessionSpecializationRule(this.libraryTypeIndex, this.factory); + + var decisionNode = new DecisionNode { Id = Guid.NewGuid() }; + var succession = BuildSuccession(decisionNode, new ActionUsage { Id = Guid.NewGuid() }); + + using (Assert.EnterMultipleScope()) + { + Assert.That(rule.ConstraintName, Is.EqualTo("checkDecisionNodeOutgoingSuccessionSpecialization")); + + var subsettings = rule.Apply(succession).OfType().ToList(); + Assert.That(subsettings, Has.Count.EqualTo(1)); + + // The Succession subsets the chain, and the chain is [node, link] IN THAT ORDER. + Assert.That(subsettings[0].SubsettingFeature, Is.SameAs(succession)); + Assert.That(subsettings[0].SubsettedFeature.chainingFeature, + Is.EqualTo([(IFeature)decisionNode, (IFeature)this.libraryFeatures[OutgoingLink]])); + + // A Succession leaving something else is not this constraint's business. + var plain = BuildSuccession(new ActionUsage { Id = Guid.NewGuid() }, new ActionUsage { Id = Guid.NewGuid() }); + Assert.That(rule.Apply(plain), Is.Empty); + + Assert.That(rule.Apply(new Feature { Id = Guid.NewGuid() }), Is.Empty); + Assert.That(() => rule.Apply(null), Throws.TypeOf()); + } + } + + [Test] + public void VerifyMergeNodeIncomingSuccessionSpecializationRule() + { + var rule = new MergeNodeIncomingSuccessionSpecializationRule(this.libraryTypeIndex, this.factory); + + var mergeNode = new MergeNode { Id = Guid.NewGuid() }; + var succession = BuildSuccession(new ActionUsage { Id = Guid.NewGuid() }, mergeNode); + + using (Assert.EnterMultipleScope()) + { + Assert.That(rule.ConstraintName, Is.EqualTo("checkMergeNodeIncomingSuccessionSpecialization")); + + var subsettings = rule.Apply(succession).OfType().ToList(); + Assert.That(subsettings, Has.Count.EqualTo(1)); + Assert.That(subsettings[0].SubsettedFeature.chainingFeature, + Is.EqualTo([(IFeature)mergeNode, (IFeature)this.libraryFeatures[IncomingLink]])); + + Assert.That(() => rule.Apply(null), Throws.TypeOf()); + } + } + + [Test] + public void VerifyFeatureChainExpressionResultSpecializationRule() + { + var rule = new FeatureChainExpressionResultSpecializationRule(this.factory); + + // `a.b` — the source is the input parameter, the target is the Feature reached through it, so + // the chain the result subsets is [source, target]. + var expression = new FeatureChainExpression { Id = Guid.NewGuid() }; + + var sourceParameter = new Feature { Id = Guid.NewGuid(), Direction = FeatureDirectionKind.In }; + var targetFeature = new Feature { Id = Guid.NewGuid() }; + Own(sourceParameter, targetFeature, new FeatureMembership { Id = Guid.NewGuid() }); + Own(expression, sourceParameter, new FeatureMembership { Id = Guid.NewGuid() }); + + var result = new Feature { Id = Guid.NewGuid() }; + Own(expression, result, new ReturnParameterMembership { Id = Guid.NewGuid() }); + + using (Assert.EnterMultipleScope()) + { + Assert.That(rule.ConstraintName, Is.EqualTo("checkFeatureChainExpressionResultSpecialization")); + + var subsettings = rule.Apply(expression).OfType().ToList(); + Assert.That(subsettings, Has.Count.EqualTo(1)); + Assert.That(subsettings[0].SubsettingFeature, Is.SameAs(result)); + Assert.That(subsettings[0].SubsettedFeature.chainingFeature, + Is.EqualTo([(IFeature)sourceParameter, (IFeature)targetFeature])); + + // No input parameter means no chain to state, so nothing is implied. + var bare = new FeatureChainExpression { Id = Guid.NewGuid() }; + Own(bare, new Feature { Id = Guid.NewGuid() }, new ReturnParameterMembership { Id = Guid.NewGuid() }); + Assert.That(rule.Apply(bare), Is.Empty); + + Assert.That(rule.Apply(new Feature { Id = Guid.NewGuid() }), Is.Empty); + Assert.That(() => rule.Apply(null), Throws.TypeOf()); + } + } + + [Test] + public void VerifyTransitionUsagePayloadSpecializationRule() + { + var rule = new TransitionUsagePayloadSpecializationRule(this.factory); + + var transitionUsage = new TransitionUsage { Id = Guid.NewGuid() }; + + // The trigger carries the payload the transition's second input parameter must subset. + var accepter = new AcceptActionUsage { Id = Guid.NewGuid() }; + var payload = new ReferenceUsage { Id = Guid.NewGuid(), Direction = FeatureDirectionKind.In }; + Own(accepter, payload, new FeatureMembership { Id = Guid.NewGuid() }); + Own(transitionUsage, accepter, new TransitionFeatureMembership { Id = Guid.NewGuid(), Kind = TransitionFeatureKind.Trigger }); + + // inputParameter(2) is 1-BASED, so two directed parameters are needed and the SECOND is the one. + var firstParameter = new Feature { Id = Guid.NewGuid(), Direction = FeatureDirectionKind.In }; + var payloadParameter = new Feature { Id = Guid.NewGuid(), Direction = FeatureDirectionKind.In }; + Own(transitionUsage, firstParameter, new FeatureMembership { Id = Guid.NewGuid() }); + Own(transitionUsage, payloadParameter, new FeatureMembership { Id = Guid.NewGuid() }); + + using (Assert.EnterMultipleScope()) + { + Assert.That(rule.ConstraintName, Is.EqualTo("checkTransitionUsagePayloadSpecialization")); + + var subsettings = rule.Apply(transitionUsage).OfType().ToList(); + Assert.That(subsettings, Has.Count.EqualTo(1)); + Assert.That(subsettings[0].SubsettingFeature, Is.SameAs(payloadParameter), "the SECOND input parameter, not the first"); + Assert.That(subsettings[0].SubsettedFeature.chainingFeature, + Is.EqualTo([(IFeature)accepter, (IFeature)payload])); + + // An untriggered transition has no payload to bind. + Assert.That(rule.Apply(new TransitionUsage { Id = Guid.NewGuid() }), Is.Empty); + + Assert.That(rule.Apply(new Feature { Id = Guid.NewGuid() }), Is.Empty); + Assert.That(() => rule.Apply(null), Throws.TypeOf()); + } + } + + [Test] + public void VerifyUnresolvedLinkThrows() + { + var rule = new DecisionNodeOutgoingSuccessionSpecializationRule(new StubIndex(new Dictionary()), this.factory); + + var succession = BuildSuccession(new DecisionNode { Id = Guid.NewGuid() }, new ActionUsage { Id = Guid.NewGuid() }); + + Assert.That(() => rule.Apply(succession), Throws.TypeOf()); + } + + private static void Own(IElement owner, IElement owned, IRelationship membership) + { + ((IContainedRelationship)membership).OwnedRelatedElement.Add(owned); + ((IContainedElement)owner).OwnedRelationship.Add(membership); + } + + private static Succession BuildSuccession(IFeature source, IFeature target) + { + // relatedFeature derives through the connector ends, each of which reaches its participant by an + // owned ReferenceSubsetting — so a Succession cannot be stated by assigning Source/Target. + var succession = new Succession { Id = Guid.NewGuid() }; + + foreach (var participant in new[] { source, target }) + { + var end = new Feature { Id = Guid.NewGuid(), IsEnd = true }; + end.AssignOwnership(new ReferenceSubsetting { Id = Guid.NewGuid(), ReferencedFeature = participant }); + succession.AssignOwnership(new EndFeatureMembership { Id = Guid.NewGuid() }, end); + } + + return succession; + } + + private sealed class StubIndex : ILibraryTypeIndex + { + private readonly IDictionary typesByQualifiedName; + + public StubIndex(IDictionary typesByQualifiedName) + { + this.typesByQualifiedName = typesByQualifiedName; + } + + public bool TryGetType(string qualifiedName, out IType type) => this.typesByQualifiedName.TryGetValue(qualifiedName, out type); + } + } +} diff --git a/SysML2.NET.Semantics.Tests/Implied/Rules/LibrarySpecializationRuleTestFixture.cs b/SysML2.NET.Semantics.Tests/Implied/Rules/LibrarySpecializationRuleTestFixture.cs new file mode 100644 index 00000000..ce8aac11 --- /dev/null +++ b/SysML2.NET.Semantics.Tests/Implied/Rules/LibrarySpecializationRuleTestFixture.cs @@ -0,0 +1,335 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Tests.Implied.Rules +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using NUnit.Framework; + + using SysML2.NET.Core.Core.Types; + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Core.Classifiers; + using SysML2.NET.Core.POCO.Core.Types; + using SysML2.NET.Core.POCO.Kernel.Expressions; + using SysML2.NET.Core.POCO.Kernel.Functions; + 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.Constraints; + using SysML2.NET.Core.POCO.Systems.Requirements; + using SysML2.NET.Semantics.Implied; + using SysML2.NET.Semantics.Implied.Rules; + + [TestFixture] + public class LibrarySpecializationRuleTestFixture + { + /// + /// Every library Feature the four condition-selected constraints can target. + /// + private static readonly string[] LibraryTargets = + [ + "Constraints::assertedConstraintChecks", + "Constraints::negatedConstraintChecks", + "Requirements::satisfiedRequirementChecks", + "Requirements::notSatisfiedRequirementChecks", + "Actions::ifThenActions", + "Actions::ifThenElseActions", + "Performances::trueEvaluations", + "Performances::falseEvaluations", + "Performances::constructorEvaluations" + ]; + + private ImpliedRelationshipFactory factory; + + private ILibraryTypeIndex libraryTypeIndex; + + private Dictionary libraryFeaturesByQualifiedName; + + [SetUp] + public void SetUp() + { + this.factory = new ImpliedRelationshipFactory(); + + this.libraryFeaturesByQualifiedName = LibraryTargets.ToDictionary( + qualifiedName => qualifiedName, + qualifiedName => (IType)new Feature { Id = Guid.NewGuid(), DeclaredName = qualifiedName }); + + this.libraryTypeIndex = new StubLibraryTypeIndex(this.libraryFeaturesByQualifiedName); + } + + [Test] + public void VerifyAssertConstraintUsageSpecializationRule() + { + var rule = new AssertConstraintUsageSpecializationRule(this.libraryTypeIndex, this.factory); + + using (Assert.EnterMultipleScope()) + { + Assert.That(rule.ConstraintName, Is.EqualTo("checkAssertConstraintUsageSpecialization")); + + Assert.That(SubsettedBy(rule, new AssertConstraintUsage { Id = Guid.NewGuid() }), + Is.EqualTo([this.Library("Constraints::assertedConstraintChecks")])); + + Assert.That(SubsettedBy(rule, new AssertConstraintUsage { Id = Guid.NewGuid(), IsNegated = true }), + Is.EqualTo([this.Library("Constraints::negatedConstraintChecks")])); + + // A SatisfyRequirementUsage IS an AssertConstraintUsage but has its own constraint, so this + // rule must stand down or the element would imply two unrelated library subsettings. + Assert.That(rule.Apply(new SatisfyRequirementUsage { Id = Guid.NewGuid() }), Is.Empty); + + Assert.That(rule.Apply(new Feature { Id = Guid.NewGuid() }), Is.Empty); + Assert.That(() => rule.Apply(null), Throws.TypeOf()); + } + } + + [Test] + public void VerifySatisfyRequirementUsageSpecializationRule() + { + var rule = new SatisfyRequirementUsageSpecializationRule(this.libraryTypeIndex, this.factory); + + using (Assert.EnterMultipleScope()) + { + Assert.That(rule.ConstraintName, Is.EqualTo("checkSatisfyRequirementUsageSpecialization")); + + Assert.That(SubsettedBy(rule, new SatisfyRequirementUsage { Id = Guid.NewGuid() }), + Is.EqualTo([this.Library("Requirements::satisfiedRequirementChecks")])); + + Assert.That(SubsettedBy(rule, new SatisfyRequirementUsage { Id = Guid.NewGuid(), IsNegated = true }), + Is.EqualTo([this.Library("Requirements::notSatisfiedRequirementChecks")])); + + // The plain AssertConstraintUsage is the other rule's business. + Assert.That(rule.Apply(new AssertConstraintUsage { Id = Guid.NewGuid() }), Is.Empty); + + Assert.That(() => rule.Apply(null), Throws.TypeOf()); + } + } + + [Test] + public void VerifyIfActionUsageSpecializationRule() + { + var rule = new IfActionUsageSpecializationRule(this.libraryTypeIndex, this.factory); + + // elseAction is inputParameter(3), so a three-parameter if action is the one with an else branch. + var withElse = new IfActionUsage { Id = Guid.NewGuid() }; + AddInputParameter(withElse, new Feature { Id = Guid.NewGuid() }); + AddInputParameter(withElse, new Feature { Id = Guid.NewGuid() }); + AddInputParameter(withElse, new ActionUsage { Id = Guid.NewGuid() }); + + using (Assert.EnterMultipleScope()) + { + Assert.That(rule.ConstraintName, Is.EqualTo("checkIfActionUsageSpecialization")); + + Assert.That(SubsettedBy(rule, new IfActionUsage { Id = Guid.NewGuid() }), + Is.EqualTo([this.Library("Actions::ifThenActions")])); + + Assert.That(SubsettedBy(rule, withElse), + Is.EqualTo([this.Library("Actions::ifThenElseActions")])); + + Assert.That(rule.Apply(new Feature { Id = Guid.NewGuid() }), Is.Empty); + Assert.That(() => rule.Apply(null), Throws.TypeOf()); + } + } + + [Test] + public void VerifyInvariantSpecializationRule() + { + var rule = new InvariantSpecializationRule(this.libraryTypeIndex, this.factory); + + using (Assert.EnterMultipleScope()) + { + Assert.That(rule.ConstraintName, Is.EqualTo("checkInvariantSpecialization")); + + Assert.That(SubsettedBy(rule, new Invariant { Id = Guid.NewGuid() }), + Is.EqualTo([this.Library("Performances::trueEvaluations")])); + + Assert.That(SubsettedBy(rule, new Invariant { Id = Guid.NewGuid(), IsNegated = true }), + Is.EqualTo([this.Library("Performances::falseEvaluations")])); + + Assert.That(rule.Apply(new Feature { Id = Guid.NewGuid() }), Is.Empty); + Assert.That(() => rule.Apply(null), Throws.TypeOf()); + } + } + + [Test] + public void VerifyConstructorExpressionResultSpecializationRule() + { + var rule = new ConstructorExpressionResultSpecializationRule(this.factory); + + // KerML 1.0 §8.4.4.9.4: a FeatureTyping when the instantiatedType is a Classifier, a Subsetting + // when it is a Feature. The OCL (result.specializes(instantiatedType)) does not say which. + var ontoClassifier = BuildConstructorExpression(new Classifier { Id = Guid.NewGuid() }, out var classifierResult); + var ontoFeature = BuildConstructorExpression(new Feature { Id = Guid.NewGuid() }, out var featureResult); + + using (Assert.EnterMultipleScope()) + { + Assert.That(rule.ConstraintName, Is.EqualTo("checkConstructorExpressionResultSpecialization")); + + var typing = rule.Apply(ontoClassifier).OfType().ToList(); + Assert.That(typing, Has.Count.EqualTo(1)); + Assert.That(typing[0].TypedFeature, Is.SameAs(classifierResult)); + + var subsetting = rule.Apply(ontoFeature).OfType().ToList(); + Assert.That(subsetting, Has.Count.EqualTo(1)); + Assert.That(subsetting[0].SubsettingFeature, Is.SameAs(featureResult)); + + // No instantiatedType and no result are both legitimately silent, not errors. + Assert.That(rule.Apply(new ConstructorExpression { Id = Guid.NewGuid() }), Is.Empty); + Assert.That(rule.Apply(new Feature { Id = Guid.NewGuid() }), Is.Empty); + Assert.That(() => rule.Apply(null), Throws.TypeOf()); + } + } + + [Test] + public void VerifyConstructorExpressionSpecializationRule() + { + var rule = new ConstructorExpressionSpecializationRule(this.libraryTypeIndex, this.factory); + + using (Assert.EnterMultipleScope()) + { + Assert.That(rule.ConstraintName, Is.EqualTo("checkConstructorExpressionSpecialization")); + + Assert.That(SubsettedBy(rule, new ConstructorExpression { Id = Guid.NewGuid() }), + Is.EqualTo([this.Library("Performances::constructorEvaluations")])); + + Assert.That(rule.Apply(new Feature { Id = Guid.NewGuid() }), Is.Empty); + Assert.That(() => rule.Apply(null), Throws.TypeOf()); + } + } + + [Test] + public void VerifyInvocationExpressionSpecializationRule() + { + var rule = new InvocationExpressionSpecializationRule(this.factory); + + // KerML 1.0 §8.4.4.9.5: ALWAYS a FeatureTyping, whether the instantiatedType is a Classifier or + // a Feature — unlike the ConstructorExpression result, where the kind depends on it. + var ontoClassifier = BuildInvocationExpression(new Classifier { Id = Guid.NewGuid() }); + var ontoFeature = BuildInvocationExpression(new Feature { Id = Guid.NewGuid() }); + + using (Assert.EnterMultipleScope()) + { + Assert.That(rule.ConstraintName, Is.EqualTo("checkInvocationExpressionSpecialization")); + + Assert.That(rule.Apply(ontoClassifier).OfType().Count(), Is.EqualTo(1)); + Assert.That(rule.Apply(ontoFeature).OfType().Count(), Is.EqualTo(1)); + Assert.That(rule.Apply(ontoFeature).OfType(), Is.Empty); + + Assert.That(rule.Apply(new InvocationExpression { Id = Guid.NewGuid() }), Is.Empty); + Assert.That(rule.Apply(new Feature { Id = Guid.NewGuid() }), Is.Empty); + Assert.That(() => rule.Apply(null), Throws.TypeOf()); + } + } + + [Test] + public void VerifyFeatureReferenceExpressionResultSpecializationRule() + { + var rule = new FeatureReferenceExpressionResultSpecializationRule(this.factory); + + var referent = new Feature { Id = Guid.NewGuid() }; + + var expression = new FeatureReferenceExpression { Id = Guid.NewGuid() }; + var result = new Feature { Id = Guid.NewGuid() }; + Own(expression, result, new ReturnParameterMembership { Id = Guid.NewGuid() }); + ((IContainedElement)expression).OwnedRelationship.Add(new Membership { Id = Guid.NewGuid(), MemberElement = referent }); + + using (Assert.EnterMultipleScope()) + { + Assert.That(rule.ConstraintName, Is.EqualTo("checkFeatureReferenceExpressionResultSpecialization")); + + var subsetting = rule.Apply(expression).OfType().ToList(); + Assert.That(subsetting, Has.Count.EqualTo(1)); + Assert.That(subsetting[0].SubsettingFeature, Is.SameAs(result)); + Assert.That(subsetting[0].SubsettedFeature, Is.SameAs(referent)); + + Assert.That(rule.Apply(new FeatureReferenceExpression { Id = Guid.NewGuid() }), Is.Empty); + Assert.That(rule.Apply(new Feature { Id = Guid.NewGuid() }), Is.Empty); + Assert.That(() => rule.Apply(null), Throws.TypeOf()); + } + } + + [Test] + public void VerifyUnresolvedLibraryTypeThrows() + { + // The library name is part of the rule, not of the model, so failing to resolve it is a + // configuration fault the caller must see rather than an element that simply implies nothing. + var rule = new InvariantSpecializationRule(new StubLibraryTypeIndex(new Dictionary()), this.factory); + + Assert.That(() => rule.Apply(new Invariant { Id = Guid.NewGuid() }), Throws.TypeOf()); + } + + private static ConstructorExpression BuildConstructorExpression(IType instantiatedType, out IFeature result) + { + var constructorExpression = new ConstructorExpression { Id = Guid.NewGuid() }; + + result = new Feature { Id = Guid.NewGuid() }; + Own(constructorExpression, result, new ReturnParameterMembership { Id = Guid.NewGuid() }); + + // instantiatedType is the member of the first ownedMembership that is NOT a FeatureMembership — + // the `alias of T` of KerML 1.0 §8.4.4.9.4 — so a plain Membership, not a typing. + ((IContainedElement)constructorExpression).OwnedRelationship.Add(new Membership { Id = Guid.NewGuid(), MemberElement = instantiatedType }); + + return constructorExpression; + } + + private static InvocationExpression BuildInvocationExpression(IType instantiatedType) + { + var invocationExpression = new InvocationExpression { Id = Guid.NewGuid() }; + ((IContainedElement)invocationExpression).OwnedRelationship.Add(new Membership { Id = Guid.NewGuid(), MemberElement = instantiatedType }); + + return invocationExpression; + } + + private static void Own(IElement owner, IElement owned, IRelationship relationship) + { + ((IContainedRelationship)relationship).OwnedRelatedElement.Add(owned); + ((IContainedElement)owner).OwnedRelationship.Add(relationship); + } + + private static void AddInputParameter(IElement owner, IFeature parameter) + { + parameter.Direction = FeatureDirectionKind.In; + + var membership = new FeatureMembership { Id = Guid.NewGuid() }; + ((IContainedRelationship)membership).OwnedRelatedElement.Add(parameter); + ((IContainedElement)owner).OwnedRelationship.Add(membership); + } + + private IFeature Library(string qualifiedName) => (IFeature)this.libraryFeaturesByQualifiedName[qualifiedName]; + + private static IReadOnlyList SubsettedBy(IImpliedRelationshipRule rule, IElement element) + { + return [..rule.Apply(element).OfType().Select(subsetting => subsetting.SubsettedFeature)]; + } + + private sealed class StubLibraryTypeIndex : ILibraryTypeIndex + { + private readonly IDictionary typesByQualifiedName; + + public StubLibraryTypeIndex(IDictionary typesByQualifiedName) + { + this.typesByQualifiedName = typesByQualifiedName; + } + + public bool TryGetType(string qualifiedName, out IType type) => this.typesByQualifiedName.TryGetValue(qualifiedName, out type); + } + } +} diff --git a/SysML2.NET.Semantics.Tests/Implied/Rules/RedefinitionRuleTestFixture.cs b/SysML2.NET.Semantics.Tests/Implied/Rules/RedefinitionRuleTestFixture.cs new file mode 100644 index 00000000..695c1aa6 --- /dev/null +++ b/SysML2.NET.Semantics.Tests/Implied/Rules/RedefinitionRuleTestFixture.cs @@ -0,0 +1,293 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Tests.Implied.Rules +{ + using System; + using System.Collections.Generic; + using System.Linq; + + 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.Associations; + using SysML2.NET.Core.POCO.Kernel.Functions; + using SysML2.NET.Core.POCO.Kernel.Interactions; + using SysML2.NET.Core.POCO.Systems.Actions; + using SysML2.NET.Core.POCO.Systems.States; + using SysML2.NET.Core.POCO.Systems.Views; + using SysML2.NET.Core.Systems.States; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Semantics.Implied; + using SysML2.NET.Semantics.Implied.Rules; + + [TestFixture] + public class RedefinitionRuleTestFixture + { + /// + /// The redefinition constraints no hand-coded rule covers yet. + /// + private static readonly string[] ExpectedUncoveredConstraints = ["checkConstructorExpressionResultFeatureRedefinition"]; + + private ImpliedRelationshipFactory factory; + + [SetUp] + public void SetUp() + { + this.factory = new ImpliedRelationshipFactory(); + } + + [Test] + public void VerifyFeatureEndRedefinitionRule() + { + var rule = new FeatureEndRedefinitionRule(this.factory); + + var supertype = new Association { Id = Guid.NewGuid() }; + var supertypeFirstEnd = AddEnd(supertype); + var supertypeSecondEnd = AddEnd(supertype); + + var subtype = new Association { Id = Guid.NewGuid() }; + var firstEnd = AddEnd(subtype); + var secondEnd = AddEnd(subtype); + + Specialize(subtype, supertype); + + using (Assert.EnterMultipleScope()) + { + Assert.That(rule.ConstraintName, Is.EqualTo("checkFeatureEndRedefinition")); + + // The correspondence is positional: end 1 redefines end 1, end 2 redefines end 2. + Assert.That(RedefinedBy(rule, firstEnd), Is.EqualTo(new[] { supertypeFirstEnd })); + Assert.That(RedefinedBy(rule, secondEnd), Is.EqualTo(new[] { supertypeSecondEnd })); + + Assert.That(() => rule.Apply(null), Throws.TypeOf()); + Assert.That(rule.Apply(new Feature { Id = Guid.NewGuid(), IsEnd = true }), Is.Empty); + Assert.That(rule.Apply(new Feature { Id = Guid.NewGuid() }), Is.Empty); + } + } + + [Test] + public void VerifyFeatureEndRedefinitionRuleWhenTheSupertypeHasFewerEnds() + { + var rule = new FeatureEndRedefinitionRule(this.factory); + + var supertype = new Association { Id = Guid.NewGuid() }; + var supertypeOnlyEnd = AddEnd(supertype); + + var subtype = new Association { Id = Guid.NewGuid() }; + var firstEnd = AddEnd(subtype); + var secondEnd = AddEnd(subtype); + + Specialize(subtype, supertype); + + using (Assert.EnterMultipleScope()) + { + Assert.That(RedefinedBy(rule, firstEnd), Is.EqualTo(new[] { supertypeOnlyEnd })); + + // The OCL guards with `endFeature->size() >= i`, so a supertype without an end at this + // position contributes nothing rather than throwing. + Assert.That(rule.Apply(secondEnd), Is.Empty); + } + } + + [Test] + public void VerifyFeatureResultRedefinitionRule() + { + var rule = new FeatureResultRedefinitionRule(this.factory); + + var supertype = new Function { Id = Guid.NewGuid() }; + var supertypeResult = AddResult(supertype); + + var subtype = new Function { Id = Guid.NewGuid() }; + var result = AddResult(subtype); + + Specialize(subtype, supertype); + + using (Assert.EnterMultipleScope()) + { + Assert.That(rule.ConstraintName, Is.EqualTo("checkFeatureResultRedefinition")); + Assert.That(RedefinedBy(rule, result), Is.EqualTo(new[] { supertypeResult })); + + // A Feature owned by the Function but which is NOT its result is out of scope. + var ordinary = new Feature { Id = Guid.NewGuid() }; + Own(subtype, ordinary); + Assert.That(rule.Apply(ordinary), Is.Empty); + + Assert.That(() => rule.Apply(null), Throws.TypeOf()); + Assert.That(rule.Apply(new Feature { Id = Guid.NewGuid() }), Is.Empty); + } + } + + [Test] + public void VerifyLibraryRedefinitionRules() + { + var payload = new Feature { Id = Guid.NewGuid(), DeclaredName = "payload" }; + var viewRendering = new Feature { Id = Guid.NewGuid(), DeclaredName = "viewRendering" }; + var entryAction = new Feature { Id = Guid.NewGuid(), DeclaredName = "entryAction" }; + var exitAction = new Feature { Id = Guid.NewGuid(), DeclaredName = "exitAction" }; + var loopVar = new Feature { Id = Guid.NewGuid(), DeclaredName = "var" }; + + var index = new StubLibraryTypeIndex(new Dictionary + { + ["Transfers::Transfer::payload"] = payload, + ["Views::View::viewRendering"] = viewRendering, + ["States::StateAction::entryAction"] = entryAction, + ["States::StateAction::exitAction"] = exitAction, + ["Actions::ForLoopAction::var"] = loopVar + }); + + var payloadFeature = new PayloadFeature { Id = Guid.NewGuid() }; + + var stateSubaction = new ActionUsage { Id = Guid.NewGuid() }; + var membership = new StateSubactionMembership { Id = Guid.NewGuid(), Kind = StateSubactionKind.Entry }; + ((IContainedRelationship)membership).OwnedRelatedElement.Add(stateSubaction); + ((IContainedElement)new ActionUsage { Id = Guid.NewGuid() }).OwnedRelationship.Add(membership); + + using (Assert.EnterMultipleScope()) + { + var payloadRule = new PayloadFeatureRedefinitionRule(index, this.factory); + Assert.That(RedefinedBy(payloadRule, payloadFeature), Is.EqualTo(new[] { payload })); + Assert.That(payloadRule.Apply(new Feature { Id = Guid.NewGuid() }), Is.Empty); + Assert.That(() => payloadRule.Apply(null), Throws.TypeOf()); + + // The kind of the owning membership selects which library action is redefined. + var stateRule = new ActionUsageStateActionRedefinitionRule(index, this.factory); + Assert.That(RedefinedBy(stateRule, stateSubaction), Is.EqualTo(new[] { entryAction })); + + membership.Kind = StateSubactionKind.Exit; + Assert.That(RedefinedBy(stateRule, stateSubaction), Is.EqualTo(new[] { exitAction })); + + // An ActionUsage owned any other way is out of scope. + Assert.That(stateRule.Apply(new ActionUsage { Id = Guid.NewGuid() }), Is.Empty); + + var renderingRule = new RenderingUsageRedefinitionRule(index, this.factory); + Assert.That(renderingRule.Apply(new RenderingUsage { Id = Guid.NewGuid() }), Is.Empty); + + var forLoopRule = new ForLoopActionUsageVarRedefinitionRule(index, this.factory); + Assert.That(forLoopRule.Apply(new ForLoopActionUsage { Id = Guid.NewGuid() }), Is.Empty); + + // An unindexed library Feature must fail loudly rather than silently omit the Redefinition. + var emptyIndex = new StubLibraryTypeIndex(new Dictionary()); + Assert.That(() => new PayloadFeatureRedefinitionRule(emptyIndex, this.factory).Apply(payloadFeature), + Throws.TypeOf()); + } + } + + /// + /// Set B is complete except for one constraint. checkConstructorExpressionResultFeatureRedefinition + /// asserts a CARDINALITY over redefinitions that already exist — + /// f.ownedRedefinition.redefinedFeature->intersection(features)->size() = 1 — rather than + /// naming which Feature must be redefined. It is a well-formedness check, not a source of implied + /// Relationships, so it stays uncovered rather than being satisfied by an invented correspondence. + /// + [Test] + public void VerifyEveryRedefinitionConstraintIsCoveredOrDeliberatelyNot() + { + var index = new StubLibraryTypeIndex(new Dictionary()); + + IImpliedRelationshipRule[] rules = + [ + new FeatureEndRedefinitionRule(this.factory), + new FeatureResultRedefinitionRule(this.factory), + new FeatureParameterRedefinitionRule(this.factory), + new RequirementUsageObjectiveRedefinitionRule(this.factory), + new AssignmentActionUsageReferentRedefinitionRule(this.factory), + new FeatureChainExpressionSourceTargetRedefinitionRule(this.factory), + new PayloadFeatureRedefinitionRule(index, this.factory), + new RenderingUsageRedefinitionRule(index, this.factory), + new ActionUsageStateActionRedefinitionRule(index, this.factory), + new ForLoopActionUsageVarRedefinitionRule(index, this.factory), + new AssignmentActionUsageStartingAtRedefinitionRule(index, this.factory), + new AssignmentActionUsageAccessedFeatureRedefinitionRule(index, this.factory), + new FeatureChainExpressionTargetRedefinitionRule(index, this.factory), + new FeatureFlowFeatureRedefinitionRule(index, this.factory) + ]; + + var covered = rules.Select(rule => rule.ConstraintName).ToList(); + + var redefinitionConstraints = ImpliedRelationshipTable.AllConstraintNames + .Where(constraintName => constraintName.EndsWith("Redefinition", StringComparison.Ordinal)) + .ToList(); + + var uncovered = redefinitionConstraints.Except(covered, StringComparer.Ordinal).ToList(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(redefinitionConstraints, Has.Count.EqualTo(15)); + Assert.That(covered, Has.Count.EqualTo(14)); + Assert.That(uncovered, Is.EqualTo(ExpectedUncoveredConstraints)); + } + } + + private sealed class StubLibraryTypeIndex : ILibraryTypeIndex + { + private readonly IDictionary typesByQualifiedName; + + public StubLibraryTypeIndex(IDictionary typesByQualifiedName) + { + this.typesByQualifiedName = typesByQualifiedName; + } + + public bool TryGetType(string qualifiedName, out IType type) => this.typesByQualifiedName.TryGetValue(qualifiedName, out type); + } + + private static IReadOnlyList RedefinedBy(IImpliedRelationshipRule rule, IElement element) + { + return [..rule.Apply(element).OfType().Select(redefinition => redefinition.RedefinedFeature)]; + } + + private static Feature AddEnd(IType owner) + { + var end = new Feature { Id = Guid.NewGuid(), IsEnd = true }; + Own(owner, end, new EndFeatureMembership { Id = Guid.NewGuid() }); + + return end; + } + + private static Feature AddResult(IType owner) + { + var result = new Feature { Id = Guid.NewGuid() }; + Own(owner, result, new ReturnParameterMembership { Id = Guid.NewGuid() }); + + return result; + } + + private static void Own(IElement owner, IElement owned, IRelationship membership = null) + { + var relationship = membership ?? new FeatureMembership { Id = Guid.NewGuid() }; + ((IContainedRelationship)relationship).OwnedRelatedElement.Add(owned); + ((IContainedElement)owner).OwnedRelationship.Add(relationship); + } + + private static void Specialize(IClassifier specific, IClassifier general) + { + var subclassification = new Subclassification + { + Id = Guid.NewGuid(), + Subclassifier = specific, + Superclassifier = general + }; + + ((IContainedElement)specific).OwnedRelationship.Add(subclassification); + } + } +} diff --git a/SysML2.NET.Semantics.Tests/Implied/Rules/VariationSpecializationRuleTestFixture.cs b/SysML2.NET.Semantics.Tests/Implied/Rules/VariationSpecializationRuleTestFixture.cs new file mode 100644 index 00000000..77712a40 --- /dev/null +++ b/SysML2.NET.Semantics.Tests/Implied/Rules/VariationSpecializationRuleTestFixture.cs @@ -0,0 +1,152 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Tests.Implied.Rules +{ + using System; + using System.Linq; + + using NUnit.Framework; + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Root.Namespaces; + using SysML2.NET.Core.POCO.Systems.DefinitionAndUsage; + using SysML2.NET.Core.POCO.Systems.Parts; + using SysML2.NET.Semantics.Implied; + using SysML2.NET.Semantics.Implied.Rules; + + [TestFixture] + public class VariationSpecializationRuleTestFixture + { + private ImpliedRelationshipFactory factory; + + private VariationUsageSpecializationRule usageRule; + + private VariationDefinitionSpecializationRule definitionRule; + + [SetUp] + public void SetUp() + { + this.factory = new ImpliedRelationshipFactory(); + this.usageRule = new VariationUsageSpecializationRule(this.factory); + this.definitionRule = new VariationDefinitionSpecializationRule(this.factory); + } + + [Test] + public void VerifyApplyForVariationUsage() + { + // variation part p { variant part p1; } -> member feature p1 subsets p + var variation = new PartUsage { Id = Guid.NewGuid(), DeclaredName = "p", IsVariation = true }; + var variant = new PartUsage { Id = Guid.NewGuid(), DeclaredName = "p1" }; + + OwnAsVariant(variation, variant); + + var implied = this.usageRule.Apply(variant); + + using (Assert.EnterMultipleScope()) + { + Assert.That(implied, Has.Count.EqualTo(1)); + Assert.That(implied[0], Is.InstanceOf()); + + var subsetting = (ISubsetting)implied[0]; + Assert.That(subsetting.IsImplied, Is.True); + Assert.That(subsetting.SubsettingFeature, Is.SameAs(variant)); + Assert.That(subsetting.SubsettedFeature, Is.SameAs(variation)); + + // The Definition rule must decline: the owner is a Usage, not a Definition. + Assert.That(this.definitionRule.Apply(variant), Is.Empty); + } + } + + [Test] + public void VerifyApplyForVariationDefinition() + { + // variation part def P { variant part p1; } -> member feature p1 : P + var variation = new PartDefinition { Id = Guid.NewGuid(), DeclaredName = "P", IsVariation = true }; + var variant = new PartUsage { Id = Guid.NewGuid(), DeclaredName = "p1" }; + + OwnAsVariant(variation, variant); + + var implied = this.definitionRule.Apply(variant); + + using (Assert.EnterMultipleScope()) + { + Assert.That(implied, Has.Count.EqualTo(1)); + Assert.That(implied[0], Is.InstanceOf()); + + var featureTyping = (IFeatureTyping)implied[0]; + Assert.That(featureTyping.IsImplied, Is.True); + Assert.That(featureTyping.TypedFeature, Is.SameAs(variant)); + Assert.That(featureTyping.Type, Is.SameAs(variation)); + + // A Definition is a Classifier, not a Usage, so the Usage rule must decline. + Assert.That(this.usageRule.Apply(variant), Is.Empty); + } + } + + [Test] + public void VerifyApplyDeclinesNonVariants() + { + var owner = new PartUsage { Id = Guid.NewGuid(), DeclaredName = "owner" }; + var ordinary = new PartUsage { Id = Guid.NewGuid(), DeclaredName = "ordinary" }; + + // An ordinary OwningMembership is NOT a VariantMembership, so neither rule applies. + var membership = new OwningMembership { Id = Guid.NewGuid() }; + ((IContainedRelationship)membership).OwnedRelatedElement.Add(ordinary); + ((IContainedElement)owner).OwnedRelationship.Add(membership); + + using (Assert.EnterMultipleScope()) + { + Assert.That(this.usageRule.Apply(ordinary), Is.Empty); + Assert.That(this.definitionRule.Apply(ordinary), Is.Empty); + Assert.That(this.usageRule.Apply(new Feature { Id = Guid.NewGuid() }), Is.Empty); + + Assert.That(() => this.usageRule.Apply(null), Throws.TypeOf()); + Assert.That(() => this.definitionRule.Apply(null), Throws.TypeOf()); + + Assert.That(() => new VariationUsageSpecializationRule(null), Throws.TypeOf()); + Assert.That(() => new VariationDefinitionSpecializationRule(null), Throws.TypeOf()); + } + } + + [Test] + public void VerifyConstraintName() + { + using (Assert.EnterMultipleScope()) + { + Assert.That(this.usageRule.ConstraintName, Is.EqualTo("checkUsageVariationUsageSpecialization")); + Assert.That(this.definitionRule.ConstraintName, Is.EqualTo("checkUsageVariationDefinitionSpecialization")); + + // Both names must exist in the generated manifest, otherwise the rule covers nothing. + Assert.That(ImpliedRelationshipTable.AllConstraintNames, Contains.Item(this.usageRule.ConstraintName)); + Assert.That(ImpliedRelationshipTable.AllConstraintNames, Contains.Item(this.definitionRule.ConstraintName)); + Assert.That(ImpliedRelationshipTable.NotCovered.Any(entry => entry.Contains(this.usageRule.ConstraintName)), Is.True); + } + } + + private static void OwnAsVariant(INamespace variation, IElement variant) + { + var membership = new VariantMembership { Id = Guid.NewGuid() }; + ((IContainedRelationship)membership).OwnedRelatedElement.Add(variant); + ((IContainedElement)variation).OwnedRelationship.Add(membership); + } + } +} diff --git a/SysML2.NET.Semantics.Tests/SysML2.NET.Semantics.Tests.csproj b/SysML2.NET.Semantics.Tests/SysML2.NET.Semantics.Tests.csproj new file mode 100644 index 00000000..a7a44e5e --- /dev/null +++ b/SysML2.NET.Semantics.Tests/SysML2.NET.Semantics.Tests.csproj @@ -0,0 +1,41 @@ + + + + net10.0 + 14.0 + Starion Group S.A. + Sam Gerene + Nunit test suite for the SysML2.NET.Semantics Library + Copyright © Starion Group S.A. + Apache-2.0 + https://github.com/STARIONGROUP/SysML2.NET.git + Git + false + disable + false + true + en-US + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/SysML2.NET.Semantics/AutoGenImplied/GeneratedImpliedRuleGuards.cs b/SysML2.NET.Semantics/AutoGenImplied/GeneratedImpliedRuleGuards.cs new file mode 100644 index 00000000..bde03ada --- /dev/null +++ b/SysML2.NET.Semantics/AutoGenImplied/GeneratedImpliedRuleGuards.cs @@ -0,0 +1,138 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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. +// +// +// ------------------------------------------------------------------------------------------------ + +// ------------------------------------------------------------------------------------------------ +// --------THIS IS AN AUTOMATICALLY GENERATED FILE. ANY MANUAL CHANGES WILL BE OVERWRITTEN!-------- +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Semantics.Implied +{ + using System.Collections.Generic; + using System.Linq; + + /// + /// The guards whose OCL was mechanically translated from the abstract syntax. + /// + /// + /// A conditional semantic constraint absent from this set has a guard expression outside the + /// translatable shapes and must be supplied by a hand-written . + /// + public static class GeneratedImpliedRuleGuards + { + /// + /// The generated guards, ordered by constraint name. + /// + public static IReadOnlyList All { get; } = + [ + // not isTriggerAction() + new GeneratedRuleGuard("checkAcceptActionUsageSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Actions.IAcceptActionUsage guardSubject && !guardSubject.IsTriggerAction()), + // isTriggerAction() + new GeneratedRuleGuard("checkAcceptActionUsageTriggerActionSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Actions.IAcceptActionUsage guardSubject && guardSubject.IsTriggerAction()), + // isComposite and owningType <> null and (owningType.oclIsKindOf(PartDefinition) or owningType.oclIsKindOf(PartUsage)) + new GeneratedRuleGuard("checkActionUsageOwnedActionSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Actions.IActionUsage { IsComposite: true, owningType: SysML2.NET.Core.POCO.Systems.Parts.IPartDefinition or SysML2.NET.Core.POCO.Systems.Parts.IPartUsage }), + // isSubactionUsage() + new GeneratedRuleGuard("checkActionUsageSubactionSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Actions.IActionUsage guardSubject && guardSubject.IsSubactionUsage()), + // isComposite and owningType <> null and (owningType.oclIsKindOf(AnalysisCaseDefinition) or owningType.oclIsKindOf(AnalysisCaseUsage)) + new GeneratedRuleGuard("checkAnalysisCaseUsageSubAnalysisCaseSpecialization", element => element is SysML2.NET.Core.POCO.Systems.AnalysisCases.IAnalysisCaseUsage { IsComposite: true, owningType: SysML2.NET.Core.POCO.Systems.AnalysisCases.IAnalysisCaseDefinition or SysML2.NET.Core.POCO.Systems.AnalysisCases.IAnalysisCaseUsage }), + // isSubactionUsage() + new GeneratedRuleGuard("checkAssignmentActionUsageSubactionSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Actions.IAssignmentActionUsage guardSubject && guardSubject.IsSubactionUsage()), + // owningType <> null and (owningType.oclIsKindOf(CalculationDefinition) or owningType.oclIsKindOf(CalculationUsage)) + new GeneratedRuleGuard("checkCalculationUsageSubcalculationSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Calculations.ICalculationUsage { owningType: SysML2.NET.Core.POCO.Systems.Calculations.ICalculationDefinition or SysML2.NET.Core.POCO.Systems.Calculations.ICalculationUsage }), + // isComposite and owningType <> null and (owningType.oclIsKindOf(CaseDefinition) or owningType.oclIsKindOf(CaseUsage)) + new GeneratedRuleGuard("checkCaseUsageSubcaseSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Cases.ICaseUsage { IsComposite: true, owningType: SysML2.NET.Core.POCO.Systems.Cases.ICaseDefinition or SysML2.NET.Core.POCO.Systems.Cases.ICaseUsage }), + // owningFeatureMembership <> null and owningFeatureMembership.oclIsKindOf(FramedConcernMembership) + new GeneratedRuleGuard("checkConcernUsageFramedConcernSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Requirements.IConcernUsage { owningFeatureMembership: SysML2.NET.Core.POCO.Systems.Requirements.IFramedConcernMembership }), + // ownedEndFeature->size() = 2 + new GeneratedRuleGuard("checkConnectionDefinitionBinarySpecialization", element => element is SysML2.NET.Core.POCO.Systems.Connections.IConnectionDefinition guardSubject && ((SysML2.NET.Core.POCO.Core.Types.IType)guardSubject).ownedEndFeature.Count == 2), + // ownedEndFeature->size() = 2 + new GeneratedRuleGuard("checkConnectionUsageBinarySpecialization", element => element is SysML2.NET.Core.POCO.Systems.Connections.IConnectionUsage guardSubject && ((SysML2.NET.Core.POCO.Core.Types.IType)guardSubject).ownedEndFeature.Count == 2), + // owningType <> null and (owningType.oclIsKindOf(ItemDefinition) or owningType.oclIsKindOf(ItemUsage)) + new GeneratedRuleGuard("checkConstraintUsageCheckedConstraintSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Constraints.IConstraintUsage { owningType: SysML2.NET.Core.POCO.Systems.Items.IItemDefinition or SysML2.NET.Core.POCO.Systems.Items.IItemUsage }), + // owningType <> null and (owningType.oclIsKindOf(OccurrenceDefinition) or owningType.oclIsKindOf(OccurrenceUsage)) + new GeneratedRuleGuard("checkEventOccurrenceUsageSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Occurrences.IEventOccurrenceUsage { owningType: SysML2.NET.Core.POCO.Systems.Occurrences.IOccurrenceDefinition or SysML2.NET.Core.POCO.Systems.Occurrences.IOccurrenceUsage }), + // owningType <> null and (owningType.oclIsKindOf(PartDefinition) or owningType.oclIsKindOf(PartUsage)) + new GeneratedRuleGuard("checkExhibitStateUsageSpecialization", element => element is SysML2.NET.Core.POCO.Systems.States.IExhibitStateUsage { owningType: SysML2.NET.Core.POCO.Systems.Parts.IPartDefinition or SysML2.NET.Core.POCO.Systems.Parts.IPartUsage }), + // ownedTyping.type->exists(selectByKind(DataType)) + new GeneratedRuleGuard("checkFeatureDataValueSpecialization", element => element is SysML2.NET.Core.POCO.Core.Features.IFeature guardSubject && guardSubject.ownedTyping.Any(featureTyping => featureTyping.Type is SysML2.NET.Core.POCO.Kernel.DataTypes.IDataType)), + // ownedTyping.type->exists(selectByKind(Structure)) + new GeneratedRuleGuard("checkFeatureObjectSpecialization", element => element is SysML2.NET.Core.POCO.Core.Features.IFeature guardSubject && guardSubject.ownedTyping.Any(featureTyping => featureTyping.Type is SysML2.NET.Core.POCO.Kernel.Structures.IStructure)), + // ownedTyping.type->exists(selectByKind(Class)) + new GeneratedRuleGuard("checkFeatureOccurrenceSpecialization", element => element is SysML2.NET.Core.POCO.Core.Features.IFeature guardSubject && guardSubject.ownedTyping.Any(featureTyping => featureTyping.Type is SysML2.NET.Core.POCO.Kernel.Classes.IClass)), + // ownedEndFeatures->notEmpty() + new GeneratedRuleGuard("checkFlowUsageFlowSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Flows.IFlowUsage guardSubject && ((SysML2.NET.Core.POCO.Core.Types.IType)guardSubject).ownedEndFeature.Count > 0), + // ownedEndFeatures->notEmpty() + new GeneratedRuleGuard("checkFlowWithEndsSpecialization", element => element is SysML2.NET.Core.POCO.Kernel.Interactions.IFlow guardSubject && ((SysML2.NET.Core.POCO.Core.Types.IType)guardSubject).ownedEndFeature.Count > 0), + // isSubactionUsage() + new GeneratedRuleGuard("checkForLoopActionUsageSubactionSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Actions.IForLoopActionUsage guardSubject && guardSubject.IsSubactionUsage()), + // isSubactionUsage() + new GeneratedRuleGuard("checkIfActionUsageSubactionSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Actions.IIfActionUsage guardSubject && guardSubject.IsSubactionUsage()), + // ownedEndFeature->size() = 2 + new GeneratedRuleGuard("checkInterfaceDefinitionBinarySpecialization", element => element is SysML2.NET.Core.POCO.Systems.Interfaces.IInterfaceDefinition guardSubject && ((SysML2.NET.Core.POCO.Core.Types.IType)guardSubject).ownedEndFeature.Count == 2), + // ownedEndFeature->size() = 2 + new GeneratedRuleGuard("checkInterfaceUsageBinarySpecialization", element => element is SysML2.NET.Core.POCO.Systems.Interfaces.IInterfaceUsage guardSubject && ((SysML2.NET.Core.POCO.Core.Types.IType)guardSubject).ownedEndFeature.Count == 2), + // isComposite and owningType <> null and (owningType.oclIsKindOf(ItemDefinition) or owningType.oclIsKindOf(ItemUsage)) + new GeneratedRuleGuard("checkItemUsageSubitemSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Items.IItemUsage { IsComposite: true, owningType: SysML2.NET.Core.POCO.Systems.Items.IItemDefinition or SysML2.NET.Core.POCO.Systems.Items.IItemUsage }), + // isIndividual + new GeneratedRuleGuard("checkOccurrenceDefinitionIndividualSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Occurrences.IOccurrenceDefinition { IsIndividual: true }), + // portionKind = PortionKind::snapshot + new GeneratedRuleGuard("checkOccurrenceUsageSnapshotSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Occurrences.IOccurrenceUsage { PortionKind: SysML2.NET.Core.Systems.Occurrences.PortionKind.Snapshot }), + // portionKind = PortionKind::timeslice + new GeneratedRuleGuard("checkOccurrenceUsageTimeSliceSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Occurrences.IOccurrenceUsage { PortionKind: SysML2.NET.Core.Systems.Occurrences.PortionKind.Timeslice }), + // owningFeatureMembership <> null and owningFeatureMembership.oclIsKindOf(StakeholderMembership) + new GeneratedRuleGuard("checkPartUsageStakeholderSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Parts.IPartUsage { owningFeatureMembership: SysML2.NET.Core.POCO.Systems.Requirements.IStakeholderMembership }), + // isComposite and owningType <> null and (owningType.oclIsKindOf(ItemDefinition) or owningType.oclIsKindOf(ItemUsage)) + new GeneratedRuleGuard("checkPartUsageSubpartSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Parts.IPartUsage { IsComposite: true, owningType: SysML2.NET.Core.POCO.Systems.Items.IItemDefinition or SysML2.NET.Core.POCO.Systems.Items.IItemUsage }), + // owningType <> null and (owningType.oclIsKindOf(PartDefinition) or owningType.oclIsKindOf(PartUsage)) + new GeneratedRuleGuard("checkPerformActionUsageSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Actions.IPerformActionUsage { owningType: SysML2.NET.Core.POCO.Systems.Parts.IPartDefinition or SysML2.NET.Core.POCO.Systems.Parts.IPartUsage }), + // owningType <> null and (owningType.oclIsKindOf(PartDefinition) or owningType.oclIsKindOf(PartUsage)) + new GeneratedRuleGuard("checkPortUsageOwnedPortSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Ports.IPortUsage { owningType: SysML2.NET.Core.POCO.Systems.Parts.IPartDefinition or SysML2.NET.Core.POCO.Systems.Parts.IPartUsage }), + // isComposite and owningType <> null and (owningType.oclIsKindOf(PortDefinition) or owningType.oclIsKindOf(PortUsage)) + new GeneratedRuleGuard("checkPortUsageSubportSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Ports.IPortUsage { IsComposite: true, owningType: SysML2.NET.Core.POCO.Systems.Ports.IPortDefinition or SysML2.NET.Core.POCO.Systems.Ports.IPortUsage }), + // owningType <> null and (owningType.oclIsKindOf(RenderingDefinition) or owningType.oclIsKindOf(RenderingUsage)) + new GeneratedRuleGuard("checkRenderingUsageSubrenderingSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Views.IRenderingUsage { owningType: SysML2.NET.Core.POCO.Systems.Views.IRenderingDefinition or SysML2.NET.Core.POCO.Systems.Views.IRenderingUsage }), + // owningFeatureMembership <> null and owningFeatureMembership.oclIsKindOf(RequirementVerificationMembership) + new GeneratedRuleGuard("checkRequirementUsageRequirementVerificationSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Requirements.IRequirementUsage { owningFeatureMembership: SysML2.NET.Core.POCO.Systems.VerificationCases.IRequirementVerificationMembership }), + // isComposite and owningType <> null and (owningType.oclIsKindOf(RequirementDefinition) or owningType.oclIsKindOf(RequirementUsage)) + new GeneratedRuleGuard("checkRequirementUsageSubrequirementSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Requirements.IRequirementUsage { IsComposite: true, owningType: SysML2.NET.Core.POCO.Systems.Requirements.IRequirementDefinition or SysML2.NET.Core.POCO.Systems.Requirements.IRequirementUsage }), + // isSubactionUsage() + new GeneratedRuleGuard("checkSendActionUsageSubactionSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Actions.ISendActionUsage guardSubject && guardSubject.IsSubactionUsage()), + // isSubstateUsage(false) + new GeneratedRuleGuard("checkStateUsageExclusiveStateSpecialization", element => element is SysML2.NET.Core.POCO.Systems.States.IStateUsage guardSubject && guardSubject.IsSubstateUsage(false)), + // isComposite and owningType <> null and (owningType.oclIsKindOf(PartDefinition) or owningType.oclIsKindOf(PartUsage)) + new GeneratedRuleGuard("checkStateUsageOwnedStateSpecialization", element => element is SysML2.NET.Core.POCO.Systems.States.IStateUsage { IsComposite: true, owningType: SysML2.NET.Core.POCO.Systems.Parts.IPartDefinition or SysML2.NET.Core.POCO.Systems.Parts.IPartUsage }), + // isSubstateUsage(true) + new GeneratedRuleGuard("checkStateUsageSubstateSpecialization", element => element is SysML2.NET.Core.POCO.Systems.States.IStateUsage guardSubject && guardSubject.IsSubstateUsage(true)), + // owningType <> null and (owningType.oclIsKindOf(Behavior) or owningType.oclIsKindOf(Step)) + new GeneratedRuleGuard("checkStepEnclosedPerformanceSpecialization", element => element is SysML2.NET.Core.POCO.Kernel.Behaviors.IStep { owningType: SysML2.NET.Core.POCO.Kernel.Behaviors.IBehavior or SysML2.NET.Core.POCO.Kernel.Behaviors.IStep }), + // isSubactionUsage() + new GeneratedRuleGuard("checkTerminateActionUsageSubactionSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Actions.ITerminateActionUsage guardSubject && guardSubject.IsSubactionUsage()), + // isComposite and owningType <> null and (owningType.oclIsKindOf(UseCaseDefinition) or owningType.oclIsKindOf(UseCaseUsage)) + new GeneratedRuleGuard("checkUseCaseUsageSubUseCaseSpecialization", element => element is SysML2.NET.Core.POCO.Systems.UseCases.IUseCaseUsage { IsComposite: true, owningType: SysML2.NET.Core.POCO.Systems.UseCases.IUseCaseDefinition or SysML2.NET.Core.POCO.Systems.UseCases.IUseCaseUsage }), + // isComposite and owningType <> null and (owningType.oclIsKindOf(VerificationCaseDefinition) or owningType.oclIsKindOf(VerificationCaseUsage)) + new GeneratedRuleGuard("checkVerificationCaseUsageSubVerificationCaseSpecialization", element => element is SysML2.NET.Core.POCO.Systems.VerificationCases.IVerificationCaseUsage { IsComposite: true, owningType: SysML2.NET.Core.POCO.Systems.VerificationCases.IVerificationCaseDefinition or SysML2.NET.Core.POCO.Systems.VerificationCases.IVerificationCaseUsage }), + // owningType <> null and (owningType.oclIsKindOf(ViewDefinition) or owningType.oclIsKindOf(ViewUsage)) + new GeneratedRuleGuard("checkViewUsageSubviewSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Views.IViewUsage { owningType: SysML2.NET.Core.POCO.Systems.Views.IViewDefinition or SysML2.NET.Core.POCO.Systems.Views.IViewUsage }), + // isComposite and owningType <> null and (owningType.oclIsKindOf(ViewDefinition) or owningType.oclIsKindOf(ViewUsage)) + new GeneratedRuleGuard("checkViewpointUsageViewpointSatisfactionSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Views.IViewpointUsage { IsComposite: true, owningType: SysML2.NET.Core.POCO.Systems.Views.IViewDefinition or SysML2.NET.Core.POCO.Systems.Views.IViewUsage }), + // isSubactionUsage() + new GeneratedRuleGuard("checkWhileLoopActionUsageSubactionSpecialization", element => element is SysML2.NET.Core.POCO.Systems.Actions.IWhileLoopActionUsage guardSubject && guardSubject.IsSubactionUsage()), + ]; + } +} diff --git a/SysML2.NET.Semantics/AutoGenImplied/ImpliedRelationshipTable.cs b/SysML2.NET.Semantics/AutoGenImplied/ImpliedRelationshipTable.cs new file mode 100644 index 00000000..f9dd1fbb --- /dev/null +++ b/SysML2.NET.Semantics/AutoGenImplied/ImpliedRelationshipTable.cs @@ -0,0 +1,3402 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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. +// +// +// ------------------------------------------------------------------------------------------------ + +// ------------------------------------------------------------------------------------------------ +// --------THIS IS AN AUTOMATICALLY GENERATED FILE. ANY MANUAL CHANGES WILL BE OVERWRITTEN!-------- +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Semantics.Implied +{ + using System.Collections.Generic; + + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// The kind of Relationship that is implied to satisfy a semantic constraint. + /// + public enum ImpliedRelationshipKind + { + /// + /// A Subclassification, implied for a Classifier. + /// + Subclassification, + + /// + /// A Subsetting, implied for a Feature. + /// + Subsetting + } + + /// + /// A single implied library Specialization, as required by one semantic constraint of the + /// KerML/SysML abstract syntax. + /// + public readonly struct ImpliedLibrarySpecialization + { + /// + /// Initializes a new instance of the struct. + /// + /// + /// The name of the semantic constraint the rule was extracted from + /// + /// + /// The qualified name of the library Type that must be specialized + /// + /// + /// The metaclass the constraint is declared on, which decides + /// + /// + /// Whether the constraint's OCL guards the specialization + /// + public ImpliedLibrarySpecialization(string constraintName, string targetLibraryName, string declaringMetaclassName, bool requiresGuard) + { + this.ConstraintName = constraintName; + this.TargetLibraryName = targetLibraryName; + this.DeclaringMetaclassName = declaringMetaclassName; + this.RequiresGuard = requiresGuard; + } + + /// + /// Gets the name of the semantic constraint the rule was extracted from. + /// + public string ConstraintName { get; } + + /// + /// Gets the qualified name of the library Type that must be specialized. + /// + public string TargetLibraryName { get; } + + /// + /// Gets the metaclass the constraint is declared on. The Relationship kind is decided from this + /// name, not from the metaclass that inherits the constraint. + /// + public string DeclaringMetaclassName { get; } + + /// + /// Gets a value indicating whether the constraint's OCL guards the specialization, in which case the + /// rule applies only when the hand-written predicate for says so. + /// + public bool RequiresGuard { get; } + + /// + /// Gets the kind of Relationship to imply, decided by whether the declaring metaclass is a + /// Classifier or a Feature. + /// + public ImpliedRelationshipKind Kind => + ImpliedRelationshipTable.SubclassificationMetaclasses.Contains(this.DeclaringMetaclassName) + ? ImpliedRelationshipKind.Subclassification + : ImpliedRelationshipKind.Subsetting; + } + + /// + /// The table of implied library Specializations that KerML 1.0 §8.4.2 allows a tool to insert to + /// satisfy the specialization constraints of the abstract syntax. + /// + /// + /// + /// The metaclass and the library target of each row come from the constraint's OCL body in the UML XMI. + /// The does NOT: the OCL says only what to specialize, never + /// whether the implied Relationship is a Subclassification or a Subsetting. That is stated only in the + /// specification tables — KerML 1.0 Table 8 (§8.4.3.1.1) and Table 10 (§8.4.4.1), and SysML 2.0 + /// Tables 31-33 — so below is transcribed by hand from those + /// tables and lives in the Handlebars template, not in the generator. + /// + /// + /// Rows are NOT reduced against the §8.4.2 redundancy rules. Rule 1 suppresses an implied Specialization + /// whose general Type is a supertype of another applicable one, and deciding that needs the library + /// Types resolved — they are not present in the metamodel XMI. The reduction therefore belongs to the + /// caller, which must also apply rule 2 (de-duplicate identical targets). Neither rule applies to + /// Redefinitions. + /// + /// + public static class ImpliedRelationshipTable + { + /// + /// The metaclasses whose implied Specialization is a Subclassification rather than a + /// Subsetting — that is, the Classifiers. + /// + /// + /// HAND-MAINTAINED. This is the one part of the table that cannot be derived from the OCL; it is + /// transcribed from KerML Table 8 / Table 10 and SysML Tables 31-33. KerML Table 8 note 1 is the + /// reason Type is absent: checkTypeSpecialization applies to every Type, but the + /// Subclassification is only implied for Classifiers. Anything not listed here is a Feature and + /// implies a Subsetting. + /// + internal static readonly HashSet SubclassificationMetaclasses = + [ + "ActionDefinition", + "AllocationDefinition", + "AnalysisCaseDefinition", + "Association", + "AssociationStructure", + "Behavior", + "CalculationDefinition", + "CaseDefinition", + "Class", + "ConcernDefinition", + "ConnectionDefinition", + "ConstraintDefinition", + "DataType", + "FlowDefinition", + "Function", + "InterfaceDefinition", + "ItemDefinition", + "Metaclass", + "MetadataDefinition", + "OccurrenceDefinition", + "PartDefinition", + "PortDefinition", + "Predicate", + "RenderingDefinition", + "RequirementDefinition", + "StateDefinition", + "Structure", + "UseCaseDefinition", + "VerificationCaseDefinition", + "ViewDefinition", + "ViewpointDefinition" + ]; + + /// + /// The semantic constraints that are NOT represented in this table, with the reason. Emitted so that + /// no constraint of KerML §8.4.2 is silently dropped while its hand-coded arm is outstanding. + /// + public static IReadOnlyList NotCovered { get; } = + [ + "AcceptActionUsage.checkAcceptActionUsageReceiverBindingConnector (BindingConnector) - OCL is not a specializesFromLibrary call", + "ActionUsage.checkActionUsageStateActionRedefinition (Redefinition) - OCL is not a specializesFromLibrary call", + "AssertConstraintUsage.checkAssertConstraintUsageSpecialization (Specialization) - OCL is not a specializesFromLibrary call", + "AssignmentActionUsage.checkAssignmentActionUsageAccessedFeatureRedefinition (Redefinition) - OCL is not a specializesFromLibrary call", + "AssignmentActionUsage.checkAssignmentActionUsageReferentRedefinition (Redefinition) - OCL is not a specializesFromLibrary call", + "AssignmentActionUsage.checkAssignmentActionUsageStartingAtRedefinition (Redefinition) - OCL is not a specializesFromLibrary call", + "Connector.checkConnectorTypeFeaturing (TypeFeaturing) - OCL is not a specializesFromLibrary call", + "ConstraintUsage.checkConstraintUsageRequirementConstraintSpecialization (Specialization) - OCL is not a specializesFromLibrary call", + "ConstructorExpression.checkConstructorExpressionResultDefaultValueBindingConnector (BindingConnector) - specification body is TBD", + "ConstructorExpression.checkConstructorExpressionResultFeatureRedefinition (Redefinition) - OCL is not a specializesFromLibrary call", + "ConstructorExpression.checkConstructorExpressionResultSpecialization (Specialization) - OCL is not a specializesFromLibrary call", + "ConstructorExpression.checkConstructorExpressionSpecialization (Specialization) - OCL is not a specializesFromLibrary call", + "DecisionNode.checkDecisionNodeOutgoingSuccessionSpecialization (Specialization) - OCL is not a specializesFromLibrary call", + "Expression.checkExpressionResultBindingConnector (BindingConnector) - OCL is not a specializesFromLibrary call", + "Expression.checkExpressionTypeFeaturing (TypeFeaturing) - OCL is not a specializesFromLibrary call", + "Feature.checkFeatureCrossingSpecialization (Specialization) - OCL is not a specializesFromLibrary call", + "Feature.checkFeatureEndRedefinition (Redefinition) - OCL is not a specializesFromLibrary call", + "Feature.checkFeatureFeatureMembershipTypeFeaturing (TypeFeaturing) - OCL is not a specializesFromLibrary call", + "Feature.checkFeatureFlowFeatureRedefinition (Redefinition) - OCL is not a specializesFromLibrary call", + "Feature.checkFeatureOwnedCrossFeatureRedefinitionSpecialization (Specialization) - OCL is not a specializesFromLibrary call", + "Feature.checkFeatureOwnedCrossFeatureSpecialization (Specialization) - OCL is not a specializesFromLibrary call", + "Feature.checkFeatureOwnedCrossFeatureTypeFeaturing (TypeFeaturing) - OCL is not a specializesFromLibrary call", + "Feature.checkFeatureParameterRedefinition (Redefinition) - OCL is not a specializesFromLibrary call", + "Feature.checkFeatureResultRedefinition (Redefinition) - OCL is not a specializesFromLibrary call", + "Feature.checkFeatureValuationSpecialization (Specialization) - OCL is not a specializesFromLibrary call", + "FeatureChainExpression.checkFeatureChainExpressionResultSpecialization (Specialization) - OCL is not a specializesFromLibrary call", + "FeatureChainExpression.checkFeatureChainExpressionSourceTargetRedefinition (Redefinition) - OCL is not a specializesFromLibrary call", + "FeatureChainExpression.checkFeatureChainExpressionTargetRedefinition (Redefinition) - OCL is not a specializesFromLibrary call", + "FeatureReferenceExpression.checkFeatureReferenceExpressionBindingConnector (BindingConnector) - OCL is not a specializesFromLibrary call", + "FeatureReferenceExpression.checkFeatureReferenceExpressionResultSpecialization (Specialization) - OCL is not a specializesFromLibrary call", + "FeatureValue.checkFeatureValueBindingConnector (BindingConnector) - OCL is not a specializesFromLibrary call", + "ForLoopActionUsage.checkForLoopActionUsageVarRedefinition (Redefinition) - OCL is not a specializesFromLibrary call", + "Function.checkFunctionResultBindingConnector (BindingConnector) - OCL is not a specializesFromLibrary call", + "IfActionUsage.checkIfActionUsageSpecialization (Specialization) - OCL is not a specializesFromLibrary call", + "IndexExpression.checkIndexExpressionResultSpecialization (Specialization) - OCL is not a specializesFromLibrary call", + "Invariant.checkInvariantSpecialization (Specialization) - OCL is not a specializesFromLibrary call", + "InvocationExpression.checkInvocationExpressionBehaviorBindingConnector (BindingConnector) - OCL is not a specializesFromLibrary call", + "InvocationExpression.checkInvocationExpressionBehaviorResultSpecialization (Specialization) - OCL is not a specializesFromLibrary call", + "InvocationExpression.checkInvocationExpressionDefaultValueBindingConnector (BindingConnector) - specification body is TBD", + "InvocationExpression.checkInvocationExpressionSpecialization (Specialization) - OCL is not a specializesFromLibrary call", + "MergeNode.checkMergeNodeIncomingSuccessionSpecialization (Specialization) - OCL is not a specializesFromLibrary call", + "MetadataFeature.checkMetadataFeatureSemanticSpecialization (Specialization) - OCL is not a specializesFromLibrary call", + "Multiplicity.checkMultiplicityTypeFeaturing (TypeFeaturing) - OCL is not a specializesFromLibrary call", + "MultiplicityRange.checkMultiplicityRangeExpressionTypeFeaturing (TypeFeaturing) - OCL is not a specializesFromLibrary call", + "OccurrenceDefinition.checkOccurrenceDefinitionMultiplicitySpecialization (Specialization) - OCL is not a specializesFromLibrary call", + "PartUsage.checkPartUsageActorSpecialization (Specialization) - OCL is not a specializesFromLibrary call", + "PayloadFeature.checkPayloadFeatureRedefinition (Redefinition) - OCL is not a specializesFromLibrary call", + "RenderingUsage.checkRenderingUsageRedefinition (Redefinition) - OCL is not a specializesFromLibrary call", + "RequirementUsage.checkRequirementUsageObjectiveRedefinition (Redefinition) - OCL is not a specializesFromLibrary call", + "SatisfyRequirementUsage.checkSatisfyRequirementUsageBindingConnector (BindingConnector) - OCL is not a specializesFromLibrary call", + "SatisfyRequirementUsage.checkSatisfyRequirementUsageSpecialization (Specialization) - OCL is not a specializesFromLibrary call", + "SelectExpression.checkSelectExpressionResultSpecialization (Specialization) - OCL is not a specializesFromLibrary call", + "TransitionUsage.checkTransitionUsagePayloadSpecialization (Specialization) - OCL is not a specializesFromLibrary call", + "TransitionUsage.checkTransitionUsageSourceBindingConnector (BindingConnector) - OCL is not a specializesFromLibrary call", + "TransitionUsage.checkTransitionUsageSuccessionBindingConnector (BindingConnector) - OCL is not a specializesFromLibrary call", + "TransitionUsage.checkTransitionUsageSuccessionSourceSpecialization (Specialization) - OCL is not a specializesFromLibrary call", + "TransitionUsage.checkTransitionUsageTransitionFeatureSpecialization (Specialization) - OCL is not a specializesFromLibrary call", + "Usage.checkUsageVariationDefinitionSpecialization (Specialization) - OCL is not a specializesFromLibrary call", + "Usage.checkUsageVariationUsageSpecialization (Specialization) - OCL is not a specializesFromLibrary call", + "Usage.checkUsageVariationUsageTypeFeaturing (TypeFeaturing) - OCL is not a specializesFromLibrary call", + ]; + + /// + /// The name of every semantic constraint declared in the abstract syntax, covered or not. + /// + public static IReadOnlyList AllConstraintNames { get; } = + [ + "checkAcceptActionUsageReceiverBindingConnector", + "checkAcceptActionUsageSpecialization", + "checkAcceptActionUsageSubactionSpecialization", + "checkAcceptActionUsageTriggerActionSpecialization", + "checkActionDefinitionSpecialization", + "checkActionUsageOwnedActionSpecialization", + "checkActionUsageSpecialization", + "checkActionUsageStateActionRedefinition", + "checkActionUsageSubactionSpecialization", + "checkAllocationDefinitionSpecialization", + "checkAllocationUsageSpecialization", + "checkAnalysisCaseDefinitionSpecialization", + "checkAnalysisCaseUsageSpecialization", + "checkAnalysisCaseUsageSubAnalysisCaseSpecialization", + "checkAssertConstraintUsageSpecialization", + "checkAssignmentActionUsageAccessedFeatureRedefinition", + "checkAssignmentActionUsageReferentRedefinition", + "checkAssignmentActionUsageSpecialization", + "checkAssignmentActionUsageStartingAtRedefinition", + "checkAssignmentActionUsageSubactionSpecialization", + "checkAssociationBinarySpecialization", + "checkAssociationSpecialization", + "checkAssociationStructureBinarySpecialization", + "checkAssociationStructureSpecialization", + "checkAttributeUsageSpecialization", + "checkBehaviorSpecialization", + "checkBindingConnectorSpecialization", + "checkBooleanExpressionSpecialization", + "checkCalculationDefinitionSpecialization", + "checkCalculationUsageSpecialization", + "checkCalculationUsageSubcalculationSpecialization", + "checkCaseDefinitionSpecialization", + "checkCaseUsageSpecialization", + "checkCaseUsageSubcaseSpecialization", + "checkClassSpecialization", + "checkConcernDefinitionSpecialization", + "checkConcernUsageFramedConcernSpecialization", + "checkConcernUsageSpecialization", + "checkConnectionDefinitionBinarySpecialization", + "checkConnectionDefinitionSpecializations", + "checkConnectionUsageBinarySpecialization", + "checkConnectionUsageSpecialization", + "checkConnectorBinaryObjectSpecialization", + "checkConnectorBinarySpecialization", + "checkConnectorObjectSpecialization", + "checkConnectorSpecialization", + "checkConnectorTypeFeaturing", + "checkConstraintDefinitionSpecialization", + "checkConstraintUsageCheckedConstraintSpecialization", + "checkConstraintUsageRequirementConstraintSpecialization", + "checkConstraintUsageSpecialization", + "checkConstructorExpressionResultDefaultValueBindingConnector", + "checkConstructorExpressionResultFeatureRedefinition", + "checkConstructorExpressionResultSpecialization", + "checkConstructorExpressionSpecialization", + "checkControlNodeSpecialization", + "checkDataTypeSpecialization", + "checkDecisionNodeOutgoingSuccessionSpecialization", + "checkDecisionNodeSpecialization", + "checkEventOccurrenceUsageSpecialization", + "checkExhibitStateUsageSpecialization", + "checkExpressionResultBindingConnector", + "checkExpressionSpecialization", + "checkExpressionTypeFeaturing", + "checkFeatureChainExpressionResultSpecialization", + "checkFeatureChainExpressionSourceTargetRedefinition", + "checkFeatureChainExpressionTargetRedefinition", + "checkFeatureCrossingSpecialization", + "checkFeatureDataValueSpecialization", + "checkFeatureEndRedefinition", + "checkFeatureEndSpecialization", + "checkFeatureFeatureMembershipTypeFeaturing", + "checkFeatureFlowFeatureRedefinition", + "checkFeatureObjectSpecialization", + "checkFeatureOccurrenceSpecialization", + "checkFeatureOwnedCrossFeatureRedefinitionSpecialization", + "checkFeatureOwnedCrossFeatureSpecialization", + "checkFeatureOwnedCrossFeatureTypeFeaturing", + "checkFeatureParameterRedefinition", + "checkFeaturePortionSpecialization", + "checkFeatureReferenceExpressionBindingConnector", + "checkFeatureReferenceExpressionResultSpecialization", + "checkFeatureResultRedefinition", + "checkFeatureSpecialization", + "checkFeatureSubobjectSpecialization", + "checkFeatureSuboccurrenceSpecialization", + "checkFeatureValuationSpecialization", + "checkFeatureValueBindingConnector", + "checkFlowDefinitionBinarySpecialization", + "checkFlowDefinitionSpecialization", + "checkFlowSpecialization", + "checkFlowUsageFlowSpecialization", + "checkFlowUsageSpecialization", + "checkFlowWithEndsSpecialization", + "checkForLoopActionUsageSpecialization", + "checkForLoopActionUsageSubactionSpecialization", + "checkForLoopActionUsageVarRedefinition", + "checkForkNodeSpecialization", + "checkFunctionResultBindingConnector", + "checkFunctionSpecialization", + "checkIfActionUsageSpecialization", + "checkIfActionUsageSubactionSpecialization", + "checkIncludeUseCaseUsageSpecialization", + "checkIndexExpressionResultSpecialization", + "checkInterfaceDefinitionBinarySpecialization", + "checkInterfaceDefinitionSpecialization", + "checkInterfaceUsageBinarySpecialization", + "checkInterfaceUsageSpecialization", + "checkInvariantSpecialization", + "checkInvocationExpressionBehaviorBindingConnector", + "checkInvocationExpressionBehaviorResultSpecialization", + "checkInvocationExpressionDefaultValueBindingConnector", + "checkInvocationExpressionSpecialization", + "checkItemDefinitionSpecialization", + "checkItemUsageSpecialization", + "checkItemUsageSubitemSpecialization", + "checkJoinNodeSpecialization", + "checkLiteralBooleanSpecialization", + "checkLiteralExpressionSpecialization", + "checkLiteralInfinitySpecialization", + "checkLiteralIntegerSpecialization", + "checkLiteralRationalSpecialization", + "checkLiteralStringSpecialization", + "checkMergeNodeIncomingSuccessionSpecialization", + "checkMergeNodeSpecialization", + "checkMetaclassSpecialization", + "checkMetadataAccessExpressionSpecialization", + "checkMetadataDefinitionSpecialization", + "checkMetadataFeatureSemanticSpecialization", + "checkMetadataFeatureSpecialization", + "checkMetadataUsageSpecialization", + "checkMultiplicityRangeExpressionTypeFeaturing", + "checkMultiplicitySpecialization", + "checkMultiplicityTypeFeaturing", + "checkNullExpressionSpecialization", + "checkOccurrenceDefinitionIndividualSpecialization", + "checkOccurrenceDefinitionMultiplicitySpecialization", + "checkOccurrenceUsageSnapshotSpecialization", + "checkOccurrenceUsageSpecialization", + "checkOccurrenceUsageSuboccurrenceSpecialization", + "checkOccurrenceUsageTimeSliceSpecialization", + "checkPartDefinitionSpecialization", + "checkPartUsageActorSpecialization", + "checkPartUsageSpecialization", + "checkPartUsageStakeholderSpecialization", + "checkPartUsageSubpartSpecialization", + "checkPayloadFeatureRedefinition", + "checkPerformActionUsageSpecialization", + "checkPortDefinitionSpecialization", + "checkPortUsageOwnedPortSpecialization", + "checkPortUsageSpecialization", + "checkPortUsageSubportSpecialization", + "checkPredicateSpecialization", + "checkRenderingDefinitionSpecialization", + "checkRenderingUsageRedefinition", + "checkRenderingUsageSpecialization", + "checkRenderingUsageSubrenderingSpecialization", + "checkRequirementDefinitionSpecialization", + "checkRequirementUsageObjectiveRedefinition", + "checkRequirementUsageRequirementVerificationSpecialization", + "checkRequirementUsageSpecialization", + "checkRequirementUsageSubrequirementSpecialization", + "checkSatisfyRequirementUsageBindingConnector", + "checkSatisfyRequirementUsageSpecialization", + "checkSelectExpressionResultSpecialization", + "checkSendActionUsageSpecialization", + "checkSendActionUsageSubactionSpecialization", + "checkStateDefinitionSpecialization", + "checkStateUsageExclusiveStateSpecialization", + "checkStateUsageOwnedStateSpecialization", + "checkStateUsageSpecialization", + "checkStateUsageSubstateSpecialization", + "checkStepEnclosedPerformanceSpecialization", + "checkStepOwnedPerformanceSpecialization", + "checkStepSpecialization", + "checkStepSubperformanceSpecialization", + "checkStructureSpecialization", + "checkSuccessionFlowSpecialization", + "checkSuccessionFlowUsageSpecialization", + "checkSuccessionSpecialization", + "checkTerminateActionUsageSpecialization", + "checkTerminateActionUsageSubactionSpecialization", + "checkTransitionUsageActionSpecialization", + "checkTransitionUsagePayloadSpecialization", + "checkTransitionUsageSourceBindingConnector", + "checkTransitionUsageSpecialization", + "checkTransitionUsageStateSpecialization", + "checkTransitionUsageSuccessionBindingConnector", + "checkTransitionUsageSuccessionSourceSpecialization", + "checkTransitionUsageTransitionFeatureSpecialization", + "checkTypeSpecialization", + "checkUsageVariationDefinitionSpecialization", + "checkUsageVariationUsageSpecialization", + "checkUsageVariationUsageTypeFeaturing", + "checkUseCaseDefinitionSpecialization", + "checkUseCaseUsageSpecialization", + "checkUseCaseUsageSubUseCaseSpecialization", + "checkVerificationCaseSpecialization", + "checkVerificationCaseUsageSpecialization", + "checkVerificationCaseUsageSubVerificationCaseSpecialization", + "checkViewDefinitionSpecialization", + "checkViewUsageSpecialization", + "checkViewUsageSubviewSpecialization", + "checkViewpointDefinitionSpecialization", + "checkViewpointUsageSpecialization", + "checkViewpointUsageViewpointSatisfactionSpecialization", + "checkWhileLoopActionUsageSpecialization", + "checkWhileLoopActionUsageSubactionSpecialization", + ]; + + /// + /// The name of every constraint whose application is conditional, i.e. every row that requires a + /// guard before its implied Relationship may be included. + /// + public static IReadOnlyList AllConditionalConstraintNames { get; } = + [ + "checkAcceptActionUsageSpecialization", + "checkAcceptActionUsageSubactionSpecialization", + "checkAcceptActionUsageTriggerActionSpecialization", + "checkActionUsageOwnedActionSpecialization", + "checkActionUsageSubactionSpecialization", + "checkAnalysisCaseUsageSubAnalysisCaseSpecialization", + "checkAssignmentActionUsageSubactionSpecialization", + "checkAssociationBinarySpecialization", + "checkAssociationStructureBinarySpecialization", + "checkCalculationUsageSubcalculationSpecialization", + "checkCaseUsageSubcaseSpecialization", + "checkConcernUsageFramedConcernSpecialization", + "checkConnectionDefinitionBinarySpecialization", + "checkConnectionUsageBinarySpecialization", + "checkConnectorBinaryObjectSpecialization", + "checkConnectorBinarySpecialization", + "checkConnectorObjectSpecialization", + "checkConstraintUsageCheckedConstraintSpecialization", + "checkEventOccurrenceUsageSpecialization", + "checkExhibitStateUsageSpecialization", + "checkFeatureDataValueSpecialization", + "checkFeatureEndSpecialization", + "checkFeatureObjectSpecialization", + "checkFeatureOccurrenceSpecialization", + "checkFeaturePortionSpecialization", + "checkFeatureSubobjectSpecialization", + "checkFeatureSuboccurrenceSpecialization", + "checkFlowDefinitionBinarySpecialization", + "checkFlowUsageFlowSpecialization", + "checkFlowWithEndsSpecialization", + "checkForLoopActionUsageSubactionSpecialization", + "checkIfActionUsageSubactionSpecialization", + "checkIncludeUseCaseUsageSpecialization", + "checkInterfaceDefinitionBinarySpecialization", + "checkInterfaceUsageBinarySpecialization", + "checkItemUsageSubitemSpecialization", + "checkOccurrenceDefinitionIndividualSpecialization", + "checkOccurrenceUsageSnapshotSpecialization", + "checkOccurrenceUsageSuboccurrenceSpecialization", + "checkOccurrenceUsageTimeSliceSpecialization", + "checkPartUsageStakeholderSpecialization", + "checkPartUsageSubpartSpecialization", + "checkPerformActionUsageSpecialization", + "checkPortUsageOwnedPortSpecialization", + "checkPortUsageSubportSpecialization", + "checkRenderingUsageSubrenderingSpecialization", + "checkRequirementUsageRequirementVerificationSpecialization", + "checkRequirementUsageSubrequirementSpecialization", + "checkSendActionUsageSubactionSpecialization", + "checkStateUsageExclusiveStateSpecialization", + "checkStateUsageOwnedStateSpecialization", + "checkStateUsageSubstateSpecialization", + "checkStepEnclosedPerformanceSpecialization", + "checkStepOwnedPerformanceSpecialization", + "checkStepSubperformanceSpecialization", + "checkTerminateActionUsageSubactionSpecialization", + "checkTransitionUsageActionSpecialization", + "checkTransitionUsageStateSpecialization", + "checkUseCaseUsageSubUseCaseSpecialization", + "checkVerificationCaseUsageSubVerificationCaseSpecialization", + "checkViewUsageSubviewSpecialization", + "checkViewpointUsageViewpointSatisfactionSpecialization", + "checkWhileLoopActionUsageSubactionSpecialization", + ]; + + /// + /// The qualified name of every library Type targeted by a row of this table, without duplicates. + /// + /// + /// Every name here must resolve against a full model-library load, or the constraint that carries + /// it can never be satisfied. Exposed so that resolution can be asserted for the whole table rather + /// than only for the rows a given corpus happens to exercise. + /// + public static IReadOnlyList AllLibraryTargets { get; } = + [ + "Actions::Action", + "Actions::Action::acceptSubactions", + "Actions::Action::assignments", + "Actions::Action::controls", + "Actions::Action::decisionTransitions", + "Actions::Action::decisions", + "Actions::Action::forLoops", + "Actions::Action::forks", + "Actions::Action::ifSubactions", + "Actions::Action::joins", + "Actions::Action::merges", + "Actions::Action::subactions", + "Actions::Action::terminateSubactions", + "Actions::Action::whileLoops", + "Actions::TransitionAction::accepter", + "Actions::acceptActions", + "Actions::actions", + "Actions::assignmentActions", + "Actions::forLoopActions", + "Actions::sendActions", + "Actions::terminateActions", + "Actions::transitionActions", + "Actions::whileLoopActions", + "Allocations::Allocation", + "Allocations::allocations", + "AnalysisCases::AnalysisCase", + "AnalysisCases::AnalysisCase::subAnalysisCases", + "AnalysisCases::analysisCases", + "Base::Anything", + "Base::DataValue", + "Base::dataValues", + "Base::naturals", + "Base::things", + "Calculations::Calculation", + "Calculations::Calculation::subcalculations", + "Calculations::calculations", + "Cases::Case", + "Cases::Case::subcases", + "Cases::cases", + "Connections::BinaryConnection", + "Connections::Connection", + "Connections::binaryConnections", + "Connections::connections", + "Constraints::ConstraintCheck", + "Constraints::constraintChecks", + "Flows::Message", + "Flows::MessageAction", + "Flows::flows", + "Flows::messages", + "Flows::successionFlows", + "Interfaces::BinaryInterface", + "Interfaces::Interface", + "Interfaces::binaryInterfaces", + "Interfaces::interfaces", + "Items::Item", + "Items::Item::checkedConstraints", + "Items::Item::subitems", + "Items::Item::subparts", + "Items::items", + "Links::BinaryLink", + "Links::Link", + "Links::Link::participant", + "Links::binaryLinks", + "Links::links", + "Links::selfLinks", + "Metadata::MetadataItem", + "Metadata::metadataItems", + "Metaobjects::Metaobject", + "Metaobjects::metaobjects", + "Objects::BinaryLinkObject", + "Objects::LinkObject", + "Objects::Object", + "Objects::Object::ownedPerformances", + "Objects::binaryLinkObjects", + "Objects::linkObjects", + "Objects::objects", + "Occurrences::Life", + "Occurrences::Occurrence", + "Occurrences::Occurrence::portions", + "Occurrences::Occurrence::snapshots", + "Occurrences::Occurrence::suboccurrences", + "Occurrences::Occurrence::timeEnclosedOccurrences", + "Occurrences::Occurrence::timeSlices", + "Occurrences::happensBeforeLinks", + "Occurrences::occurrences", + "Parts::Part", + "Parts::Part::exhibitedStates", + "Parts::Part::ownedActions", + "Parts::Part::ownedPorts", + "Parts::Part::ownedStates", + "Parts::Part::performedActions", + "Parts::parts", + "Performances::BooleanEvaluation", + "Performances::Evaluation", + "Performances::Performance", + "Performances::Performance::enclosedPerformances", + "Performances::Performance::subperformances", + "Performances::booleanEvaluations", + "Performances::evaluations", + "Performances::literalBooleanEvaluations", + "Performances::literalEvaluations", + "Performances::literalIntegerEvaluations", + "Performances::literalRationalEvaluations", + "Performances::literalStringEvaluations", + "Performances::metadataAccessEvaluations", + "Performances::nullEvaluations", + "Performances::performances", + "Ports::Port", + "Ports::Port::subports", + "Ports::ports", + "Requirements::ConcernCheck", + "Requirements::RequirementCheck", + "Requirements::RequirementCheck::concerns", + "Requirements::RequirementCheck::stakeholders", + "Requirements::RequirementCheck::subrequirements", + "Requirements::concernChecks", + "Requirements::requirementChecks", + "States::StateAction", + "States::StateAction::exclusiveStates", + "States::StateAction::stateTransitions", + "States::StateAction::substates", + "States::stateActions", + "Transfers::flowTransfers", + "Transfers::flowTransfersBefore", + "Transfers::transfers", + "UseCases::UseCase", + "UseCases::UseCase::includedUseCases", + "UseCases::UseCase::subUseCases", + "UseCases::useCases", + "VerificationCases::VerificationCase", + "VerificationCases::VerificationCase::obj::requirementVerifications", + "VerificationCases::VerificationCase::subVerificationCases", + "VerificationCases::verificationCases", + "Views::Rendering", + "Views::Rendering::subrenderings", + "Views::View", + "Views::View::subviews", + "Views::View::viewpointSatisfactions", + "Views::ViewpointCheck", + "Views::renderings", + "Views::viewpointChecks", + "Views::views", + ]; + + /// + /// Returns the implied library Specializations that apply to the supplied element, including + /// those inherited from its supertypes in the metamodel. + /// + /// + /// The to query + /// + /// + /// The applicable rules, or an empty list when the metaclass carries none + /// + public static IReadOnlyList QueryImpliedLibrarySpecializations(IElement element) + { + return element switch + { + SysML2.NET.Core.POCO.Systems.Flows.ISuccessionFlowUsage => SuccessionFlowUsageRules, + SysML2.NET.Core.POCO.Systems.Allocations.IAllocationDefinition => AllocationDefinitionRules, + SysML2.NET.Core.POCO.Systems.UseCases.IIncludeUseCaseUsage => IncludeUseCaseUsageRules, + SysML2.NET.Core.POCO.Systems.Interfaces.IInterfaceDefinition => InterfaceDefinitionRules, + SysML2.NET.Core.POCO.Systems.Connections.IConnectionDefinition => ConnectionDefinitionRules, + SysML2.NET.Core.POCO.Systems.Requirements.ISatisfyRequirementUsage => SatisfyRequirementUsageRules, + SysML2.NET.Core.POCO.Systems.Allocations.IAllocationUsage => AllocationUsageRules, + SysML2.NET.Core.POCO.Systems.AnalysisCases.IAnalysisCaseDefinition => AnalysisCaseDefinitionRules, + SysML2.NET.Core.POCO.Systems.Requirements.IConcernDefinition => ConcernDefinitionRules, + SysML2.NET.Core.POCO.Systems.Flows.IFlowDefinition => FlowDefinitionRules, + SysML2.NET.Core.POCO.Systems.Flows.IFlowUsage => FlowUsageRules, + SysML2.NET.Core.POCO.Systems.Interfaces.IInterfaceUsage => InterfaceUsageRules, + SysML2.NET.Core.POCO.Systems.UseCases.IUseCaseDefinition => UseCaseDefinitionRules, + SysML2.NET.Core.POCO.Systems.VerificationCases.IVerificationCaseDefinition => VerificationCaseDefinitionRules, + SysML2.NET.Core.POCO.Systems.Views.IViewpointDefinition => ViewpointDefinitionRules, + SysML2.NET.Core.POCO.Systems.AnalysisCases.IAnalysisCaseUsage => AnalysisCaseUsageRules, + SysML2.NET.Core.POCO.Systems.Constraints.IAssertConstraintUsage => AssertConstraintUsageRules, + SysML2.NET.Core.POCO.Systems.Cases.ICaseDefinition => CaseDefinitionRules, + SysML2.NET.Core.POCO.Systems.Requirements.IConcernUsage => ConcernUsageRules, + SysML2.NET.Core.POCO.Systems.Connections.IConnectionUsage => ConnectionUsageRules, + SysML2.NET.Core.POCO.Systems.States.IExhibitStateUsage => ExhibitStateUsageRules, + SysML2.NET.Core.POCO.Systems.Requirements.IRequirementDefinition => RequirementDefinitionRules, + SysML2.NET.Core.POCO.Systems.UseCases.IUseCaseUsage => UseCaseUsageRules, + SysML2.NET.Core.POCO.Systems.VerificationCases.IVerificationCaseUsage => VerificationCaseUsageRules, + SysML2.NET.Core.POCO.Systems.Views.IViewpointUsage => ViewpointUsageRules, + SysML2.NET.Core.POCO.Systems.Calculations.ICalculationDefinition => CalculationDefinitionRules, + SysML2.NET.Core.POCO.Systems.Cases.ICaseUsage => CaseUsageRules, + SysML2.NET.Core.POCO.Systems.Constraints.IConstraintDefinition => ConstraintDefinitionRules, + SysML2.NET.Core.POCO.Systems.Metadata.IMetadataDefinition => MetadataDefinitionRules, + SysML2.NET.Core.POCO.Systems.Views.IRenderingDefinition => RenderingDefinitionRules, + SysML2.NET.Core.POCO.Systems.Requirements.IRequirementUsage => RequirementUsageRules, + SysML2.NET.Core.POCO.Systems.Views.IViewDefinition => ViewDefinitionRules, + SysML2.NET.Core.POCO.Systems.Connections.IBindingConnectorAsUsage => BindingConnectorAsUsageRules, + SysML2.NET.Core.POCO.Systems.Calculations.ICalculationUsage => CalculationUsageRules, + SysML2.NET.Core.POCO.Kernel.Expressions.ICollectExpression => CollectExpressionRules, + SysML2.NET.Core.POCO.Systems.Ports.IConjugatedPortDefinition => ConjugatedPortDefinitionRules, + SysML2.NET.Core.POCO.Systems.Constraints.IConstraintUsage => ConstraintUsageRules, + SysML2.NET.Core.POCO.Systems.Actions.IDecisionNode => DecisionNodeRules, + SysML2.NET.Core.POCO.Kernel.Expressions.IFeatureChainExpression => FeatureChainExpressionRules, + SysML2.NET.Core.POCO.Systems.Actions.IForLoopActionUsage => ForLoopActionUsageRules, + SysML2.NET.Core.POCO.Systems.Actions.IForkNode => ForkNodeRules, + SysML2.NET.Core.POCO.Kernel.Expressions.IIndexExpression => IndexExpressionRules, + SysML2.NET.Core.POCO.Systems.Actions.IJoinNode => JoinNodeRules, + SysML2.NET.Core.POCO.Systems.Actions.IMergeNode => MergeNodeRules, + SysML2.NET.Core.POCO.Systems.Metadata.IMetadataUsage => MetadataUsageRules, + SysML2.NET.Core.POCO.Systems.Parts.IPartDefinition => PartDefinitionRules, + SysML2.NET.Core.POCO.Systems.Actions.IPerformActionUsage => PerformActionUsageRules, + SysML2.NET.Core.POCO.Kernel.Expressions.ISelectExpression => SelectExpressionRules, + SysML2.NET.Core.POCO.Systems.States.IStateDefinition => StateDefinitionRules, + SysML2.NET.Core.POCO.Systems.Connections.ISuccessionAsUsage => SuccessionAsUsageRules, + SysML2.NET.Core.POCO.Kernel.Interactions.ISuccessionFlow => SuccessionFlowRules, + SysML2.NET.Core.POCO.Systems.Actions.IWhileLoopActionUsage => WhileLoopActionUsageRules, + SysML2.NET.Core.POCO.Systems.Actions.IAcceptActionUsage => AcceptActionUsageRules, + SysML2.NET.Core.POCO.Systems.Actions.IActionDefinition => ActionDefinitionRules, + SysML2.NET.Core.POCO.Systems.Actions.IAssignmentActionUsage => AssignmentActionUsageRules, + SysML2.NET.Core.POCO.Kernel.Associations.IAssociationStructure => AssociationStructureRules, + SysML2.NET.Core.POCO.Systems.Actions.IControlNode => ControlNodeRules, + SysML2.NET.Core.POCO.Systems.Actions.IIfActionUsage => IfActionUsageRules, + SysML2.NET.Core.POCO.Kernel.Interactions.IInteraction => InteractionRules, + SysML2.NET.Core.POCO.Systems.Items.IItemDefinition => ItemDefinitionRules, + SysML2.NET.Core.POCO.Systems.Actions.ILoopActionUsage => LoopActionUsageRules, + SysML2.NET.Core.POCO.Kernel.Expressions.IOperatorExpression => OperatorExpressionRules, + SysML2.NET.Core.POCO.Systems.Ports.IPortDefinition => PortDefinitionRules, + SysML2.NET.Core.POCO.Systems.Views.IRenderingUsage => RenderingUsageRules, + SysML2.NET.Core.POCO.Systems.Actions.ISendActionUsage => SendActionUsageRules, + SysML2.NET.Core.POCO.Systems.States.IStateUsage => StateUsageRules, + SysML2.NET.Core.POCO.Systems.Actions.ITerminateActionUsage => TerminateActionUsageRules, + SysML2.NET.Core.POCO.Systems.States.ITransitionUsage => TransitionUsageRules, + SysML2.NET.Core.POCO.Systems.Actions.ITriggerInvocationExpression => TriggerInvocationExpressionRules, + SysML2.NET.Core.POCO.Systems.Views.IViewUsage => ViewUsageRules, + SysML2.NET.Core.POCO.Systems.Actions.IActionUsage => ActionUsageRules, + SysML2.NET.Core.POCO.Systems.Connections.IConnectorAsUsage => ConnectorAsUsageRules, + SysML2.NET.Core.POCO.Kernel.Expressions.IConstructorExpression => ConstructorExpressionRules, + SysML2.NET.Core.POCO.Systems.Enumerations.IEnumerationDefinition => EnumerationDefinitionRules, + SysML2.NET.Core.POCO.Kernel.Interactions.IFlow => FlowRules, + SysML2.NET.Core.POCO.Kernel.Functions.IInvariant => InvariantRules, + SysML2.NET.Core.POCO.Kernel.Expressions.IInvocationExpression => InvocationExpressionRules, + SysML2.NET.Core.POCO.Kernel.Expressions.ILiteralBoolean => LiteralBooleanRules, + SysML2.NET.Core.POCO.Kernel.Expressions.ILiteralInfinity => LiteralInfinityRules, + SysML2.NET.Core.POCO.Kernel.Expressions.ILiteralInteger => LiteralIntegerRules, + SysML2.NET.Core.POCO.Kernel.Expressions.ILiteralRational => LiteralRationalRules, + SysML2.NET.Core.POCO.Kernel.Expressions.ILiteralString => LiteralStringRules, + SysML2.NET.Core.POCO.Systems.Parts.IPartUsage => PartUsageRules, + SysML2.NET.Core.POCO.Kernel.Functions.IPredicate => PredicateRules, + SysML2.NET.Core.POCO.Systems.Attributes.IAttributeDefinition => AttributeDefinitionRules, + SysML2.NET.Core.POCO.Kernel.Connectors.IBindingConnector => BindingConnectorRules, + SysML2.NET.Core.POCO.Kernel.Functions.IBooleanExpression => BooleanExpressionRules, + SysML2.NET.Core.POCO.Systems.Enumerations.IEnumerationUsage => EnumerationUsageRules, + SysML2.NET.Core.POCO.Systems.Occurrences.IEventOccurrenceUsage => EventOccurrenceUsageRules, + SysML2.NET.Core.POCO.Kernel.Expressions.IFeatureReferenceExpression => FeatureReferenceExpressionRules, + SysML2.NET.Core.POCO.Kernel.Functions.IFunction => FunctionRules, + SysML2.NET.Core.POCO.Kernel.Expressions.IInstantiationExpression => InstantiationExpressionRules, + SysML2.NET.Core.POCO.Systems.Items.IItemUsage => ItemUsageRules, + SysML2.NET.Core.POCO.Kernel.Expressions.ILiteralExpression => LiteralExpressionRules, + SysML2.NET.Core.POCO.Kernel.Metadata.IMetaclass => MetaclassRules, + SysML2.NET.Core.POCO.Kernel.Expressions.IMetadataAccessExpression => MetadataAccessExpressionRules, + SysML2.NET.Core.POCO.Kernel.Expressions.INullExpression => NullExpressionRules, + SysML2.NET.Core.POCO.Systems.Occurrences.IOccurrenceDefinition => OccurrenceDefinitionRules, + SysML2.NET.Core.POCO.Systems.Ports.IPortUsage => PortUsageRules, + SysML2.NET.Core.POCO.Kernel.Connectors.ISuccession => SuccessionRules, + SysML2.NET.Core.POCO.Kernel.Associations.IAssociation => AssociationRules, + SysML2.NET.Core.POCO.Systems.Attributes.IAttributeUsage => AttributeUsageRules, + SysML2.NET.Core.POCO.Kernel.Behaviors.IBehavior => BehaviorRules, + SysML2.NET.Core.POCO.Kernel.Connectors.IConnector => ConnectorRules, + SysML2.NET.Core.POCO.Kernel.Functions.IExpression => ExpressionRules, + SysML2.NET.Core.POCO.Kernel.Metadata.IMetadataFeature => MetadataFeatureRules, + SysML2.NET.Core.POCO.Kernel.Multiplicities.IMultiplicityRange => MultiplicityRangeRules, + SysML2.NET.Core.POCO.Systems.Occurrences.IOccurrenceUsage => OccurrenceUsageRules, + SysML2.NET.Core.POCO.Systems.DefinitionAndUsage.IReferenceUsage => ReferenceUsageRules, + SysML2.NET.Core.POCO.Kernel.Structures.IStructure => StructureRules, + SysML2.NET.Core.POCO.Kernel.Classes.IClass => ClassRules, + SysML2.NET.Core.POCO.Kernel.DataTypes.IDataType => DataTypeRules, + SysML2.NET.Core.POCO.Systems.DefinitionAndUsage.IDefinition => DefinitionRules, + SysML2.NET.Core.POCO.Kernel.Interactions.IFlowEnd => FlowEndRules, + SysML2.NET.Core.POCO.Core.Types.IMultiplicity => MultiplicityRules, + SysML2.NET.Core.POCO.Kernel.Interactions.IPayloadFeature => PayloadFeatureRules, + SysML2.NET.Core.POCO.Kernel.Behaviors.IStep => StepRules, + SysML2.NET.Core.POCO.Systems.DefinitionAndUsage.IUsage => UsageRules, + SysML2.NET.Core.POCO.Core.Classifiers.IClassifier => ClassifierRules, + SysML2.NET.Core.POCO.Core.Features.IFeature => FeatureRules, + SysML2.NET.Core.POCO.Core.Types.IType => TypeRules, + _ => [] + }; + } + + /// + /// The implied library Specializations applying to SuccessionFlowUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] SuccessionFlowUsageRules = + [ + new("checkActionUsageOwnedActionSpecialization", "Parts::Part::ownedActions", "ActionUsage", true), + new("checkActionUsageSpecialization", "Actions::actions", "ActionUsage", false), + new("checkActionUsageSubactionSpecialization", "Actions::Action::subactions", "ActionUsage", true), + new("checkConnectorBinaryObjectSpecialization", "Objects::binaryLinkObjects", "Connector", true), + new("checkConnectorBinarySpecialization", "Links::binaryLinks", "Connector", true), + new("checkConnectorObjectSpecialization", "Objects::linkObjects", "Connector", true), + new("checkConnectorSpecialization", "Links::links", "Connector", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFlowSpecialization", "Transfers::transfers", "Flow", false), + new("checkFlowUsageFlowSpecialization", "Flows::flows", "FlowUsage", true), + new("checkFlowUsageSpecialization", "Flows::messages", "FlowUsage", false), + new("checkFlowWithEndsSpecialization", "Transfers::flowTransfers", "Flow", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkSuccessionFlowSpecialization", "Transfers::flowTransfersBefore", "SuccessionFlow", false), + new("checkSuccessionFlowUsageSpecialization", "Flows::successionFlows", "SuccessionFlowUsage", false), + new("checkSuccessionSpecialization", "Occurrences::happensBeforeLinks", "Succession", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to AllocationDefinition, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] AllocationDefinitionRules = + [ + new("checkAllocationDefinitionSpecialization", "Allocations::Allocation", "AllocationDefinition", false), + new("checkAssociationBinarySpecialization", "Links::BinaryLink", "Association", true), + new("checkAssociationSpecialization", "Links::Link", "Association", false), + new("checkAssociationStructureBinarySpecialization", "Objects::BinaryLinkObject", "AssociationStructure", true), + new("checkAssociationStructureSpecialization", "Objects::LinkObject", "AssociationStructure", false), + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkConnectionDefinitionBinarySpecialization", "Connections::BinaryConnection", "ConnectionDefinition", true), + new("checkConnectionDefinitionSpecializations", "Connections::Connection", "ConnectionDefinition", false), + new("checkItemDefinitionSpecialization", "Items::Item", "ItemDefinition", false), + new("checkOccurrenceDefinitionIndividualSpecialization", "Occurrences::Life", "OccurrenceDefinition", true), + new("checkPartDefinitionSpecialization", "Parts::Part", "PartDefinition", false), + new("checkStructureSpecialization", "Objects::Object", "Structure", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to IncludeUseCaseUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] IncludeUseCaseUsageRules = + [ + new("checkActionUsageOwnedActionSpecialization", "Parts::Part::ownedActions", "ActionUsage", true), + new("checkActionUsageSpecialization", "Actions::actions", "ActionUsage", false), + new("checkActionUsageSubactionSpecialization", "Actions::Action::subactions", "ActionUsage", true), + new("checkCalculationUsageSpecialization", "Calculations::calculations", "CalculationUsage", false), + new("checkCalculationUsageSubcalculationSpecialization", "Calculations::Calculation::subcalculations", "CalculationUsage", true), + new("checkCaseUsageSpecialization", "Cases::cases", "CaseUsage", false), + new("checkCaseUsageSubcaseSpecialization", "Cases::Case::subcases", "CaseUsage", true), + new("checkEventOccurrenceUsageSpecialization", "Occurrences::Occurrence::timeEnclosedOccurrences", "EventOccurrenceUsage", true), + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkIncludeUseCaseUsageSpecialization", "UseCases::UseCase::includedUseCases", "IncludeUseCaseUsage", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkPerformActionUsageSpecialization", "Parts::Part::performedActions", "PerformActionUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + new("checkUseCaseUsageSpecialization", "UseCases::useCases", "UseCaseUsage", false), + new("checkUseCaseUsageSubUseCaseSpecialization", "UseCases::UseCase::subUseCases", "UseCaseUsage", true), + ]; + + /// + /// The implied library Specializations applying to InterfaceDefinition, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] InterfaceDefinitionRules = + [ + new("checkAssociationBinarySpecialization", "Links::BinaryLink", "Association", true), + new("checkAssociationSpecialization", "Links::Link", "Association", false), + new("checkAssociationStructureBinarySpecialization", "Objects::BinaryLinkObject", "AssociationStructure", true), + new("checkAssociationStructureSpecialization", "Objects::LinkObject", "AssociationStructure", false), + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkConnectionDefinitionBinarySpecialization", "Connections::BinaryConnection", "ConnectionDefinition", true), + new("checkConnectionDefinitionSpecializations", "Connections::Connection", "ConnectionDefinition", false), + new("checkInterfaceDefinitionBinarySpecialization", "Interfaces::BinaryInterface", "InterfaceDefinition", true), + new("checkInterfaceDefinitionSpecialization", "Interfaces::Interface", "InterfaceDefinition", false), + new("checkItemDefinitionSpecialization", "Items::Item", "ItemDefinition", false), + new("checkOccurrenceDefinitionIndividualSpecialization", "Occurrences::Life", "OccurrenceDefinition", true), + new("checkPartDefinitionSpecialization", "Parts::Part", "PartDefinition", false), + new("checkStructureSpecialization", "Objects::Object", "Structure", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to ConnectionDefinition, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] ConnectionDefinitionRules = + [ + new("checkAssociationBinarySpecialization", "Links::BinaryLink", "Association", true), + new("checkAssociationSpecialization", "Links::Link", "Association", false), + new("checkAssociationStructureBinarySpecialization", "Objects::BinaryLinkObject", "AssociationStructure", true), + new("checkAssociationStructureSpecialization", "Objects::LinkObject", "AssociationStructure", false), + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkConnectionDefinitionBinarySpecialization", "Connections::BinaryConnection", "ConnectionDefinition", true), + new("checkConnectionDefinitionSpecializations", "Connections::Connection", "ConnectionDefinition", false), + new("checkItemDefinitionSpecialization", "Items::Item", "ItemDefinition", false), + new("checkOccurrenceDefinitionIndividualSpecialization", "Occurrences::Life", "OccurrenceDefinition", true), + new("checkPartDefinitionSpecialization", "Parts::Part", "PartDefinition", false), + new("checkStructureSpecialization", "Objects::Object", "Structure", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to SatisfyRequirementUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] SatisfyRequirementUsageRules = + [ + new("checkBooleanExpressionSpecialization", "Performances::booleanEvaluations", "BooleanExpression", false), + new("checkConstraintUsageCheckedConstraintSpecialization", "Items::Item::checkedConstraints", "ConstraintUsage", true), + new("checkConstraintUsageSpecialization", "Constraints::constraintChecks", "ConstraintUsage", false), + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkRequirementUsageRequirementVerificationSpecialization", "VerificationCases::VerificationCase::obj::requirementVerifications", "RequirementUsage", true), + new("checkRequirementUsageSpecialization", "Requirements::requirementChecks", "RequirementUsage", false), + new("checkRequirementUsageSubrequirementSpecialization", "Requirements::RequirementCheck::subrequirements", "RequirementUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to AllocationUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] AllocationUsageRules = + [ + new("checkAllocationUsageSpecialization", "Allocations::allocations", "AllocationUsage", false), + new("checkConnectionUsageBinarySpecialization", "Connections::binaryConnections", "ConnectionUsage", true), + new("checkConnectionUsageSpecialization", "Connections::connections", "ConnectionUsage", false), + new("checkConnectorBinaryObjectSpecialization", "Objects::binaryLinkObjects", "Connector", true), + new("checkConnectorBinarySpecialization", "Links::binaryLinks", "Connector", true), + new("checkConnectorObjectSpecialization", "Objects::linkObjects", "Connector", true), + new("checkConnectorSpecialization", "Links::links", "Connector", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkItemUsageSpecialization", "Items::items", "ItemUsage", false), + new("checkItemUsageSubitemSpecialization", "Items::Item::subitems", "ItemUsage", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkPartUsageSpecialization", "Parts::parts", "PartUsage", false), + new("checkPartUsageStakeholderSpecialization", "Requirements::RequirementCheck::stakeholders", "PartUsage", true), + new("checkPartUsageSubpartSpecialization", "Items::Item::subparts", "PartUsage", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to AnalysisCaseDefinition, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] AnalysisCaseDefinitionRules = + [ + new("checkActionDefinitionSpecialization", "Actions::Action", "ActionDefinition", false), + new("checkAnalysisCaseDefinitionSpecialization", "AnalysisCases::AnalysisCase", "AnalysisCaseDefinition", false), + new("checkBehaviorSpecialization", "Performances::Performance", "Behavior", false), + new("checkCalculationDefinitionSpecialization", "Calculations::Calculation", "CalculationDefinition", false), + new("checkCaseDefinitionSpecialization", "Cases::Case", "CaseDefinition", false), + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkFunctionSpecialization", "Performances::Evaluation", "Function", false), + new("checkOccurrenceDefinitionIndividualSpecialization", "Occurrences::Life", "OccurrenceDefinition", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to ConcernDefinition, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] ConcernDefinitionRules = + [ + new("checkBehaviorSpecialization", "Performances::Performance", "Behavior", false), + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkConcernDefinitionSpecialization", "Requirements::ConcernCheck", "ConcernDefinition", false), + new("checkConstraintDefinitionSpecialization", "Constraints::ConstraintCheck", "ConstraintDefinition", false), + new("checkFunctionSpecialization", "Performances::Evaluation", "Function", false), + new("checkOccurrenceDefinitionIndividualSpecialization", "Occurrences::Life", "OccurrenceDefinition", true), + new("checkPredicateSpecialization", "Performances::BooleanEvaluation", "Predicate", false), + new("checkRequirementDefinitionSpecialization", "Requirements::RequirementCheck", "RequirementDefinition", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to FlowDefinition, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] FlowDefinitionRules = + [ + new("checkActionDefinitionSpecialization", "Actions::Action", "ActionDefinition", false), + new("checkAssociationBinarySpecialization", "Links::BinaryLink", "Association", true), + new("checkAssociationSpecialization", "Links::Link", "Association", false), + new("checkBehaviorSpecialization", "Performances::Performance", "Behavior", false), + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkFlowDefinitionBinarySpecialization", "Flows::Message", "FlowDefinition", true), + new("checkFlowDefinitionSpecialization", "Flows::MessageAction", "FlowDefinition", false), + new("checkOccurrenceDefinitionIndividualSpecialization", "Occurrences::Life", "OccurrenceDefinition", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to FlowUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] FlowUsageRules = + [ + new("checkActionUsageOwnedActionSpecialization", "Parts::Part::ownedActions", "ActionUsage", true), + new("checkActionUsageSpecialization", "Actions::actions", "ActionUsage", false), + new("checkActionUsageSubactionSpecialization", "Actions::Action::subactions", "ActionUsage", true), + new("checkConnectorBinaryObjectSpecialization", "Objects::binaryLinkObjects", "Connector", true), + new("checkConnectorBinarySpecialization", "Links::binaryLinks", "Connector", true), + new("checkConnectorObjectSpecialization", "Objects::linkObjects", "Connector", true), + new("checkConnectorSpecialization", "Links::links", "Connector", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFlowSpecialization", "Transfers::transfers", "Flow", false), + new("checkFlowUsageFlowSpecialization", "Flows::flows", "FlowUsage", true), + new("checkFlowUsageSpecialization", "Flows::messages", "FlowUsage", false), + new("checkFlowWithEndsSpecialization", "Transfers::flowTransfers", "Flow", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to InterfaceUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] InterfaceUsageRules = + [ + new("checkConnectionUsageBinarySpecialization", "Connections::binaryConnections", "ConnectionUsage", true), + new("checkConnectionUsageSpecialization", "Connections::connections", "ConnectionUsage", false), + new("checkConnectorBinaryObjectSpecialization", "Objects::binaryLinkObjects", "Connector", true), + new("checkConnectorBinarySpecialization", "Links::binaryLinks", "Connector", true), + new("checkConnectorObjectSpecialization", "Objects::linkObjects", "Connector", true), + new("checkConnectorSpecialization", "Links::links", "Connector", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkInterfaceUsageBinarySpecialization", "Interfaces::binaryInterfaces", "InterfaceUsage", true), + new("checkInterfaceUsageSpecialization", "Interfaces::interfaces", "InterfaceUsage", false), + new("checkItemUsageSpecialization", "Items::items", "ItemUsage", false), + new("checkItemUsageSubitemSpecialization", "Items::Item::subitems", "ItemUsage", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkPartUsageSpecialization", "Parts::parts", "PartUsage", false), + new("checkPartUsageStakeholderSpecialization", "Requirements::RequirementCheck::stakeholders", "PartUsage", true), + new("checkPartUsageSubpartSpecialization", "Items::Item::subparts", "PartUsage", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to UseCaseDefinition, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] UseCaseDefinitionRules = + [ + new("checkActionDefinitionSpecialization", "Actions::Action", "ActionDefinition", false), + new("checkBehaviorSpecialization", "Performances::Performance", "Behavior", false), + new("checkCalculationDefinitionSpecialization", "Calculations::Calculation", "CalculationDefinition", false), + new("checkCaseDefinitionSpecialization", "Cases::Case", "CaseDefinition", false), + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkFunctionSpecialization", "Performances::Evaluation", "Function", false), + new("checkOccurrenceDefinitionIndividualSpecialization", "Occurrences::Life", "OccurrenceDefinition", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + new("checkUseCaseDefinitionSpecialization", "UseCases::UseCase", "UseCaseDefinition", false), + ]; + + /// + /// The implied library Specializations applying to VerificationCaseDefinition, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] VerificationCaseDefinitionRules = + [ + new("checkActionDefinitionSpecialization", "Actions::Action", "ActionDefinition", false), + new("checkBehaviorSpecialization", "Performances::Performance", "Behavior", false), + new("checkCalculationDefinitionSpecialization", "Calculations::Calculation", "CalculationDefinition", false), + new("checkCaseDefinitionSpecialization", "Cases::Case", "CaseDefinition", false), + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkFunctionSpecialization", "Performances::Evaluation", "Function", false), + new("checkOccurrenceDefinitionIndividualSpecialization", "Occurrences::Life", "OccurrenceDefinition", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + new("checkVerificationCaseSpecialization", "VerificationCases::VerificationCase", "VerificationCaseDefinition", false), + ]; + + /// + /// The implied library Specializations applying to ViewpointDefinition, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] ViewpointDefinitionRules = + [ + new("checkBehaviorSpecialization", "Performances::Performance", "Behavior", false), + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkConstraintDefinitionSpecialization", "Constraints::ConstraintCheck", "ConstraintDefinition", false), + new("checkFunctionSpecialization", "Performances::Evaluation", "Function", false), + new("checkOccurrenceDefinitionIndividualSpecialization", "Occurrences::Life", "OccurrenceDefinition", true), + new("checkPredicateSpecialization", "Performances::BooleanEvaluation", "Predicate", false), + new("checkRequirementDefinitionSpecialization", "Requirements::RequirementCheck", "RequirementDefinition", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + new("checkViewpointDefinitionSpecialization", "Views::ViewpointCheck", "ViewpointDefinition", false), + ]; + + /// + /// The implied library Specializations applying to AnalysisCaseUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] AnalysisCaseUsageRules = + [ + new("checkActionUsageOwnedActionSpecialization", "Parts::Part::ownedActions", "ActionUsage", true), + new("checkActionUsageSpecialization", "Actions::actions", "ActionUsage", false), + new("checkActionUsageSubactionSpecialization", "Actions::Action::subactions", "ActionUsage", true), + new("checkAnalysisCaseUsageSpecialization", "AnalysisCases::analysisCases", "AnalysisCaseUsage", false), + new("checkAnalysisCaseUsageSubAnalysisCaseSpecialization", "AnalysisCases::AnalysisCase::subAnalysisCases", "AnalysisCaseUsage", true), + new("checkCalculationUsageSpecialization", "Calculations::calculations", "CalculationUsage", false), + new("checkCalculationUsageSubcalculationSpecialization", "Calculations::Calculation::subcalculations", "CalculationUsage", true), + new("checkCaseUsageSpecialization", "Cases::cases", "CaseUsage", false), + new("checkCaseUsageSubcaseSpecialization", "Cases::Case::subcases", "CaseUsage", true), + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to AssertConstraintUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] AssertConstraintUsageRules = + [ + new("checkBooleanExpressionSpecialization", "Performances::booleanEvaluations", "BooleanExpression", false), + new("checkConstraintUsageCheckedConstraintSpecialization", "Items::Item::checkedConstraints", "ConstraintUsage", true), + new("checkConstraintUsageSpecialization", "Constraints::constraintChecks", "ConstraintUsage", false), + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to CaseDefinition, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] CaseDefinitionRules = + [ + new("checkActionDefinitionSpecialization", "Actions::Action", "ActionDefinition", false), + new("checkBehaviorSpecialization", "Performances::Performance", "Behavior", false), + new("checkCalculationDefinitionSpecialization", "Calculations::Calculation", "CalculationDefinition", false), + new("checkCaseDefinitionSpecialization", "Cases::Case", "CaseDefinition", false), + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkFunctionSpecialization", "Performances::Evaluation", "Function", false), + new("checkOccurrenceDefinitionIndividualSpecialization", "Occurrences::Life", "OccurrenceDefinition", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to ConcernUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] ConcernUsageRules = + [ + new("checkBooleanExpressionSpecialization", "Performances::booleanEvaluations", "BooleanExpression", false), + new("checkConcernUsageFramedConcernSpecialization", "Requirements::RequirementCheck::concerns", "ConcernUsage", true), + new("checkConcernUsageSpecialization", "Requirements::concernChecks", "ConcernUsage", false), + new("checkConstraintUsageCheckedConstraintSpecialization", "Items::Item::checkedConstraints", "ConstraintUsage", true), + new("checkConstraintUsageSpecialization", "Constraints::constraintChecks", "ConstraintUsage", false), + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkRequirementUsageRequirementVerificationSpecialization", "VerificationCases::VerificationCase::obj::requirementVerifications", "RequirementUsage", true), + new("checkRequirementUsageSpecialization", "Requirements::requirementChecks", "RequirementUsage", false), + new("checkRequirementUsageSubrequirementSpecialization", "Requirements::RequirementCheck::subrequirements", "RequirementUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to ConnectionUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] ConnectionUsageRules = + [ + new("checkConnectionUsageBinarySpecialization", "Connections::binaryConnections", "ConnectionUsage", true), + new("checkConnectionUsageSpecialization", "Connections::connections", "ConnectionUsage", false), + new("checkConnectorBinaryObjectSpecialization", "Objects::binaryLinkObjects", "Connector", true), + new("checkConnectorBinarySpecialization", "Links::binaryLinks", "Connector", true), + new("checkConnectorObjectSpecialization", "Objects::linkObjects", "Connector", true), + new("checkConnectorSpecialization", "Links::links", "Connector", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkItemUsageSpecialization", "Items::items", "ItemUsage", false), + new("checkItemUsageSubitemSpecialization", "Items::Item::subitems", "ItemUsage", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkPartUsageSpecialization", "Parts::parts", "PartUsage", false), + new("checkPartUsageStakeholderSpecialization", "Requirements::RequirementCheck::stakeholders", "PartUsage", true), + new("checkPartUsageSubpartSpecialization", "Items::Item::subparts", "PartUsage", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to ExhibitStateUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] ExhibitStateUsageRules = + [ + new("checkActionUsageOwnedActionSpecialization", "Parts::Part::ownedActions", "ActionUsage", true), + new("checkActionUsageSpecialization", "Actions::actions", "ActionUsage", false), + new("checkActionUsageSubactionSpecialization", "Actions::Action::subactions", "ActionUsage", true), + new("checkEventOccurrenceUsageSpecialization", "Occurrences::Occurrence::timeEnclosedOccurrences", "EventOccurrenceUsage", true), + new("checkExhibitStateUsageSpecialization", "Parts::Part::exhibitedStates", "ExhibitStateUsage", true), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkPerformActionUsageSpecialization", "Parts::Part::performedActions", "PerformActionUsage", true), + new("checkStateUsageExclusiveStateSpecialization", "States::StateAction::exclusiveStates", "StateUsage", true), + new("checkStateUsageOwnedStateSpecialization", "Parts::Part::ownedStates", "StateUsage", true), + new("checkStateUsageSpecialization", "States::stateActions", "StateUsage", false), + new("checkStateUsageSubstateSpecialization", "States::StateAction::substates", "StateUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to RequirementDefinition, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] RequirementDefinitionRules = + [ + new("checkBehaviorSpecialization", "Performances::Performance", "Behavior", false), + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkConstraintDefinitionSpecialization", "Constraints::ConstraintCheck", "ConstraintDefinition", false), + new("checkFunctionSpecialization", "Performances::Evaluation", "Function", false), + new("checkOccurrenceDefinitionIndividualSpecialization", "Occurrences::Life", "OccurrenceDefinition", true), + new("checkPredicateSpecialization", "Performances::BooleanEvaluation", "Predicate", false), + new("checkRequirementDefinitionSpecialization", "Requirements::RequirementCheck", "RequirementDefinition", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to UseCaseUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] UseCaseUsageRules = + [ + new("checkActionUsageOwnedActionSpecialization", "Parts::Part::ownedActions", "ActionUsage", true), + new("checkActionUsageSpecialization", "Actions::actions", "ActionUsage", false), + new("checkActionUsageSubactionSpecialization", "Actions::Action::subactions", "ActionUsage", true), + new("checkCalculationUsageSpecialization", "Calculations::calculations", "CalculationUsage", false), + new("checkCalculationUsageSubcalculationSpecialization", "Calculations::Calculation::subcalculations", "CalculationUsage", true), + new("checkCaseUsageSpecialization", "Cases::cases", "CaseUsage", false), + new("checkCaseUsageSubcaseSpecialization", "Cases::Case::subcases", "CaseUsage", true), + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + new("checkUseCaseUsageSpecialization", "UseCases::useCases", "UseCaseUsage", false), + new("checkUseCaseUsageSubUseCaseSpecialization", "UseCases::UseCase::subUseCases", "UseCaseUsage", true), + ]; + + /// + /// The implied library Specializations applying to VerificationCaseUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] VerificationCaseUsageRules = + [ + new("checkActionUsageOwnedActionSpecialization", "Parts::Part::ownedActions", "ActionUsage", true), + new("checkActionUsageSpecialization", "Actions::actions", "ActionUsage", false), + new("checkActionUsageSubactionSpecialization", "Actions::Action::subactions", "ActionUsage", true), + new("checkCalculationUsageSpecialization", "Calculations::calculations", "CalculationUsage", false), + new("checkCalculationUsageSubcalculationSpecialization", "Calculations::Calculation::subcalculations", "CalculationUsage", true), + new("checkCaseUsageSpecialization", "Cases::cases", "CaseUsage", false), + new("checkCaseUsageSubcaseSpecialization", "Cases::Case::subcases", "CaseUsage", true), + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + new("checkVerificationCaseUsageSpecialization", "VerificationCases::verificationCases", "VerificationCaseUsage", false), + new("checkVerificationCaseUsageSubVerificationCaseSpecialization", "VerificationCases::VerificationCase::subVerificationCases", "VerificationCaseUsage", true), + ]; + + /// + /// The implied library Specializations applying to ViewpointUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] ViewpointUsageRules = + [ + new("checkBooleanExpressionSpecialization", "Performances::booleanEvaluations", "BooleanExpression", false), + new("checkConstraintUsageCheckedConstraintSpecialization", "Items::Item::checkedConstraints", "ConstraintUsage", true), + new("checkConstraintUsageSpecialization", "Constraints::constraintChecks", "ConstraintUsage", false), + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkRequirementUsageRequirementVerificationSpecialization", "VerificationCases::VerificationCase::obj::requirementVerifications", "RequirementUsage", true), + new("checkRequirementUsageSpecialization", "Requirements::requirementChecks", "RequirementUsage", false), + new("checkRequirementUsageSubrequirementSpecialization", "Requirements::RequirementCheck::subrequirements", "RequirementUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + new("checkViewpointUsageSpecialization", "Views::viewpointChecks", "ViewpointUsage", false), + new("checkViewpointUsageViewpointSatisfactionSpecialization", "Views::View::viewpointSatisfactions", "ViewpointUsage", true), + ]; + + /// + /// The implied library Specializations applying to CalculationDefinition, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] CalculationDefinitionRules = + [ + new("checkActionDefinitionSpecialization", "Actions::Action", "ActionDefinition", false), + new("checkBehaviorSpecialization", "Performances::Performance", "Behavior", false), + new("checkCalculationDefinitionSpecialization", "Calculations::Calculation", "CalculationDefinition", false), + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkFunctionSpecialization", "Performances::Evaluation", "Function", false), + new("checkOccurrenceDefinitionIndividualSpecialization", "Occurrences::Life", "OccurrenceDefinition", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to CaseUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] CaseUsageRules = + [ + new("checkActionUsageOwnedActionSpecialization", "Parts::Part::ownedActions", "ActionUsage", true), + new("checkActionUsageSpecialization", "Actions::actions", "ActionUsage", false), + new("checkActionUsageSubactionSpecialization", "Actions::Action::subactions", "ActionUsage", true), + new("checkCalculationUsageSpecialization", "Calculations::calculations", "CalculationUsage", false), + new("checkCalculationUsageSubcalculationSpecialization", "Calculations::Calculation::subcalculations", "CalculationUsage", true), + new("checkCaseUsageSpecialization", "Cases::cases", "CaseUsage", false), + new("checkCaseUsageSubcaseSpecialization", "Cases::Case::subcases", "CaseUsage", true), + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to ConstraintDefinition, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] ConstraintDefinitionRules = + [ + new("checkBehaviorSpecialization", "Performances::Performance", "Behavior", false), + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkConstraintDefinitionSpecialization", "Constraints::ConstraintCheck", "ConstraintDefinition", false), + new("checkFunctionSpecialization", "Performances::Evaluation", "Function", false), + new("checkOccurrenceDefinitionIndividualSpecialization", "Occurrences::Life", "OccurrenceDefinition", true), + new("checkPredicateSpecialization", "Performances::BooleanEvaluation", "Predicate", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to MetadataDefinition, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] MetadataDefinitionRules = + [ + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkItemDefinitionSpecialization", "Items::Item", "ItemDefinition", false), + new("checkMetaclassSpecialization", "Metaobjects::Metaobject", "Metaclass", false), + new("checkMetadataDefinitionSpecialization", "Metadata::MetadataItem", "MetadataDefinition", false), + new("checkOccurrenceDefinitionIndividualSpecialization", "Occurrences::Life", "OccurrenceDefinition", true), + new("checkStructureSpecialization", "Objects::Object", "Structure", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to RenderingDefinition, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] RenderingDefinitionRules = + [ + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkItemDefinitionSpecialization", "Items::Item", "ItemDefinition", false), + new("checkOccurrenceDefinitionIndividualSpecialization", "Occurrences::Life", "OccurrenceDefinition", true), + new("checkPartDefinitionSpecialization", "Parts::Part", "PartDefinition", false), + new("checkRenderingDefinitionSpecialization", "Views::Rendering", "RenderingDefinition", false), + new("checkStructureSpecialization", "Objects::Object", "Structure", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to RequirementUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] RequirementUsageRules = + [ + new("checkBooleanExpressionSpecialization", "Performances::booleanEvaluations", "BooleanExpression", false), + new("checkConstraintUsageCheckedConstraintSpecialization", "Items::Item::checkedConstraints", "ConstraintUsage", true), + new("checkConstraintUsageSpecialization", "Constraints::constraintChecks", "ConstraintUsage", false), + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkRequirementUsageRequirementVerificationSpecialization", "VerificationCases::VerificationCase::obj::requirementVerifications", "RequirementUsage", true), + new("checkRequirementUsageSpecialization", "Requirements::requirementChecks", "RequirementUsage", false), + new("checkRequirementUsageSubrequirementSpecialization", "Requirements::RequirementCheck::subrequirements", "RequirementUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to ViewDefinition, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] ViewDefinitionRules = + [ + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkItemDefinitionSpecialization", "Items::Item", "ItemDefinition", false), + new("checkOccurrenceDefinitionIndividualSpecialization", "Occurrences::Life", "OccurrenceDefinition", true), + new("checkPartDefinitionSpecialization", "Parts::Part", "PartDefinition", false), + new("checkStructureSpecialization", "Objects::Object", "Structure", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + new("checkViewDefinitionSpecialization", "Views::View", "ViewDefinition", false), + ]; + + /// + /// The implied library Specializations applying to BindingConnectorAsUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] BindingConnectorAsUsageRules = + [ + new("checkBindingConnectorSpecialization", "Links::selfLinks", "BindingConnector", false), + new("checkConnectorBinaryObjectSpecialization", "Objects::binaryLinkObjects", "Connector", true), + new("checkConnectorBinarySpecialization", "Links::binaryLinks", "Connector", true), + new("checkConnectorObjectSpecialization", "Objects::linkObjects", "Connector", true), + new("checkConnectorSpecialization", "Links::links", "Connector", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to CalculationUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] CalculationUsageRules = + [ + new("checkActionUsageOwnedActionSpecialization", "Parts::Part::ownedActions", "ActionUsage", true), + new("checkActionUsageSpecialization", "Actions::actions", "ActionUsage", false), + new("checkActionUsageSubactionSpecialization", "Actions::Action::subactions", "ActionUsage", true), + new("checkCalculationUsageSpecialization", "Calculations::calculations", "CalculationUsage", false), + new("checkCalculationUsageSubcalculationSpecialization", "Calculations::Calculation::subcalculations", "CalculationUsage", true), + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to CollectExpression, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] CollectExpressionRules = + [ + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to ConjugatedPortDefinition, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] ConjugatedPortDefinitionRules = + [ + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkOccurrenceDefinitionIndividualSpecialization", "Occurrences::Life", "OccurrenceDefinition", true), + new("checkPortDefinitionSpecialization", "Ports::Port", "PortDefinition", false), + new("checkStructureSpecialization", "Objects::Object", "Structure", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to ConstraintUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] ConstraintUsageRules = + [ + new("checkBooleanExpressionSpecialization", "Performances::booleanEvaluations", "BooleanExpression", false), + new("checkConstraintUsageCheckedConstraintSpecialization", "Items::Item::checkedConstraints", "ConstraintUsage", true), + new("checkConstraintUsageSpecialization", "Constraints::constraintChecks", "ConstraintUsage", false), + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to DecisionNode, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] DecisionNodeRules = + [ + new("checkActionUsageOwnedActionSpecialization", "Parts::Part::ownedActions", "ActionUsage", true), + new("checkActionUsageSpecialization", "Actions::actions", "ActionUsage", false), + new("checkActionUsageSubactionSpecialization", "Actions::Action::subactions", "ActionUsage", true), + new("checkControlNodeSpecialization", "Actions::Action::controls", "ControlNode", false), + new("checkDecisionNodeSpecialization", "Actions::Action::decisions", "DecisionNode", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to FeatureChainExpression, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] FeatureChainExpressionRules = + [ + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to ForLoopActionUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] ForLoopActionUsageRules = + [ + new("checkActionUsageOwnedActionSpecialization", "Parts::Part::ownedActions", "ActionUsage", true), + new("checkActionUsageSpecialization", "Actions::actions", "ActionUsage", false), + new("checkActionUsageSubactionSpecialization", "Actions::Action::subactions", "ActionUsage", true), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkForLoopActionUsageSpecialization", "Actions::forLoopActions", "ForLoopActionUsage", false), + new("checkForLoopActionUsageSubactionSpecialization", "Actions::Action::forLoops", "ForLoopActionUsage", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to ForkNode, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] ForkNodeRules = + [ + new("checkActionUsageOwnedActionSpecialization", "Parts::Part::ownedActions", "ActionUsage", true), + new("checkActionUsageSpecialization", "Actions::actions", "ActionUsage", false), + new("checkActionUsageSubactionSpecialization", "Actions::Action::subactions", "ActionUsage", true), + new("checkControlNodeSpecialization", "Actions::Action::controls", "ControlNode", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkForkNodeSpecialization", "Actions::Action::forks", "ForkNode", false), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to IndexExpression, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] IndexExpressionRules = + [ + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to JoinNode, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] JoinNodeRules = + [ + new("checkActionUsageOwnedActionSpecialization", "Parts::Part::ownedActions", "ActionUsage", true), + new("checkActionUsageSpecialization", "Actions::actions", "ActionUsage", false), + new("checkActionUsageSubactionSpecialization", "Actions::Action::subactions", "ActionUsage", true), + new("checkControlNodeSpecialization", "Actions::Action::controls", "ControlNode", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkJoinNodeSpecialization", "Actions::Action::joins", "JoinNode", false), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to MergeNode, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] MergeNodeRules = + [ + new("checkActionUsageOwnedActionSpecialization", "Parts::Part::ownedActions", "ActionUsage", true), + new("checkActionUsageSpecialization", "Actions::actions", "ActionUsage", false), + new("checkActionUsageSubactionSpecialization", "Actions::Action::subactions", "ActionUsage", true), + new("checkControlNodeSpecialization", "Actions::Action::controls", "ControlNode", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkMergeNodeSpecialization", "Actions::Action::merges", "MergeNode", false), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to MetadataUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] MetadataUsageRules = + [ + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkItemUsageSpecialization", "Items::items", "ItemUsage", false), + new("checkItemUsageSubitemSpecialization", "Items::Item::subitems", "ItemUsage", true), + new("checkMetadataFeatureSpecialization", "Metaobjects::metaobjects", "MetadataFeature", false), + new("checkMetadataUsageSpecialization", "Metadata::metadataItems", "MetadataUsage", false), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to PartDefinition, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] PartDefinitionRules = + [ + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkItemDefinitionSpecialization", "Items::Item", "ItemDefinition", false), + new("checkOccurrenceDefinitionIndividualSpecialization", "Occurrences::Life", "OccurrenceDefinition", true), + new("checkPartDefinitionSpecialization", "Parts::Part", "PartDefinition", false), + new("checkStructureSpecialization", "Objects::Object", "Structure", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to PerformActionUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] PerformActionUsageRules = + [ + new("checkActionUsageOwnedActionSpecialization", "Parts::Part::ownedActions", "ActionUsage", true), + new("checkActionUsageSpecialization", "Actions::actions", "ActionUsage", false), + new("checkActionUsageSubactionSpecialization", "Actions::Action::subactions", "ActionUsage", true), + new("checkEventOccurrenceUsageSpecialization", "Occurrences::Occurrence::timeEnclosedOccurrences", "EventOccurrenceUsage", true), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkPerformActionUsageSpecialization", "Parts::Part::performedActions", "PerformActionUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to SelectExpression, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] SelectExpressionRules = + [ + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to StateDefinition, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] StateDefinitionRules = + [ + new("checkActionDefinitionSpecialization", "Actions::Action", "ActionDefinition", false), + new("checkBehaviorSpecialization", "Performances::Performance", "Behavior", false), + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkOccurrenceDefinitionIndividualSpecialization", "Occurrences::Life", "OccurrenceDefinition", true), + new("checkStateDefinitionSpecialization", "States::StateAction", "StateDefinition", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to SuccessionAsUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] SuccessionAsUsageRules = + [ + new("checkConnectorBinaryObjectSpecialization", "Objects::binaryLinkObjects", "Connector", true), + new("checkConnectorBinarySpecialization", "Links::binaryLinks", "Connector", true), + new("checkConnectorObjectSpecialization", "Objects::linkObjects", "Connector", true), + new("checkConnectorSpecialization", "Links::links", "Connector", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkSuccessionSpecialization", "Occurrences::happensBeforeLinks", "Succession", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to SuccessionFlow, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] SuccessionFlowRules = + [ + new("checkConnectorBinaryObjectSpecialization", "Objects::binaryLinkObjects", "Connector", true), + new("checkConnectorBinarySpecialization", "Links::binaryLinks", "Connector", true), + new("checkConnectorObjectSpecialization", "Objects::linkObjects", "Connector", true), + new("checkConnectorSpecialization", "Links::links", "Connector", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFlowSpecialization", "Transfers::transfers", "Flow", false), + new("checkFlowWithEndsSpecialization", "Transfers::flowTransfers", "Flow", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkSuccessionFlowSpecialization", "Transfers::flowTransfersBefore", "SuccessionFlow", false), + new("checkSuccessionSpecialization", "Occurrences::happensBeforeLinks", "Succession", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to WhileLoopActionUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] WhileLoopActionUsageRules = + [ + new("checkActionUsageOwnedActionSpecialization", "Parts::Part::ownedActions", "ActionUsage", true), + new("checkActionUsageSpecialization", "Actions::actions", "ActionUsage", false), + new("checkActionUsageSubactionSpecialization", "Actions::Action::subactions", "ActionUsage", true), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + new("checkWhileLoopActionUsageSpecialization", "Actions::whileLoopActions", "WhileLoopActionUsage", false), + new("checkWhileLoopActionUsageSubactionSpecialization", "Actions::Action::whileLoops", "WhileLoopActionUsage", true), + ]; + + /// + /// The implied library Specializations applying to AcceptActionUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] AcceptActionUsageRules = + [ + new("checkAcceptActionUsageSpecialization", "Actions::acceptActions", "AcceptActionUsage", true), + new("checkAcceptActionUsageSubactionSpecialization", "Actions::Action::acceptSubactions", "AcceptActionUsage", true), + new("checkAcceptActionUsageTriggerActionSpecialization", "Actions::TransitionAction::accepter", "AcceptActionUsage", true), + new("checkActionUsageOwnedActionSpecialization", "Parts::Part::ownedActions", "ActionUsage", true), + new("checkActionUsageSpecialization", "Actions::actions", "ActionUsage", false), + new("checkActionUsageSubactionSpecialization", "Actions::Action::subactions", "ActionUsage", true), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to ActionDefinition, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] ActionDefinitionRules = + [ + new("checkActionDefinitionSpecialization", "Actions::Action", "ActionDefinition", false), + new("checkBehaviorSpecialization", "Performances::Performance", "Behavior", false), + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkOccurrenceDefinitionIndividualSpecialization", "Occurrences::Life", "OccurrenceDefinition", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to AssignmentActionUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] AssignmentActionUsageRules = + [ + new("checkActionUsageOwnedActionSpecialization", "Parts::Part::ownedActions", "ActionUsage", true), + new("checkActionUsageSpecialization", "Actions::actions", "ActionUsage", false), + new("checkActionUsageSubactionSpecialization", "Actions::Action::subactions", "ActionUsage", true), + new("checkAssignmentActionUsageSpecialization", "Actions::assignmentActions", "AssignmentActionUsage", false), + new("checkAssignmentActionUsageSubactionSpecialization", "Actions::Action::assignments", "AssignmentActionUsage", true), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to AssociationStructure, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] AssociationStructureRules = + [ + new("checkAssociationBinarySpecialization", "Links::BinaryLink", "Association", true), + new("checkAssociationSpecialization", "Links::Link", "Association", false), + new("checkAssociationStructureBinarySpecialization", "Objects::BinaryLinkObject", "AssociationStructure", true), + new("checkAssociationStructureSpecialization", "Objects::LinkObject", "AssociationStructure", false), + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkStructureSpecialization", "Objects::Object", "Structure", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to ControlNode, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] ControlNodeRules = + [ + new("checkActionUsageOwnedActionSpecialization", "Parts::Part::ownedActions", "ActionUsage", true), + new("checkActionUsageSpecialization", "Actions::actions", "ActionUsage", false), + new("checkActionUsageSubactionSpecialization", "Actions::Action::subactions", "ActionUsage", true), + new("checkControlNodeSpecialization", "Actions::Action::controls", "ControlNode", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to IfActionUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] IfActionUsageRules = + [ + new("checkActionUsageOwnedActionSpecialization", "Parts::Part::ownedActions", "ActionUsage", true), + new("checkActionUsageSpecialization", "Actions::actions", "ActionUsage", false), + new("checkActionUsageSubactionSpecialization", "Actions::Action::subactions", "ActionUsage", true), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkIfActionUsageSubactionSpecialization", "Actions::Action::ifSubactions", "IfActionUsage", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to Interaction, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] InteractionRules = + [ + new("checkAssociationBinarySpecialization", "Links::BinaryLink", "Association", true), + new("checkAssociationSpecialization", "Links::Link", "Association", false), + new("checkBehaviorSpecialization", "Performances::Performance", "Behavior", false), + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to ItemDefinition, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] ItemDefinitionRules = + [ + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkItemDefinitionSpecialization", "Items::Item", "ItemDefinition", false), + new("checkOccurrenceDefinitionIndividualSpecialization", "Occurrences::Life", "OccurrenceDefinition", true), + new("checkStructureSpecialization", "Objects::Object", "Structure", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to LoopActionUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] LoopActionUsageRules = + [ + new("checkActionUsageOwnedActionSpecialization", "Parts::Part::ownedActions", "ActionUsage", true), + new("checkActionUsageSpecialization", "Actions::actions", "ActionUsage", false), + new("checkActionUsageSubactionSpecialization", "Actions::Action::subactions", "ActionUsage", true), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to OperatorExpression, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] OperatorExpressionRules = + [ + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to PortDefinition, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] PortDefinitionRules = + [ + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkOccurrenceDefinitionIndividualSpecialization", "Occurrences::Life", "OccurrenceDefinition", true), + new("checkPortDefinitionSpecialization", "Ports::Port", "PortDefinition", false), + new("checkStructureSpecialization", "Objects::Object", "Structure", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to RenderingUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] RenderingUsageRules = + [ + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkItemUsageSpecialization", "Items::items", "ItemUsage", false), + new("checkItemUsageSubitemSpecialization", "Items::Item::subitems", "ItemUsage", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkPartUsageSpecialization", "Parts::parts", "PartUsage", false), + new("checkPartUsageStakeholderSpecialization", "Requirements::RequirementCheck::stakeholders", "PartUsage", true), + new("checkPartUsageSubpartSpecialization", "Items::Item::subparts", "PartUsage", true), + new("checkRenderingUsageSpecialization", "Views::renderings", "RenderingUsage", false), + new("checkRenderingUsageSubrenderingSpecialization", "Views::Rendering::subrenderings", "RenderingUsage", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to SendActionUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] SendActionUsageRules = + [ + new("checkActionUsageOwnedActionSpecialization", "Parts::Part::ownedActions", "ActionUsage", true), + new("checkActionUsageSpecialization", "Actions::actions", "ActionUsage", false), + new("checkActionUsageSubactionSpecialization", "Actions::Action::subactions", "ActionUsage", true), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkSendActionUsageSpecialization", "Actions::sendActions", "SendActionUsage", false), + new("checkSendActionUsageSubactionSpecialization", "Actions::Action::acceptSubactions", "SendActionUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to StateUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] StateUsageRules = + [ + new("checkActionUsageOwnedActionSpecialization", "Parts::Part::ownedActions", "ActionUsage", true), + new("checkActionUsageSpecialization", "Actions::actions", "ActionUsage", false), + new("checkActionUsageSubactionSpecialization", "Actions::Action::subactions", "ActionUsage", true), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkStateUsageExclusiveStateSpecialization", "States::StateAction::exclusiveStates", "StateUsage", true), + new("checkStateUsageOwnedStateSpecialization", "Parts::Part::ownedStates", "StateUsage", true), + new("checkStateUsageSpecialization", "States::stateActions", "StateUsage", false), + new("checkStateUsageSubstateSpecialization", "States::StateAction::substates", "StateUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to TerminateActionUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] TerminateActionUsageRules = + [ + new("checkActionUsageOwnedActionSpecialization", "Parts::Part::ownedActions", "ActionUsage", true), + new("checkActionUsageSpecialization", "Actions::actions", "ActionUsage", false), + new("checkActionUsageSubactionSpecialization", "Actions::Action::subactions", "ActionUsage", true), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTerminateActionUsageSpecialization", "Actions::terminateActions", "TerminateActionUsage", false), + new("checkTerminateActionUsageSubactionSpecialization", "Actions::Action::terminateSubactions", "TerminateActionUsage", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to TransitionUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] TransitionUsageRules = + [ + new("checkActionUsageOwnedActionSpecialization", "Parts::Part::ownedActions", "ActionUsage", true), + new("checkActionUsageSpecialization", "Actions::actions", "ActionUsage", false), + new("checkActionUsageSubactionSpecialization", "Actions::Action::subactions", "ActionUsage", true), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTransitionUsageActionSpecialization", "Actions::Action::decisionTransitions", "TransitionUsage", true), + new("checkTransitionUsageSpecialization", "Actions::transitionActions", "TransitionUsage", false), + new("checkTransitionUsageStateSpecialization", "States::StateAction::stateTransitions", "TransitionUsage", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to TriggerInvocationExpression, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] TriggerInvocationExpressionRules = + [ + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to ViewUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] ViewUsageRules = + [ + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkItemUsageSpecialization", "Items::items", "ItemUsage", false), + new("checkItemUsageSubitemSpecialization", "Items::Item::subitems", "ItemUsage", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkPartUsageSpecialization", "Parts::parts", "PartUsage", false), + new("checkPartUsageStakeholderSpecialization", "Requirements::RequirementCheck::stakeholders", "PartUsage", true), + new("checkPartUsageSubpartSpecialization", "Items::Item::subparts", "PartUsage", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + new("checkViewUsageSpecialization", "Views::views", "ViewUsage", false), + new("checkViewUsageSubviewSpecialization", "Views::View::subviews", "ViewUsage", true), + ]; + + /// + /// The implied library Specializations applying to ActionUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] ActionUsageRules = + [ + new("checkActionUsageOwnedActionSpecialization", "Parts::Part::ownedActions", "ActionUsage", true), + new("checkActionUsageSpecialization", "Actions::actions", "ActionUsage", false), + new("checkActionUsageSubactionSpecialization", "Actions::Action::subactions", "ActionUsage", true), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to ConnectorAsUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] ConnectorAsUsageRules = + [ + new("checkConnectorBinaryObjectSpecialization", "Objects::binaryLinkObjects", "Connector", true), + new("checkConnectorBinarySpecialization", "Links::binaryLinks", "Connector", true), + new("checkConnectorObjectSpecialization", "Objects::linkObjects", "Connector", true), + new("checkConnectorSpecialization", "Links::links", "Connector", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to ConstructorExpression, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] ConstructorExpressionRules = + [ + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to EnumerationDefinition, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] EnumerationDefinitionRules = + [ + new("checkDataTypeSpecialization", "Base::DataValue", "DataType", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to Flow, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] FlowRules = + [ + new("checkConnectorBinaryObjectSpecialization", "Objects::binaryLinkObjects", "Connector", true), + new("checkConnectorBinarySpecialization", "Links::binaryLinks", "Connector", true), + new("checkConnectorObjectSpecialization", "Objects::linkObjects", "Connector", true), + new("checkConnectorSpecialization", "Links::links", "Connector", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFlowSpecialization", "Transfers::transfers", "Flow", false), + new("checkFlowWithEndsSpecialization", "Transfers::flowTransfers", "Flow", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to Invariant, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] InvariantRules = + [ + new("checkBooleanExpressionSpecialization", "Performances::booleanEvaluations", "BooleanExpression", false), + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to InvocationExpression, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] InvocationExpressionRules = + [ + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to LiteralBoolean, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] LiteralBooleanRules = + [ + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkLiteralBooleanSpecialization", "Performances::literalBooleanEvaluations", "LiteralBoolean", false), + new("checkLiteralExpressionSpecialization", "Performances::literalEvaluations", "LiteralExpression", false), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to LiteralInfinity, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] LiteralInfinityRules = + [ + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkLiteralExpressionSpecialization", "Performances::literalEvaluations", "LiteralExpression", false), + new("checkLiteralInfinitySpecialization", "Performances::literalIntegerEvaluations", "LiteralInfinity", false), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to LiteralInteger, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] LiteralIntegerRules = + [ + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkLiteralExpressionSpecialization", "Performances::literalEvaluations", "LiteralExpression", false), + new("checkLiteralIntegerSpecialization", "Performances::literalIntegerEvaluations", "LiteralInteger", false), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to LiteralRational, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] LiteralRationalRules = + [ + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkLiteralExpressionSpecialization", "Performances::literalEvaluations", "LiteralExpression", false), + new("checkLiteralRationalSpecialization", "Performances::literalRationalEvaluations", "LiteralRational", false), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to LiteralString, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] LiteralStringRules = + [ + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkLiteralExpressionSpecialization", "Performances::literalEvaluations", "LiteralExpression", false), + new("checkLiteralStringSpecialization", "Performances::literalStringEvaluations", "LiteralString", false), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to PartUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] PartUsageRules = + [ + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkItemUsageSpecialization", "Items::items", "ItemUsage", false), + new("checkItemUsageSubitemSpecialization", "Items::Item::subitems", "ItemUsage", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkPartUsageSpecialization", "Parts::parts", "PartUsage", false), + new("checkPartUsageStakeholderSpecialization", "Requirements::RequirementCheck::stakeholders", "PartUsage", true), + new("checkPartUsageSubpartSpecialization", "Items::Item::subparts", "PartUsage", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to Predicate, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] PredicateRules = + [ + new("checkBehaviorSpecialization", "Performances::Performance", "Behavior", false), + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkFunctionSpecialization", "Performances::Evaluation", "Function", false), + new("checkPredicateSpecialization", "Performances::BooleanEvaluation", "Predicate", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to AttributeDefinition, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] AttributeDefinitionRules = + [ + new("checkDataTypeSpecialization", "Base::DataValue", "DataType", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to BindingConnector, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] BindingConnectorRules = + [ + new("checkBindingConnectorSpecialization", "Links::selfLinks", "BindingConnector", false), + new("checkConnectorBinaryObjectSpecialization", "Objects::binaryLinkObjects", "Connector", true), + new("checkConnectorBinarySpecialization", "Links::binaryLinks", "Connector", true), + new("checkConnectorObjectSpecialization", "Objects::linkObjects", "Connector", true), + new("checkConnectorSpecialization", "Links::links", "Connector", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to BooleanExpression, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] BooleanExpressionRules = + [ + new("checkBooleanExpressionSpecialization", "Performances::booleanEvaluations", "BooleanExpression", false), + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to EnumerationUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] EnumerationUsageRules = + [ + new("checkAttributeUsageSpecialization", "Base::dataValues", "AttributeUsage", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to EventOccurrenceUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] EventOccurrenceUsageRules = + [ + new("checkEventOccurrenceUsageSpecialization", "Occurrences::Occurrence::timeEnclosedOccurrences", "EventOccurrenceUsage", true), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to FeatureReferenceExpression, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] FeatureReferenceExpressionRules = + [ + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to Function, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] FunctionRules = + [ + new("checkBehaviorSpecialization", "Performances::Performance", "Behavior", false), + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkFunctionSpecialization", "Performances::Evaluation", "Function", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to InstantiationExpression, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] InstantiationExpressionRules = + [ + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to ItemUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] ItemUsageRules = + [ + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkItemUsageSpecialization", "Items::items", "ItemUsage", false), + new("checkItemUsageSubitemSpecialization", "Items::Item::subitems", "ItemUsage", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to LiteralExpression, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] LiteralExpressionRules = + [ + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkLiteralExpressionSpecialization", "Performances::literalEvaluations", "LiteralExpression", false), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to Metaclass, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] MetaclassRules = + [ + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkMetaclassSpecialization", "Metaobjects::Metaobject", "Metaclass", false), + new("checkStructureSpecialization", "Objects::Object", "Structure", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to MetadataAccessExpression, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] MetadataAccessExpressionRules = + [ + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkMetadataAccessExpressionSpecialization", "Performances::metadataAccessEvaluations", "MetadataAccessExpression", false), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to NullExpression, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] NullExpressionRules = + [ + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkNullExpressionSpecialization", "Performances::nullEvaluations", "NullExpression", false), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to OccurrenceDefinition, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] OccurrenceDefinitionRules = + [ + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkOccurrenceDefinitionIndividualSpecialization", "Occurrences::Life", "OccurrenceDefinition", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to PortUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] PortUsageRules = + [ + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkPortUsageOwnedPortSpecialization", "Parts::Part::ownedPorts", "PortUsage", true), + new("checkPortUsageSpecialization", "Ports::ports", "PortUsage", false), + new("checkPortUsageSubportSpecialization", "Ports::Port::subports", "PortUsage", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to Succession, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] SuccessionRules = + [ + new("checkConnectorBinaryObjectSpecialization", "Objects::binaryLinkObjects", "Connector", true), + new("checkConnectorBinarySpecialization", "Links::binaryLinks", "Connector", true), + new("checkConnectorObjectSpecialization", "Objects::linkObjects", "Connector", true), + new("checkConnectorSpecialization", "Links::links", "Connector", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkSuccessionSpecialization", "Occurrences::happensBeforeLinks", "Succession", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to Association, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] AssociationRules = + [ + new("checkAssociationBinarySpecialization", "Links::BinaryLink", "Association", true), + new("checkAssociationSpecialization", "Links::Link", "Association", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to AttributeUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] AttributeUsageRules = + [ + new("checkAttributeUsageSpecialization", "Base::dataValues", "AttributeUsage", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to Behavior, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] BehaviorRules = + [ + new("checkBehaviorSpecialization", "Performances::Performance", "Behavior", false), + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to Connector, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] ConnectorRules = + [ + new("checkConnectorBinaryObjectSpecialization", "Objects::binaryLinkObjects", "Connector", true), + new("checkConnectorBinarySpecialization", "Links::binaryLinks", "Connector", true), + new("checkConnectorObjectSpecialization", "Objects::linkObjects", "Connector", true), + new("checkConnectorSpecialization", "Links::links", "Connector", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to Expression, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] ExpressionRules = + [ + new("checkExpressionSpecialization", "Performances::evaluations", "Expression", false), + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to MetadataFeature, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] MetadataFeatureRules = + [ + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkMetadataFeatureSpecialization", "Metaobjects::metaobjects", "MetadataFeature", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to MultiplicityRange, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] MultiplicityRangeRules = + [ + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkMultiplicitySpecialization", "Base::naturals", "Multiplicity", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to OccurrenceUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] OccurrenceUsageRules = + [ + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkOccurrenceUsageSnapshotSpecialization", "Occurrences::Occurrence::snapshots", "OccurrenceUsage", true), + new("checkOccurrenceUsageSpecialization", "Occurrences::occurrences", "OccurrenceUsage", false), + new("checkOccurrenceUsageSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "OccurrenceUsage", true), + new("checkOccurrenceUsageTimeSliceSpecialization", "Occurrences::Occurrence::timeSlices", "OccurrenceUsage", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to ReferenceUsage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] ReferenceUsageRules = + [ + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to Structure, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] StructureRules = + [ + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkStructureSpecialization", "Objects::Object", "Structure", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to Class, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] ClassRules = + [ + new("checkClassSpecialization", "Occurrences::Occurrence", "Class", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to DataType, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] DataTypeRules = + [ + new("checkDataTypeSpecialization", "Base::DataValue", "DataType", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to Definition, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] DefinitionRules = + [ + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to FlowEnd, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] FlowEndRules = + [ + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to Multiplicity, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] MultiplicityRules = + [ + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkMultiplicitySpecialization", "Base::naturals", "Multiplicity", false), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to PayloadFeature, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] PayloadFeatureRules = + [ + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to Step, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] StepRules = + [ + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkStepEnclosedPerformanceSpecialization", "Performances::Performance::enclosedPerformances", "Step", true), + new("checkStepOwnedPerformanceSpecialization", "Objects::Object::ownedPerformances", "Step", true), + new("checkStepSpecialization", "Performances::performances", "Step", false), + new("checkStepSubperformanceSpecialization", "Performances::Performance::subperformances", "Step", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to Usage, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] UsageRules = + [ + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to Classifier, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] ClassifierRules = + [ + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to Feature, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] FeatureRules = + [ + new("checkFeatureDataValueSpecialization", "Base::dataValues", "Feature", true), + new("checkFeatureEndSpecialization", "Links::Link::participant", "Feature", true), + new("checkFeatureObjectSpecialization", "Objects::objects", "Feature", true), + new("checkFeatureOccurrenceSpecialization", "Occurrences::occurrences", "Feature", true), + new("checkFeaturePortionSpecialization", "Occurrences::Occurrence::portions", "Feature", true), + new("checkFeatureSpecialization", "Base::things", "Feature", false), + new("checkFeatureSubobjectSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkFeatureSuboccurrenceSpecialization", "Occurrences::Occurrence::suboccurrences", "Feature", true), + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + /// + /// The implied library Specializations applying to Type, own and inherited. + /// + private static readonly ImpliedLibrarySpecialization[] TypeRules = + [ + new("checkTypeSpecialization", "Base::Anything", "Type", false), + ]; + + } +} diff --git a/SysML2.NET.Semantics/Extensions/ServiceCollectionExtensions.cs b/SysML2.NET.Semantics/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..f14b12c0 --- /dev/null +++ b/SysML2.NET.Semantics/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,210 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Extensions +{ + using System; + using System.Collections.Generic; + + using Microsoft.Extensions.DependencyInjection; + + using SysML2.NET.Semantics.Implied; + using SysML2.NET.Semantics.Implied.Guards; + using SysML2.NET.Semantics.Implied.Rules; + + /// + /// Registers the semantics layer with a dependency-injection container. + /// + public static class ServiceCollectionExtensions + { + /// + /// Registers the implied-relationship services with their default configuration. + /// + /// The service collection to register with. + /// The same service collection, to allow chaining. + /// Thrown when is null. + /// + /// An is NOT registered here: only the caller knows where the model + /// libraries were loaded from, so it must register one built from its own loaded Namespaces. + /// + public static IServiceCollection AddSysML2Semantics(this IServiceCollection services) => services.AddSysML2Semantics(_ => { }); + + /// + /// Registers the implied-relationship services with a caller-supplied configuration. + /// + /// The service collection to register with. + /// The delegate configuring the options. + /// The same service collection, to allow chaining. + /// Thrown when either argument is null. + public static IServiceCollection AddSysML2Semantics(this IServiceCollection services, Action configure) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + var options = new ImpliedRelationshipOptions(); + configure(options); + + services.AddSingleton(options); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(serviceProvider => new ImpliedRuleGuardRegistry(serviceProvider.GetServices())); + services.AddScoped(); + + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + + // Constraints whose OCL selects BETWEEN two library Features by a condition, which the + // single-target generated table cannot express. + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + + // Constraints whose OCL calls specializes(…) rather than specializesFromLibrary(…), or whose + // Relationship KIND the OCL leaves to the specification prose. + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + + // Chain-subsetting constraints: the Subsetting's general is a SYNTHESIZED feature chain. + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + services.AddImpliedRelationshipRule(); + + // The mechanically translatable guards come from the generator; only the shapes its parser + // deliberately declines are hand-written. + foreach (var generatedGuard in GeneratedImpliedRuleGuards.All) + { + services.AddSingleton(generatedGuard); + } + + services.AddImpliedRuleGuard(); + services.AddImpliedRuleGuard(); + services.AddImpliedRuleGuard(); + services.AddImpliedRuleGuard(); + services.AddImpliedRuleGuard(); + services.AddImpliedRuleGuard(); + services.AddImpliedRuleGuard(); + services.AddImpliedRuleGuard(); + services.AddImpliedRuleGuard(); + services.AddImpliedRuleGuard(); + services.AddImpliedRuleGuard(); + services.AddImpliedRuleGuard(); + services.AddImpliedRuleGuard(); + services.AddImpliedRuleGuard(); + services.AddImpliedRuleGuard(); + services.AddImpliedRuleGuard(); + services.AddImpliedRuleGuard(); + + return services; + } + + /// + /// Registers a hand-coded rule for a semantic constraint the generated table cannot express. + /// + /// The rule to register. + /// The service collection to register with. + /// The same service collection, to allow chaining. + /// Thrown when is null. + public static IServiceCollection AddImpliedRelationshipRule(this IServiceCollection services) + where TRule : class, IImpliedRelationshipRule + { + return services == null + ? throw new ArgumentNullException(nameof(services)) + : services.AddScoped(); + } + + /// + /// Registers a guard for a conditional semantic constraint. + /// + /// The guard to register. + /// The service collection to register with. + /// The same service collection, to allow chaining. + /// Thrown when is null. + /// + /// Guards are registered explicitly rather than discovered by assembly scanning, so the registered + /// set stays visible in source and the assembly stays trimmable. + /// + public static IServiceCollection AddImpliedRuleGuard(this IServiceCollection services) + where TGuard : class, IImpliedRuleGuard + { + return services == null + ? throw new ArgumentNullException(nameof(services)) + : services.AddScoped(); + } + + /// + /// Registers an built from the supplied library root Namespaces. + /// + /// The service collection to register with. + /// The library root Namespaces to index. + /// The same service collection, to allow chaining. + /// Thrown when either argument is null. + public static IServiceCollection AddLibraryTypeIndex(this IServiceCollection services, IEnumerable libraryNamespaces) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (libraryNamespaces == null) + { + throw new ArgumentNullException(nameof(libraryNamespaces)); + } + + var index = OwnershipTreeLibraryTypeIndex.Build(libraryNamespaces); + + return services.AddSingleton(index); + } + } +} diff --git a/SysML2.NET.Semantics/Implied/GeneratedRuleGuard.cs b/SysML2.NET.Semantics/Implied/GeneratedRuleGuard.cs new file mode 100644 index 00000000..ddd65e1b --- /dev/null +++ b/SysML2.NET.Semantics/Implied/GeneratedRuleGuard.cs @@ -0,0 +1,73 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied +{ + using System; + + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Adapts a predicate translated from a constraint's guard OCL to the + /// contract. + /// + /// + /// This exists so the mechanically translatable guards are emitted as data — one line each — rather than + /// as a class file each, while still reaching the provider through the same interface as a hand-written + /// guard. + /// + public class GeneratedRuleGuard : IImpliedRuleGuard + { + /// + /// The translated guard expression. + /// + private readonly Func predicate; + + /// + /// Initializes a new instance of the class. + /// + /// The constraint the guard decides. + /// The translated guard expression. + /// Thrown when either argument is null. + public GeneratedRuleGuard(string constraintName, Func predicate) + { + this.ConstraintName = constraintName ?? throw new ArgumentNullException(nameof(constraintName)); + this.predicate = predicate ?? throw new ArgumentNullException(nameof(predicate)); + } + + /// + /// Gets the name of the semantic constraint this guard decides. + /// + public string ConstraintName { get; } + + /// + /// Asserts whether the constraint applies to the supplied Element. + /// + /// The Element under evaluation. + /// True when the constraint applies. + /// Thrown when is null. + public bool Applies(IElement element) + { + return element == null + ? throw new ArgumentNullException(nameof(element)) + : this.predicate(element); + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Guards/AcceptActionUsageSubactionSpecializationGuard.cs b/SysML2.NET.Semantics/Implied/Guards/AcceptActionUsageSubactionSpecializationGuard.cs new file mode 100644 index 00000000..14ce99b6 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Guards/AcceptActionUsageSubactionSpecializationGuard.cs @@ -0,0 +1,54 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Guards +{ + using System; + + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Systems.Actions; + + /// + /// Guards checkAcceptActionUsageSubactionSpecialization: a subaction AcceptActionUsage that is not a trigger action specializes Actions::Action::acceptSubactions. + /// + /// + /// OCL: isSubactionUsage() and not isTriggerAction() implies specializesFromLibrary('Actions::Action::acceptSubactions') + /// + public class AcceptActionUsageSubactionSpecializationGuard : IImpliedRuleGuard + { + /// + /// Gets the name of the semantic constraint this guard decides. + /// + public string ConstraintName => "checkAcceptActionUsageSubactionSpecialization"; + + /// + /// Asserts whether the constraint applies to the supplied Element. + /// + /// The Element under evaluation. + /// True when the constraint applies. + /// Thrown when is null. + public bool Applies(IElement element) + { + return element == null + ? throw new ArgumentNullException(nameof(element)) + : element is IAcceptActionUsage acceptActionUsage && acceptActionUsage.IsSubactionUsage() && !acceptActionUsage.IsTriggerAction(); + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Guards/AssociationBinarySpecializationGuard.cs b/SysML2.NET.Semantics/Implied/Guards/AssociationBinarySpecializationGuard.cs new file mode 100644 index 00000000..b3b3568e --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Guards/AssociationBinarySpecializationGuard.cs @@ -0,0 +1,57 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Guards +{ + using System; + + using SysML2.NET.Core.POCO.Kernel.Associations; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Guards checkAssociationBinarySpecialization: an Association with exactly two ends specializes + /// Links::BinaryLink. + /// + /// + /// OCL: associationEnd->size() = 2 implies specializesFromLibrary('Links::BinaryLink'). Hand + /// written because the count is over associationEnd, a different property from the + /// ownedEndFeature the generator's cardinality shape recognises. + /// + public class AssociationBinarySpecializationGuard : IImpliedRuleGuard + { + /// + /// Gets the name of the semantic constraint this guard decides. + /// + public string ConstraintName => "checkAssociationBinarySpecialization"; + + /// + /// Asserts whether the Association has exactly two ends. + /// + /// The Element under evaluation. + /// True when the Element is an Association with exactly two association ends. + /// Thrown when is null. + public bool Applies(IElement element) + { + return element == null + ? throw new ArgumentNullException(nameof(element)) + : element is IAssociation { associationEnd.Count: 2 }; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Guards/AssociationStructureBinarySpecializationGuard.cs b/SysML2.NET.Semantics/Implied/Guards/AssociationStructureBinarySpecializationGuard.cs new file mode 100644 index 00000000..a0c83b0c --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Guards/AssociationStructureBinarySpecializationGuard.cs @@ -0,0 +1,55 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Guards +{ + using System; + + using SysML2.NET.Core.POCO.Core.Types; + using SysML2.NET.Core.POCO.Kernel.Associations; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Guards checkAssociationStructureBinarySpecialization: an AssociationStructure with exactly two end Features specializes Objects::BinaryLinkObject. + /// + /// + /// OCL: endFeature->size() = 2 implies specializesFromLibrary('Objects::BinaryLinkObject') + /// + public class AssociationStructureBinarySpecializationGuard : IImpliedRuleGuard + { + /// + /// Gets the name of the semantic constraint this guard decides. + /// + public string ConstraintName => "checkAssociationStructureBinarySpecialization"; + + /// + /// Asserts whether the constraint applies to the supplied Element. + /// + /// The Element under evaluation. + /// True when the constraint applies. + /// Thrown when is null. + public bool Applies(IElement element) + { + return element == null + ? throw new ArgumentNullException(nameof(element)) + : element is IAssociationStructure associationStructure && ((IType)associationStructure).endFeature.Count == 2; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Guards/ConnectorBinaryObjectSpecializationGuard.cs b/SysML2.NET.Semantics/Implied/Guards/ConnectorBinaryObjectSpecializationGuard.cs new file mode 100644 index 00000000..93a1882e --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Guards/ConnectorBinaryObjectSpecializationGuard.cs @@ -0,0 +1,61 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Guards +{ + using System; + using System.Linq; + + using SysML2.NET.Core.POCO.Kernel.Associations; + using SysML2.NET.Core.POCO.Kernel.Connectors; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Guards checkConnectorBinaryObjectSpecialization: a binary Connector typed by an AssociationStructure + /// specializes Objects::binaryLinkObjects. + /// + /// + /// OCL: connectorEnds->size() = 2 and + /// association->exists(oclIsKindOf(AssociationStructure)) implies + /// specializesFromLibrary('Objects::binaryLinkObjects'). Both conjuncts are required: a binary + /// Connector typed by a plain Association carries a different library Specialization. + /// + public class ConnectorBinaryObjectSpecializationGuard : IImpliedRuleGuard + { + /// + /// Gets the name of the semantic constraint this guard decides. + /// + public string ConstraintName => "checkConnectorBinaryObjectSpecialization"; + + /// + /// Asserts whether the Connector has exactly two ends and is typed by an AssociationStructure. + /// + /// The Element under evaluation. + /// True when the Element is a binary Connector typed by an AssociationStructure. + /// Thrown when is null. + public bool Applies(IElement element) + { + return element == null + ? throw new ArgumentNullException(nameof(element)) + : element is IConnector { connectorEnd.Count: 2 } connector + && connector.association.Any(association => association is IAssociationStructure); + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Guards/ConnectorBinarySpecializationGuard.cs b/SysML2.NET.Semantics/Implied/Guards/ConnectorBinarySpecializationGuard.cs new file mode 100644 index 00000000..a03ee0d7 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Guards/ConnectorBinarySpecializationGuard.cs @@ -0,0 +1,58 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Guards +{ + using System; + + using SysML2.NET.Core.POCO.Kernel.Connectors; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Guards checkConnectorBinarySpecialization: a Connector with exactly two ends specializes + /// Links::binaryLinks. + /// + /// + /// OCL: connectorEnd->size() = 2 implies specializesFromLibrary('Links::binaryLinks'). This is + /// the unconditioned binary case; adds the + /// AssociationStructure condition for the stronger Objects::binaryLinkObjects Specialization, and the + /// 8.4.2 redundancy rules decide which survives when both apply. + /// + public class ConnectorBinarySpecializationGuard : IImpliedRuleGuard + { + /// + /// Gets the name of the semantic constraint this guard decides. + /// + public string ConstraintName => "checkConnectorBinarySpecialization"; + + /// + /// Asserts whether the Connector has exactly two ends. + /// + /// The Element under evaluation. + /// True when the Element is a Connector with exactly two ends. + /// Thrown when is null. + public bool Applies(IElement element) + { + return element == null + ? throw new ArgumentNullException(nameof(element)) + : element is IConnector { connectorEnd.Count: 2 }; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Guards/ConnectorObjectSpecializationGuard.cs b/SysML2.NET.Semantics/Implied/Guards/ConnectorObjectSpecializationGuard.cs new file mode 100644 index 00000000..e29455d4 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Guards/ConnectorObjectSpecializationGuard.cs @@ -0,0 +1,61 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Guards +{ + using System; + using System.Linq; + + using SysML2.NET.Core.POCO.Kernel.Associations; + using SysML2.NET.Core.POCO.Kernel.Connectors; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Guards checkConnectorObjectSpecialization: a Connector typed by an AssociationStructure specializes + /// Objects::linkObjects. + /// + /// + /// OCL: association->exists(oclIsKindOf(AssociationStructure)) implies + /// specializesFromLibrary('Objects::linkObjects'). The exists navigation over a collection is + /// outside the generator's translatable shapes, so the guard is written by hand. It is the unconditioned + /// counterpart of , which adds the two-end + /// condition. + /// + public class ConnectorObjectSpecializationGuard : IImpliedRuleGuard + { + /// + /// Gets the name of the semantic constraint this guard decides. + /// + public string ConstraintName => "checkConnectorObjectSpecialization"; + + /// + /// Asserts whether the Connector is typed by an AssociationStructure. + /// + /// The Element under evaluation. + /// True when the Element is a Connector with an AssociationStructure among its associations. + /// Thrown when is null. + public bool Applies(IElement element) + { + return element == null + ? throw new ArgumentNullException(nameof(element)) + : element is IConnector connector && connector.association.Any(association => association is IAssociationStructure); + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Guards/FeatureEndSpecializationGuard.cs b/SysML2.NET.Semantics/Implied/Guards/FeatureEndSpecializationGuard.cs new file mode 100644 index 00000000..ebb580bb --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Guards/FeatureEndSpecializationGuard.cs @@ -0,0 +1,60 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Guards +{ + using System; + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Kernel.Associations; + using SysML2.NET.Core.POCO.Kernel.Connectors; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Guards checkFeatureEndSpecialization: an end Feature of an Association or Connector specializes + /// Links::Link::participant. + /// + /// + /// OCL: isEnd and owningType <> null and (owningType.oclIsKindOf(Association) or + /// owningType.oclIsKindOf(Connector)) implies + /// specializesFromLibrary('Links::Link::participant'). An end Feature owned by anything else — a + /// plain Type, for instance — is out of scope. + /// + public class FeatureEndSpecializationGuard : IImpliedRuleGuard + { + /// + /// Gets the name of the semantic constraint this guard decides. + /// + public string ConstraintName => "checkFeatureEndSpecialization"; + + /// + /// Asserts whether the Feature is an end of an Association or Connector. + /// + /// The Element under evaluation. + /// True when the Element is an end Feature owned by an Association or Connector. + /// Thrown when is null. + public bool Applies(IElement element) + { + return element == null + ? throw new ArgumentNullException(nameof(element)) + : element is IFeature { IsEnd: true, owningType: IAssociation or IConnector }; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Guards/FeaturePortionSpecializationGuard.cs b/SysML2.NET.Semantics/Implied/Guards/FeaturePortionSpecializationGuard.cs new file mode 100644 index 00000000..f4f31a4e --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Guards/FeaturePortionSpecializationGuard.cs @@ -0,0 +1,82 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Guards +{ + using System; + using System.Linq; + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Core.Types; + using SysML2.NET.Core.POCO.Kernel.Classes; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Guards checkFeaturePortionSpecialization: a portion Feature typed by a Class, owned by a Class or by + /// a Class-typed Feature, specializes Occurrences::Occurrence::portions. + /// + /// + /// OCL: isPortion and ownedTyping.type->includes(oclIsKindOf(Class)) and owningType <> null + /// and (owningType.oclIsKindOf(Class) or owningType.oclIsKindOf(Feature) and + /// owningType.oclAsType(Feature).type->exists(oclIsKindOf(Class))). The nested + /// oclAsType(Feature).type navigation in the final disjunct is outside the generator's + /// translatable shapes, so the guard is written by hand. + /// + public class FeaturePortionSpecializationGuard : IImpliedRuleGuard + { + /// + /// Gets the name of the semantic constraint this guard decides. + /// + public string ConstraintName => "checkFeaturePortionSpecialization"; + + /// + /// Asserts whether the Feature is a Class-typed portion owned by a Class or a Class-typed Feature. + /// + /// The Element under evaluation. + /// True when every conjunct of the constraint holds. + /// Thrown when is null. + public bool Applies(IElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + return element is IFeature { IsPortion: true } feature + && feature.ownedTyping.Any(featureTyping => featureTyping.Type is IClass) + && IsOwnedByAClassOrAClassTypedFeature(feature.owningType); + } + + /// + /// Asserts whether an owning Type is a Class, or a Feature that is itself typed by a Class. + /// + /// The owning Type, which may be null. + /// True when the owning Type satisfies the constraint's final disjunct. + private static bool IsOwnedByAClassOrAClassTypedFeature(IType owningType) + { + return owningType switch + { + IClass => true, + IFeature owningFeature => owningFeature.type.Any(type => type is IClass), + _ => false + }; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Guards/FeatureSubobjectSpecializationGuard.cs b/SysML2.NET.Semantics/Implied/Guards/FeatureSubobjectSpecializationGuard.cs new file mode 100644 index 00000000..cefa43ee --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Guards/FeatureSubobjectSpecializationGuard.cs @@ -0,0 +1,60 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Guards +{ + using System; + using System.Linq; + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Kernel.Structures; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Guards checkFeatureSubobjectSpecialization: a composite Structure-typed Feature owned by a Structure specializes Occurrence::Occurrence::suboccurrences. + /// + /// + /// OCL: isComposite and ownedTyping.type->includes(oclIsKindOf(Structure)) and owningType <> null and (owningType.oclIsKindOf(Structure) or owningType.type->includes(oclIsKindOf(Structure))) + /// Hand written because the owner disjunct navigates oclAsType(Feature).type, which is + /// outside the generator's translatable shapes. + /// + public class FeatureSubobjectSpecializationGuard : IImpliedRuleGuard + { + /// + /// Gets the name of the semantic constraint this guard decides. + /// + public string ConstraintName => "checkFeatureSubobjectSpecialization"; + + /// + /// Asserts whether the constraint applies to the supplied Element. + /// + /// The Element under evaluation. + /// True when every conjunct of the constraint holds. + /// Thrown when is null. + public bool Applies(IElement element) + { + return element == null + ? throw new ArgumentNullException(nameof(element)) + : element is IFeature { IsComposite: true } feature + && feature.ownedTyping.Any(featureTyping => featureTyping.Type is IStructure) + && OwningTypePredicates.IsOrIsTypedBy(feature.owningType); + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Guards/FeatureSuboccurrenceSpecializationGuard.cs b/SysML2.NET.Semantics/Implied/Guards/FeatureSuboccurrenceSpecializationGuard.cs new file mode 100644 index 00000000..42c7243b --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Guards/FeatureSuboccurrenceSpecializationGuard.cs @@ -0,0 +1,60 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Guards +{ + using System; + using System.Linq; + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Kernel.Classes; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Guards checkFeatureSuboccurrenceSpecialization: a composite Class-typed Feature owned by a Class specializes Occurrence::Occurrence::suboccurrences. + /// + /// + /// OCL: isComposite and ownedTyping.type->includes(oclIsKindOf(Class)) and owningType <> null and (owningType.oclIsKindOf(Class) or owningType.oclIsKindOf(Feature) and owningType.oclAsType(Feature).type->exists(oclIsKindOf(Class))) + /// Hand written because the owner disjunct navigates oclAsType(Feature).type, which is + /// outside the generator's translatable shapes. + /// + public class FeatureSuboccurrenceSpecializationGuard : IImpliedRuleGuard + { + /// + /// Gets the name of the semantic constraint this guard decides. + /// + public string ConstraintName => "checkFeatureSuboccurrenceSpecialization"; + + /// + /// Asserts whether the constraint applies to the supplied Element. + /// + /// The Element under evaluation. + /// True when every conjunct of the constraint holds. + /// Thrown when is null. + public bool Applies(IElement element) + { + return element == null + ? throw new ArgumentNullException(nameof(element)) + : element is IFeature { IsComposite: true } feature + && feature.ownedTyping.Any(featureTyping => featureTyping.Type is IClass) + && OwningTypePredicates.IsOrIsTypedBy(feature.owningType); + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Guards/FlowDefinitionBinarySpecializationGuard.cs b/SysML2.NET.Semantics/Implied/Guards/FlowDefinitionBinarySpecializationGuard.cs new file mode 100644 index 00000000..8608e2fa --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Guards/FlowDefinitionBinarySpecializationGuard.cs @@ -0,0 +1,54 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Guards +{ + using System; + + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Systems.Flows; + + /// + /// Guards checkFlowDefinitionBinarySpecialization: a FlowDefinition with exactly two flow ends specializes Flows::Message. + /// + /// + /// OCL: flowEnd->size() = 2 implies specializesFromLibrary('Flows::Message') + /// + public class FlowDefinitionBinarySpecializationGuard : IImpliedRuleGuard + { + /// + /// Gets the name of the semantic constraint this guard decides. + /// + public string ConstraintName => "checkFlowDefinitionBinarySpecialization"; + + /// + /// Asserts whether the constraint applies to the supplied Element. + /// + /// The Element under evaluation. + /// True when the constraint applies. + /// Thrown when is null. + public bool Applies(IElement element) + { + return element == null + ? throw new ArgumentNullException(nameof(element)) + : element is IFlowDefinition { flowEnd.Count: 2 }; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Guards/IncludeUseCaseUsageSpecializationGuard.cs b/SysML2.NET.Semantics/Implied/Guards/IncludeUseCaseUsageSpecializationGuard.cs new file mode 100644 index 00000000..a9362d55 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Guards/IncludeUseCaseUsageSpecializationGuard.cs @@ -0,0 +1,54 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Guards +{ + using System; + + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Systems.UseCases; + + /// + /// Guards checkIncludeUseCaseUsageSpecialization: an IncludeUseCaseUsage owned by a use case specializes UseCases::UseCase::includedUseCases. + /// + /// + /// OCL: owningType <> null and (owningType.oclIsKindOf(UseCaseDefinition) or owningType.oclIsKindOf(UseCaseUsage) implies specializesFromLibrary('UseCases::UseCase::includedUseCases') + /// + public class IncludeUseCaseUsageSpecializationGuard : IImpliedRuleGuard + { + /// + /// Gets the name of the semantic constraint this guard decides. + /// + public string ConstraintName => "checkIncludeUseCaseUsageSpecialization"; + + /// + /// Asserts whether the constraint applies to the supplied Element. + /// + /// The Element under evaluation. + /// True when the constraint applies. + /// Thrown when is null. + public bool Applies(IElement element) + { + return element == null + ? throw new ArgumentNullException(nameof(element)) + : element is IIncludeUseCaseUsage { owningType: IUseCaseDefinition or IUseCaseUsage }; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Guards/OccurrenceUsageSuboccurrenceSpecializationGuard.cs b/SysML2.NET.Semantics/Implied/Guards/OccurrenceUsageSuboccurrenceSpecializationGuard.cs new file mode 100644 index 00000000..30490365 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Guards/OccurrenceUsageSuboccurrenceSpecializationGuard.cs @@ -0,0 +1,60 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Guards +{ + using System; + using System.Linq; + + using SysML2.NET.Core.POCO.Kernel.Classes; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Systems.Occurrences; + + /// + /// Guards checkOccurrenceUsageSuboccurrenceSpecialization: a composite OccurrenceUsage owned by a Class, OccurrenceUsage or Class-typed Feature specializes Occurrences::Occurrence::suboccurrences. + /// + /// + /// OCL: isComposite and owningType <> null and (owningType.oclIsKindOf(Class) or owningType.oclIsKindOf(OccurrenceUsage) or owningType.oclIsKindOf(Feature) and owningType.oclAsType(Feature).type->exists(oclIsKind(Class))) + /// Hand written because the owner disjunct navigates oclAsType(Feature).type, which is + /// outside the generator's translatable shapes. + /// + public class OccurrenceUsageSuboccurrenceSpecializationGuard : IImpliedRuleGuard + { + /// + /// Gets the name of the semantic constraint this guard decides. + /// + public string ConstraintName => "checkOccurrenceUsageSuboccurrenceSpecialization"; + + /// + /// Asserts whether the constraint applies to the supplied Element. + /// + /// The Element under evaluation. + /// True when every conjunct of the constraint holds. + /// Thrown when is null. + public bool Applies(IElement element) + { + return element == null + ? throw new ArgumentNullException(nameof(element)) + : element is IOccurrenceUsage { IsComposite: true } occurrenceUsage + && (occurrenceUsage.owningType is IOccurrenceUsage + || OwningTypePredicates.IsOrIsTypedBy(occurrenceUsage.owningType)); + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Guards/OwningTypePredicates.cs b/SysML2.NET.Semantics/Implied/Guards/OwningTypePredicates.cs new file mode 100644 index 00000000..c45cf741 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Guards/OwningTypePredicates.cs @@ -0,0 +1,55 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Guards +{ + using System.Linq; + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Core.Types; + + /// + /// The owning-Type navigations shared by several semantic-constraint guards. + /// + /// + /// Four constraints repeat the disjunct owningType.oclIsKindOf(T) or + /// owningType.oclAsType(Feature).type->exists(oclIsKindOf(T)) — "owned by a T, or by a Feature + /// typed by a T". It is factored out here so the navigation is written and reasoned about once. + /// + internal static class OwningTypePredicates + { + /// + /// Asserts whether an owning Type is a , or a Feature typed by one. + /// + /// The Type kind the owner must be, or be typed by. + /// The owning Type, which may be null. + /// True when the owning Type satisfies either disjunct. + internal static bool IsOrIsTypedBy(IType owningType) + where TType : class, IType + { + return owningType switch + { + TType => true, + IFeature owningFeature => owningFeature.type.Any(type => type is TType), + _ => false + }; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Guards/StepOwnedPerformanceSpecializationGuard.cs b/SysML2.NET.Semantics/Implied/Guards/StepOwnedPerformanceSpecializationGuard.cs new file mode 100644 index 00000000..05324916 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Guards/StepOwnedPerformanceSpecializationGuard.cs @@ -0,0 +1,58 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Guards +{ + using System; + using System.Linq; + + using SysML2.NET.Core.POCO.Kernel.Behaviors; + using SysML2.NET.Core.POCO.Kernel.Structures; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Guards checkStepOwnedPerformanceSpecialization: a composite Step owned by a Structure or Structure-typed Feature specializes Objects::Object::ownedPerformance. + /// + /// + /// OCL: isComposite and owningType <> null and (owningType.oclIsKindOf(Structure) or owningType.oclIsKindOf(Feature) and owningType.oclAsType(Feature).type->exists(oclIsKindOf(Structure)) + /// Hand written because the owner disjunct navigates oclAsType(Feature).type, which is + /// outside the generator's translatable shapes. + /// + public class StepOwnedPerformanceSpecializationGuard : IImpliedRuleGuard + { + /// + /// Gets the name of the semantic constraint this guard decides. + /// + public string ConstraintName => "checkStepOwnedPerformanceSpecialization"; + + /// + /// Asserts whether the constraint applies to the supplied Element. + /// + /// The Element under evaluation. + /// True when every conjunct of the constraint holds. + /// Thrown when is null. + public bool Applies(IElement element) + { + return element == null + ? throw new ArgumentNullException(nameof(element)) + : element is IStep { IsComposite: true } step && OwningTypePredicates.IsOrIsTypedBy(step.owningType); + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Guards/StepSubperformanceSpecializationGuard.cs b/SysML2.NET.Semantics/Implied/Guards/StepSubperformanceSpecializationGuard.cs new file mode 100644 index 00000000..372338e7 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Guards/StepSubperformanceSpecializationGuard.cs @@ -0,0 +1,54 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Guards +{ + using System; + + using SysML2.NET.Core.POCO.Kernel.Behaviors; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Guards checkStepSubperformanceSpecialization: a composite Step owned by a Behavior or Step specializes Performances::Performance::subperformance. + /// + /// + /// OCL: owningType <> null and (owningType.oclIsKindOf(Behavior) or owningType.oclIsKindOf(Step)) and self.isComposite implies specializesFromLibrary('Performances::Performance::subperformance') + /// + public class StepSubperformanceSpecializationGuard : IImpliedRuleGuard + { + /// + /// Gets the name of the semantic constraint this guard decides. + /// + public string ConstraintName => "checkStepSubperformanceSpecialization"; + + /// + /// Asserts whether the constraint applies to the supplied Element. + /// + /// The Element under evaluation. + /// True when the constraint applies. + /// Thrown when is null. + public bool Applies(IElement element) + { + return element == null + ? throw new ArgumentNullException(nameof(element)) + : element is IStep { IsComposite: true, owningType: IBehavior or IStep }; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Guards/TransitionUsageActionSpecializationGuard.cs b/SysML2.NET.Semantics/Implied/Guards/TransitionUsageActionSpecializationGuard.cs new file mode 100644 index 00000000..436dac41 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Guards/TransitionUsageActionSpecializationGuard.cs @@ -0,0 +1,62 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Guards +{ + using System; + + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Systems.Actions; + using SysML2.NET.Core.POCO.Systems.States; + + /// + /// Guards checkTransitionUsageActionSpecialization: a composite TransitionUsage owned by an action, whose source is not a StateUsage, specializes Actions::Action::decisionTransitions. + /// + /// + /// OCL: isComposite and owningType <> null and (owningType.oclIsKindOf(ActionDefinition) or owningType.oclIsKindOf(ActionUsage)) and source <> null and not source.oclIsKindOf(StateUsage) + /// Hand written because of the trailing source conjunct, which is outside the generator's + /// translatable shapes. It is what separates this constraint from its state counterpart. + /// + public class TransitionUsageActionSpecializationGuard : IImpliedRuleGuard + { + /// + /// Gets the name of the semantic constraint this guard decides. + /// + public string ConstraintName => "checkTransitionUsageActionSpecialization"; + + /// + /// Asserts whether the constraint applies to the supplied Element. + /// + /// The Element under evaluation. + /// True when every conjunct of the constraint holds. + /// Thrown when is null. + public bool Applies(IElement element) + { + return element == null + ? throw new ArgumentNullException(nameof(element)) + : element is ITransitionUsage + { + IsComposite: true, + owningType: IActionDefinition or IActionUsage, + source: not null and not IStateUsage + }; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Guards/TransitionUsageStateSpecializationGuard.cs b/SysML2.NET.Semantics/Implied/Guards/TransitionUsageStateSpecializationGuard.cs new file mode 100644 index 00000000..4b4f9599 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Guards/TransitionUsageStateSpecializationGuard.cs @@ -0,0 +1,57 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Guards +{ + using System; + + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Systems.Actions; + using SysML2.NET.Core.POCO.Systems.States; + + /// + /// Guards checkTransitionUsageStateSpecialization: a composite TransitionUsage owned by a state, whose source is a StateUsage, specializes States::StateAction::stateTransitions. + /// + /// + /// OCL: isComposite and owningType <> null and (owningType.oclIsKindOf(StateDefinition) or owningType.oclIsKindOf(StateUsage)) and source <> null and source.oclIsKindOf(StateUsage) + /// Hand written because of the trailing source conjunct, which is outside the generator's + /// translatable shapes. It is what separates this constraint from its action counterpart. + /// + public class TransitionUsageStateSpecializationGuard : IImpliedRuleGuard + { + /// + /// Gets the name of the semantic constraint this guard decides. + /// + public string ConstraintName => "checkTransitionUsageStateSpecialization"; + + /// + /// Asserts whether the constraint applies to the supplied Element. + /// + /// The Element under evaluation. + /// True when every conjunct of the constraint holds. + /// Thrown when is null. + public bool Applies(IElement element) + { + return element == null + ? throw new ArgumentNullException(nameof(element)) + : element is ITransitionUsage { IsComposite: true, owningType: IStateDefinition or IStateUsage, source: IStateUsage }; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/IImpliedRelationshipFactory.cs b/SysML2.NET.Semantics/Implied/IImpliedRelationshipFactory.cs new file mode 100644 index 00000000..8130e806 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/IImpliedRelationshipFactory.cs @@ -0,0 +1,89 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied +{ + using SysML2.NET.Core.POCO.Core.Classifiers; + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Core.Types; + + /// + /// Creates the Relationship instances that satisfy a semantic constraint. + /// + /// + /// Every product carries isImplied and is DETACHED: it is not added to any ownedRelationship, so the + /// model stays a faithful match to what was read and isImpliedIncluded stays false. + /// + public interface IImpliedRelationshipFactory + { + /// + /// Creates an implied Subclassification between two Classifiers. + /// + /// The specializing Classifier. + /// The Classifier being specialized. + /// A detached Subclassification with isImplied set. + /// Thrown when either argument is null. + ISubclassification CreateImpliedSubclassification(IClassifier specific, IClassifier general); + + /// + /// Creates an implied Subsetting between two Features. + /// + /// The subsetting Feature. + /// The Feature being subsetted. + /// A detached Subsetting with isImplied set. + /// Thrown when either argument is null. + ISubsetting CreateImpliedSubsetting(IFeature specific, IFeature general); + + /// + /// Creates a detached Feature whose chainingFeatures are the two supplied Features, in order. + /// + /// The first Feature of the chain. + /// The second Feature of the chain. + /// A detached Feature standing for the chain first.second. + /// + /// The subsetsChain(first, second) constraints are satisfied by specializing a Feature whose + /// last two chainingFeatures are the given pair. No such Feature need exist in the model, so one is + /// synthesized here to be the general of the implied Subsetting. + /// Unlike every other product of this factory, this is an ELEMENT rather than a Relationship, + /// and it is a NEW element rather than one the caller already holds. A consumer that walks + /// SubsettedFeature must therefore be prepared for a Feature that is absent from the model and + /// carries no name — its meaning is entirely in its chainingFeature list. + /// + IFeature CreateImpliedFeatureChain(IFeature first, IFeature second); + + /// + /// Creates an implied Redefinition between two Features. + /// + /// The redefining Feature. + /// The Feature being redefined. + /// A detached Redefinition with isImplied set. + /// Thrown when either argument is null. + IRedefinition CreateImpliedRedefinition(IFeature specific, IFeature general); + + /// + /// Creates an implied FeatureTyping between a Feature and the Type that types it. + /// + /// The Feature being typed. + /// The Type typing the Feature. + /// A detached FeatureTyping with isImplied set. + /// Thrown when either argument is null. + IFeatureTyping CreateImpliedFeatureTyping(IFeature typedFeature, IType type); + } +} diff --git a/SysML2.NET.Semantics/Implied/IImpliedRelationshipProvider.cs b/SysML2.NET.Semantics/Implied/IImpliedRelationshipProvider.cs new file mode 100644 index 00000000..01c0604c --- /dev/null +++ b/SysML2.NET.Semantics/Implied/IImpliedRelationshipProvider.cs @@ -0,0 +1,77 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied +{ + using System.Collections.Generic; + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Core.Types; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Computes the implied Relationships that KerML 8.4.2 semantic constraints require of a model, + /// without adding them to the model. + /// + /// + /// Element.isImpliedIncluded is all-or-nothing: an Element whose ownedRelationship contains an implied + /// Relationship must declare isImpliedIncluded, and while is + /// non-empty no complete closure can be produced. Implementations therefore never mutate the model and + /// never set that flag. + /// + public interface IImpliedRelationshipProvider + { + /// + /// Gets the names of the semantic constraints this provider cannot yet compute. + /// + IReadOnlyList NotCoveredConstraints { get; } + + /// + /// Returns the implied Relationships required of the supplied Element. + /// + /// The Element to compute implied Relationships for. + /// The detached implied Relationships; empty when none are required. + /// Thrown when is null. + IReadOnlyList GetImpliedRelationships(IElement element); + + /// + /// Returns the implied Specializations required of the supplied Type, after 8.4.2 redundancy reduction. + /// + /// The Type to compute implied Specializations for. + /// The detached implied Specializations; empty when none are required. + /// Thrown when is null. + IReadOnlyList GetImpliedSpecializations(IType type); + + /// + /// Returns the implied Redefinitions required of the supplied Feature. + /// + /// The Feature to compute implied Redefinitions for. + /// The detached implied Redefinitions; empty when none are required. + /// Thrown when is null. + IReadOnlyList GetImpliedRedefinitions(IFeature feature); + + /// + /// Asserts whether the named semantic constraint is computed by this provider. + /// + /// The constraint name, for example checkPortUsageSpecialization. + /// True when the constraint is computed, false when it is listed as not covered. + bool IsConstraintCovered(string constraintName); + } +} diff --git a/SysML2.NET.Semantics/Implied/IImpliedRelationshipRule.cs b/SysML2.NET.Semantics/Implied/IImpliedRelationshipRule.cs new file mode 100644 index 00000000..b44d4cd0 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/IImpliedRelationshipRule.cs @@ -0,0 +1,53 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied +{ + using System.Collections.Generic; + + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Computes the implied Relationships of a single semantic constraint that the generated table cannot + /// express. + /// + /// + /// The generated table only covers constraints whose OCL is a specializesFromLibrary call. Constraints + /// that relate two elements of the USER model — every Redefinition constraint, and the variation + /// Specialization constraints among others — are hand-coded as rules and registered explicitly. A + /// registered rule removes its constraint from the provider's not-covered manifest. + /// + public interface IImpliedRelationshipRule + { + /// + /// Gets the name of the semantic constraint this rule implements, for example + /// checkUsageVariationUsageSpecialization. + /// + string ConstraintName { get; } + + /// + /// Computes the implied Relationships the constraint requires of the supplied Element. + /// + /// The Element under evaluation. + /// The detached implied Relationships; empty when the constraint does not apply. + /// Thrown when is null. + IReadOnlyList Apply(IElement element); + } +} diff --git a/SysML2.NET.Semantics/Implied/IImpliedRuleGuard.cs b/SysML2.NET.Semantics/Implied/IImpliedRuleGuard.cs new file mode 100644 index 00000000..d8835a71 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/IImpliedRuleGuard.cs @@ -0,0 +1,49 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied +{ + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Decides whether a conditional semantic constraint applies to a given Element. + /// + /// + /// The generated rule table flags a row as requiring a guard when its OCL is a conditional + /// specializesFromLibrary call. Such a row must never be applied unconditionally, so a missing guard is + /// an error rather than an implicit yes. + /// + public interface IImpliedRuleGuard + { + /// + /// Gets the name of the semantic constraint this guard decides, for example + /// checkPortUsageSubportSpecialization. + /// + string ConstraintName { get; } + + /// + /// Asserts whether the constraint applies to the supplied Element. + /// + /// The Element under evaluation. + /// True when the constraint applies and its implied Relationship is required. + /// Thrown when is null. + bool Applies(IElement element); + } +} diff --git a/SysML2.NET.Semantics/Implied/IImpliedRuleGuardRegistry.cs b/SysML2.NET.Semantics/Implied/IImpliedRuleGuardRegistry.cs new file mode 100644 index 00000000..55c3d2ec --- /dev/null +++ b/SysML2.NET.Semantics/Implied/IImpliedRuleGuardRegistry.cs @@ -0,0 +1,44 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied +{ + /// + /// Resolves the registered for a conditional semantic constraint. + /// + public interface IImpliedRuleGuardRegistry + { + /// + /// Returns the guard registered for the named constraint. + /// + /// The constraint name to resolve a guard for. + /// The registered guard. + /// Thrown when is null. + /// Thrown when no guard is registered for the constraint. + IImpliedRuleGuard GetGuard(string constraintName); + + /// + /// Asserts whether a guard is registered for the named constraint. + /// + /// The constraint name to test. + /// True when a guard is registered. + bool HasGuard(string constraintName); + } +} diff --git a/SysML2.NET.Semantics/Implied/IImpliedSpecializationReducer.cs b/SysML2.NET.Semantics/Implied/IImpliedSpecializationReducer.cs new file mode 100644 index 00000000..ec94b15a --- /dev/null +++ b/SysML2.NET.Semantics/Implied/IImpliedSpecializationReducer.cs @@ -0,0 +1,47 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied +{ + using System.Collections.Generic; + + using SysML2.NET.Core.POCO.Core.Types; + + /// + /// Drops the implied Specializations that KerML 8.4.2 considers redundant for a Type. + /// + /// + /// Two rules apply: an implied Specialization is dropped when the Type already has an ownedSpecialization + /// with the same general Type, or when any owned or implied Specialization has a general Type that is a + /// strict subtype of it; and only one of several implied Specializations sharing a general Type is kept. + /// Neither rule applies to Redefinitions, whose semantics go beyond basic Specialization. + /// + public interface IImpliedSpecializationReducer + { + /// + /// Reduces the candidate implied Specializations of a Type to the non-redundant set. + /// + /// The Type the candidates were computed for. + /// The implied Specializations to reduce. + /// The retained Specializations, in the order the candidates were supplied. + /// Thrown when either argument is null. + IReadOnlyList Reduce(IType type, IReadOnlyList candidates); + } +} diff --git a/SysML2.NET.Semantics/Implied/ILibraryTypeIndex.cs b/SysML2.NET.Semantics/Implied/ILibraryTypeIndex.cs new file mode 100644 index 00000000..b4b443ee --- /dev/null +++ b/SysML2.NET.Semantics/Implied/ILibraryTypeIndex.cs @@ -0,0 +1,45 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied +{ + using SysML2.NET.Core.POCO.Core.Types; + + /// + /// Resolves a model-library Type by its qualified name, for the semantic constraints that require a + /// user Type to specialize a specific library Type. + /// + /// + /// Implementations must index the library ownership tree directly and must NOT resolve through + /// Namespace.resolve: resolution consults inheritedMembership, which is what the implied layer exists to + /// supply, so routing through it re-enters that bootstrap cycle. For the same reason the index is + /// populated eagerly, before any resolution runs, rather than faulting libraries in on first miss. + /// + public interface ILibraryTypeIndex + { + /// + /// Attempts to resolve the library Type carrying the supplied qualified name. + /// + /// The qualified name, for example Occurrences::Occurrence::suboccurrences. + /// When this method returns true, the resolved Type; otherwise null. + /// True when the qualified name resolves to an indexed Type. + bool TryGetType(string qualifiedName, out IType type); + } +} diff --git a/SysML2.NET.Semantics/Implied/ImpliedRelationshipFactory.cs b/SysML2.NET.Semantics/Implied/ImpliedRelationshipFactory.cs new file mode 100644 index 00000000..bcba158f --- /dev/null +++ b/SysML2.NET.Semantics/Implied/ImpliedRelationshipFactory.cs @@ -0,0 +1,181 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied +{ + using System; + + using SysML2.NET.Core.POCO.Core.Classifiers; + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Core.Types; + using SysML2.NET.Extensions; + + /// + /// Creates detached Relationship instances that carry isImplied. + /// + /// + /// Each product is given a fresh Id so that instances remain distinguishable in the dictionaries and sets + /// the computation uses. Nothing created here is attached to an ownedRelationship, so the model read from + /// disk is left untouched and isImpliedIncluded stays false. + /// + public class ImpliedRelationshipFactory : IImpliedRelationshipFactory + { + /// + /// Creates an implied Subclassification between two Classifiers. + /// + /// The specializing Classifier. + /// The Classifier being specialized. + /// A detached Subclassification with isImplied set. + /// Thrown when either argument is null. + public ISubclassification CreateImpliedSubclassification(IClassifier specific, IClassifier general) + { + if (specific == null) + { + throw new ArgumentNullException(nameof(specific)); + } + + if (general == null) + { + throw new ArgumentNullException(nameof(general)); + } + + return new Subclassification + { + Id = Guid.NewGuid(), + IsImplied = true, + Subclassifier = specific, + Superclassifier = general + }; + } + + /// + /// Creates an implied Subsetting between two Features. + /// + /// The subsetting Feature. + /// The Feature being subsetted. + /// A detached Subsetting with isImplied set. + /// Thrown when either argument is null. + public ISubsetting CreateImpliedSubsetting(IFeature specific, IFeature general) + { + if (specific == null) + { + throw new ArgumentNullException(nameof(specific)); + } + + if (general == null) + { + throw new ArgumentNullException(nameof(general)); + } + + return new Subsetting + { + Id = Guid.NewGuid(), + IsImplied = true, + SubsettingFeature = specific, + SubsettedFeature = general + }; + } + + /// + /// Creates a detached Feature whose chainingFeatures are the two supplied Features, in order. + /// + /// The first Feature of the chain. + /// The second Feature of the chain. + /// A detached Feature standing for the chain first.second. + /// Thrown when either Feature is null. + public IFeature CreateImpliedFeatureChain(IFeature first, IFeature second) + { + if (first == null) + { + throw new ArgumentNullException(nameof(first)); + } + + if (second == null) + { + throw new ArgumentNullException(nameof(second)); + } + + var chain = new Feature { Id = Guid.NewGuid() }; + + // The chain is expressed through owned FeatureChainings, which is what chainingFeature derives + // from — setting the derived list directly would not survive a re-read of the property. + chain.AssignOwnership(new FeatureChaining { Id = Guid.NewGuid(), IsImplied = true, ChainingFeature = first }); + chain.AssignOwnership(new FeatureChaining { Id = Guid.NewGuid(), IsImplied = true, ChainingFeature = second }); + + return chain; + } + + /// + /// Creates an implied Redefinition between two Features. + /// + /// The redefining Feature. + /// The Feature being redefined. + /// A detached Redefinition with isImplied set. + /// Thrown when either argument is null. + public IRedefinition CreateImpliedRedefinition(IFeature specific, IFeature general) + { + if (specific == null) + { + throw new ArgumentNullException(nameof(specific)); + } + + if (general == null) + { + throw new ArgumentNullException(nameof(general)); + } + + return new Redefinition + { + Id = Guid.NewGuid(), + IsImplied = true, + RedefiningFeature = specific, + RedefinedFeature = general + }; + } + + /// + /// Creates an implied FeatureTyping between a Feature and the Type that types it. + /// + /// The Feature being typed. + /// The Type typing the Feature. + /// A detached FeatureTyping with isImplied set. + /// Thrown when either argument is null. + public IFeatureTyping CreateImpliedFeatureTyping(IFeature typedFeature, IType type) + { + if (typedFeature == null) + { + throw new ArgumentNullException(nameof(typedFeature)); + } + + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + return new FeatureTyping + { + Id = Guid.NewGuid(), + IsImplied = true, + TypedFeature = typedFeature, + Type = type + }; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/ImpliedRelationshipOptions.cs b/SysML2.NET.Semantics/Implied/ImpliedRelationshipOptions.cs new file mode 100644 index 00000000..8737f83b --- /dev/null +++ b/SysML2.NET.Semantics/Implied/ImpliedRelationshipOptions.cs @@ -0,0 +1,48 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied +{ + /// + /// Configures which families of semantic constraint the provider computes. + /// + public class ImpliedRelationshipOptions + { + /// + /// Gets or sets a value indicating whether the library-specialization constraints are computed. + /// + /// + /// Off by default. These constraints attach the bulk of the Kernel Semantic Library to a model and + /// change what inheritance yields for almost every Type, so enabling them re-baselines any output + /// derived from inheritance. Turn on deliberately, in a change of its own. + /// + public bool EnableLibrarySpecializations { get; set; } + + /// + /// Gets or sets a value indicating whether redundant implied Specializations are dropped per + /// KerML 8.4.2. + /// + /// + /// On by default. Switching it off is a diagnostic aid for seeing every constraint a Type triggers, + /// not a supported production configuration. + /// + public bool ReduceRedundantSpecializations { get; set; } = true; + } +} diff --git a/SysML2.NET.Semantics/Implied/ImpliedRelationshipProvider.cs b/SysML2.NET.Semantics/Implied/ImpliedRelationshipProvider.cs new file mode 100644 index 00000000..198f0f94 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/ImpliedRelationshipProvider.cs @@ -0,0 +1,372 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied +{ + using System; + using System.Collections.Generic; + using System.Linq; + + 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.Root.Elements; + + /// + /// Computes implied Relationships from the generated constraint table, the registered guards and the + /// model-library index. + /// + /// + /// Nothing computed here is attached to the model: every product is a detached Relationship carrying + /// isImplied, so isImpliedIncluded stays false and the model remains a faithful match to what was read. + /// Results are memoised for the lifetime of this instance, which is therefore scoped to one model — the + /// object graph is mutable and the SDK offers no invalidation hook. + /// + public class ImpliedRelationshipProvider : IImpliedRelationshipProvider + { + /// + /// The memoised implied Specializations, keyed by the Type they were computed for. + /// + private readonly Dictionary> specializationsByType = []; + + /// + /// The index used to resolve the library Types the constraints target. + /// + private readonly ILibraryTypeIndex libraryTypeIndex; + + /// + /// The registry consulted for conditional constraints. + /// + private readonly IImpliedRuleGuardRegistry guardRegistry; + + /// + /// The factory creating the detached Relationships. + /// + private readonly IImpliedRelationshipFactory factory; + + /// + /// The reducer applying the KerML 8.4.2 redundancy rules. + /// + private readonly IImpliedSpecializationReducer reducer; + + /// + /// The configured behaviour. + /// + private readonly ImpliedRelationshipOptions options; + + /// + /// The hand-coded rules for constraints the generated table cannot express. + /// + private readonly IReadOnlyList rules; + + /// + /// The constraint names the hand-coded rules cover. + /// + private readonly HashSet ruleConstraintNames; + + /// + /// The constraints this provider cannot compute, settled once at construction. + /// + /// + /// Every input is fixed for the lifetime of the instance, so the answer is too. Computing it per + /// access allocated a fresh list on a property that reads as a field — and + /// consults it per constraint, so the copy was on a hot path + /// rather than an occasional one. + /// + private readonly IReadOnlyList notCoveredConstraints; + + /// + /// Initializes a new instance of the class. + /// + /// The index resolving the library Types the constraints target. + /// The registry of guards for conditional constraints. + /// The factory creating the detached Relationships. + /// The reducer applying the redundancy rules. + /// The configured behaviour. + /// The hand-coded rules for constraints the generated table cannot express. + /// Thrown when any argument is null. + public ImpliedRelationshipProvider(ILibraryTypeIndex libraryTypeIndex, IImpliedRuleGuardRegistry guardRegistry, IImpliedRelationshipFactory factory, IImpliedSpecializationReducer reducer, ImpliedRelationshipOptions options, IEnumerable rules) + { + if (rules == null) + { + throw new ArgumentNullException(nameof(rules)); + } + + this.libraryTypeIndex = libraryTypeIndex ?? throw new ArgumentNullException(nameof(libraryTypeIndex)); + this.guardRegistry = guardRegistry ?? throw new ArgumentNullException(nameof(guardRegistry)); + this.factory = factory ?? throw new ArgumentNullException(nameof(factory)); + this.reducer = reducer ?? throw new ArgumentNullException(nameof(reducer)); + this.options = options ?? throw new ArgumentNullException(nameof(options)); + this.rules = rules.ToList(); + this.ruleConstraintNames = [..this.rules.Select(rule => rule.ConstraintName)]; + + var uncovered = this.options.EnableLibrarySpecializations + ? ImpliedRelationshipTable.NotCovered + : ImpliedRelationshipTable.AllConstraintNames; + + this.notCoveredConstraints = [..uncovered + .Where(constraint => !this.ruleConstraintNames.Any(ruleConstraintName => constraint.Contains(ruleConstraintName, StringComparison.Ordinal)))]; + } + + /// + /// Gets the names of the semantic constraints this provider cannot yet compute. + /// + /// + /// The manifest is the table's own not-covered list, minus the constraints a registered hand-coded + /// rule supplies. When library specializations are disabled the answer widens to every constraint, + /// since nothing table-driven is computed at all. + /// + public IReadOnlyList NotCoveredConstraints => this.notCoveredConstraints; + + /// + /// Returns the implied Relationships required of the supplied Element. + /// + /// The Element to compute implied Relationships for. + /// The detached implied Relationships; empty when none are required. + /// Thrown when is null. + /// Thrown when a conditional constraint has no registered guard. + /// Thrown when a targeted library Type is not indexed. + public IReadOnlyList GetImpliedRelationships(IElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + var relationships = new List(); + + if (element is IType type) + { + relationships.AddRange(this.GetImpliedSpecializations(type)); + } + + // Specializations are already accounted for above — a Redefinition, Subsetting and FeatureTyping + // are all Specializations, so re-adding them here would double-count. + relationships.AddRange(this.ApplyRules(element).Where(relationship => relationship is not ISpecialization)); + + return relationships; + } + + /// + /// Returns the implied Specializations required of the supplied Type, after 8.4.2 redundancy reduction. + /// + /// The Type to compute implied Specializations for. + /// The detached implied Specializations; empty when none are required. + /// Thrown when is null. + /// Thrown when a conditional constraint has no registered guard. + /// Thrown when a targeted library Type is not indexed. + public IReadOnlyList GetImpliedSpecializations(IType type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (this.specializationsByType.TryGetValue(type, out var memoised)) + { + return memoised; + } + + var candidates = new List(); + + if (this.options.EnableLibrarySpecializations) + { + candidates.AddRange(ImpliedRelationshipTable.QueryImpliedLibrarySpecializations(type) + .Where(rule => this.Applies(rule, type)) + .Select(rule => this.CreateSpecializationOrNull(rule, type)) + .Where(specialization => specialization != null)); + } + + var ruleSpecializations = this.ApplyRules(type).OfType().ToList(); + + // A rule may return a Specialization whose SPECIFIC is a nested Element rather than the Type + // under evaluation — the result parameter of an Expression, the multiplicity of a Definition, + // the trigger of a TransitionUsage. Reduction compares candidates against THIS Type's declared + // generals and against each other, so admitting those would let a coincidental match on an + // unrelated Element's general discard a valid Specialization. They bypass reduction entirely. + var nonRedefinitions = ruleSpecializations.Where(specialization => specialization is not IRedefinition).ToList(); + var notReducible = nonRedefinitions.Where(specialization => !ReferenceEquals(specialization.Specific, type)).ToList(); + + candidates.AddRange(nonRedefinitions.Where(specialization => ReferenceEquals(specialization.Specific, type))); + + // Redundancy reduction is deliberately NOT applied to Redefinitions: KerML 8.4.2 exempts them + // because a Redefinition carries semantics beyond basic Specialization. + IReadOnlyList reduced = this.options.ReduceRedundantSpecializations + ? this.reducer.Reduce(type, candidates) + : candidates; + + IReadOnlyList result = + [ + ..reduced, + ..notReducible, + ..ruleSpecializations.OfType() + ]; + + this.specializationsByType[type] = result; + + return result; + } + + /// + /// Returns the implied Redefinitions required of the supplied Feature. + /// + /// The Feature to compute implied Redefinitions for. + /// The detached implied Redefinitions; empty when none are required. + /// Thrown when is null. + /// + /// Redefinition constraints relate two Features of the USER model rather than a user Type and a + /// library Type, so none is expressible in the generated table; each is supplied by a hand-coded + /// rule. Constraints with no registered rule are reported by . + /// + public IReadOnlyList GetImpliedRedefinitions(IFeature feature) + { + return feature == null + ? throw new ArgumentNullException(nameof(feature)) + : this.ApplyRules(feature).OfType().ToList(); + } + + /// + /// Asserts whether the named semantic constraint is computed by this provider. + /// + /// The constraint name, for example checkPortUsageSpecialization. + /// True when the constraint is computed, false when it is listed as not covered. + public bool IsConstraintCovered(string constraintName) + { + return !string.IsNullOrWhiteSpace(constraintName) + && !this.NotCoveredConstraints.Any(notCovered => notCovered.Contains(constraintName, StringComparison.Ordinal)); + } + + /// + /// Runs every registered hand-coded rule against an Element. + /// + /// The Element under evaluation. + /// The implied Relationships the rules produced, in registration order. + private IReadOnlyList ApplyRules(IElement element) => [..this.rules.SelectMany(rule => ApplyRule(rule, element))]; + + /// + /// Applies one rule, degrading to no contribution when the library Type it targets cannot be resolved. + /// + /// The rule to apply. + /// The Element under evaluation. + /// The rule's implied Relationships, or empty when its library target is unresolvable. + /// + /// An unresolvable target has two causes and only one of them is the caller's to fix. Libraries not + /// loaded is a misconfiguration; a constraint naming a Feature that no library declares — a defect in + /// the specification OCL, or a path the index cannot express — is not. Throwing treats both alike and + /// costs the ENTIRE document: the exception escapes the writer mid-write and the output is truncated. + /// Degrading costs one Relationship instead. A name that would have been shortened through it + /// falls back to a longer — never an invalid — form, which is the same failure mode the writer + /// already accepts elsewhere. + /// + private static IReadOnlyList ApplyRule(IImpliedRelationshipRule rule, IElement element) + { + try + { + return rule.Apply(element); + } + catch (UnresolvedLibraryTypeException) + { + return []; + } + } + + /// + /// Asserts whether a table row applies to an Element, consulting the registered guard when the row + /// is conditional. + /// + /// The table row under evaluation. + /// The Element the row was matched against. + /// True when the constraint applies. + /// Thrown when the row is conditional and no guard is registered. + private bool Applies(ImpliedLibrarySpecialization rule, IElement element) + { + if (!rule.RequiresGuard) + { + return true; + } + + if (!this.guardRegistry.HasGuard(rule.ConstraintName)) + { + throw new MissingImpliedRuleGuardException(rule.ConstraintName, rule.DeclaringMetaclassName); + } + + return this.guardRegistry.GetGuard(rule.ConstraintName).Applies(element); + } + + /// + /// Creates the implied Specialization for a table row, degrading to null when its library Type is + /// unresolvable — see for why this does not throw. + /// + /// The table row. + /// The Type under evaluation. + /// The Specialization, or null. + private ISpecialization CreateSpecializationOrNull(ImpliedLibrarySpecialization rule, IType type) + { + try + { + return this.CreateSpecialization(rule, type); + } + catch (UnresolvedLibraryTypeException) + { + return null; + } + } + + /// + /// Creates the Specialization a table row implies for a Type. + /// + /// The table row to realise. + /// The Type the Specialization specializes from. + /// The detached Specialization, or null when the row's kind does not match the Type. + /// Thrown when the targeted library Type is not indexed. + private ISpecialization CreateSpecialization(ImpliedLibrarySpecialization rule, IType type) + { + if (!this.libraryTypeIndex.TryGetType(rule.TargetLibraryName, out var libraryType)) + { + throw new UnresolvedLibraryTypeException(rule.TargetLibraryName, rule.ConstraintName); + } + + return QueryKind(rule) switch + { + ImpliedRelationshipKind.Subclassification when type is IClassifier specificClassifier && libraryType is IClassifier generalClassifier => + this.factory.CreateImpliedSubclassification(specificClassifier, generalClassifier), + ImpliedRelationshipKind.Subsetting when type is IFeature specificFeature && libraryType is IFeature generalFeature => + this.factory.CreateImpliedSubsetting(specificFeature, generalFeature), + _ => null + }; + } + + /// + /// Determines whether a table row implies a Subclassification or a Subsetting. + /// + /// The table row to classify. + /// The kind of Relationship the row implies. + /// + /// The OCL does not carry the distinction, so the generator emits the set of metaclasses whose + /// constraints imply Subclassification; everything else implies Subsetting. + /// + private static ImpliedRelationshipKind QueryKind(ImpliedLibrarySpecialization rule) + { + return ImpliedRelationshipTable.SubclassificationMetaclasses.Contains(rule.DeclaringMetaclassName) + ? ImpliedRelationshipKind.Subclassification + : ImpliedRelationshipKind.Subsetting; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/ImpliedRuleGuardRegistry.cs b/SysML2.NET.Semantics/Implied/ImpliedRuleGuardRegistry.cs new file mode 100644 index 00000000..dc8f3718 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/ImpliedRuleGuardRegistry.cs @@ -0,0 +1,94 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied +{ + using System; + using System.Collections.Generic; + using System.Linq; + + /// + /// Resolves guards by constraint name from an explicitly supplied set. + /// + /// + /// Guards are registered explicitly rather than discovered by assembly scanning, so the assembly stays + /// trimmable and the registered set is visible in source. + /// + public class ImpliedRuleGuardRegistry : IImpliedRuleGuardRegistry + { + /// + /// The registered guards, keyed by the constraint each decides. + /// + private readonly Dictionary guards; + + /// + /// Initializes a new instance of the class. + /// + /// The guards to register. + /// Thrown when is null. + /// Thrown when two guards declare the same constraint name. + public ImpliedRuleGuardRegistry(IEnumerable guards) + { + if (guards == null) + { + throw new ArgumentNullException(nameof(guards)); + } + + var materialised = guards.ToList(); + + var duplicate = materialised + .GroupBy(guard => guard.ConstraintName, StringComparer.Ordinal) + .FirstOrDefault(group => group.Count() > 1); + + if (duplicate != null) + { + throw new ArgumentException($"More than one IImpliedRuleGuard is registered for the constraint '{duplicate.Key}'.", nameof(guards)); + } + + this.guards = materialised.ToDictionary(guard => guard.ConstraintName, StringComparer.Ordinal); + } + + /// + /// Returns the guard registered for the named constraint. + /// + /// The constraint name to resolve a guard for. + /// The registered guard. + /// Thrown when is null. + /// Thrown when no guard is registered for the constraint. + public IImpliedRuleGuard GetGuard(string constraintName) + { + if (constraintName == null) + { + throw new ArgumentNullException(nameof(constraintName)); + } + + return this.guards.TryGetValue(constraintName, out var guard) + ? guard + : throw new MissingImpliedRuleGuardException(constraintName, "unknown"); + } + + /// + /// Asserts whether a guard is registered for the named constraint. + /// + /// The constraint name to test. + /// True when a guard is registered. + public bool HasGuard(string constraintName) => constraintName != null && this.guards.ContainsKey(constraintName); + } +} diff --git a/SysML2.NET.Semantics/Implied/ImpliedSpecializationReducer.cs b/SysML2.NET.Semantics/Implied/ImpliedSpecializationReducer.cs new file mode 100644 index 00000000..65ad3487 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/ImpliedSpecializationReducer.cs @@ -0,0 +1,122 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using SysML2.NET.Core.POCO.Core.Types; + + /// + /// Drops the implied Specializations that KerML 8.4.2 considers redundant for a Type. + /// + /// + /// Rule 1 drops a candidate when the Type already owns a Specialization with the same general Type, or + /// when any owned or surviving implied Specialization has a general Type that is a STRICT subtype of the + /// candidate's; the more specific Specialization already satisfies the looser constraint. Rule 2 keeps + /// only the first of several candidates sharing a general Type. Neither rule is applied to + /// Redefinitions, whose semantics go beyond basic Specialization. + /// + public class ImpliedSpecializationReducer : IImpliedSpecializationReducer + { + /// + /// Reduces the candidate implied Specializations of a Type to the non-redundant set. + /// + /// The Type the candidates were computed for. + /// The implied Specializations to reduce. + /// The retained Specializations, in the order the candidates were supplied. + /// Thrown when either argument is null. + public IReadOnlyList Reduce(IType type, IReadOnlyList candidates) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (candidates == null) + { + throw new ArgumentNullException(nameof(candidates)); + } + + var declaredGenerals = type.ownedSpecialization + .Select(specialization => specialization.General) + .Where(general => general != null) + .ToList(); + + var retained = new List(); + var retainedGenerals = new List(); + + foreach (var candidate in candidates.Where(candidate => candidate?.General != null)) + { + // Rule 2: a general Type already retained makes this candidate a duplicate. + if (retainedGenerals.Contains(candidate.General)) + { + continue; + } + + if (IsSupersededBy(candidate.General, declaredGenerals)) + { + continue; + } + + retained.Add(candidate); + retainedGenerals.Add(candidate.General); + } + + // Rule 1 across the implied set itself: a retained candidate whose general Type is a strict + // SUPERtype of another retained candidate's is redundant. Applied after the pass above because + // it needs the full surviving set, not a prefix of it. The candidate's own general Type is + // excluded from the comparison, since AllSupertypes includes the Type itself and would + // otherwise make every candidate supersede itself. + return retained + .Where(candidate => !IsStrictlySupersededBy(candidate.General, retainedGenerals)) + .ToList(); + } + + /// + /// Asserts whether a candidate general Type is already covered by one of the supplied general Types, + /// treating an identical general Type as covering it. + /// + /// The general Type of the candidate Specialization. + /// The general Types to test against. + /// True when one of is the same Type or a subtype of it. + private static bool IsSupersededBy(IType candidateGeneral, IReadOnlyList generals) + { + return generals.Any(general => general == candidateGeneral + || (general != null && general.AllSupertypes().Contains(candidateGeneral))); + } + + /// + /// Asserts whether a candidate general Type is covered by a DIFFERENT general Type in the supplied + /// set, i.e. one that is a strict subtype of it. + /// + /// The general Type of the candidate Specialization. + /// The general Types to test against, which may include the candidate's own. + /// True when a different Type in is a strict subtype of it. + private static bool IsStrictlySupersededBy(IType candidateGeneral, IReadOnlyList generals) + { + return generals + .Where(general => general != null && general != candidateGeneral) + .Any(general => general.AllSupertypes().Contains(candidateGeneral)); + } + } +} diff --git a/SysML2.NET.Semantics/Implied/MissingImpliedRuleGuardException.cs b/SysML2.NET.Semantics/Implied/MissingImpliedRuleGuardException.cs new file mode 100644 index 00000000..ceebb60a --- /dev/null +++ b/SysML2.NET.Semantics/Implied/MissingImpliedRuleGuardException.cs @@ -0,0 +1,85 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied +{ + using System; + + /// + /// Thrown when a semantic constraint is flagged as conditional but no + /// is registered to decide it. + /// + /// + /// This is deliberately fatal rather than a silent yes: applying a conditional rule unconditionally + /// injects Specializations the model does not require, which corrupts every inheritance result computed + /// from it. + /// + public class MissingImpliedRuleGuardException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public MissingImpliedRuleGuardException() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public MissingImpliedRuleGuardException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + /// The exception that caused this exception. + public MissingImpliedRuleGuardException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class for a + /// named constraint. + /// + /// The constraint whose guard is missing. + /// The metaclass declaring the constraint. + public MissingImpliedRuleGuardException(string constraintName, string declaringMetaclassName) + : base($"The semantic constraint '{constraintName}' declared by '{declaringMetaclassName}' is conditional, but no IImpliedRuleGuard is registered for it.") + { + this.ConstraintName = constraintName; + this.DeclaringMetaclassName = declaringMetaclassName; + } + + /// + /// Gets the name of the constraint whose guard is missing. + /// + public string ConstraintName { get; } + + /// + /// Gets the name of the metaclass declaring the constraint. + /// + public string DeclaringMetaclassName { get; } + } +} diff --git a/SysML2.NET.Semantics/Implied/NullImpliedRelationshipProvider.cs b/SysML2.NET.Semantics/Implied/NullImpliedRelationshipProvider.cs new file mode 100644 index 00000000..a33b11ae --- /dev/null +++ b/SysML2.NET.Semantics/Implied/NullImpliedRelationshipProvider.cs @@ -0,0 +1,78 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied +{ + using System.Collections.Generic; + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Core.Types; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// An that computes nothing. + /// + /// + /// This is the default collaborator on the optional-provider constructors, so a caller that has not opted + /// into implied-relationship computation gets the SDK's pre-existing behaviour without null checks at + /// every call site. Every constraint is reported as not covered, which is accurate for this + /// implementation. + /// + public class NullImpliedRelationshipProvider : IImpliedRelationshipProvider + { + /// + /// Gets the shared instance. + /// + public static NullImpliedRelationshipProvider Instance { get; } = new NullImpliedRelationshipProvider(); + + /// + /// Gets the names of the semantic constraints this provider cannot compute, which is all of them. + /// + public IReadOnlyList NotCoveredConstraints => ImpliedRelationshipTable.AllConstraintNames; + + /// + /// Returns no implied Relationships. + /// + /// The Element, which is not inspected. + /// An empty collection. + public IReadOnlyList GetImpliedRelationships(IElement element) => []; + + /// + /// Returns no implied Specializations. + /// + /// The Type, which is not inspected. + /// An empty collection. + public IReadOnlyList GetImpliedSpecializations(IType type) => []; + + /// + /// Returns no implied Redefinitions. + /// + /// The Feature, which is not inspected. + /// An empty collection. + public IReadOnlyList GetImpliedRedefinitions(IFeature feature) => []; + + /// + /// Reports every constraint as not covered. + /// + /// The constraint name, which is not inspected. + /// Always false. + public bool IsConstraintCovered(string constraintName) => false; + } +} diff --git a/SysML2.NET.Semantics/Implied/OwnershipTreeLibraryTypeIndex.cs b/SysML2.NET.Semantics/Implied/OwnershipTreeLibraryTypeIndex.cs new file mode 100644 index 00000000..17e19595 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/OwnershipTreeLibraryTypeIndex.cs @@ -0,0 +1,191 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using SysML2.NET.Core.POCO.Core.Types; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Root.Namespaces; + + /// + /// Indexes model-library Types by qualified name from the library ownership tree. + /// + /// + /// The walk reads the raw ownedRelationship of each Namespace rather than any derived membership + /// property, so it cannot re-enter name resolution: resolution consults inheritedMembership, which is + /// what the implied layer supplies. The index is fully populated by before any + /// lookup, for the same reason. + /// + public class OwnershipTreeLibraryTypeIndex : ILibraryTypeIndex + { + /// + /// The separator between the segments of a qualified name. + /// + private const string QualifiedNameSeparator = "::"; + + /// + /// The indexed Types, keyed by qualified name. + /// + private readonly Dictionary typesByQualifiedName; + + /// + /// Initializes a new instance of the class. + /// + /// The indexed Types, keyed by qualified name. + private OwnershipTreeLibraryTypeIndex(Dictionary typesByQualifiedName) + { + this.typesByQualifiedName = typesByQualifiedName; + } + + /// + /// Gets the number of indexed Types. + /// + public int Count => this.typesByQualifiedName.Count; + + /// + /// Builds an index over the supplied library root Namespaces. + /// + /// The library root Namespaces, typically the referenced Namespaces reported by a deserializer. + /// A fully populated index. + /// Thrown when is null. + public static OwnershipTreeLibraryTypeIndex Build(IEnumerable libraryNamespaces) + { + if (libraryNamespaces == null) + { + throw new ArgumentNullException(nameof(libraryNamespaces)); + } + + var typesByQualifiedName = new Dictionary(StringComparer.Ordinal); + var visited = new HashSet(); + + foreach (var libraryNamespace in libraryNamespaces.Where(libraryNamespace => libraryNamespace != null)) + { + Index(libraryNamespace, null, typesByQualifiedName, visited); + } + + return new OwnershipTreeLibraryTypeIndex(typesByQualifiedName); + } + + /// + /// The character a qualified name uses to quote a segment that is not a valid bare name. + /// + private const char QuoteCharacter = (char)39; + + /// + /// Attempts to resolve the library Type carrying the supplied qualified name. + /// + /// The qualified name, for example Occurrences::Occurrence::suboccurrences. + /// When this method returns true, the resolved Type; otherwise null. + /// True when the qualified name resolves to an indexed Type. + public bool TryGetType(string qualifiedName, out IType type) + { + if (string.IsNullOrWhiteSpace(qualifiedName)) + { + type = null; + + return false; + } + + return this.typesByQualifiedName.TryGetValue(qualifiedName, out type) + || this.typesByQualifiedName.TryGetValue(Unquote(qualifiedName), out type); + } + + /// + /// Removes the single quotes a qualified name uses around segments that are not valid bare names. + /// + /// The qualified name to normalise. + /// The name with each segment unquoted. + /// + /// A constraint may target a Feature whose name needs quoting in the textual notation — the dot + /// operator is declared as . but written '.', so the OCL says + /// ControlFunctions::'.'::source::target while the index is keyed on the declared names. The + /// quotes are notation, not part of the name, so a lookup falls back to the unquoted form. + /// + private static string Unquote(string qualifiedName) + { + return string.Join("::", qualifiedName + .Split(["::"], StringSplitOptions.None) + .Select(segment => segment.Length > 1 && segment[0] == QuoteCharacter && segment[segment.Length - 1] == QuoteCharacter + ? segment.Substring(1, segment.Length - 2) + : segment)); + } + + /// + /// Indexes an Element and, when it is a Namespace, everything it owns. + /// + /// The Element to index. + /// The qualified name of the owning Namespace, or null at a root. + /// The index being populated. + /// The Elements already walked, guarding against a cyclic ownership graph. + private static void Index(IElement element, string parentQualifiedName, Dictionary typesByQualifiedName, HashSet visited) + { + if (!visited.Add(element)) + { + return; + } + + var qualifiedName = QueryQualifiedName(element, parentQualifiedName); + + if (element is IType indexableType && qualifiedName != null) + { + typesByQualifiedName[qualifiedName] = indexableType; + } + + if (element is not INamespace owningNamespace) + { + return; + } + + // The raw ownedRelationship is read rather than the derived ownedMembership so the walk stays + // independent of every derivation the implied layer is meant to feed. + var ownedElements = owningNamespace.OwnedRelationship + .OfType() + .Select(membership => membership.ownedMemberElement) + .Where(ownedElement => ownedElement != null); + + foreach (var ownedElement in ownedElements) + { + Index(ownedElement, qualifiedName ?? parentQualifiedName, typesByQualifiedName, visited); + } + } + + /// + /// Composes the qualified name of an Element from its owner's qualified name and its declared name. + /// + /// The Element to name. + /// The qualified name of the owning Namespace, or null at a root. + /// The qualified name, or null when the Element has no declared name. + private static string QueryQualifiedName(IElement element, string parentQualifiedName) + { + if (string.IsNullOrWhiteSpace(element.DeclaredName)) + { + return null; + } + + return string.IsNullOrWhiteSpace(parentQualifiedName) + ? element.DeclaredName + : $"{parentQualifiedName}{QualifiedNameSeparator}{element.DeclaredName}"; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/ActionUsageStateActionRedefinitionRule.cs b/SysML2.NET.Semantics/Implied/Rules/ActionUsageStateActionRedefinitionRule.cs new file mode 100644 index 00000000..9d20fe97 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/ActionUsageStateActionRedefinitionRule.cs @@ -0,0 +1,77 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using SysML2.NET.Core.Systems.States; + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Systems.Actions; + using SysML2.NET.Core.POCO.Systems.States; + + /// + /// Implements checkActionUsageStateActionRedefinition: an ActionUsage owned as a state subaction + /// redefines the entry, do or exit action of States::StateAction, according to its kind. + /// + /// + /// OCL: owningFeatureMembership <> null and + /// owningFeatureMembership.oclIsKindOf(StateSubactionMembership) implies … if kind = entry then + /// redefinesFromLibrary('States::StateAction::entryAction') else if kind = do then … else … + /// exitAction. The kind selects the target, so all three branches are covered here rather than + /// split across three rules. + /// + public class ActionUsageStateActionRedefinitionRule : LibraryRedefinitionRule + { + /// + /// Initializes a new instance of the class. + /// + /// The index resolving the library Feature by qualified name. + /// The factory creating the detached Redefinition. + public ActionUsageStateActionRedefinitionRule(ILibraryTypeIndex libraryTypeIndex, IImpliedRelationshipFactory factory) + : base(libraryTypeIndex, factory) + { + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public override string ConstraintName => "checkActionUsageStateActionRedefinition"; + + /// + /// Returns the ActionUsage together with the library action its subaction kind selects. + /// + /// The Element under evaluation. + /// The Element and the library qualified name, or null when it is not a state subaction. + protected override (IFeature RedefiningFeature, string LibraryQualifiedName)? QueryRedefinition(IElement element) + { + if (element is not IActionUsage { owningFeatureMembership: IStateSubactionMembership membership } actionUsage) + { + return null; + } + + return (actionUsage, membership.Kind switch + { + StateSubactionKind.Entry => "States::StateAction::entryAction", + StateSubactionKind.Do => "States::StateAction::doAction", + _ => "States::StateAction::exitAction" + }); + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/ArgumentResultSpecializationRule.cs b/SysML2.NET.Semantics/Implied/Rules/ArgumentResultSpecializationRule.cs new file mode 100644 index 00000000..108b77b7 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/ArgumentResultSpecializationRule.cs @@ -0,0 +1,100 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using System; + using System.Collections.Generic; + + using SysML2.NET.Core.POCO.Kernel.Expressions; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Base for a rule whose result parameter subsets the result of the Expression's FIRST argument. + /// + /// + /// The target is a result parameter, hence a Feature, so the Specialization is a Subsetting — + /// Feature-to-Feature admits no other kind. This is why the family does NOT need the + /// Classifier-or-Feature test that makes. + /// The OCL elects arguments->first() explicitly, so taking the first of many is the + /// contract rather than an arbitrary pick. + /// + public abstract class ArgumentResultSpecializationRule : IImpliedRelationshipRule + { + /// + /// Initializes a new instance of the class. + /// + /// The factory creating the detached Subsetting. + /// Thrown when is null. + protected ArgumentResultSpecializationRule(IImpliedRelationshipFactory factory) + { + this.Factory = factory ?? throw new ArgumentNullException(nameof(factory)); + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public abstract string ConstraintName { get; } + + /// + /// Gets the factory creating the detached Subsetting. + /// + protected IImpliedRelationshipFactory Factory { get; } + + /// + /// Computes the Subsetting binding the Expression's result to its first argument's result. + /// + /// The Element under evaluation. + /// The Subsetting, or empty when the constraint does not apply. + /// Thrown when is null. + public IReadOnlyList Apply(IElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + if (!this.IsInScope(element) || element is not IInstantiationExpression { result: not null } expression) + { + return []; + } + + var firstArgumentResult = expression.argument.Count == 0 ? null : expression.argument[0].result; + + return firstArgumentResult == null || !this.AppliesTo(firstArgumentResult) + ? [] + : [this.Factory.CreateImpliedSubsetting(expression.result, firstArgumentResult)]; + } + + /// + /// Asserts whether the Element is the metaclass this rule constrains. + /// + /// The Element under evaluation. + /// True when the rule applies to the Element's metaclass. + protected abstract bool IsInScope(IElement element); + + /// + /// Asserts any further condition the constraint places on the first argument's result. + /// + /// The result parameter of the first argument Expression. + /// True when the Subsetting is required; the base implementation always agrees. + protected virtual bool AppliesTo(SysML2.NET.Core.POCO.Core.Features.IFeature firstArgumentResult) => true; + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/AssertConstraintUsageSpecializationRule.cs b/SysML2.NET.Semantics/Implied/Rules/AssertConstraintUsageSpecializationRule.cs new file mode 100644 index 00000000..5c39787a --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/AssertConstraintUsageSpecializationRule.cs @@ -0,0 +1,74 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Systems.Constraints; + using SysML2.NET.Core.POCO.Systems.Requirements; + + /// + /// Implements checkAssertConstraintUsageSpecialization: an asserted ConstraintUsage subsets the library + /// checks for the sense in which it is asserted. + /// + /// + /// OCL: if isNegated then specializesFromLibrary('Constraints::negatedConstraintChecks') + /// else specializesFromLibrary('Constraints::assertedConstraintChecks'). + /// A SatisfyRequirementUsage IS an AssertConstraintUsage, but carries its own more specific + /// constraint selecting from Requirements:: instead. The two targets are unrelated library + /// Features, so redundancy reduction would not collapse them and BOTH would be implied — hence the + /// explicit exclusion here, which mirrors the specific constraint taking precedence over the general. + /// + public class AssertConstraintUsageSpecializationRule : LibrarySpecializationRule + { + /// + /// Initializes a new instance of the class. + /// + /// The index resolving the library Feature by qualified name. + /// The factory creating the detached Subsetting. + public AssertConstraintUsageSpecializationRule(ILibraryTypeIndex libraryTypeIndex, IImpliedRelationshipFactory factory) + : base(libraryTypeIndex, factory) + { + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public override string ConstraintName => "checkAssertConstraintUsageSpecialization"; + + /// + /// Returns the ConstraintUsage together with the library Feature its negation selects. + /// + /// The Element under evaluation. + /// The Feature and the library qualified name, or null when the constraint does not apply. + protected override (IFeature SpecificFeature, string LibraryQualifiedName)? QuerySpecialization(IElement element) + { + if (element is not IAssertConstraintUsage assertConstraintUsage || element is ISatisfyRequirementUsage) + { + return null; + } + + return (assertConstraintUsage, assertConstraintUsage.IsNegated + ? "Constraints::negatedConstraintChecks" + : "Constraints::assertedConstraintChecks"); + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/AssignmentActionUsageAccessedFeatureRedefinitionRule.cs b/SysML2.NET.Semantics/Implied/Rules/AssignmentActionUsageAccessedFeatureRedefinitionRule.cs new file mode 100644 index 00000000..4c267420 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/AssignmentActionUsageAccessedFeatureRedefinitionRule.cs @@ -0,0 +1,64 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Systems.Actions; + + /// + /// Implements checkAssignmentActionUsageAccessedFeatureRedefinition: the accessed Feature of an assignment action's target parameter redefines AssigmentAction::target::startingAt::accessedFeature. + /// + /// + /// OCL: let targetParameter : Feature = inputParameter(1) in targetParameter <> null and targetParameter.ownedFeature->notEmpty() and targetParameter.ownedFeature->first().ownedFeature->notEmpty() and targetParameter.ownedFeature->first().ownedFeature->first().redefinesFromLibrary('AssigmentAction::target::startingAt::accessedFeature') + /// + public class AssignmentActionUsageAccessedFeatureRedefinitionRule : LibraryRedefinitionRule + { + /// + /// Initializes a new instance of the class. + /// + /// The index resolving the library Feature by qualified name. + /// The factory creating the detached Redefinition. + public AssignmentActionUsageAccessedFeatureRedefinitionRule(ILibraryTypeIndex libraryTypeIndex, IImpliedRelationshipFactory factory) + : base(libraryTypeIndex, factory) + { + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public override string ConstraintName => "checkAssignmentActionUsageAccessedFeatureRedefinition"; + + /// + /// Returns the Feature that must redefine the library Feature. + /// + /// The Element under evaluation. + /// The Feature and the library qualified name, or null when the constraint does not apply. + protected override (IFeature RedefiningFeature, string LibraryQualifiedName)? QueryRedefinition(IElement element) + { + var redefiningFeature = element is IAssignmentActionUsage assignmentActionUsage ? AssignmentActionUsageNavigation.QueryAccessedFeature(assignmentActionUsage) : null; + + return redefiningFeature == null + ? null + : (redefiningFeature, "AssigmentAction::target::startingAt::accessedFeature"); + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/AssignmentActionUsageNavigation.cs b/SysML2.NET.Semantics/Implied/Rules/AssignmentActionUsageNavigation.cs new file mode 100644 index 00000000..0de535cc --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/AssignmentActionUsageNavigation.cs @@ -0,0 +1,62 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using System.Linq; + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Systems.Actions; + + /// + /// The target-parameter navigations shared by the AssignmentActionUsage redefinition constraints. + /// + /// + /// Three constraints walk the same chain from an assignment action's first input parameter — + /// inputParameter(1).ownedFeature->first() for the starting-at Feature and one hop further for + /// the accessed Feature. Each hop is guarded by a notEmpty() in the OCL, so a partially-built + /// action yields null rather than throwing. + /// + internal static class AssignmentActionUsageNavigation + { + /// + /// Returns the starting-at Feature: the first owned Feature of the target parameter. + /// + /// The assignment action to navigate from. + /// The Feature, or null when the chain is incomplete. + internal static IFeature QueryStartingAt(IAssignmentActionUsage assignmentActionUsage) + { + // inputParameter is a 1-based metamodel operation, so the OCL argument passes through unchanged. + var targetParameter = assignmentActionUsage.InputParameter(1); + + return targetParameter?.ownedFeature.FirstOrDefault(); + } + + /// + /// Returns the accessed Feature: the first owned Feature of the starting-at Feature. + /// + /// The assignment action to navigate from. + /// The Feature, or null when the chain is incomplete. + internal static IFeature QueryAccessedFeature(IAssignmentActionUsage assignmentActionUsage) + { + return QueryStartingAt(assignmentActionUsage)?.ownedFeature.FirstOrDefault(); + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/AssignmentActionUsageReferentRedefinitionRule.cs b/SysML2.NET.Semantics/Implied/Rules/AssignmentActionUsageReferentRedefinitionRule.cs new file mode 100644 index 00000000..6ed00855 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/AssignmentActionUsageReferentRedefinitionRule.cs @@ -0,0 +1,81 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using System; + using System.Collections.Generic; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Systems.Actions; + + /// + /// Implements checkAssignmentActionUsageReferentRedefinition: the accessed Feature of an assignment action's target parameter redefines the action's referent. + /// + /// + /// OCL: let targetParameter : Feature = inputParameter(1) in targetParameter <> null and targetParameter.ownedFeature->notEmpty() and targetParameter.ownedFeature->first().ownedFeature->notEmpty() and targetParameter.ownedFeature->first().ownedFeature->first().redefines(referent) + /// + public class AssignmentActionUsageReferentRedefinitionRule : IImpliedRelationshipRule + { + /// + /// The factory creating the detached Redefinition. + /// + private readonly IImpliedRelationshipFactory factory; + + /// + /// Initializes a new instance of the class. + /// + /// The factory creating the detached Redefinition. + /// Thrown when is null. + public AssignmentActionUsageReferentRedefinitionRule(IImpliedRelationshipFactory factory) + { + this.factory = factory ?? throw new ArgumentNullException(nameof(factory)); + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public string ConstraintName => "checkAssignmentActionUsageReferentRedefinition"; + + /// + /// Computes the implied Redefinition the constraint requires of the supplied Element. + /// + /// The Element under evaluation. + /// A single Redefinition, or empty when the constraint does not apply. + /// Thrown when is null. + public IReadOnlyList Apply(IElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + if (element is not IAssignmentActionUsage { referent: not null } assignmentActionUsage) + { + return []; + } + + var accessedFeature = AssignmentActionUsageNavigation.QueryAccessedFeature(assignmentActionUsage); + + return accessedFeature == null + ? [] + : [this.factory.CreateImpliedRedefinition(accessedFeature, assignmentActionUsage.referent)]; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/AssignmentActionUsageStartingAtRedefinitionRule.cs b/SysML2.NET.Semantics/Implied/Rules/AssignmentActionUsageStartingAtRedefinitionRule.cs new file mode 100644 index 00000000..656dfd58 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/AssignmentActionUsageStartingAtRedefinitionRule.cs @@ -0,0 +1,71 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Systems.Actions; + + /// + /// Implements checkAssignmentActionUsageStartingAtRedefinition: the first owned Feature of an assignment + /// action's target parameter redefines AssignmentAction::target::startingAt. + /// + /// + /// OCL: let targetParameter : Feature = inputParameter(1) in targetParameter <> null and + /// targetParameter.ownedFeature->notEmpty() and + /// targetParameter.ownedFeature->first().redefinesFromLibrary('AssignmentAction::target::startingAt'). + /// inputParameter(1) is a 1-based metamodel operation and is called with the OCL argument + /// unchanged. + /// + public class AssignmentActionUsageStartingAtRedefinitionRule : LibraryRedefinitionRule + { + /// + /// Initializes a new instance of the class. + /// + /// The index resolving the library Feature by qualified name. + /// The factory creating the detached Redefinition. + public AssignmentActionUsageStartingAtRedefinitionRule(ILibraryTypeIndex libraryTypeIndex, IImpliedRelationshipFactory factory) + : base(libraryTypeIndex, factory) + { + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public override string ConstraintName => "checkAssignmentActionUsageStartingAtRedefinition"; + + /// + /// Returns the first owned Feature of the target parameter as the redefining Feature. + /// + /// The Element under evaluation. + /// The Feature and the library qualified name, or null when the chain is absent. + protected override (IFeature RedefiningFeature, string LibraryQualifiedName)? QueryRedefinition(IElement element) + { + var startingAt = element is IAssignmentActionUsage assignmentActionUsage + ? AssignmentActionUsageNavigation.QueryStartingAt(assignmentActionUsage) + : null; + + return startingAt == null + ? null + : (startingAt, "AssignmentAction::target::startingAt"); + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/ChainSubsettingRule.cs b/SysML2.NET.Semantics/Implied/Rules/ChainSubsettingRule.cs new file mode 100644 index 00000000..4a213751 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/ChainSubsettingRule.cs @@ -0,0 +1,98 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Base for a rule that satisfies a subsetsChain(first, second) obligation. + /// + /// + /// KerML 1.0 §8.3.3.3.4 Feature defines subsetsChain(first, second) as holding when the Feature + /// "directly or indirectly specializes a Feature whose last two chainingFeatures are the given Features + /// first and second". The general of that Subsetting need not exist in the model, so it is SYNTHESIZED + /// by . + /// This is the one place the layer emits a Relationship whose other end is a new Element rather + /// than one the caller already holds. The synthesized chain is detached and unnamed; everything it means + /// is in its chainingFeature list. A consumer that resolves names through implied Specializations + /// must therefore tolerate a general it cannot find in the model — see the note on + /// . + /// A rule yields nothing when either end of the chain is absent: an incomplete model states no + /// chain, and fabricating half of one would assert something the model does not. + /// + public abstract class ChainSubsettingRule : IImpliedRelationshipRule + { + /// + /// Initializes a new instance of the class. + /// + /// The factory creating the chain and the Subsetting. + /// Thrown when is null. + protected ChainSubsettingRule(IImpliedRelationshipFactory factory) + { + this.Factory = factory ?? throw new ArgumentNullException(nameof(factory)); + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public abstract string ConstraintName { get; } + + /// + /// Gets the factory creating the chain and the Subsetting. + /// + protected IImpliedRelationshipFactory Factory { get; } + + /// + /// Computes the chain Subsettings the Element requires. + /// + /// The Element under evaluation. + /// One Subsetting per required chain; empty when the constraint does not apply. + /// Thrown when is null. + public IReadOnlyList Apply(IElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + return + [ + ..this.QueryChains(element) + .Where(chain => chain.Subsetting != null && chain.First != null && chain.Second != null) + .Select(chain => this.Factory.CreateImpliedSubsetting( + chain.Subsetting, + this.Factory.CreateImpliedFeatureChain(chain.First, chain.Second))) + ]; + } + + /// + /// Returns each chain obligation the constraint places on the Element. + /// + /// The Element under evaluation. + /// The Feature that must subset the chain, and the two Features forming it. + protected abstract IEnumerable<(IFeature Subsetting, IFeature First, IFeature Second)> QueryChains(IElement element); + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/ConstraintUsageRequirementConstraintSpecializationRule.cs b/SysML2.NET.Semantics/Implied/Rules/ConstraintUsageRequirementConstraintSpecializationRule.cs new file mode 100644 index 00000000..a1e5de9d --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/ConstraintUsageRequirementConstraintSpecializationRule.cs @@ -0,0 +1,78 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Systems.Constraints; + using SysML2.NET.Core.POCO.Systems.Requirements; + using SysML2.NET.Core.Systems.Requirements; + + /// + /// Implements checkConstraintUsageRequirementConstraintSpecialization: a composite ConstraintUsage owned + /// by a requirement subsets the library assumptions or constraints according to the kind of its + /// membership. + /// + /// + /// OCL: isComposite and owningFeatureMembership <> null and + /// owningFeatureMembership.oclIsKindOf(RequirementConstraintMembership) implies if + /// owningFeatureMembership.oclAsType(RequirementConstraintMembership).kind = + /// RequirementConstraintKind::assumption then + /// specializesFromLibrary('Requirements::RequirementCheck::assumptions') else + /// specializesFromLibrary('Requirements::RequirementCheck::constraints') endif. + /// The isComposite guard matters: a referential ConstraintUsage in the same membership is + /// NOT subject to the constraint. + /// + public class ConstraintUsageRequirementConstraintSpecializationRule : LibrarySpecializationRule + { + /// + /// Initializes a new instance of the class. + /// + /// The index resolving the library Feature by qualified name. + /// The factory creating the detached Subsetting. + public ConstraintUsageRequirementConstraintSpecializationRule(ILibraryTypeIndex libraryTypeIndex, IImpliedRelationshipFactory factory) + : base(libraryTypeIndex, factory) + { + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public override string ConstraintName => "checkConstraintUsageRequirementConstraintSpecialization"; + + /// + /// Returns the ConstraintUsage together with the library Feature its membership kind selects. + /// + /// The Element under evaluation. + /// The Feature and the library qualified name, or null when the constraint does not apply. + protected override (IFeature SpecificFeature, string LibraryQualifiedName)? QuerySpecialization(IElement element) + { + if (element is not IConstraintUsage { IsComposite: true, owningFeatureMembership: IRequirementConstraintMembership membership } constraintUsage) + { + return null; + } + + return (constraintUsage, membership.Kind == RequirementConstraintKind.Assumption + ? "Requirements::RequirementCheck::assumptions" + : "Requirements::RequirementCheck::constraints"); + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/ConstructorExpressionResultSpecializationRule.cs b/SysML2.NET.Semantics/Implied/Rules/ConstructorExpressionResultSpecializationRule.cs new file mode 100644 index 00000000..3dc02d17 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/ConstructorExpressionResultSpecializationRule.cs @@ -0,0 +1,91 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using System; + using System.Collections.Generic; + + using SysML2.NET.Core.POCO.Core.Classifiers; + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Kernel.Expressions; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Implements checkConstructorExpressionResultSpecialization: the result of a ConstructorExpression + /// specializes the Type being instantiated. + /// + /// + /// OCL: result.specializes(instantiatedType). + /// The OCL says THAT the result specializes, not by which Relationship. The specification supplies + /// the missing half — KerML 1.0 §8.4.4.9.4 Constructor Expressions (p. 261): the result specializes the + /// instantiatedType "via a FeatureTyping if the instantiatedType is a Classifier or a Subsetting if it + /// is a Feature". Emitting one kind for both cases would be syntactically faithful to the OCL and + /// semantically wrong. + /// + public class ConstructorExpressionResultSpecializationRule : IImpliedRelationshipRule + { + /// + /// The factory creating the detached Relationship. + /// + private readonly IImpliedRelationshipFactory factory; + + /// + /// Initializes a new instance of the class. + /// + /// The factory creating the detached Relationship. + /// Thrown when is null. + public ConstructorExpressionResultSpecializationRule(IImpliedRelationshipFactory factory) + { + this.factory = factory ?? throw new ArgumentNullException(nameof(factory)); + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public string ConstraintName => "checkConstructorExpressionResultSpecialization"; + + /// + /// Computes the Relationship binding a ConstructorExpression's result to the Type it instantiates. + /// + /// The Element under evaluation. + /// The FeatureTyping or Subsetting, or empty when the constraint does not apply. + /// Thrown when is null. + public IReadOnlyList Apply(IElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + if (element is not IConstructorExpression { result: not null, instantiatedType: not null } constructorExpression) + { + return []; + } + + return constructorExpression.instantiatedType switch + { + IFeature instantiatedFeature => [this.factory.CreateImpliedSubsetting(constructorExpression.result, instantiatedFeature)], + IClassifier instantiatedClassifier => [this.factory.CreateImpliedFeatureTyping(constructorExpression.result, instantiatedClassifier)], + _ => [] + }; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/ConstructorExpressionSpecializationRule.cs b/SysML2.NET.Semantics/Implied/Rules/ConstructorExpressionSpecializationRule.cs new file mode 100644 index 00000000..e613572e --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/ConstructorExpressionSpecializationRule.cs @@ -0,0 +1,69 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Kernel.Expressions; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Implements checkConstructorExpressionSpecialization: a ConstructorExpression subsets the library + /// constructor evaluations. + /// + /// + /// OCL: specializes('Performances::constructorEvaluations'). + /// Reached the not-covered manifest only because the OCL calls specializes rather than + /// specializesFromLibrary, which is what the generated table's classifier matches; the target is + /// a library Feature all the same, so the rule is the ordinary library-subsetting shape. + /// KerML 1.0 §8.4.4.9.4 Constructor Expressions (p. 261) records what this buys: the library + /// Expression subsets Performances::evaluations and redefines its result parameter to + /// multiplicity 1..1, so a ConstructorExpression always produces a single value. + /// + public class ConstructorExpressionSpecializationRule : LibrarySpecializationRule + { + /// + /// Initializes a new instance of the class. + /// + /// The index resolving the library Feature by qualified name. + /// The factory creating the detached Subsetting. + public ConstructorExpressionSpecializationRule(ILibraryTypeIndex libraryTypeIndex, IImpliedRelationshipFactory factory) + : base(libraryTypeIndex, factory) + { + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public override string ConstraintName => "checkConstructorExpressionSpecialization"; + + /// + /// Returns the ConstructorExpression together with the library Feature it subsets. + /// + /// The Element under evaluation. + /// The Feature and the library qualified name, or null when the constraint does not apply. + protected override (IFeature SpecificFeature, string LibraryQualifiedName)? QuerySpecialization(IElement element) + { + return element is not IConstructorExpression constructorExpression + ? null + : (constructorExpression, "Performances::constructorEvaluations"); + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/ControlNodeSuccessionChainRule.cs b/SysML2.NET.Semantics/Implied/Rules/ControlNodeSuccessionChainRule.cs new file mode 100644 index 00000000..f076dc12 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/ControlNodeSuccessionChainRule.cs @@ -0,0 +1,105 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using System; + using System.Collections.Generic; + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Kernel.Connectors; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Base for the two constraints requiring a Succession attached to a control node to subset a chain + /// through that node's library happens-before link. + /// + /// + /// The OCL reads sourceConnector->selectByKind(Succession)->forAll(subsetsChain(self, …)) — + /// a REVERSE navigation from the node to the Successions it is an end of. sourceConnector and + /// targetConnector are not available as derived properties, but they need not be: the subject of + /// subsetsChain is each Succession, not the node, so evaluating the rule on the SUCCESSION and + /// asking whether its own end is a control node yields exactly the same set with no reverse walk. + /// The chain is [controlNode, happensBeforeLink]: the Succession subsets the link as + /// reached THROUGH the node, which is what ties the ordering to that particular node rather than to + /// control performances at large. + /// + public abstract class ControlNodeSuccessionChainRule : ChainSubsettingRule + { + /// + /// Initializes a new instance of the class. + /// + /// The index resolving the library Feature by qualified name. + /// The factory creating the chain and the Subsetting. + /// Thrown when is null. + protected ControlNodeSuccessionChainRule(ILibraryTypeIndex libraryTypeIndex, IImpliedRelationshipFactory factory) + : base(factory) + { + this.LibraryTypeIndex = libraryTypeIndex ?? throw new ArgumentNullException(nameof(libraryTypeIndex)); + } + + /// + /// Gets the index resolving the library Feature by qualified name. + /// + protected ILibraryTypeIndex LibraryTypeIndex { get; } + + /// + /// Gets the qualified name of the library happens-before link the chain ends in. + /// + protected abstract string LinkQualifiedName { get; } + + /// + /// Returns the chain obligation a Succession carries, when its relevant end is the control node. + /// + /// The Element under evaluation. + /// The Succession and the two Features forming the chain; empty otherwise. + /// Thrown when the library Feature is not indexed. + protected override IEnumerable<(IFeature Subsetting, IFeature First, IFeature Second)> QueryChains(IElement element) + { + if (element is not ISuccession succession) + { + return []; + } + + var controlNode = this.QueryControlNode(succession); + + if (controlNode == null) + { + return []; + } + + if (!this.LibraryTypeIndex.TryGetType(this.LinkQualifiedName, out var libraryType)) + { + throw new UnresolvedLibraryTypeException(this.LinkQualifiedName, this.ConstraintName); + } + + return libraryType is not IFeature libraryFeature + ? [] + : [(succession, controlNode, libraryFeature)]; + } + + /// + /// Returns the control node at the end of the Succession this constraint governs. + /// + /// The Succession under evaluation. + /// The control node, or null when the relevant end is not one. + protected abstract IFeature QueryControlNode(ISuccession succession); + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/DecisionNodeOutgoingSuccessionSpecializationRule.cs b/SysML2.NET.Semantics/Implied/Rules/DecisionNodeOutgoingSuccessionSpecializationRule.cs new file mode 100644 index 00000000..aa32f35f --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/DecisionNodeOutgoingSuccessionSpecializationRule.cs @@ -0,0 +1,72 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using System.Linq; + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Kernel.Connectors; + using SysML2.NET.Core.POCO.Systems.Actions; + + /// + /// Implements checkDecisionNodeOutgoingSuccessionSpecialization: a Succession leaving a DecisionNode + /// subsets that node's outgoing happens-before link. + /// + /// + /// OCL: sourceConnector->selectByKind(Succession)->forAll(subsetsChain(self, + /// resolveGlobal('ControlPerformances::DecisionPerformance::outgoingHBLink'))). + /// Evaluated on the Succession — see for why that is + /// equivalent to the OCL's reverse navigation from the node. + /// + public class DecisionNodeOutgoingSuccessionSpecializationRule : ControlNodeSuccessionChainRule + { + /// + /// Initializes a new instance of the class. + /// + /// The index resolving the library Feature by qualified name. + /// The factory creating the chain and the Subsetting. + public DecisionNodeOutgoingSuccessionSpecializationRule(ILibraryTypeIndex libraryTypeIndex, IImpliedRelationshipFactory factory) + : base(libraryTypeIndex, factory) + { + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public override string ConstraintName => "checkDecisionNodeOutgoingSuccessionSpecialization"; + + /// + /// Gets the qualified name of the library happens-before link the chain ends in. + /// + protected override string LinkQualifiedName => "ControlPerformances::DecisionPerformance::outgoingHBLink"; + + /// + /// Returns the DecisionNode the Succession leaves, if it leaves one. + /// + /// The Succession under evaluation. + /// The DecisionNode, or null when the source is not one. + protected override IFeature QueryControlNode(ISuccession succession) + { + // OUTGOING: the node is the SOURCE end. + return succession.sourceFeature as IDecisionNode; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/FeatureChainExpressionResultSpecializationRule.cs b/SysML2.NET.Semantics/Implied/Rules/FeatureChainExpressionResultSpecializationRule.cs new file mode 100644 index 00000000..a50682d2 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/FeatureChainExpressionResultSpecializationRule.cs @@ -0,0 +1,80 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using System.Collections.Generic; + using System.Linq; + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Kernel.Expressions; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Implements checkFeatureChainExpressionResultSpecialization: the result of a FeatureChainExpression + /// subsets the feature chain the Expression denotes. + /// + /// + /// OCL: let inputParameters = ownedFeatures->select(direction = 'in') in … + /// result.subsetsChain(inputParameters->first(), sourceTargetFeature) and result.owningType = self. + /// KerML 1.0 §8.3.4.8.4: "The result parameter of a FeatureChainExpression must specialize the + /// feature chain of the FeatureChainExpression." The chain is + /// [first input parameter, sourceTargetFeature] — the Expression's source, then the feature + /// reached through it, which is exactly what a.b denotes. + /// The OCL writes owningExpression.sourceTargetFeature(), but sourceTargetFeature() + /// is declared on FeatureChainExpression itself and the constraint is too, so it is read as + /// self.sourceTargetFeature(). + /// + public class FeatureChainExpressionResultSpecializationRule : ChainSubsettingRule + { + /// + /// Initializes a new instance of the class. + /// + /// The factory creating the chain and the Subsetting. + public FeatureChainExpressionResultSpecializationRule(IImpliedRelationshipFactory factory) + : base(factory) + { + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public override string ConstraintName => "checkFeatureChainExpressionResultSpecialization"; + + /// + /// Returns the chain the Expression's result must subset. + /// + /// The Element under evaluation. + /// The result and the two Features forming the chain; empty otherwise. + protected override IEnumerable<(IFeature Subsetting, IFeature First, IFeature Second)> QueryChains(IElement element) + { + if (element is not IFeatureChainExpression { result: not null } featureChainExpression + || !ReferenceEquals(featureChainExpression.result.owningType, featureChainExpression)) + { + return []; + } + + var firstInputParameter = featureChainExpression.ownedFeature + .FirstOrDefault(ownedFeature => ownedFeature.Direction == Core.Core.Types.FeatureDirectionKind.In); + + return [(featureChainExpression.result, firstInputParameter, featureChainExpression.SourceTargetFeature())]; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/FeatureChainExpressionSourceTargetRedefinitionRule.cs b/SysML2.NET.Semantics/Implied/Rules/FeatureChainExpressionSourceTargetRedefinitionRule.cs new file mode 100644 index 00000000..399e6c63 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/FeatureChainExpressionSourceTargetRedefinitionRule.cs @@ -0,0 +1,81 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using System; + using System.Collections.Generic; + using SysML2.NET.Core.POCO.Kernel.Expressions; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Implements checkFeatureChainExpressionSourceTargetRedefinition: the source-target Feature of a FeatureChainExpression redefines the expression's target Feature. + /// + /// + /// OCL: let sourceTargetFeature : Feature = sourceTargetFeature() in sourceTargetFeature <> null and sourceTargetFeature.redefines(targetFeature) + /// + public class FeatureChainExpressionSourceTargetRedefinitionRule : IImpliedRelationshipRule + { + /// + /// The factory creating the detached Redefinition. + /// + private readonly IImpliedRelationshipFactory factory; + + /// + /// Initializes a new instance of the class. + /// + /// The factory creating the detached Redefinition. + /// Thrown when is null. + public FeatureChainExpressionSourceTargetRedefinitionRule(IImpliedRelationshipFactory factory) + { + this.factory = factory ?? throw new ArgumentNullException(nameof(factory)); + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public string ConstraintName => "checkFeatureChainExpressionSourceTargetRedefinition"; + + /// + /// Computes the implied Redefinition the constraint requires of the supplied Element. + /// + /// The Element under evaluation. + /// A single Redefinition, or empty when the constraint does not apply. + /// Thrown when is null. + public IReadOnlyList Apply(IElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + if (element is not IFeatureChainExpression { targetFeature: not null } featureChainExpression) + { + return []; + } + + var sourceTargetFeature = featureChainExpression.SourceTargetFeature(); + + return sourceTargetFeature == null + ? [] + : [this.factory.CreateImpliedRedefinition(sourceTargetFeature, featureChainExpression.targetFeature)]; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/FeatureChainExpressionTargetRedefinitionRule.cs b/SysML2.NET.Semantics/Implied/Rules/FeatureChainExpressionTargetRedefinitionRule.cs new file mode 100644 index 00000000..befc3477 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/FeatureChainExpressionTargetRedefinitionRule.cs @@ -0,0 +1,64 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Kernel.Expressions; + + /// + /// Implements checkFeatureChainExpressionTargetRedefinition: the source-target Feature of a FeatureChainExpression redefines ControlFunctions::'.'::source::target. + /// + /// + /// OCL: let sourceTargetFeature : Feature = sourceTargetFeature() in sourceTargetFeature <> null and sourceTargetFeature.redefinesFromLibrary('ControlFunctions::\'.\'::source::target') + /// + public class FeatureChainExpressionTargetRedefinitionRule : LibraryRedefinitionRule + { + /// + /// Initializes a new instance of the class. + /// + /// The index resolving the library Feature by qualified name. + /// The factory creating the detached Redefinition. + public FeatureChainExpressionTargetRedefinitionRule(ILibraryTypeIndex libraryTypeIndex, IImpliedRelationshipFactory factory) + : base(libraryTypeIndex, factory) + { + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public override string ConstraintName => "checkFeatureChainExpressionTargetRedefinition"; + + /// + /// Returns the Feature that must redefine the library Feature. + /// + /// The Element under evaluation. + /// The Feature and the library qualified name, or null when the constraint does not apply. + protected override (IFeature RedefiningFeature, string LibraryQualifiedName)? QueryRedefinition(IElement element) + { + var redefiningFeature = element is IFeatureChainExpression featureChainExpression ? featureChainExpression.SourceTargetFeature() : null; + + return redefiningFeature == null + ? null + : (redefiningFeature, "ControlFunctions::'.'::source::target"); + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/FeatureEndRedefinitionRule.cs b/SysML2.NET.Semantics/Implied/Rules/FeatureEndRedefinitionRule.cs new file mode 100644 index 00000000..cd275f1b --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/FeatureEndRedefinitionRule.cs @@ -0,0 +1,101 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Core.Types; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Implements checkFeatureEndRedefinition: the nth end Feature of a Type redefines the nth end Feature + /// of each of its supertypes. + /// + /// + /// OCL: isEnd and owningType <> null implies let i : Integer = + /// owningType.ownedEndFeature->indexOf(self) in owningType.ownedSpecialization.general->forAll( + /// supertype | supertype.endFeature->size() >= i implies + /// redefines(supertype.endFeature->at(i))). + /// The correspondence is POSITIONAL, not by name: end 1 redefines end 1. OCL collections are + /// 1-based, so indexOf and at(i) are translated against a 0-based list accordingly, and a + /// supertype with fewer ends than the position contributes nothing. + /// + public class FeatureEndRedefinitionRule : IImpliedRelationshipRule + { + /// + /// The factory creating the detached Redefinitions. + /// + private readonly IImpliedRelationshipFactory factory; + + /// + /// Initializes a new instance of the class. + /// + /// The factory creating the detached Redefinitions. + /// Thrown when is null. + public FeatureEndRedefinitionRule(IImpliedRelationshipFactory factory) + { + this.factory = factory ?? throw new ArgumentNullException(nameof(factory)); + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public string ConstraintName => "checkFeatureEndRedefinition"; + + /// + /// Computes the Redefinitions an end Feature requires towards the corresponding ends of its owning + /// Type's supertypes. + /// + /// The Element under evaluation. + /// One Redefinition per supertype that has an end at the same position; empty otherwise. + /// Thrown when is null. + public IReadOnlyList Apply(IElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + if (element is not IFeature { IsEnd: true, owningType: not null } endFeature) + { + return []; + } + + var position = endFeature.owningType.ownedEndFeature.IndexOf(endFeature); + + if (position < 0) + { + return []; + } + + return + [ + ..endFeature.owningType.ownedSpecialization + .Select(specialization => specialization.General) + .Where(supertype => supertype != null && supertype.endFeature.Count > position) + .Select(supertype => this.factory.CreateImpliedRedefinition(endFeature, supertype.endFeature[position])) + ]; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/FeatureFlowFeatureRedefinitionRule.cs b/SysML2.NET.Semantics/Implied/Rules/FeatureFlowFeatureRedefinitionRule.cs new file mode 100644 index 00000000..b8f524a0 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/FeatureFlowFeatureRedefinitionRule.cs @@ -0,0 +1,86 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using System.Linq; + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Kernel.Interactions; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Implements checkFeatureFlowFeatureRedefinition: the first owned Feature of a Flow's first or second + /// FlowEnd redefines the library source output or target input respectively. + /// + /// + /// OCL: owningType <> null and owningType.oclIsKindOf(FlowEnd) and + /// owningType.ownedFeature->at(1) = self implies let flowType : Type = owningType.owningType in + /// flowType <> null implies let i : Integer = flowType.ownedFeature.indexOf(owningType) in + /// (i = 1 implies redefinesFromLibrary('Transfers::Transfer::source::sourceOutput')) and + /// (i = 2 implies redefinesFromLibrary('Transfers::Transfer::target::targetInput')). + /// OCL positions are 1-based: the FIRST FlowEnd of the flow carries the source output and the + /// SECOND the target input. Any further end is unconstrained, which is why a position outside those two + /// yields nothing. + /// + public class FeatureFlowFeatureRedefinitionRule : LibraryRedefinitionRule + { + /// + /// Initializes a new instance of the class. + /// + /// The index resolving the library Feature by qualified name. + /// The factory creating the detached Redefinition. + public FeatureFlowFeatureRedefinitionRule(ILibraryTypeIndex libraryTypeIndex, IImpliedRelationshipFactory factory) + : base(libraryTypeIndex, factory) + { + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public override string ConstraintName => "checkFeatureFlowFeatureRedefinition"; + + /// + /// Returns the Feature together with the library Feature its FlowEnd's position selects. + /// + /// The Element under evaluation. + /// The Feature and the library qualified name, or null when the constraint does not apply. + protected override (IFeature RedefiningFeature, string LibraryQualifiedName)? QueryRedefinition(IElement element) + { + if (element is not IFeature { owningType: IFlowEnd flowEnd } feature + || !ReferenceEquals(flowEnd.ownedFeature.FirstOrDefault(), feature) + || flowEnd.owningType == null) + { + return null; + } + + var libraryQualifiedName = flowEnd.owningType.ownedFeature.IndexOf(flowEnd) switch + { + 0 => "Transfers::Transfer::source::sourceOutput", + 1 => "Transfers::Transfer::target::targetInput", + _ => null + }; + + return libraryQualifiedName == null + ? null + : (feature, libraryQualifiedName); + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/FeatureOwnedCrossFeatureRedefinitionSpecializationRule.cs b/SysML2.NET.Semantics/Implied/Rules/FeatureOwnedCrossFeatureRedefinitionSpecializationRule.cs new file mode 100644 index 00000000..42d51b47 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/FeatureOwnedCrossFeatureRedefinitionSpecializationRule.cs @@ -0,0 +1,91 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Implements checkFeatureOwnedCrossFeatureRedefinitionSpecialization: an owned cross Feature subsets the + /// cross Features of everything its owner redefines. + /// + /// + /// OCL: isOwnedCrossFeature() implies ownedSubsetting.subsettedFeature->includesAll( + /// owner.oclAsType(Feature).ownedRedefinition.redefinedFeature->select(crossFeature <> null).crossFeature). + /// When the owning Feature redefines another, the cross Feature must line up with that Feature's + /// own cross Feature — so the redefinition of an end carries through to the opposite end. A redefined + /// Feature without a cross Feature contributes nothing. + /// + public class FeatureOwnedCrossFeatureRedefinitionSpecializationRule : IImpliedRelationshipRule + { + /// + /// The factory creating the detached Subsettings. + /// + private readonly IImpliedRelationshipFactory factory; + + /// + /// Initializes a new instance of the class. + /// + /// The factory creating the detached Subsettings. + /// Thrown when is null. + public FeatureOwnedCrossFeatureRedefinitionSpecializationRule(IImpliedRelationshipFactory factory) + { + this.factory = factory ?? throw new ArgumentNullException(nameof(factory)); + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public string ConstraintName => "checkFeatureOwnedCrossFeatureRedefinitionSpecialization"; + + /// + /// Computes the Subsettings an owned cross Feature requires towards the cross Features of the + /// Features its owner redefines. + /// + /// The Element under evaluation. + /// One Subsetting per redefined Feature carrying a cross Feature; empty otherwise. + /// Thrown when is null. + public IReadOnlyList Apply(IElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + if (element is not IFeature crossFeature || !crossFeature.IsOwnedCrossFeature() || crossFeature.owner is not IFeature owningFeature) + { + return []; + } + + return + [ + ..owningFeature.ownedRedefinition + .Select(redefinition => redefinition.RedefinedFeature?.crossFeature) + .Where(redefinedCrossFeature => redefinedCrossFeature != null) + .Select(redefinedCrossFeature => this.factory.CreateImpliedSubsetting(crossFeature, redefinedCrossFeature)) + ]; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/FeatureOwnedCrossFeatureSpecializationRule.cs b/SysML2.NET.Semantics/Implied/Rules/FeatureOwnedCrossFeatureSpecializationRule.cs new file mode 100644 index 00000000..9ba6eb6a --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/FeatureOwnedCrossFeatureSpecializationRule.cs @@ -0,0 +1,94 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using SysML2.NET.Core.POCO.Core.Classifiers; + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Implements checkFeatureOwnedCrossFeatureSpecialization: an owned cross Feature specializes every Type + /// of the Feature that owns it. + /// + /// + /// OCL: isOwnedCrossFeature() implies owner.oclAsType(Feature).type->forAll(t | self.specializes(t)). + /// The cross Feature stands for the other end of its owner, so it carries the owner's typing. The + /// Relationship kind follows the general rule for what "specialize" means of a Feature: a FeatureTyping + /// onto a Classifier, a Subsetting onto a Feature. + /// + public class FeatureOwnedCrossFeatureSpecializationRule : IImpliedRelationshipRule + { + /// + /// The factory creating the detached Relationships. + /// + private readonly IImpliedRelationshipFactory factory; + + /// + /// Initializes a new instance of the class. + /// + /// The factory creating the detached Relationships. + /// Thrown when is null. + public FeatureOwnedCrossFeatureSpecializationRule(IImpliedRelationshipFactory factory) + { + this.factory = factory ?? throw new ArgumentNullException(nameof(factory)); + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public string ConstraintName => "checkFeatureOwnedCrossFeatureSpecialization"; + + /// + /// Computes the Relationships an owned cross Feature requires towards its owner's Types. + /// + /// The Element under evaluation. + /// One Relationship per Type of the owning Feature; empty otherwise. + /// Thrown when is null. + public IReadOnlyList Apply(IElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + if (element is not IFeature crossFeature || !crossFeature.IsOwnedCrossFeature() || crossFeature.owner is not IFeature owningFeature) + { + return []; + } + + return + [ + ..owningFeature.type + .Select(type => type switch + { + IFeature ownerTypeFeature => this.factory.CreateImpliedSubsetting(crossFeature, ownerTypeFeature), + IClassifier ownerTypeClassifier => (IRelationship)this.factory.CreateImpliedFeatureTyping(crossFeature, ownerTypeClassifier), + _ => null + }) + .Where(relationship => relationship != null) + ]; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/FeatureParameterRedefinitionRule.cs b/SysML2.NET.Semantics/Implied/Rules/FeatureParameterRedefinitionRule.cs new file mode 100644 index 00000000..0d0a0754 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/FeatureParameterRedefinitionRule.cs @@ -0,0 +1,135 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using System; + using System.Collections.Generic; + using System.Linq; + + 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.Expressions; + using SysML2.NET.Core.POCO.Kernel.Functions; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Implements checkFeatureParameterRedefinition: the nth parameter of a Behavior or Step redefines the + /// nth parameter of each Behavior or Step it specializes. + /// + /// + /// OCL: owningType <> null and (owningType.oclIsKindOf(Behavior) or + /// owningType.oclIsKindOf(Step) and (owningType.oclIsKindOf(InvocationExpression) implies not + /// ownedRedefinition->exists(not isImplied))) implies let ownerParameters = owningType.ownedFeature + /// ->select(direction <> null)->reject(owningFeatureMembership.oclIsKindOf( + /// ReturnParameterMembership)) in … ownedParameters->size() >= i implies + /// redefines(ownedParameters->at(i)). + /// Parameters are the directed owned Features EXCLUDING the return parameter, matched positionally + /// against the supertype's. An InvocationExpression that already declares an explicit (non-implied) + /// Redefinition is excluded, since the modeller has bound its arguments by hand. + /// + public class FeatureParameterRedefinitionRule : IImpliedRelationshipRule + { + /// + /// The factory creating the detached Redefinitions. + /// + private readonly IImpliedRelationshipFactory factory; + + /// + /// Initializes a new instance of the class. + /// + /// The factory creating the detached Redefinitions. + /// Thrown when is null. + public FeatureParameterRedefinitionRule(IImpliedRelationshipFactory factory) + { + this.factory = factory ?? throw new ArgumentNullException(nameof(factory)); + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public string ConstraintName => "checkFeatureParameterRedefinition"; + + /// + /// Computes the Redefinitions a parameter requires towards the corresponding parameters of its + /// owning Type's supertypes. + /// + /// The Element under evaluation. + /// One Redefinition per supertype with a parameter at the same position; empty otherwise. + /// Thrown when is null. + public IReadOnlyList Apply(IElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + if (element is not IFeature { owningType: not null } parameter || !IsInScope(parameter.owningType)) + { + return []; + } + + var position = QueryParameters(parameter.owningType).IndexOf(parameter); + + if (position < 0) + { + return []; + } + + return + [ + ..parameter.owningType.ownedSpecialization + .Select(specialization => specialization.General) + .Where(supertype => supertype is IBehavior or IStep) + .Select(QueryParameters) + .Where(supertypeParameters => supertypeParameters.Count > position) + .Select(supertypeParameters => this.factory.CreateImpliedRedefinition(parameter, supertypeParameters[position])) + ]; + } + + /// + /// Asserts whether a Type's parameters are subject to the constraint. + /// + /// The Type owning the parameter. + /// True when the Type is a Behavior, or a Step whose explicit Redefinitions do not already bind it. + private static bool IsInScope(IType owningType) + { + return owningType switch + { + IInvocationExpression invocationExpression => !invocationExpression.ownedRedefinition.Any(redefinition => !redefinition.IsImplied), + IBehavior or IStep => true, + _ => false + }; + } + + /// + /// Returns the parameters of a Type: its directed owned Features, excluding the return parameter. + /// + /// The Type to inspect. + /// The parameters, in declaration order. + private static List QueryParameters(IType type) + { + return [..type.ownedFeature + .Where(ownedFeature => ownedFeature.Direction.HasValue) + .Where(ownedFeature => ownedFeature.owningFeatureMembership is not IReturnParameterMembership)]; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/FeatureReferenceExpressionResultSpecializationRule.cs b/SysML2.NET.Semantics/Implied/Rules/FeatureReferenceExpressionResultSpecializationRule.cs new file mode 100644 index 00000000..20e6e585 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/FeatureReferenceExpressionResultSpecializationRule.cs @@ -0,0 +1,88 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using System; + using System.Collections.Generic; + + using SysML2.NET.Core.POCO.Kernel.Expressions; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Implements checkFeatureReferenceExpressionResultSpecialization: the result of a + /// FeatureReferenceExpression subsets the Feature it refers to. + /// + /// + /// OCL: result.owningType() = self and result.specializes(referent). + /// KerML 1.0 §8.4.4.9.3 Feature Reference Expressions (p. 260) gives both the Relationship kind + /// and the reason: the result parameter "also subset the Feature", and although "this subsetting is + /// technically implied by the semantics of the BindingConnector … including the Subsetting relationship + /// allows for simpler static type checking". + /// The first conjunct of the OCL is a precondition, not a second Relationship: it holds only when + /// the result really is this Expression's own parameter, so a result reached from elsewhere implies + /// nothing. + /// + public class FeatureReferenceExpressionResultSpecializationRule : IImpliedRelationshipRule + { + /// + /// The factory creating the detached Subsetting. + /// + private readonly IImpliedRelationshipFactory factory; + + /// + /// Initializes a new instance of the class. + /// + /// The factory creating the detached Subsetting. + /// Thrown when is null. + public FeatureReferenceExpressionResultSpecializationRule(IImpliedRelationshipFactory factory) + { + this.factory = factory ?? throw new ArgumentNullException(nameof(factory)); + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public string ConstraintName => "checkFeatureReferenceExpressionResultSpecialization"; + + /// + /// Computes the Subsetting binding a FeatureReferenceExpression's result to its referent. + /// + /// The Element under evaluation. + /// The Subsetting, or empty when the constraint does not apply. + /// Thrown when is null. + public IReadOnlyList Apply(IElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + if (element is not IFeatureReferenceExpression { result: not null, referent: not null } featureReferenceExpression) + { + return []; + } + + return ReferenceEquals(featureReferenceExpression.result.owningType, featureReferenceExpression) + ? [this.factory.CreateImpliedSubsetting(featureReferenceExpression.result, featureReferenceExpression.referent)] + : []; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/FeatureResultRedefinitionRule.cs b/SysML2.NET.Semantics/Implied/Rules/FeatureResultRedefinitionRule.cs new file mode 100644 index 00000000..26023f39 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/FeatureResultRedefinitionRule.cs @@ -0,0 +1,110 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Core.Types; + using SysML2.NET.Core.POCO.Kernel.Functions; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Implements checkFeatureResultRedefinition: the result parameter of a Function or Expression redefines + /// the result of each Function or Expression it specializes. + /// + /// + /// OCL: owningType <> null and (owningType.oclIsKindOf(Function) and self = + /// owningType.oclAsType(Function).result or owningType.oclIsKindOf(Expression) and self = + /// owningType.oclAsType(Expression).result) implies owningType.ownedSpecialization.general-> + /// select(oclIsKindOf(Function) or oclIsKindOf(Expression))->forAll(supertype | + /// redefines(… supertype's result …)). + /// The Feature must BE its owner's result, not merely be owned by a Function — which is why the + /// identity comparison against result is what gates the rule. + /// + public class FeatureResultRedefinitionRule : IImpliedRelationshipRule + { + /// + /// The factory creating the detached Redefinitions. + /// + private readonly IImpliedRelationshipFactory factory; + + /// + /// Initializes a new instance of the class. + /// + /// The factory creating the detached Redefinitions. + /// Thrown when is null. + public FeatureResultRedefinitionRule(IImpliedRelationshipFactory factory) + { + this.factory = factory ?? throw new ArgumentNullException(nameof(factory)); + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public string ConstraintName => "checkFeatureResultRedefinition"; + + /// + /// Computes the Redefinitions a result parameter requires towards the results of its owning Type's + /// Function or Expression supertypes. + /// + /// The Element under evaluation. + /// One Redefinition per Function or Expression supertype that has a result; empty otherwise. + /// Thrown when is null. + public IReadOnlyList Apply(IElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + if (element is not IFeature { owningType: not null } feature || !ReferenceEquals(QueryResult(feature.owningType), feature)) + { + return []; + } + + return + [ + ..feature.owningType.ownedSpecialization + .Select(specialization => QueryResult(specialization.General)) + .Where(supertypeResult => supertypeResult != null) + .Select(supertypeResult => this.factory.CreateImpliedRedefinition(feature, supertypeResult)) + ]; + } + + /// + /// Returns the result parameter of a Type when it is a Function or an Expression. + /// + /// The Type to inspect, which may be null. + /// The result parameter, or null when the Type has none. + private static IFeature QueryResult(IType type) + { + return type switch + { + IFunction function => function.result, + IExpression expression => expression.result, + _ => null + }; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/FeatureValuationSpecializationRule.cs b/SysML2.NET.Semantics/Implied/Rules/FeatureValuationSpecializationRule.cs new file mode 100644 index 00000000..3aee4d95 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/FeatureValuationSpecializationRule.cs @@ -0,0 +1,99 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Kernel.FeatureValues; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Implements checkFeatureValuationSpecialization: an undeclared, undirected Feature with a value takes + /// its typing from that value by subsetting the value Expression's result. + /// + /// + /// OCL: direction = null and ownedSpecializations->forAll(isImplied) implies + /// ownedMembership->selectByKind(FeatureValue)->forAll(fv | specializes(fv.value.result)). + /// KerML 1.0 §8.4.4.11 Feature Values (p. 265): "if the featureWithValue has no explicit + /// ownedSpecializations and is not directed, then it SUBSETS the result parameter of the value + /// Expression. This reflects the semantics that the values of the featureWithValue is determined by the + /// value Expression, giving the featureWithValue an implied typing that is useful for static type + /// checking." + /// The converse is the reason for the guard: a Feature that DOES declare a Specialization, or that + /// is directed, already has its static typing from its declaration, and the spec says that typing + /// "should then be validated against" the value's result rather than derived from it. Note that + /// "no explicit ownedSpecializations" means every one of them is implied — not that there are none. + /// + public class FeatureValuationSpecializationRule : IImpliedRelationshipRule + { + /// + /// The factory creating the detached Subsetting. + /// + private readonly IImpliedRelationshipFactory factory; + + /// + /// Initializes a new instance of the class. + /// + /// The factory creating the detached Subsetting. + /// Thrown when is null. + public FeatureValuationSpecializationRule(IImpliedRelationshipFactory factory) + { + this.factory = factory ?? throw new ArgumentNullException(nameof(factory)); + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public string ConstraintName => "checkFeatureValuationSpecialization"; + + /// + /// Computes the Subsetting a valued Feature takes from its value Expression's result. + /// + /// The Element under evaluation. + /// One Subsetting per FeatureValue carrying a result; empty otherwise. + /// Thrown when is null. + public IReadOnlyList Apply(IElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + if (element is not IFeature { Direction: null } feature + || feature.ownedSpecialization.Any(specialization => !specialization.IsImplied)) + { + return []; + } + + return + [ + ..feature.ownedMembership + .OfType() + .Select(featureValue => featureValue.value?.result) + .Where(valueResult => valueResult != null) + .Select(valueResult => this.factory.CreateImpliedSubsetting(feature, valueResult)) + ]; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/ForLoopActionUsageVarRedefinitionRule.cs b/SysML2.NET.Semantics/Implied/Rules/ForLoopActionUsageVarRedefinitionRule.cs new file mode 100644 index 00000000..a92e99ad --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/ForLoopActionUsageVarRedefinitionRule.cs @@ -0,0 +1,66 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Systems.Actions; + + /// + /// Implements checkForLoopActionUsageVarRedefinition: the loop variable of a ForLoopActionUsage redefines + /// Actions::ForLoopAction::var. + /// + /// + /// OCL: loopVariable <> null and + /// loopVariable.redefinesFromLibrary('Actions::ForLoopAction::var'). The redefining Feature is the + /// loop VARIABLE, not the ForLoopActionUsage itself — which is why the rule is keyed on the loop action + /// but produces a Redefinition owned by a different Feature. + /// + public class ForLoopActionUsageVarRedefinitionRule : LibraryRedefinitionRule + { + /// + /// Initializes a new instance of the class. + /// + /// The index resolving the library Feature by qualified name. + /// The factory creating the detached Redefinition. + public ForLoopActionUsageVarRedefinitionRule(ILibraryTypeIndex libraryTypeIndex, IImpliedRelationshipFactory factory) + : base(libraryTypeIndex, factory) + { + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public override string ConstraintName => "checkForLoopActionUsageVarRedefinition"; + + /// + /// Returns the loop variable of a ForLoopActionUsage as the redefining Feature. + /// + /// The Element under evaluation. + /// The loop variable and the library qualified name, or null when there is none. + protected override (IFeature RedefiningFeature, string LibraryQualifiedName)? QueryRedefinition(IElement element) + { + return element is IForLoopActionUsage { loopVariable: not null } forLoopActionUsage + ? (forLoopActionUsage.loopVariable, "Actions::ForLoopAction::var") + : null; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/IfActionUsageSpecializationRule.cs b/SysML2.NET.Semantics/Implied/Rules/IfActionUsageSpecializationRule.cs new file mode 100644 index 00000000..6c540da5 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/IfActionUsageSpecializationRule.cs @@ -0,0 +1,71 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Systems.Actions; + + /// + /// Implements checkIfActionUsageSpecialization: an if action subsets the two-branch or three-branch + /// library action according to whether it declares an else branch. + /// + /// + /// OCL: if elseAction = null then specializesFromLibrary('Actions::ifThenActions') + /// else specializesFromLibrary('Actions::ifThenElseActions') endif. + /// + public class IfActionUsageSpecializationRule : LibrarySpecializationRule + { + /// + /// Initializes a new instance of the class. + /// + /// The index resolving the library Feature by qualified name. + /// The factory creating the detached Subsetting. + public IfActionUsageSpecializationRule(ILibraryTypeIndex libraryTypeIndex, IImpliedRelationshipFactory factory) + : base(libraryTypeIndex, factory) + { + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public override string ConstraintName => "checkIfActionUsageSpecialization"; + + /// + /// Returns the if action together with the library Feature its else branch selects. + /// + /// The Element under evaluation. + /// The Feature and the library qualified name, or null when the constraint does not apply. + protected override (IFeature SpecificFeature, string LibraryQualifiedName)? QuerySpecialization(IElement element) + { + if (element is not IIfActionUsage ifActionUsage) + { + return null; + } + + var libraryQualifiedName = ifActionUsage.elseAction == null + ? "Actions::ifThenActions" + : "Actions::ifThenElseActions"; + + return (ifActionUsage, libraryQualifiedName); + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/IndexExpressionResultSpecializationRule.cs b/SysML2.NET.Semantics/Implied/Rules/IndexExpressionResultSpecializationRule.cs new file mode 100644 index 00000000..dc895c2f --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/IndexExpressionResultSpecializationRule.cs @@ -0,0 +1,71 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Kernel.Expressions; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Implements checkIndexExpressionResultSpecialization: the result of an IndexExpression subsets the + /// result of the collection it indexes, unless that collection is an Array. + /// + /// + /// OCL: arguments->notEmpty() and not + /// arguments->first().result.specializesFromLibrary('Collections::Array') implies + /// result.specializes(arguments->first().result). + /// Indexing an Array is excluded because an Array's element type is not a subset of the Array — + /// the Subsetting that holds for an ordinary collection would be wrong there. + /// + public class IndexExpressionResultSpecializationRule : ArgumentResultSpecializationRule + { + /// + /// Initializes a new instance of the class. + /// + /// The factory creating the detached Subsetting. + public IndexExpressionResultSpecializationRule(IImpliedRelationshipFactory factory) + : base(factory) + { + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public override string ConstraintName => "checkIndexExpressionResultSpecialization"; + + /// + /// Asserts whether the Element is an IndexExpression. + /// + /// The Element under evaluation. + /// True when the Element is an IndexExpression. + protected override bool IsInScope(IElement element) => element is IIndexExpression; + + /// + /// Excludes an indexed Array. + /// + /// The result parameter of the first argument Expression. + /// True unless the indexed collection specializes the library Array. + protected override bool AppliesTo(IFeature firstArgumentResult) + { + return !firstArgumentResult.SpecializesFromLibrary("Collections::Array"); + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/InvariantSpecializationRule.cs b/SysML2.NET.Semantics/Implied/Rules/InvariantSpecializationRule.cs new file mode 100644 index 00000000..4184188b --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/InvariantSpecializationRule.cs @@ -0,0 +1,71 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Kernel.Functions; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Implements checkInvariantSpecialization: an Invariant subsets the library evaluations for the truth + /// value it asserts. + /// + /// + /// OCL: if isNegated then specializesFromLibrary('Performances::falseEvaluations') + /// else specializesFromLibrary('Performances::trueEvaluations') endif. + /// + public class InvariantSpecializationRule : LibrarySpecializationRule + { + /// + /// Initializes a new instance of the class. + /// + /// The index resolving the library Feature by qualified name. + /// The factory creating the detached Subsetting. + public InvariantSpecializationRule(ILibraryTypeIndex libraryTypeIndex, IImpliedRelationshipFactory factory) + : base(libraryTypeIndex, factory) + { + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public override string ConstraintName => "checkInvariantSpecialization"; + + /// + /// Returns the Invariant together with the library Feature its negation selects. + /// + /// The Element under evaluation. + /// The Feature and the library qualified name, or null when the constraint does not apply. + protected override (IFeature SpecificFeature, string LibraryQualifiedName)? QuerySpecialization(IElement element) + { + if (element is not IInvariant invariant) + { + return null; + } + + var libraryQualifiedName = invariant.IsNegated + ? "Performances::falseEvaluations" + : "Performances::trueEvaluations"; + + return (invariant, libraryQualifiedName); + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/InvocationExpressionBehaviorResultSpecializationRule.cs b/SysML2.NET.Semantics/Implied/Rules/InvocationExpressionBehaviorResultSpecializationRule.cs new file mode 100644 index 00000000..e7202eb1 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/InvocationExpressionBehaviorResultSpecializationRule.cs @@ -0,0 +1,112 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using System; + using System.Collections.Generic; + using System.Linq; + + 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.Expressions; + using SysML2.NET.Core.POCO.Kernel.Functions; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Implements checkInvocationExpressionBehaviorResultSpecialization: when an InvocationExpression + /// instantiates a Behavior that is NOT a Function, its result parameter specializes that Behavior. + /// + /// + /// OCL: not instantiatedType.oclIsKindOf(Function) and not + /// (instantiatedType.oclIsKindOf(Feature) and + /// instantiatedType.oclAsType(Feature).type->exists(oclIsKindOf(Function))) implies + /// result.specializes(instantiatedType). + /// KerML 1.0 §8.4.4.9.5 Invocation Expressions (p. 262): "the result parameter of the expression + /// specialize the instantiatedType" — the expression "evaluates, as an Expression, to itself, as an + /// instance of B". A Function is excluded because a Function already declares its own result, so the + /// invocation's result takes that instead of the Function itself. + /// The Relationship kind follows the general rule the specification states for what "specialize" + /// means of a Feature (§8.4.4.9.4): a FeatureTyping onto a Classifier, a Subsetting onto a Feature. + /// Unlike , this clause does not name one kind, and + /// the guard here explicitly contemplates a Feature instantiatedType — so both cases are live. + /// + public class InvocationExpressionBehaviorResultSpecializationRule : IImpliedRelationshipRule + { + /// + /// The factory creating the detached Relationship. + /// + private readonly IImpliedRelationshipFactory factory; + + /// + /// Initializes a new instance of the class. + /// + /// The factory creating the detached Relationship. + /// Thrown when is null. + public InvocationExpressionBehaviorResultSpecializationRule(IImpliedRelationshipFactory factory) + { + this.factory = factory ?? throw new ArgumentNullException(nameof(factory)); + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public string ConstraintName => "checkInvocationExpressionBehaviorResultSpecialization"; + + /// + /// Computes the Relationship binding an InvocationExpression's result to the Behavior it instantiates. + /// + /// The Element under evaluation. + /// The FeatureTyping or Subsetting, or empty when the constraint does not apply. + /// Thrown when is null. + public IReadOnlyList Apply(IElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + if (element is not IInvocationExpression { result: not null, instantiatedType: not null } invocationExpression + || IsFunctionValued(invocationExpression.instantiatedType)) + { + return []; + } + + return invocationExpression.instantiatedType switch + { + IFeature instantiatedFeature => [this.factory.CreateImpliedSubsetting(invocationExpression.result, instantiatedFeature)], + IClassifier instantiatedClassifier => [this.factory.CreateImpliedFeatureTyping(invocationExpression.result, instantiatedClassifier)], + _ => [] + }; + } + + /// + /// Asserts whether a Type is a Function, or a Feature typed by one. + /// + /// The Type being instantiated. + /// True when the Type resolves to a Function. + private static bool IsFunctionValued(IType instantiatedType) + { + return instantiatedType is IFunction + || (instantiatedType is IFeature instantiatedFeature && instantiatedFeature.type.Any(type => type is IFunction)); + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/InvocationExpressionSpecializationRule.cs b/SysML2.NET.Semantics/Implied/Rules/InvocationExpressionSpecializationRule.cs new file mode 100644 index 00000000..8f1ade44 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/InvocationExpressionSpecializationRule.cs @@ -0,0 +1,81 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using System; + using System.Collections.Generic; + + using SysML2.NET.Core.POCO.Kernel.Expressions; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Implements checkInvocationExpressionSpecialization: an InvocationExpression is typed by the Type it + /// instantiates. + /// + /// + /// OCL: specializes(instantiatedType). + /// KerML 1.0 §8.4.4.9.5 Invocation Expressions (p. 262) supplies the Relationship kind the OCL + /// omits: an InvocationExpression specializes its instantiatedType "via a FeatureTyping" — always, with + /// no dependence on whether the instantiatedType is a Classifier or a Feature. This differs from + /// , where the kind DOES depend on that, so + /// the two must not be generalised into one rule. + /// + public class InvocationExpressionSpecializationRule : IImpliedRelationshipRule + { + /// + /// The factory creating the detached FeatureTyping. + /// + private readonly IImpliedRelationshipFactory factory; + + /// + /// Initializes a new instance of the class. + /// + /// The factory creating the detached FeatureTyping. + /// Thrown when is null. + public InvocationExpressionSpecializationRule(IImpliedRelationshipFactory factory) + { + this.factory = factory ?? throw new ArgumentNullException(nameof(factory)); + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public string ConstraintName => "checkInvocationExpressionSpecialization"; + + /// + /// Computes the FeatureTyping binding an InvocationExpression to the Type it instantiates. + /// + /// The Element under evaluation. + /// The FeatureTyping, or empty when the constraint does not apply. + /// Thrown when is null. + public IReadOnlyList Apply(IElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + return element is not IInvocationExpression { instantiatedType: not null } invocationExpression + ? [] + : [this.factory.CreateImpliedFeatureTyping(invocationExpression, invocationExpression.instantiatedType)]; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/LibraryRedefinitionRule.cs b/SysML2.NET.Semantics/Implied/Rules/LibraryRedefinitionRule.cs new file mode 100644 index 00000000..348ab437 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/LibraryRedefinitionRule.cs @@ -0,0 +1,108 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using System; + using System.Collections.Generic; + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// The shared behaviour of the constraints expressed as redefinesFromLibrary('…'). + /// + /// + /// These constraints all resolve a library Feature by qualified name and require a Redefinition to it. + /// What differs between them is only WHICH Feature must redefine it — sometimes the Element itself, + /// sometimes one reached by navigation — so a subclass supplies just that, via + /// . + /// + public abstract class LibraryRedefinitionRule : IImpliedRelationshipRule + { + /// + /// Initializes a new instance of the class. + /// + /// The index resolving the library Feature by qualified name. + /// The factory creating the detached Redefinition. + /// Thrown when either argument is null. + protected LibraryRedefinitionRule(ILibraryTypeIndex libraryTypeIndex, IImpliedRelationshipFactory factory) + { + this.LibraryTypeIndex = libraryTypeIndex ?? throw new ArgumentNullException(nameof(libraryTypeIndex)); + this.Factory = factory ?? throw new ArgumentNullException(nameof(factory)); + } + + /// + /// Gets the name of the semantic constraint the concrete rule implements. + /// + public abstract string ConstraintName { get; } + + /// + /// Gets the index resolving the library Feature by qualified name. + /// + protected ILibraryTypeIndex LibraryTypeIndex { get; } + + /// + /// Gets the factory creating the detached Redefinition. + /// + protected IImpliedRelationshipFactory Factory { get; } + + /// + /// Computes the implied Redefinition the constraint requires of the supplied Element. + /// + /// The Element under evaluation. + /// A single Redefinition, or empty when the constraint does not apply. + /// Thrown when is null. + /// Thrown when the targeted library Feature is not indexed. + public IReadOnlyList Apply(IElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + var redefinition = this.QueryRedefinition(element); + + if (redefinition == null) + { + return []; + } + + var (redefiningFeature, libraryQualifiedName) = redefinition.Value; + + if (!this.LibraryTypeIndex.TryGetType(libraryQualifiedName, out var libraryType)) + { + throw new UnresolvedLibraryTypeException(libraryQualifiedName, this.ConstraintName); + } + + return libraryType is IFeature libraryFeature + ? [this.Factory.CreateImpliedRedefinition(redefiningFeature, libraryFeature)] + : []; + } + + /// + /// Returns the Feature that must redefine a library Feature, together with that Feature's qualified + /// name. + /// + /// The Element under evaluation, never null. + /// The redefining Feature and the library qualified name, or null when the constraint does not apply. + protected abstract (IFeature RedefiningFeature, string LibraryQualifiedName)? QueryRedefinition(IElement element); + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/LibrarySpecializationRule.cs b/SysML2.NET.Semantics/Implied/Rules/LibrarySpecializationRule.cs new file mode 100644 index 00000000..fff72141 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/LibrarySpecializationRule.cs @@ -0,0 +1,111 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using System; + using System.Collections.Generic; + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Base for a rule whose OCL selects BETWEEN library Features by a condition, rather than naming one + /// unconditionally. + /// + /// + /// The generated table carries one target per row, so it can express + /// specializesFromLibrary(X) and C implies specializesFromLibrary(X) but not + /// if C then specializesFromLibrary(X) else specializesFromLibrary(Y) endif — the target itself + /// varies. Those constraints therefore fall to the not-covered manifest and are hand-coded on this base. + /// The implied Relationship is a Subsetting: every metaclass in this family is a Usage or + /// an Expression, hence a Feature, and Subclassification applies only to the Classifier metaclasses the + /// table tracks separately. + /// + public abstract class LibrarySpecializationRule : IImpliedRelationshipRule + { + /// + /// Initializes a new instance of the class. + /// + /// The index resolving the library Feature by qualified name. + /// The factory creating the detached Subsetting. + /// Thrown when either argument is null. + protected LibrarySpecializationRule(ILibraryTypeIndex libraryTypeIndex, IImpliedRelationshipFactory factory) + { + this.LibraryTypeIndex = libraryTypeIndex ?? throw new ArgumentNullException(nameof(libraryTypeIndex)); + this.Factory = factory ?? throw new ArgumentNullException(nameof(factory)); + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public abstract string ConstraintName { get; } + + /// + /// Gets the index resolving the library Feature by qualified name. + /// + protected ILibraryTypeIndex LibraryTypeIndex { get; } + + /// + /// Gets the factory creating the detached Subsetting. + /// + protected IImpliedRelationshipFactory Factory { get; } + + /// + /// Computes the Subsetting the Element requires towards the library Feature its condition selects. + /// + /// The Element under evaluation. + /// The Subsetting, or empty when the constraint does not apply. + /// Thrown when is null. + /// Thrown when the selected library Feature is not indexed. + public IReadOnlyList Apply(IElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + var specialization = this.QuerySpecialization(element); + + if (specialization == null) + { + return []; + } + + var (specificFeature, libraryQualifiedName) = specialization.Value; + + if (!this.LibraryTypeIndex.TryGetType(libraryQualifiedName, out var libraryType)) + { + throw new UnresolvedLibraryTypeException(libraryQualifiedName, this.ConstraintName); + } + + return libraryType is IFeature libraryFeature + ? [this.Factory.CreateImpliedSubsetting(specificFeature, libraryFeature)] + : []; + } + + /// + /// Returns the Feature together with the library Feature the constraint's condition selects. + /// + /// The Element under evaluation. + /// The Feature and the library qualified name, or null when the constraint does not apply. + protected abstract (IFeature SpecificFeature, string LibraryQualifiedName)? QuerySpecialization(IElement element); + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/MergeNodeIncomingSuccessionSpecializationRule.cs b/SysML2.NET.Semantics/Implied/Rules/MergeNodeIncomingSuccessionSpecializationRule.cs new file mode 100644 index 00000000..c90d028a --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/MergeNodeIncomingSuccessionSpecializationRule.cs @@ -0,0 +1,72 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using System.Linq; + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Kernel.Connectors; + using SysML2.NET.Core.POCO.Systems.Actions; + + /// + /// Implements checkMergeNodeIncomingSuccessionSpecialization: a Succession arriving at a MergeNode + /// subsets that node's incoming happens-before link. + /// + /// + /// OCL: targetConnector->selectByKind(Succession)->forAll(subsetsChain(self, + /// resolveGlobal('ControlPerformances::MergePerformance::incomingHBLink'))). + /// Evaluated on the Succession — see for why that is + /// equivalent to the OCL's reverse navigation from the node. + /// + public class MergeNodeIncomingSuccessionSpecializationRule : ControlNodeSuccessionChainRule + { + /// + /// Initializes a new instance of the class. + /// + /// The index resolving the library Feature by qualified name. + /// The factory creating the chain and the Subsetting. + public MergeNodeIncomingSuccessionSpecializationRule(ILibraryTypeIndex libraryTypeIndex, IImpliedRelationshipFactory factory) + : base(libraryTypeIndex, factory) + { + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public override string ConstraintName => "checkMergeNodeIncomingSuccessionSpecialization"; + + /// + /// Gets the qualified name of the library happens-before link the chain ends in. + /// + protected override string LinkQualifiedName => "ControlPerformances::MergePerformance::incomingHBLink"; + + /// + /// Returns the MergeNode the Succession arrives at, if it arrives at one. + /// + /// The Succession under evaluation. + /// The MergeNode, or null when no target end is one. + protected override IFeature QueryControlNode(ISuccession succession) + { + // INCOMING: the node is a TARGET end, and targetFeature is [0..*] rather than single. + return succession.targetFeature.OfType().FirstOrDefault(); + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/OccurrenceDefinitionMultiplicitySpecializationRule.cs b/SysML2.NET.Semantics/Implied/Rules/OccurrenceDefinitionMultiplicitySpecializationRule.cs new file mode 100644 index 00000000..18eec1f6 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/OccurrenceDefinitionMultiplicitySpecializationRule.cs @@ -0,0 +1,68 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Systems.Occurrences; + + /// + /// Implements checkOccurrenceDefinitionMultiplicitySpecialization: an individual OccurrenceDefinition + /// has at most one instance. + /// + /// + /// OCL: isIndividual implies multiplicity <> null and + /// multiplicity.specializesFromLibrary('Base::zeroOrOne'). + /// The Subsetting is carried by the definition's MULTIPLICITY, not by the definition itself — a + /// Multiplicity IS a Feature, so it can subset a library Feature in its own right. Declaring an + /// OccurrenceDefinition individual asserts it denotes a single thing, which is what bounding its + /// multiplicity to Base::zeroOrOne expresses. + /// + public class OccurrenceDefinitionMultiplicitySpecializationRule : LibrarySpecializationRule + { + /// + /// Initializes a new instance of the class. + /// + /// The index resolving the library Feature by qualified name. + /// The factory creating the detached Subsetting. + public OccurrenceDefinitionMultiplicitySpecializationRule(ILibraryTypeIndex libraryTypeIndex, IImpliedRelationshipFactory factory) + : base(libraryTypeIndex, factory) + { + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public override string ConstraintName => "checkOccurrenceDefinitionMultiplicitySpecialization"; + + /// + /// Returns an individual OccurrenceDefinition's multiplicity together with the library bound. + /// + /// The Element under evaluation. + /// The Feature and the library qualified name, or null when the constraint does not apply. + protected override (IFeature SpecificFeature, string LibraryQualifiedName)? QuerySpecialization(IElement element) + { + return element is not IOccurrenceDefinition { IsIndividual: true, multiplicity: not null } occurrenceDefinition + ? null + : (occurrenceDefinition.multiplicity, "Base::zeroOrOne"); + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/PartUsageActorSpecializationRule.cs b/SysML2.NET.Semantics/Implied/Rules/PartUsageActorSpecializationRule.cs new file mode 100644 index 00000000..55532fad --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/PartUsageActorSpecializationRule.cs @@ -0,0 +1,75 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Systems.Parts; + using SysML2.NET.Core.POCO.Systems.Requirements; + + /// + /// Implements checkPartUsageActorSpecialization: an actor parameter subsets the library actors of the + /// requirement or the case that owns it. + /// + /// + /// OCL: owningFeatureMembership <> null and + /// owningFeatureMembership.oclIsKindOf(ActorMembership) implies if + /// owningType.oclIsKindOf(RequirementDefinition) or owningType.oclIsKindOf(RequirementUsage) then + /// specializesFromLibrary('Requirements::RequirementCheck::actors') else + /// specializesFromLibrary('Cases::Case::actors'). + /// The else branch is the DEFAULT, not a case-only branch: an actor owned by anything other than a + /// requirement — a case, or any other Type that admits an ActorMembership — takes Cases::Case::actors. + /// + public class PartUsageActorSpecializationRule : LibrarySpecializationRule + { + /// + /// Initializes a new instance of the class. + /// + /// The index resolving the library Feature by qualified name. + /// The factory creating the detached Subsetting. + public PartUsageActorSpecializationRule(ILibraryTypeIndex libraryTypeIndex, IImpliedRelationshipFactory factory) + : base(libraryTypeIndex, factory) + { + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public override string ConstraintName => "checkPartUsageActorSpecialization"; + + /// + /// Returns the actor parameter together with the library Feature its owning Type selects. + /// + /// The Element under evaluation. + /// The Feature and the library qualified name, or null when the constraint does not apply. + protected override (IFeature SpecificFeature, string LibraryQualifiedName)? QuerySpecialization(IElement element) + { + if (element is not IPartUsage { owningFeatureMembership: IActorMembership } actor) + { + return null; + } + + return (actor, actor.owningType is IRequirementDefinition or IRequirementUsage + ? "Requirements::RequirementCheck::actors" + : "Cases::Case::actors"); + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/PayloadFeatureRedefinitionRule.cs b/SysML2.NET.Semantics/Implied/Rules/PayloadFeatureRedefinitionRule.cs new file mode 100644 index 00000000..4d11f409 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/PayloadFeatureRedefinitionRule.cs @@ -0,0 +1,63 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Kernel.Interactions; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Implements checkPayloadFeatureRedefinition: a PayloadFeature redefines Transfers::Transfer::payload. + /// + /// + /// OCL: redefinesFromLibrary('Transfers::Transfer::payload') — unconditional, so every + /// PayloadFeature carries it. + /// + public class PayloadFeatureRedefinitionRule : LibraryRedefinitionRule + { + /// + /// Initializes a new instance of the class. + /// + /// The index resolving the library Feature by qualified name. + /// The factory creating the detached Redefinition. + public PayloadFeatureRedefinitionRule(ILibraryTypeIndex libraryTypeIndex, IImpliedRelationshipFactory factory) + : base(libraryTypeIndex, factory) + { + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public override string ConstraintName => "checkPayloadFeatureRedefinition"; + + /// + /// Returns the PayloadFeature itself as the redefining Feature. + /// + /// The Element under evaluation. + /// The Element and the library qualified name, or null when it is not a PayloadFeature. + protected override (IFeature RedefiningFeature, string LibraryQualifiedName)? QueryRedefinition(IElement element) + { + return element is IPayloadFeature payloadFeature + ? (payloadFeature, "Transfers::Transfer::payload") + : null; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/RenderingUsageRedefinitionRule.cs b/SysML2.NET.Semantics/Implied/Rules/RenderingUsageRedefinitionRule.cs new file mode 100644 index 00000000..ad11c651 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/RenderingUsageRedefinitionRule.cs @@ -0,0 +1,66 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Systems.Views; + + /// + /// Implements checkRenderingUsageRedefinition: a RenderingUsage owned by a ViewRenderingMembership + /// redefines Views::View::viewRendering. + /// + /// + /// OCL: owningFeatureMembership <> null and + /// owningFeatureMembership.oclIsKindOf(ViewRenderingMembership) implies + /// redefinesFromLibrary('Views::View::viewRendering'). A RenderingUsage owned any other way is out + /// of scope. + /// + public class RenderingUsageRedefinitionRule : LibraryRedefinitionRule + { + /// + /// Initializes a new instance of the class. + /// + /// The index resolving the library Feature by qualified name. + /// The factory creating the detached Redefinition. + public RenderingUsageRedefinitionRule(ILibraryTypeIndex libraryTypeIndex, IImpliedRelationshipFactory factory) + : base(libraryTypeIndex, factory) + { + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public override string ConstraintName => "checkRenderingUsageRedefinition"; + + /// + /// Returns the RenderingUsage itself when it is owned by a ViewRenderingMembership. + /// + /// The Element under evaluation. + /// The Element and the library qualified name, or null when the constraint does not apply. + protected override (IFeature RedefiningFeature, string LibraryQualifiedName)? QueryRedefinition(IElement element) + { + return element is IRenderingUsage { owningFeatureMembership: IViewRenderingMembership } renderingUsage + ? (renderingUsage, "Views::View::viewRendering") + : null; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/RequirementUsageObjectiveRedefinitionRule.cs b/SysML2.NET.Semantics/Implied/Rules/RequirementUsageObjectiveRedefinitionRule.cs new file mode 100644 index 00000000..5785d75b --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/RequirementUsageObjectiveRedefinitionRule.cs @@ -0,0 +1,113 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Core.Types; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Systems.Cases; + using SysML2.NET.Core.POCO.Systems.Requirements; + + /// + /// Implements checkRequirementUsageObjectiveRedefinition: the objective of a case redefines the + /// objective requirement of each case it specializes. + /// + /// + /// OCL: owningfeatureMembership <> null and + /// owningfeatureMembership.oclIsKindOf(ObjectiveMembership) implies + /// owningType.ownedSpecialization.general->forAll(gen | + /// (gen.oclIsKindOf(CaseDefinition) implies redefines(gen.oclAsType(CaseDefinition).objectiveRequirement)) + /// and (gen.oclIsKindOf(Feature) and gen.oclAsType(Feature).featureTarget.oclIsKindOf(CaseUsage) implies + /// redefines(gen.oclAsType(Feature).featureTarget.oclAsType(CaseUsage).objectiveRequirement))). + /// A supertype reached as a Feature is resolved through its featureTarget before its + /// objective is taken, so a case USAGE supertype contributes as well as a case DEFINITION. + /// + public class RequirementUsageObjectiveRedefinitionRule : IImpliedRelationshipRule + { + /// + /// The factory creating the detached Redefinitions. + /// + private readonly IImpliedRelationshipFactory factory; + + /// + /// Initializes a new instance of the class. + /// + /// The factory creating the detached Redefinitions. + /// Thrown when is null. + public RequirementUsageObjectiveRedefinitionRule(IImpliedRelationshipFactory factory) + { + this.factory = factory ?? throw new ArgumentNullException(nameof(factory)); + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public string ConstraintName => "checkRequirementUsageObjectiveRedefinition"; + + /// + /// Computes the Redefinitions an objective requirement requires towards the objectives of its owning + /// Type's case supertypes. + /// + /// The Element under evaluation. + /// One Redefinition per case supertype carrying an objective; empty otherwise. + /// Thrown when is null. + public IReadOnlyList Apply(IElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + if (element is not IRequirementUsage { owningFeatureMembership: IObjectiveMembership, owningType: not null } objective) + { + return []; + } + + return + [ + ..objective.owningType.ownedSpecialization + .Select(specialization => QueryObjectiveRequirement(specialization.General)) + .Where(supertypeObjective => supertypeObjective != null) + .Select(supertypeObjective => this.factory.CreateImpliedRedefinition(objective, supertypeObjective)) + ]; + } + + /// + /// Returns the objective requirement of a supertype, resolving a Feature supertype through its + /// feature target first. + /// + /// The supertype to inspect, which may be null. + /// The objective requirement, or null when the supertype is not a case. + private static IRequirementUsage QueryObjectiveRequirement(IType supertype) + { + return supertype switch + { + ICaseDefinition caseDefinition => caseDefinition.objectiveRequirement, + IFeature { featureTarget: ICaseUsage caseUsage } => caseUsage.objectiveRequirement, + _ => null + }; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/SatisfyRequirementUsageSpecializationRule.cs b/SysML2.NET.Semantics/Implied/Rules/SatisfyRequirementUsageSpecializationRule.cs new file mode 100644 index 00000000..db760c2a --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/SatisfyRequirementUsageSpecializationRule.cs @@ -0,0 +1,73 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Systems.Requirements; + + /// + /// Implements checkSatisfyRequirementUsageSpecialization: a satisfy assertion subsets the library checks + /// for the sense in which the requirement is asserted to be satisfied. + /// + /// + /// OCL: if isNegated then specializesFromLibrary('Requirements::notSatisfiedRequirementChecks') + /// else specializesFromLibrary('Requirements::satisfiedRequirementChecks'). + /// Takes precedence over , which excludes + /// this metaclass for that reason. + /// + public class SatisfyRequirementUsageSpecializationRule : LibrarySpecializationRule + { + /// + /// Initializes a new instance of the class. + /// + /// The index resolving the library Feature by qualified name. + /// The factory creating the detached Subsetting. + public SatisfyRequirementUsageSpecializationRule(ILibraryTypeIndex libraryTypeIndex, IImpliedRelationshipFactory factory) + : base(libraryTypeIndex, factory) + { + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public override string ConstraintName => "checkSatisfyRequirementUsageSpecialization"; + + /// + /// Returns the satisfy assertion together with the library Feature its negation selects. + /// + /// The Element under evaluation. + /// The Feature and the library qualified name, or null when the constraint does not apply. + protected override (IFeature SpecificFeature, string LibraryQualifiedName)? QuerySpecialization(IElement element) + { + if (element is not ISatisfyRequirementUsage satisfyRequirementUsage) + { + return null; + } + + var libraryQualifiedName = satisfyRequirementUsage.IsNegated + ? "Requirements::notSatisfiedRequirementChecks" + : "Requirements::satisfiedRequirementChecks"; + + return (satisfyRequirementUsage, libraryQualifiedName); + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/SelectExpressionResultSpecializationRule.cs b/SysML2.NET.Semantics/Implied/Rules/SelectExpressionResultSpecializationRule.cs new file mode 100644 index 00000000..345f4ad7 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/SelectExpressionResultSpecializationRule.cs @@ -0,0 +1,57 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using SysML2.NET.Core.POCO.Kernel.Expressions; + using SysML2.NET.Core.POCO.Root.Elements; + + /// + /// Implements checkSelectExpressionResultSpecialization: the result of a SelectExpression subsets the + /// result of the collection it selects from. + /// + /// + /// OCL: arguments->notEmpty() implies result.specializes(arguments->first().result). + /// Selecting from a collection yields a subset of it, so the result subsets the source's result. + /// + public class SelectExpressionResultSpecializationRule : ArgumentResultSpecializationRule + { + /// + /// Initializes a new instance of the class. + /// + /// The factory creating the detached Subsetting. + public SelectExpressionResultSpecializationRule(IImpliedRelationshipFactory factory) + : base(factory) + { + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public override string ConstraintName => "checkSelectExpressionResultSpecialization"; + + /// + /// Asserts whether the Element is a SelectExpression. + /// + /// The Element under evaluation. + /// True when the Element is a SelectExpression. + protected override bool IsInScope(IElement element) => element is ISelectExpression; + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/TransitionUsagePayloadSpecializationRule.cs b/SysML2.NET.Semantics/Implied/Rules/TransitionUsagePayloadSpecializationRule.cs new file mode 100644 index 00000000..fd7be998 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/TransitionUsagePayloadSpecializationRule.cs @@ -0,0 +1,74 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using System.Collections.Generic; + using System.Linq; + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Systems.States; + + /// + /// Implements checkTransitionUsagePayloadSpecialization: a triggered TransitionUsage's payload parameter + /// subsets the payload of its trigger. + /// + /// + /// OCL: triggerAction->notEmpty() implies let payloadParameter : Feature = inputParameter(2) in + /// payloadParameter <> null and + /// payloadParameter.subsetsChain(triggerAction->at(1), triggerPayloadParameter()). + /// Both OCL positions are 1-BASED. inputParameter(2) is passed through unchanged because the + /// metamodel operation is itself 1-based, whereas triggerAction->at(1) becomes index 0 on the + /// C# list — the two conventions coexist and each call site keeps its own. + /// + public class TransitionUsagePayloadSpecializationRule : ChainSubsettingRule + { + /// + /// Initializes a new instance of the class. + /// + /// The factory creating the chain and the Subsetting. + public TransitionUsagePayloadSpecializationRule(IImpliedRelationshipFactory factory) + : base(factory) + { + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public override string ConstraintName => "checkTransitionUsagePayloadSpecialization"; + + /// + /// Returns the chain the payload parameter must subset. + /// + /// The Element under evaluation. + /// The payload parameter and the two Features forming the chain; empty otherwise. + protected override IEnumerable<(IFeature Subsetting, IFeature First, IFeature Second)> QueryChains(IElement element) + { + if (element is not ITransitionUsage transitionUsage || transitionUsage.triggerAction.Count == 0) + { + return []; + } + + // inputParameter(2) — 1-based operation, argument passed through unchanged. + return [(transitionUsage.InputParameter(2), transitionUsage.triggerAction[0], transitionUsage.TriggerPayloadParameter())]; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/TransitionUsageTransitionFeatureSpecializationRule.cs b/SysML2.NET.Semantics/Implied/Rules/TransitionUsageTransitionFeatureSpecializationRule.cs new file mode 100644 index 00000000..8a8c8140 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/TransitionUsageTransitionFeatureSpecializationRule.cs @@ -0,0 +1,140 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Systems.States; + + /// + /// Implements checkTransitionUsageTransitionFeatureSpecialization: each transition feature subsets the + /// library part of a TransitionAction that plays its role. + /// + /// + /// OCL: triggerAction->forAll(specializesFromLibrary('Actions::TransitionAction::accepter') and + /// guardExpression->forAll(specializesFromLibrary('Actions::TransitionAction::guard') and + /// effectAction->forAll(specializesFromLibrary('Actions::TransitionAction::effect')). + /// Three independent roles on ONE constraint, so this rule yields up to three Subsettings per + /// transition feature collection rather than the single Relationship the library base class emits — which + /// is why it does not use . + /// + public class TransitionUsageTransitionFeatureSpecializationRule : IImpliedRelationshipRule + { + /// + /// The library Feature each transition-feature role subsets. + /// + private const string AccepterQualifiedName = "Actions::TransitionAction::accepter"; + + /// + /// The library Feature a guard Expression subsets. + /// + private const string GuardQualifiedName = "TransitionPerformances::TransitionPerformance::guard"; + + /// + /// The library Feature an effect ActionUsage subsets. + /// + private const string EffectQualifiedName = "Actions::TransitionAction::effect"; + + /// + /// The index resolving the library Features by qualified name. + /// + private readonly ILibraryTypeIndex libraryTypeIndex; + + /// + /// The factory creating the detached Subsettings. + /// + private readonly IImpliedRelationshipFactory factory; + + /// + /// Initializes a new instance of the class. + /// + /// The index resolving the library Features by qualified name. + /// The factory creating the detached Subsettings. + /// Thrown when either argument is null. + public TransitionUsageTransitionFeatureSpecializationRule(ILibraryTypeIndex libraryTypeIndex, IImpliedRelationshipFactory factory) + { + this.libraryTypeIndex = libraryTypeIndex ?? throw new ArgumentNullException(nameof(libraryTypeIndex)); + this.factory = factory ?? throw new ArgumentNullException(nameof(factory)); + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public string ConstraintName => "checkTransitionUsageTransitionFeatureSpecialization"; + + /// + /// Computes the Subsettings each of a TransitionUsage's transition features requires. + /// + /// The Element under evaluation. + /// One Subsetting per trigger, guard and effect; empty when the constraint does not apply. + /// Thrown when is null. + /// Thrown when a targeted library Feature is not indexed. + public IReadOnlyList Apply(IElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + if (element is not ITransitionUsage transitionUsage) + { + return []; + } + + return + [ + ..this.Subset(transitionUsage.triggerAction, AccepterQualifiedName), + ..this.Subset(transitionUsage.guardExpression, GuardQualifiedName), + ..this.Subset(transitionUsage.effectAction, EffectQualifiedName) + ]; + } + + /// + /// Creates a Subsetting from each transition feature towards the library Feature for its role. + /// + /// The transition features playing one role. + /// The library Feature the role subsets. + /// The Subsettings, or empty when there are no such features. + /// Thrown when the library Feature is not indexed. + private IEnumerable Subset(IEnumerable transitionFeatures, string libraryQualifiedName) + { + var features = transitionFeatures.ToList(); + + if (features.Count == 0) + { + return []; + } + + if (!this.libraryTypeIndex.TryGetType(libraryQualifiedName, out var libraryType)) + { + throw new UnresolvedLibraryTypeException(libraryQualifiedName, this.ConstraintName); + } + + return libraryType is not IFeature libraryFeature + ? [] + : features.Select(feature => this.factory.CreateImpliedSubsetting(feature, libraryFeature)); + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/VariationDefinitionSpecializationRule.cs b/SysML2.NET.Semantics/Implied/Rules/VariationDefinitionSpecializationRule.cs new file mode 100644 index 00000000..d029f7bc --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/VariationDefinitionSpecializationRule.cs @@ -0,0 +1,80 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using System; + using System.Collections.Generic; + + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Systems.DefinitionAndUsage; + + /// + /// Implements checkUsageVariationDefinitionSpecialization: a variant Usage is typed by the variation + /// Definition that owns it. + /// + /// + /// SysML 2.0 8.4.2.3 gives the kernel equivalent of variation part def P { variant part p1; } as + /// class P specializes Parts::Part { member feature p1 : P subsets Parts::parts; }. The variant is + /// TYPED BY the Definition rather than subsetting it, because a Usage is a Feature and a Definition is a + /// Classifier — which is why this rule produces a FeatureTyping where its Usage counterpart produces a + /// Subsetting. + /// + public class VariationDefinitionSpecializationRule : IImpliedRelationshipRule + { + /// + /// The factory creating the detached FeatureTyping. + /// + private readonly IImpliedRelationshipFactory factory; + + /// + /// Initializes a new instance of the class. + /// + /// The factory creating the detached FeatureTyping. + /// Thrown when is null. + public VariationDefinitionSpecializationRule(IImpliedRelationshipFactory factory) + { + this.factory = factory ?? throw new ArgumentNullException(nameof(factory)); + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public string ConstraintName => "checkUsageVariationDefinitionSpecialization"; + + /// + /// Computes the FeatureTyping a variant Usage requires towards its owning variation Definition. + /// + /// The Element under evaluation. + /// A single FeatureTyping, or empty when the Element is not a variant of a variation Definition. + /// Thrown when is null. + public IReadOnlyList Apply(IElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + return element is IUsage { owningMembership: IVariantMembership, owningNamespace: IDefinition variationDefinition } variantUsage + ? [this.factory.CreateImpliedFeatureTyping(variantUsage, variationDefinition)] + : []; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/Rules/VariationUsageSpecializationRule.cs b/SysML2.NET.Semantics/Implied/Rules/VariationUsageSpecializationRule.cs new file mode 100644 index 00000000..ebfb02b7 --- /dev/null +++ b/SysML2.NET.Semantics/Implied/Rules/VariationUsageSpecializationRule.cs @@ -0,0 +1,80 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied.Rules +{ + using System; + using System.Collections.Generic; + + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Systems.DefinitionAndUsage; + + /// + /// Implements checkUsageVariationUsageSpecialization: a variant Usage subsets the variation Usage that + /// owns it. + /// + /// + /// SysML 2.0 8.4.2.3 gives the kernel equivalent of variation part p { variant part p1; } as + /// feature p subsets Parts::parts { member feature p1 subsets p; }, so the implied Relationship is + /// a Subsetting from the variant to the variation. The owning variation is reached through the + /// VariantMembership's owning Namespace, which subsets Usage::owningVariationUsage in the abstract syntax. + /// + public class VariationUsageSpecializationRule : IImpliedRelationshipRule + { + /// + /// The factory creating the detached Subsetting. + /// + private readonly IImpliedRelationshipFactory factory; + + /// + /// Initializes a new instance of the class. + /// + /// The factory creating the detached Subsetting. + /// Thrown when is null. + public VariationUsageSpecializationRule(IImpliedRelationshipFactory factory) + { + this.factory = factory ?? throw new ArgumentNullException(nameof(factory)); + } + + /// + /// Gets the name of the semantic constraint this rule implements. + /// + public string ConstraintName => "checkUsageVariationUsageSpecialization"; + + /// + /// Computes the Subsetting a variant Usage requires towards its owning variation Usage. + /// + /// The Element under evaluation. + /// A single Subsetting, or empty when the Element is not a variant of a variation Usage. + /// Thrown when is null. + public IReadOnlyList Apply(IElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + return element is IUsage { owningMembership: IVariantMembership, owningNamespace: IUsage variationUsage } variantUsage + && !ReferenceEquals(variationUsage, variantUsage) + ? [this.factory.CreateImpliedSubsetting(variantUsage, variationUsage)] + : []; + } + } +} diff --git a/SysML2.NET.Semantics/Implied/UnresolvedLibraryTypeException.cs b/SysML2.NET.Semantics/Implied/UnresolvedLibraryTypeException.cs new file mode 100644 index 00000000..f0c58afb --- /dev/null +++ b/SysML2.NET.Semantics/Implied/UnresolvedLibraryTypeException.cs @@ -0,0 +1,85 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Semantics.Implied +{ + using System; + + /// + /// Thrown when a semantic constraint targets a model-library Type that the + /// cannot resolve. + /// + /// + /// The usual cause is that the standard libraries were never loaded, so the index is empty or partial. + /// Failing loudly keeps that configuration error distinct from a model that genuinely requires no + /// implied Relationship. + /// + public class UnresolvedLibraryTypeException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public UnresolvedLibraryTypeException() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public UnresolvedLibraryTypeException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + /// The exception that caused this exception. + public UnresolvedLibraryTypeException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class for a + /// qualified name. + /// + /// The qualified name that failed to resolve. + /// The constraint requiring the Type. + public UnresolvedLibraryTypeException(string qualifiedName, string constraintName) + : base($"The library Type '{qualifiedName}' required by the semantic constraint '{constraintName}' could not be resolved. Ensure the model libraries are loaded and indexed.") + { + this.QualifiedName = qualifiedName; + this.ConstraintName = constraintName; + } + + /// + /// Gets the qualified name that failed to resolve. + /// + public string QualifiedName { get; } + + /// + /// Gets the name of the constraint requiring the Type. + /// + public string ConstraintName { get; } + } +} diff --git a/SysML2.NET.Semantics/SysML2.NET.Semantics.csproj b/SysML2.NET.Semantics/SysML2.NET.Semantics.csproj new file mode 100644 index 00000000..47330338 --- /dev/null +++ b/SysML2.NET.Semantics/SysML2.NET.Semantics.csproj @@ -0,0 +1,41 @@ + + + + netstandard2.1;net10.0 + 12.0 + disable + 0.22.0 + A .NET implementation of the OMG SysML v2 specification semantic constraints and implied Relationships. + SysML2.NET.Semantics + Starion Group S.A. + Copyright © Starion Group S.A. + Apache-2.0 + https://github.com/STARIONGROUP/SysML2.NET.git + Git + Sam Gerené + true + + [Add] Implied Relationship computation (KerML 8.4.2) + + cdp4-icon.png + README.md + true + true + + + + + + + + + + + + + + + + + + diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/03-Function-based Behavior/3a-Function-based Behavior-2.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/03-Function-based Behavior/3a-Function-based Behavior-2.sysml index bdcac0fb..9c9fdb5a 100644 --- a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/03-Function-based Behavior/3a-Function-based Behavior-2.sysml +++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/03-Function-based Behavior/3a-Function-based Behavior-2.sysml @@ -52,7 +52,7 @@ package '3a-Function-based Behavior-2' { * and the target of each succeeding first is indicated by * using the "then" keyword. */ - first Actions::Action::start; + first start; then merge continue; then action engineStarted accept engineStart: EngineStart; then action engineStopped accept engineOff: EngineOff; diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/07-Variant Configuration/7a-Variant Configuration - General Concept.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/07-Variant Configuration/7a-Variant Configuration - General Concept.sysml index 2363b837..9c30ab3c 100644 --- a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/07-Variant Configuration/7a-Variant Configuration - General Concept.sysml +++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/07-Variant Configuration/7a-Variant Configuration - General Concept.sysml @@ -30,11 +30,11 @@ package '7a-Variant Configuration - General Concept' { assert constraint { subsystemA != subsystemA::subsystem2 | subsystemB == subsystemB::subsystem3 } } part vehicleConfigA :> anyVehicleConfig { - part :>> subsystemA = subsystem1; - part :>> subsystemB = subsystem3; + part :>> subsystemA = subsystemA::subsystem1; + part :>> subsystemB = subsystemB::subsystem3; } part VehicleConfigB :> anyVehicleConfig { - part :>> subsystemA = subsystem2; - part :>> subsystemB = subsystem3; + part :>> subsystemA = subsystemA::subsystem2; + part :>> subsystemB = subsystemB::subsystem3; } } diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/07-Variant Configuration/7a1-Variant Configuration - General Concept-a.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/07-Variant Configuration/7a1-Variant Configuration - General Concept-a.sysml index 6a74a769..dacbec9d 100644 --- a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/07-Variant Configuration/7a1-Variant Configuration - General Concept-a.sysml +++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/07-Variant Configuration/7a1-Variant Configuration - General Concept-a.sysml @@ -46,16 +46,16 @@ package '7a1-Variant Configuration - General Concept-a' { assert constraint { subsystemA != subsystemA::subsystem2 | subsystemB == subsystemB::subsystem3 } } part vehicleConfigA :> anyVehicleConfig { - part :>> subsystemA = subsystem1; - part :>> subsystemB = subsystem3 { + part :>> subsystemA = subsystemA::subsystem1; + part :>> subsystemB = subsystemB::subsystem3 { part :>> part5 { perform action :>> doXorY = '7a1-Variant Configuration - General Concept-a'::doX; } } } part VehicleConfigB :> anyVehicleConfig { - part :>> subsystemA = subsystem2; - part :>> subsystemB = subsystem4 { + part :>> subsystemA = subsystemA::subsystem2; + part :>> subsystemB = subsystemB::subsystem4 { part :>> part5 { perform action :>> doXorY = '7a1-Variant Configuration - General Concept-a'::doY; } diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Writers/ImpliedLibrarySpecializationTestFixture.cs b/SysML2.NET.Serializer.TextualNotation.Tests/Writers/ImpliedLibrarySpecializationTestFixture.cs new file mode 100644 index 00000000..e0a32cfd --- /dev/null +++ b/SysML2.NET.Serializer.TextualNotation.Tests/Writers/ImpliedLibrarySpecializationTestFixture.cs @@ -0,0 +1,146 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Serializer.TextualNotation.Tests.Writers +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Threading.Tasks; + + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Logging; + + using NUnit.Framework; + + using SysML2.NET.Core.POCO.Core.Types; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Root.Namespaces; + using SysML2.NET.Semantics.Extensions; + using SysML2.NET.Semantics.Implied; + using SysML2.NET.Serializer.TextualNotation.Tests.Wrapper; + using SysML2.NET.Serializer.Xmi; + + /// + /// Exercises the table-driven library Specializations (KerML §8.4.2 "Set C"). + /// + /// + /// The textual-notation corpus is byte-identical with EnableLibrarySpecializations on and off, + /// because none of these Specializations happens to shorten or lengthen a qualified name in those + /// models. That makes the corpus blind to this half of the layer: it would stay green if the whole + /// table silently produced nothing. This fixture is the discriminating check. + /// + [TestFixture] + public class ImpliedLibrarySpecializationTestFixture + { + private IReadOnlyCollection libraryNamespaces; + + private IReadOnlyList modelTypes; + + [OneTimeSetUp] + public async Task OneTimeSetUp() + { + var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Error)); + + var libraryRoot = Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources"); + + var redirectingService = new LibraryRedirectingExternalReferenceService( + libraryRoot, + loggerFactory.CreateLogger()); + + this.libraryNamespaces = await new ModelLibraryLoader(loggerFactory, redirectingService).LoadAsync(libraryRoot); + + var filePath = Path.Combine(TestContext.CurrentContext.TestDirectory, "Validation", "01-Parts Tree", "1a-Parts Tree.sysmlx"); + var readResult = await new DeSerializer(loggerFactory, redirectingService).DeSerializeAsync(new Uri(filePath)); + + var types = new List(); + CollectTypes(readResult.RootNamespace, types, new HashSet()); + + this.modelTypes = types; + } + + [Test] + public void VerifyLibrarySpecializationsAreComputed() + { + var withoutSetC = this.QueryImpliedGenerals(enableLibrarySpecializations: false); + var withSetC = this.QueryImpliedGenerals(enableLibrarySpecializations: true); + + using (Assert.EnterMultipleScope()) + { + Assert.That(this.modelTypes, Is.Not.Empty, "the model must yield Types to evaluate"); + + // Off, only the hand-coded rules contribute; on, the table adds the library Specializations. + Assert.That(withSetC, Has.Count.GreaterThan(withoutSetC.Count), + "enabling library Specializations must add implied Specializations"); + + // The canonical library Specializations every Parts model carries. If the table, the guards + // or the library index regress, these disappear while the corpus stays green. + Assert.That(withSetC, Does.Contain("Part"), "a PartDefinition subclassifies Parts::Part"); + Assert.That(withSetC, Does.Contain("parts"), "a PartUsage subsets Parts::parts"); + Assert.That(withSetC, Does.Contain("things"), "a Feature subsets Base::things"); + } + } + + /// + /// Returns the names of the general Types of every implied Specialization in the model. + /// + /// Whether the table-driven Specializations are enabled. + /// The general names, with duplicates. + private List QueryImpliedGenerals(bool enableLibrarySpecializations) + { + var services = new ServiceCollection(); + services.AddSysML2Semantics(options => options.EnableLibrarySpecializations = enableLibrarySpecializations); + services.AddSingleton(OwnershipTreeLibraryTypeIndex.Build(this.libraryNamespaces)); + + using var serviceProvider = services.BuildServiceProvider(); + var provider = serviceProvider.GetRequiredService(); + + return [..this.modelTypes + .SelectMany(provider.GetImpliedSpecializations) + .Select(specialization => specialization.General?.name ?? specialization.General?.DeclaredName) + .Where(generalName => generalName != null)]; + } + + /// + /// Collects every Type reachable from an Element through owned relationships. + /// + /// The Element to walk from. + /// The accumulator. + /// The Elements already walked, guarding against cycles. + private static void CollectTypes(IElement element, List types, HashSet visited) + { + if (element == null || !visited.Add(element)) + { + return; + } + + if (element is IType type) + { + types.Add(type); + } + + foreach (var owned in element.OwnedRelationship.SelectMany(relationship => relationship.OwnedRelatedElement)) + { + CollectTypes(owned, types, visited); + } + } + } +} diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Writers/ModelLibraryTypeIndexTestFixture.cs b/SysML2.NET.Serializer.TextualNotation.Tests/Writers/ModelLibraryTypeIndexTestFixture.cs new file mode 100644 index 00000000..2524dc81 --- /dev/null +++ b/SysML2.NET.Serializer.TextualNotation.Tests/Writers/ModelLibraryTypeIndexTestFixture.cs @@ -0,0 +1,100 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Serializer.TextualNotation.Tests.Writers +{ + using System.IO; + using System.Linq; + using System.Threading.Tasks; + + using Microsoft.Extensions.Logging; + + using NUnit.Framework; + + using SysML2.NET.Semantics.Implied; + using SysML2.NET.Serializer.TextualNotation.Tests.Wrapper; + using SysML2.NET.Serializer.Xmi; + + [TestFixture] + public class ModelLibraryTypeIndexTestFixture + { + private static readonly string[] ConstraintTargets = + [ + "Base::Anything", + "Base::things", + "Base::dataValues", + "Occurrences::Occurrence", + "Occurrences::occurrences", + "Objects::objects", + "Links::Link::participant", + "Performances::performances" + ]; + + private OwnershipTreeLibraryTypeIndex index; + + [OneTimeSetUp] + public async Task OneTimeSetUp() + { + var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Error)); + + var libraryRoot = Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources"); + + var redirectingService = new LibraryRedirectingExternalReferenceService( + libraryRoot, + loggerFactory.CreateLogger()); + + var loader = new ModelLibraryLoader(loggerFactory, redirectingService); + + this.index = OwnershipTreeLibraryTypeIndex.Build(await loader.LoadAsync(libraryRoot)); + } + + [Test] + public void VerifyEveryTableTargetResolves() + { + // The whole table, not just the rows a corpus happens to exercise: a row whose library Type + // does not resolve can never be satisfied, and surfaces only when a model reaches that row. + // Eight such rows were found this way, all traced to typos in the XMI OCL and corrected by the + // generator's errata map (SysML2.NET.CodeGenerator/Extensions/OclErrata.cs). + var unresolved = ImpliedRelationshipTable.AllLibraryTargets + .Where(libraryTarget => !this.index.TryGetType(libraryTarget, out _)) + .ToList(); + + Assert.That(unresolved, Is.Empty, $"unresolved library targets: {string.Join(", ", unresolved)}"); + } + + [Test] + public void VerifyConstraintTargetsResolve() + { + // A model-independent library load must resolve the qualified names the KerML 8.4.2 constraints + // target. Deserializing a user model resolves only what that model imports, which is why the + // index cannot be built from XmiReadResult.ReferencedNamespaces. + using (Assert.EnterMultipleScope()) + { + foreach (var qualifiedName in ConstraintTargets) + { + Assert.That(this.index.TryGetType(qualifiedName, out var resolved), Is.True, $"'{qualifiedName}' must resolve from a full library load."); + Assert.That(resolved, Is.Not.Null); + } + + Assert.That(this.index.TryGetType("Base::NoSuchTypeExists", out _), Is.False); + } + } + } +} diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Writers/TextualNotationValidationTestFixture.cs b/SysML2.NET.Serializer.TextualNotation.Tests/Writers/TextualNotationValidationTestFixture.cs index f2b37ab1..93d17874 100644 --- a/SysML2.NET.Serializer.TextualNotation.Tests/Writers/TextualNotationValidationTestFixture.cs +++ b/SysML2.NET.Serializer.TextualNotation.Tests/Writers/TextualNotationValidationTestFixture.cs @@ -24,10 +24,13 @@ namespace SysML2.NET.Serializer.TextualNotation.Tests.Writers using System.IO; using System.Threading.Tasks; + using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using NUnit.Framework; + using SysML2.NET.Semantics.Extensions; + using SysML2.NET.Semantics.Implied; using SysML2.NET.Serializer.TextualNotation.Tests.Wrapper; using SysML2.NET.Serializer.TextualNotation.Writers; using SysML2.NET.Serializer.Xmi; @@ -35,6 +38,50 @@ namespace SysML2.NET.Serializer.TextualNotation.Tests.Writers [TestFixture] public class TextualNotationValidationTestFixture { + /// + /// The container supplying the implied-relationship services, built once for the whole fixture. + /// + private ServiceProvider serviceProvider; + + /// + /// Builds the semantics container once for the fixture. + /// + /// + /// The library index is built from a FULL, model-independent load: the 8.4.2 constraints target + /// library Types a given model need not import, so an index built from a model's referenced + /// Namespaces cannot resolve them. It is shared across every case because the load is the + /// expensive part — repeating it per case dominates the suite. + /// + /// An awaitable task. + [OneTimeSetUp] + public async Task OneTimeSetUp() + { + var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Error)); + + var libraryRoot = Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources"); + + var redirectingService = new LibraryRedirectingExternalReferenceService( + libraryRoot, + loggerFactory.CreateLogger()); + + var libraryNamespaces = await new ModelLibraryLoader(loggerFactory, redirectingService).LoadAsync(libraryRoot); + + var services = new ServiceCollection(); + services.AddSysML2Semantics(options => options.EnableLibrarySpecializations = true); + services.AddSingleton(OwnershipTreeLibraryTypeIndex.Build(libraryNamespaces)); + + this.serviceProvider = services.BuildServiceProvider(); + } + + /// + /// Disposes the fixture's container. + /// + [OneTimeTearDown] + public void OneTimeTearDown() + { + this.serviceProvider?.Dispose(); + } + [Test] [TestCase("01-Parts Tree", "1a-Parts Tree.sysmlx")] [TestCase("01-Parts Tree", "1c-Parts Tree Redefinition.sysmlx")] @@ -78,10 +125,15 @@ public async Task VerifyValidationTextualNotationXmi(string folderName, string f var readResult = await deSerializer.DeSerializeAsync(new Uri(filePath)); var rootNamespace = readResult.RootNamespace; + // The provider memoises per Type, so each case gets its own scope while the library index and + // the registered guards and rules stay shared across the fixture. + using var serviceScope = this.serviceProvider.CreateScope(); + var impliedRelationshipProvider = serviceScope.ServiceProvider.GetRequiredService(); + // The referenced namespaces are the roots of the model libraries pulled in while resolving the // file's external references. They form the global Namespace (KerML §8.2.3.5.2), so the writer // needs them to shorten a reference routed through a library the model does not itself import. - using var writerContext = new TextualNotationWriterContext(rootNamespace, readResult.ReferencedNamespaces); + using var writerContext = new TextualNotationWriterContext(rootNamespace, readResult.ReferencedNamespaces, impliedRelationshipProvider); writerContext.EmitOperatorParentheses = true; var stringBuilder = new IndentedStringBuilder(); diff --git a/SysML2.NET.Serializer.TextualNotation/SysML2.NET.Serializer.TextualNotation.csproj b/SysML2.NET.Serializer.TextualNotation/SysML2.NET.Serializer.TextualNotation.csproj index 41f5179e..63fbcc46 100644 --- a/SysML2.NET.Serializer.TextualNotation/SysML2.NET.Serializer.TextualNotation.csproj +++ b/SysML2.NET.Serializer.TextualNotation/SysML2.NET.Serializer.TextualNotation.csproj @@ -42,6 +42,7 @@ + \ No newline at end of file diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/NameResolutionCache.cs b/SysML2.NET.Serializer.TextualNotation/Writers/NameResolutionCache.cs index f1f9fdde..7e620c73 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/NameResolutionCache.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/NameResolutionCache.cs @@ -30,6 +30,7 @@ namespace SysML2.NET.Serializer.TextualNotation.Writers using SysML2.NET.Core.POCO.Kernel.Behaviors; using SysML2.NET.Core.POCO.Kernel.Connectors; using SysML2.NET.Core.POCO.Kernel.Expressions; + using SysML2.NET.Core.POCO.Kernel.FeatureValues; using SysML2.NET.Core.POCO.Kernel.Interactions; using SysML2.NET.Core.POCO.Root.Elements; using SysML2.NET.Core.POCO.Root.Namespaces; @@ -37,6 +38,7 @@ namespace SysML2.NET.Serializer.TextualNotation.Writers using SysML2.NET.Core.POCO.Systems.DefinitionAndUsage; using SysML2.NET.Core.Root.Namespaces; using SysML2.NET.Extensions; + using SysML2.NET.Semantics.Implied; /// /// Resolves the shortest unambiguous textual name for a reference, mirroring KerML §8.2.3.5. @@ -59,13 +61,13 @@ private static readonly IReadOnlyDictionary> EmptyInde /// /// Lazy cache: source-POCO id → its upward containment chain of namespaces. /// - private readonly Dictionary> sourceScopeChains + private readonly Dictionary sourceScopeChains = new (); /// - /// Lazy cache: (target.Id, sourceLocalScope.Id) → emitted string. + /// Lazy cache: (target.Id, sourceLocalScope.Id, matchFloorScope.Id) → emitted string. /// - private readonly Dictionary<(Guid TargetId, Guid SourceScopeId), string> resolvedReferences + private readonly Dictionary<(Guid TargetId, Guid SourceScopeId, Guid MatchFloorId), string> resolvedReferences = new (); /// @@ -87,6 +89,19 @@ private readonly Dictionary> sourceScopeChains /// private readonly List globalNamespaces; + /// + /// Supplies the implied Relationships (KerML §8.4.2) that a model exported without them + /// omits, so a name reachable only through one can still be shortened. + /// + private readonly IImpliedRelationshipProvider impliedRelationshipProvider; + + /// + /// Elements of the resolution graph keyed by Id, built lazily on the first implied-general + /// translation. Instance state, never shared: each writer context carries its own cache, so + /// parallel writers cannot observe or pollute one another. + /// + private Dictionary resolutionGraphElementsById; + /// /// Initializes the cache and eagerly indexes every namespace reachable from /// . @@ -96,7 +111,12 @@ private readonly Dictionary> sourceScopeChains /// The other loaded root namespaces (model libraries), forming the global namespace per /// KerML §8.2.3.5.2. Optional — without them resolution falls back to longer, equally valid names. /// - public NameResolutionCache(INamespace rootNamespace, IEnumerable globalNamespaces = null) + /// + /// The provider supplying the implied Relationships a model exported without them omits. + /// Optional — when absent, resolution sees only the declared Specializations, so a name + /// reachable ONLY through an implied one degrades to a longer, equally valid form. + /// + public NameResolutionCache(INamespace rootNamespace, IEnumerable globalNamespaces = null, IImpliedRelationshipProvider impliedRelationshipProvider = null) { this.RootNamespace = rootNamespace ?? throw new ArgumentNullException(nameof(rootNamespace)); @@ -105,6 +125,8 @@ public NameResolutionCache(INamespace rootNamespace, IEnumerable glo .Distinct() .ToList() ?? []; + this.impliedRelationshipProvider = impliedRelationshipProvider ?? NullImpliedRelationshipProvider.Instance; + this.simpleNameIndices = this.BuildSimpleNameIndices(rootNamespace); } @@ -162,6 +184,11 @@ public string Resolve(IElement target, IElement sourcePoco) var sourceLocalScope = this.GetSourceLocalScope(sourcePoco); + // The innermost scopes of the chain are SHADOW-ONLY when the reference sits in a FeatureValue + // expression: the two readings of KerML §8.2.3.5.2 disagree about them, so a simple name matched + // there would not resolve to this target under both. See QueryValueExpressionMatchFloor. + var matchFloorScope = QueryValueExpressionMatchFloor(sourcePoco) ?? sourceLocalScope; + // KerML §8.2.3.5.1: the ONE exception to basic resolution — a Redefinition's redefinedFeature is // resolved against the general Type of each ownedSpecialization of the owningType, NOT against the // reference site's local namespace. This is what keeps `:>> fuelCmdPort` short while the ordinary @@ -176,6 +203,10 @@ public string Resolve(IElement target, IElement sourcePoco) && this.QueryRedefinedFeatureScope(redefinitionContext, target) is { } redefinitionScope) { sourceLocalScope = redefinitionScope; + + // The exception REPLACES the local Namespace, so the elected general type is itself the + // innermost scope a match may come from — no scope below it to hold shadow-only. + matchFloorScope = redefinitionScope; } // A redefinition's redefining feature — and equally a reference subsetting's referencing @@ -192,17 +223,17 @@ IReferenceSubsetting referenceSubsetting when ReferenceEquals(target, referenceS if (localReferencer != null && !RedefinerDeclaredNameCollidesWith(localReferencer, target)) { - return this.ResolveFresh(target, sourcePoco, sourceLocalScope, escapedName, localReferencer, QuerySelfBindingScope(sourcePoco)); + return this.ResolveFresh(target, this.BuildReferenceSite(sourcePoco, sourceLocalScope, matchFloorScope, localReferencer), escapedName); } - var cacheKey = (target.Id, sourceLocalScope?.Id ?? Guid.Empty); + var cacheKey = (target.Id, sourceLocalScope?.Id ?? Guid.Empty, matchFloorScope?.Id ?? Guid.Empty); if (this.resolvedReferences.TryGetValue(cacheKey, out var cached)) { return cached; } - var resolved = this.ResolveFresh(target, sourcePoco, sourceLocalScope, escapedName, localRedefiner: null, QuerySelfBindingScope(sourcePoco)); + var resolved = this.ResolveFresh(target, this.BuildReferenceSite(sourcePoco, sourceLocalScope, matchFloorScope, localRedefiner: null), escapedName); this.resolvedReferences[cacheKey] = resolved; return resolved; } @@ -272,7 +303,9 @@ private string QueryImportPath(IElement target) for (var anchorIndex = 0; anchorIndex < namedAncestors.Count; anchorIndex++) { - if (namedAncestors[anchorIndex] is not INamespace anchor || !this.BindsDirectly(anchor, target, targetSegment)) + if (namedAncestors[anchorIndex] is not INamespace anchor + || !this.BindsDirectly(anchor, target, targetSegment) + || !this.IsSuffixVisible(namedAncestors.Take(anchorIndex + 1).Append(target))) { continue; } @@ -292,7 +325,8 @@ private string QueryImportPath(IElement target) /// /// Determines whether 's index binds uniquely - /// to — i.e. the target is nameable directly from that scope. + /// to , VISIBLY — i.e. the target is nameable from outside that scope, + /// which is what a qualified path through it requires (KerML §8.2.3.5.3). /// /// The candidate anchor namespace. /// The element being named. @@ -306,42 +340,57 @@ private bool BindsDirectly(INamespace scope, IElement target, string segment) && this.GetSimpleNameIndex(scope).TryGetValue(rawName, out var bucket) && bucket.Count == 1 && bucket.Contains(target) - && !string.IsNullOrWhiteSpace(segment); + && !string.IsNullOrWhiteSpace(segment) + && this.BindsVisibly(scope, rawName, target); } /// - /// Returns the scope in which is itself the name binding for the - /// target — a non-owning without a name override IS the reference being - /// emitted, and its binding does not exist yet at parse time. Its entry must be ignored in that - /// scope or every reference would resolve trivially at depth 0. + /// Returns the binding that IS — a non-owning + /// without a name override is the reference being emitted, and its + /// binding does not exist yet at parse time. Its entry must be discounted in its own scope or + /// every such reference would resolve trivially at depth 0. /// /// The source POCO at the reference site. - /// The scope whose binding for the target must be ignored, or . - private static INamespace QuerySelfBindingScope(IElement sourcePoco) + /// The self binding, or when the source is not one. + private static SelfBinding QuerySelfBinding(IElement sourcePoco) { return sourcePoco is IMembership membership and not IOwningMembership && string.IsNullOrWhiteSpace(membership.MemberName) && string.IsNullOrWhiteSpace(membership.MemberShortName) - ? membership.OwningRelatedElement as INamespace + && membership.OwningRelatedElement is INamespace bindingScope + ? new SelfBinding(bindingScope, membership) : null; } + /// + /// Assembles the for one reference: its scope chain plus the + /// exclusions every probe of it must honour. + /// + /// The POCO bearing the reference. + /// The pre-computed local scope (may be ). + /// The innermost scope a match may come from (may be ). + /// Feature to exclude from every bucket, or . + /// The reference site. + private ReferenceSite BuildReferenceSite(IElement sourcePoco, INamespace sourceLocalScope, INamespace matchFloorScope, IFeature localRedefiner) + { + return new ReferenceSite( + sourcePoco, + this.GetSourceScopeChain(sourcePoco, sourceLocalScope, matchFloorScope), + localRedefiner, + QuerySelfBinding(sourcePoco)); + } + /// /// First-time resolution: probes the target's own simple names (short first, per the SST /// convention), then aliases, then facade re-exports, then owner-chain ancestors as anchors for a /// partially-qualified suffix, and finally falls back to . /// /// The referenced element. - /// The reference site's source POCO. - /// The pre-computed local scope (may be ). + /// The reference site: its scope chain and the exclusions that apply to every probe. /// The target's escaped raw name. - /// Local feature to exclude from scope buckets, or . - /// Scope whose binding of the target must be ignored, or . /// The resolved emission string. - private string ResolveFresh(IElement target, IElement sourcePoco, INamespace sourceLocalScope, string escapedName, IFeature localRedefiner, INamespace selfBindingScope) + private string ResolveFresh(IElement target, ReferenceSite site, string escapedName) { - var chain = this.GetSourceScopeChain(sourcePoco, sourceLocalScope); - var rawShortName = target.shortName; string escapedShortName = null; @@ -349,7 +398,7 @@ private string ResolveFresh(IElement target, IElement sourcePoco, INamespace sou { escapedShortName = Escape(rawShortName); - if (this.TryResolveSimpleNameAcrossChain(chain, target, rawShortName, escapedShortName, localRedefiner, selfBindingScope, out var matchedShort)) + if (this.TryResolveSimpleNameAcrossChain(site, target, rawShortName, escapedShortName, accept: null, out var matchedShort)) { return matchedShort; } @@ -358,7 +407,7 @@ private string ResolveFresh(IElement target, IElement sourcePoco, INamespace sou var rawName = target.name; if (!string.IsNullOrWhiteSpace(rawName) - && this.TryResolveSimpleNameAcrossChain(chain, target, rawName, escapedName, localRedefiner, selfBindingScope, out var matchedLong)) + && this.TryResolveSimpleNameAcrossChain(site, target, rawName, escapedName, accept: null, out var matchedLong)) { return matchedLong; } @@ -366,24 +415,26 @@ private string ResolveFresh(IElement target, IElement sourcePoco, INamespace sou // An alias binds the target under a name it does not carry itself, so the probes above // can never find it. Preferred over facade/qualified forms — it is how the model names // the element at this site. - if (this.TryResolveViaAlias(chain, target, sourcePoco, localRedefiner, selfBindingScope, out var matchedAlias)) + if (this.TryResolveViaAlias(site, target, out var matchedAlias)) { return matchedAlias; } // Facade re-export: `ISQ::mass` over `ISQBase::mass` — the SST canonical idiom // (KerML §8.2.3.5.4 leaves the choice open; both forms parse to the same element). - if (this.TryResolveViaDirectFacade(chain, target, escapedShortName, escapedName, out var matchedFacade)) + if (this.TryResolveViaDirectFacade(site.Chain, target, escapedShortName, escapedName, out var matchedFacade)) { return matchedFacade; } // Walk owner-chain ancestors outward; the first that resolves uniquely anchors a - // partially-qualified suffix down to the target. - var segmentsDownToTarget = new Stack(); + // partially-qualified suffix down to the target. The suffix is kept as ELEMENTS: every segment + // of it is resolved by the parser with visible resolution, which has to be verified per hop. + var pathDownToTarget = new Stack(); - segmentsDownToTarget.Push(QueryPreferredEscapedSegment(target) ?? string.Empty); + pathDownToTarget.Push(target); + var descendant = target; var ancestor = (IElement)QueryOwningContainer(target); var visitedAncestors = new HashSet(); @@ -401,27 +452,60 @@ private string ResolveFresh(IElement target, IElement sourcePoco, INamespace sou var ancestorRawName = QueryPreferredRawName(ancestor); if (!string.IsNullOrWhiteSpace(ancestorRawName) - && this.TryResolveSimpleNameAcrossChain(chain, ancestor, ancestorRawName, ancestorSegment, localRedefiner, selfBindingScope, out var matchedAnchor)) + && this.IsSuffixVisible(pathDownToTarget) + && this.TryResolveSimpleNameAcrossChain(site, ancestor, ancestorRawName, ancestorSegment, this.BuildAnchorAcceptance(ancestor, descendant), out var matchedAnchor)) { var builder = new StringBuilder(matchedAnchor); - foreach (var segment in segmentsDownToTarget) + foreach (var segment in pathDownToTarget) { builder.Append("::"); - builder.Append(segment); + builder.Append(QueryPreferredEscapedSegment(segment) ?? string.Empty); } return builder.ToString(); } - segmentsDownToTarget.Push(ancestorSegment); + pathDownToTarget.Push(ancestor); + descendant = ancestor; ancestor = QueryOwningContainer(ancestor); } return target.qualifiedName ?? string.Empty; } + /// + /// Determines whether every hop WITHIN resolves visibly — the + /// hop from the anchor into the path is checked separately, against whatever the anchor segment + /// actually resolves to (see ). + /// + /// The suffix elements, outermost first. + /// when the suffix re-resolves segment by segment. + private bool IsSuffixVisible(IEnumerable pathDownToTarget) + { + IElement predecessor = null; + + foreach (var segmentElement in pathDownToTarget) + { + if (predecessor != null) + { + var rawName = QueryPreferredRawName(segmentElement); + + if (predecessor is not INamespace predecessorScope + || string.IsNullOrWhiteSpace(rawName) + || !this.BindsVisibly(predecessorScope, rawName, segmentElement)) + { + return false; + } + } + + predecessor = segmentElement; + } + + return true; + } + /// /// Returns 's owningNamespace, or when /// unreachable or the derived property is not implemented. @@ -538,7 +622,7 @@ private static bool RedefinerDeclaredNameCollidesWith(IFeature localRedefiner, I /// Pre-escaped target name. /// On a hit, the emitted facade::simpleName string. /// when a reachable facade was found. - private bool TryResolveViaDirectFacade(IReadOnlyList chain, IElement target, string escapedShortName, string escapedName, out string matched) + private bool TryResolveViaDirectFacade(SourceScopeChain chain, IElement target, string escapedShortName, string escapedName, out string matched) { matched = null; @@ -559,9 +643,11 @@ private bool TryResolveViaDirectFacade(IReadOnlyList chain, IElement INamespace bestFacade = null; var bestScopeDepth = int.MaxValue; - for (var scopeDepth = 0; scopeDepth < chain.Count; scopeDepth++) + // Starts at the match floor: a facade reachable only from a scope the pilot parser never + // consults would emit a name that does not re-resolve there. + for (var scopeDepth = chain.MatchFloor; scopeDepth < chain.Scopes.Count; scopeDepth++) { - var scope = chain[scopeDepth]; + var scope = chain.Scopes[scopeDepth]; var scopeIndex = this.GetSimpleNameIndex(scope); foreach (var facade in facades) @@ -630,6 +716,15 @@ private bool TryResolveViaDirectFacade(IReadOnlyList chain, IElement return false; } + // The facade re-exports the owner, but the target still has to be VISIBLE through it: a + // non-public member, or one an `import all` pulled in without re-exporting, is not. + var targetRawName = QueryPreferredRawName(target); + + if (string.IsNullOrWhiteSpace(targetRawName) || !this.BindsVisibly(bestFacade, targetRawName, target)) + { + return false; + } + matched = bestFacadeSegment + "::" + targetSimpleName; return true; } @@ -665,19 +760,23 @@ private static string QueryPreferredEscapedSegment(IElement element) } /// - /// Walks innermost-out for a scope binding - /// uniquely to . A scope that binds the name to anything else stops the - /// walk — the parser's resolution would already have claimed the name there. + /// Walks 's scope chain innermost-out for a scope binding + /// uniquely to . A scope that binds the name + /// to anything else stops the walk — the parser's resolution would already have claimed the name + /// there. + /// Scopes BELOW the chain's match floor are consulted for shadowing only: a hit there is + /// skipped and the walk continues outward, because the pilot parser does not consult them (see + /// ) and the emitted name has to resolve to the same + /// element under both readings. /// - /// The pre-built source-scope chain (innermost first). + /// The reference site: its scope chain and the exclusions that apply to every probe. /// The referenced element. /// The simple-name lexical form to probe (may be blank). /// The escaped form to emit on a hit. - /// Local feature to exclude from scope buckets, or . - /// Scope whose binding of the target must be ignored, or . + /// Predicate deciding whether a bound element counts as the target, or for reference identity. /// On a hit, the simple-name string to emit. /// when the name resolves uniquely to the target. - private bool TryResolveSimpleNameAcrossChain(IReadOnlyList chain, IElement target, string rawName, string escapedName, IFeature localRedefiner, INamespace selfBindingScope, out string matched) + private bool TryResolveSimpleNameAcrossChain(ReferenceSite site, IElement target, string rawName, string escapedName, Func accept, out string matched) { matched = null; @@ -686,17 +785,19 @@ private bool TryResolveSimpleNameAcrossChain(IReadOnlyList chain, IE return false; } - foreach (var scope in chain) + for (var scopeDepth = 0; scopeDepth < site.Chain.Scopes.Count; scopeDepth++) { - var resolution = this.ResolveSimpleNameInScope(scope, target, rawName, localRedefiner, selfBindingScope); + var resolution = this.ResolveSimpleNameInScope(site.Chain.Scopes[scopeDepth], target, rawName, site.LocalRedefiner, site.SelfBinding, accept); - switch (resolution) + if (resolution == SimpleNameResolution.Shadowed) { - case SimpleNameResolution.Matched: - matched = escapedName; - return true; - case SimpleNameResolution.Shadowed: - return false; + return false; + } + + if (resolution == SimpleNameResolution.Matched && scopeDepth >= site.Chain.MatchFloor) + { + matched = escapedName; + return true; } } @@ -744,12 +845,13 @@ private IReadOnlyDictionary> GetSimpleNameIndex(INames /// /// The source POCO bearing the reference; may be . /// The pre-computed local scope (may be ). + /// The innermost scope a match may come from (may be ). /// The cached chain. - private IReadOnlyList GetSourceScopeChain(IElement sourcePoco, INamespace sourceLocalScope) + private SourceScopeChain GetSourceScopeChain(IElement sourcePoco, INamespace sourceLocalScope, INamespace matchFloorScope) { if (sourcePoco == null) { - return BuildChain(sourceLocalScope ?? this.RootNamespace); + return BuildChain(sourceLocalScope ?? this.RootNamespace, matchFloorScope); } if (this.sourceScopeChains.TryGetValue(sourcePoco.Id, out var cached)) @@ -757,29 +859,63 @@ private IReadOnlyList GetSourceScopeChain(IElement sourcePoco, IName return cached; } - var chain = BuildChain(sourceLocalScope ?? this.RootNamespace); + var chain = BuildChain(sourceLocalScope ?? this.RootNamespace, matchFloorScope); this.sourceScopeChains[sourcePoco.Id] = chain; return chain; } /// - /// Materialises the owningNamespace chain from up to the root. + /// Materialises the owningNamespace chain from up to the root, and + /// locates in it. /// /// The starting namespace. + /// The innermost scope a match may come from (may be ). /// The chain. - private static IReadOnlyList BuildChain(INamespace start) + private static SourceScopeChain BuildChain(INamespace start, INamespace matchFloorScope) { - var chain = new List(); + var scopes = new List(); var current = start; while (current != null) { - chain.Add(current); + scopes.Add(current); current = QueryOwningContainer(current); } - return chain; + return new SourceScopeChain(scopes, QueryMatchFloorDepth(scopes, matchFloorScope)); + } + + /// + /// Returns the depth in at which a match becomes admissible. + /// + /// The chain, innermost first. + /// The floor scope, or for no floor. + /// The depth; 0 admits the whole chain. + /// + /// The floor is reached by a CONTAINMENT climb while the chain is materialised through + /// owningNamespace, so the floor is not guaranteed to sit on the chain — the invocation + /// redirect of can elect a Namespace the chain does not pass + /// through. Falling back to depth 0 there would silently re-admit the very scopes the floor exists + /// to exclude, so the floor's own CONTAINERS are tried next: the innermost of them that IS on the + /// chain sits at or outside the floor, which keeps the constraint at least as strict as intended. + /// Depth 0 is reached only when the two are genuinely unrelated. + /// + private static int QueryMatchFloorDepth(List scopes, INamespace matchFloorScope) + { + var visited = new HashSet(); + + for (var candidate = matchFloorScope; candidate != null && visited.Add(candidate); candidate = QueryParentNamespace(candidate)) + { + var depth = scopes.IndexOf(candidate); + + if (depth >= 0) + { + return depth; + } + } + + return 0; } /// @@ -873,6 +1009,129 @@ private INamespace GetSourceLocalScope(IElement sourcePoco) return this.RootNamespace; } + /// + /// Returns the innermost scope of a reference's chain that may produce a MATCH, or + /// when every scope of the chain may. + /// + /// The source POCO at the reference site. + /// The floor scope, or . + /// + /// KerML §8.2.3.5.2 anchors a inside a + /// at the "non-invocation Namespace" — the nearest + /// containing Namespace that is neither an expression nor a parameter of one. For a + /// that is the value-carrying Feature ITSELF, so its inherited members + /// are in scope. The pilot parser (NamespaceUtil.getNonExpressionNamespaceFor) steps one + /// scope FURTHER out whenever the climb passes a FeatureValue, so those inherited members are NOT. + /// The disagreement is observable: part :>> subsystemA = subsystem1; resolves under the + /// spec reading (the redefining Feature inherits the variation's variant Memberships) but is a name + /// resolution ERROR under the pilot's. This floor keeps such scopes in the chain as SHADOW sources + /// while barring them from producing a match, so every name emitted resolves to the same element + /// under both readings. + /// + private static INamespace QueryValueExpressionMatchFloor(IElement sourcePoco) + { + if (sourcePoco is not IMembership sourceMembership) + { + return null; + } + + var subject = sourceMembership; + var scope = QueryExpressionScope(subject); + var visited = new HashSet(); + + while (scope != null + && (subject is IFeatureValue || scope is IInstantiationExpression || scope is IFeatureReferenceExpression)) + { + subject = QueryOwningMembershipSafe(scope); + + if (subject == null || !visited.Add(subject)) + { + break; + } + + scope = QueryExpressionScope(subject); + } + + return scope; + } + + /// + /// Returns the namespace containing , except for a + /// on a parameter of an , whose + /// value expression is resolved against the invocation rather than against the parameter. + /// + /// The membership whose containing scope is requested. + /// The scope, or when the membership has none. + private static INamespace QueryExpressionScope(IMembership membership) + { + var scope = QueryParentNamespace(membership); + + if (scope == null) + { + return null; + } + + return membership is IFeatureValue && QueryOwningContainer(scope) is IInstantiationExpression invocation + ? invocation + : scope; + } + + /// + /// Returns the nearest containing , by + /// CONTAINMENT rather than by the derived owningNamespace — which is null for a Relationship. + /// + /// The element to climb from; may be . + /// The containing namespace, or at the top of the containment tree. + private static INamespace QueryParentNamespace(IElement element) + { + var visited = new HashSet(); + + for (var current = QueryContainer(element); current != null && visited.Add(current); current = QueryContainer(current)) + { + if (current is INamespace containingNamespace) + { + return containingNamespace; + } + } + + return null; + } + + /// + /// Returns the element CONTAINING : the owning related element of a + /// Relationship — whose owner is null, since it is owned as a relationship rather than as a + /// member — and the owner of anything else. + /// + /// The element to climb from; may be . + /// The container, or at the top of the containment tree. + private static IElement QueryContainer(IElement element) + { + return element switch + { + null => null, + IRelationship { OwningRelatedElement: { } owningRelatedElement } => owningRelatedElement, + _ => QueryOwnerSafe(element), + }; + } + + /// + /// Returns 's owningMembership, or when + /// unreachable or the derived property is not implemented. + /// + /// The element whose owning membership is requested; must be non-null. + /// The owning membership or . + private static IMembership QueryOwningMembershipSafe(IElement element) + { + try + { + return element.owningMembership; + } + catch (NotSupportedException) + { + return null; + } + } + /// /// Determines whether also REFERENCES a different element that /// shares a simple name with — the exhibit X :>> Y shape, where the @@ -936,20 +1195,14 @@ private INamespace QueryRedefinedFeatureScope(IRedefinition redefinition, IEleme return null; } - // A variant Usage must directly or indirectly specialize its owning variation — SysML v2 - // §8.3.6.4, checkUsageVariationUsageSpecialization ("If a Usage has an owningVariationUsage, - // then it must directly or indirectly specialize that Usage") and its Definition counterpart. - // That Specialization is IMPLIED, so it is absent from a model exported without implied - // relationships; without adding the variation scope here, a redefinition of a member the - // variant inherits THROUGH the variation cannot shorten and degrades to a fully qualified - // name. Appended last so declared supertypes keep priority. - if (owningType.owningMembership is IVariantMembership - && owningType.owningNamespace is { } owningVariation - && !ReferenceEquals(owningVariation, owningType) - && !generalScopes.Contains(owningVariation)) - { - generalScopes.Add(owningVariation); - } + // A model exported without implied Relationships (KerML §8.4.2) omits Specializations the + // abstract syntax requires — a variant Usage specializing its owning variation, for one. Without + // them a redefinition of a member inherited THROUGH such a Specialization cannot shorten and + // degrades to a fully qualified name. Appended last so declared supertypes keep priority. + generalScopes.AddRange(this.impliedRelationshipProvider.GetImpliedSpecializations(owningType) + .Select(specialization => this.TranslateToResolutionGraph(specialization.General)) + .OfType() + .Where(general => !ReferenceEquals(general, owningType) && !generalScopes.Contains(general))); if (generalScopes.Count == 0) { @@ -958,12 +1211,17 @@ private INamespace QueryRedefinedFeatureScope(IRedefinition redefinition, IEleme var rawName = QueryPreferredRawName(target); - var bindingScope = string.IsNullOrWhiteSpace(rawName) - ? null - : generalScopes.FirstOrDefault(scope => - this.ResolveSimpleNameInScope(scope, target, rawName, localRedefiner: null, selfBindingScope: null) == SimpleNameResolution.Matched); + if (string.IsNullOrWhiteSpace(rawName)) + { + return generalScopes[0]; + } - return bindingScope ?? generalScopes[0]; + // Only a scope that actually binds the name can be the scope the redefinition's own binding + // would have occupied. Electing one that does not — which became reachable once implied + // Specializations joined the candidates — makes the caller treat the name as self-bound there + // and walk past the scope that really holds it, ending in a needlessly qualified name. + return generalScopes.FirstOrDefault(scope => + this.ResolveSimpleNameInScope(scope, target, rawName, localRedefiner: null, selfBinding: null) == SimpleNameResolution.Matched); } /// @@ -973,14 +1231,13 @@ private INamespace QueryRedefinedFeatureScope(IRedefinition redefinition, IEleme /// For a the spec anchors resolution at the /// owningNamespace of the owningType — one level OUT from the owning feature — so the /// owning feature's own and inherited members are NOT in scope. - /// NOT implemented: the clause also anchors a whose - /// referencingFeature is an end feature of a at the CONNECTOR's owning - /// namespace. Applying that emits Actions::Action::start where 3a-1 needs the short start - /// the pilot writes; the cause is NOT diagnosed, since that namespace inherits start and ought - /// to resolve it. Meanwhile the climb anchors deeper than the spec allows — at the end feature, so the - /// end's and the connector's own members are wrongly in scope — which may be masking an indexing gap. - /// On odd resolution around connector ends, check first whether the connector's owning namespace binds - /// the name at all. + /// A whose referencingFeature is an end feature of a + /// anchors at the CONNECTOR's owning namespace. That namespace inherits the + /// referenced name only through IMPLIED Specializations, so this anchoring works only because the + /// simple-name index folds implied generals in — translated into THIS graph first, since the implied + /// layer may be wired against a separate library load whose instances never satisfy reference + /// equality here. See TranslateToResolutionGraph; diagnosis in + /// .team-notes/start-overqualification-diagnosis.md. /// /// The context relationship at the reference site. /// The local scope, or when the generic climb applies. @@ -991,13 +1248,113 @@ private static INamespace QueryContextRelationshipLocalScope(IElement sourcePoco // Connector ends keep the containment climb — see the remark above. This case must precede // ISpecialization: a ReferenceSubsetting IS a Specialization and would otherwise be // re-anchored by the general rule below. - IReferenceSubsetting { referencingFeature: { IsEnd: true, owningType: IConnector } } => null, + IReferenceSubsetting { referencingFeature: { IsEnd: true, owningType: IConnector connector } } => QueryOwningContainer(connector), ISpecialization specialization => specialization.owningType != null ? QueryOwningContainer(specialization.owningType) : QueryOwningContainer(specialization), IConjugation conjugation => conjugation.owningType != null ? QueryOwningContainer(conjugation.owningType) : QueryOwningContainer(conjugation), _ => null }; } + /// + /// What stays fixed while one reference is resolved: the scopes to probe and the two exclusions + /// that apply to every probe of it. Only the name being looked up varies. + /// + private sealed class ReferenceSite + { + /// + /// Initializes a new instance of the class. + /// + /// The POCO bearing the reference. + /// The scopes to probe, innermost first, with the match floor. + /// Feature to exclude from every bucket, or . + /// The binding the reference itself is, or . + internal ReferenceSite(IElement sourcePoco, SourceScopeChain chain, IFeature localRedefiner, SelfBinding selfBinding) + { + this.SourcePoco = sourcePoco; + this.Chain = chain; + this.LocalRedefiner = localRedefiner; + this.SelfBinding = selfBinding; + } + + /// + /// Gets the POCO bearing the reference. + /// + internal IElement SourcePoco { get; } + + /// + /// Gets the scopes to probe, innermost first, with the depth at which a match is admissible. + /// + internal SourceScopeChain Chain { get; } + + /// + /// Gets the Feature excluded from every bucket — the reference's own redefining or + /// referencing Feature, which must not shadow its own target. + /// + internal IFeature LocalRedefiner { get; } + + /// + /// Gets the binding the reference itself is, whose entry does not exist at parse time. + /// + internal SelfBinding SelfBinding { get; } + } + + /// + /// The name binding a reference IS: the Membership being emitted and the scope it binds in. + /// + private sealed class SelfBinding + { + /// + /// Initializes a new instance of the class. + /// + /// The scope the membership binds in. + /// The Membership that IS the reference. + internal SelfBinding(INamespace scope, IMembership membership) + { + this.Scope = scope; + this.Membership = membership; + } + + /// + /// Gets the scope the membership binds in. + /// + internal INamespace Scope { get; } + + /// + /// Gets the Membership that IS the reference, and whose binding therefore does not exist yet + /// at parse time. + /// + internal IMembership Membership { get; } + } + + /// + /// A reference site's scope chain, innermost first, together with the depth at which a MATCH + /// becomes admissible. + /// + private sealed class SourceScopeChain + { + /// + /// Initializes a new instance of the class. + /// + /// The scopes, innermost first. + /// The index of the innermost scope a match may come from. + internal SourceScopeChain(IReadOnlyList scopes, int matchFloor) + { + this.Scopes = scopes; + this.MatchFloor = matchFloor; + } + + /// + /// Gets the scopes of the chain, innermost first. + /// + internal IReadOnlyList Scopes { get; } + + /// + /// Gets the index of the innermost scope a match may come from; scopes below it are consulted + /// for shadowing only (see ). + /// + internal int MatchFloor { get; } + } + /// /// Tri-state result of probing one scope for one lexical form. /// @@ -1023,9 +1380,10 @@ private enum SimpleNameResolution /// The element to look up. /// The simple-name lexical form to probe; must be non-blank. /// Feature to exclude from the bucket, or . - /// Scope whose binding of the target must be ignored, or . + /// The binding the reference itself is, or . + /// Predicate deciding whether a bound element counts as the target, or for reference identity. /// The resolution state. - private SimpleNameResolution ResolveSimpleNameInScope(INamespace scope, IElement target, string rawName, IFeature localRedefiner, INamespace selfBindingScope) + private SimpleNameResolution ResolveSimpleNameInScope(INamespace scope, IElement target, string rawName, IFeature localRedefiner, SelfBinding selfBinding, Func accept = null) { var index = this.GetSimpleNameIndex(scope); @@ -1034,15 +1392,26 @@ private SimpleNameResolution ResolveSimpleNameInScope(INamespace scope, IElement return SimpleNameResolution.NotBound; } + accept ??= candidate => ReferenceEquals(candidate, target); + // The reference's own binding does not exist at parse time; only OTHER elements bound // under the name in this scope shadow the target. - if (selfBindingScope != null && ReferenceEquals(scope, selfBindingScope)) + if (selfBinding != null && ReferenceEquals(scope, selfBinding.Scope)) { var isBoundToOtherElement = elements.Any(element => - !ReferenceEquals(element, target) && !ReferenceEquals(element, localRedefiner)); + !accept(element) && !ReferenceEquals(element, localRedefiner)); + + if (isBoundToOtherElement) + { + return SimpleNameResolution.Shadowed; + } - return isBoundToOtherElement - ? SimpleNameResolution.Shadowed + // The index is keyed by ELEMENT, so the reference's own entry is indistinguishable from + // one the scope holds anyway — inherited, imported or aliased. Only the former is absent + // at parse time: when another Membership of this scope binds the same name to the same + // element, the parser resolves the simple name here and the bare form is correct. + return this.BindsTargetIndependently(scope, rawName, target, selfBinding.Membership) + ? SimpleNameResolution.Matched : SimpleNameResolution.NotBound; } @@ -1055,7 +1424,7 @@ private SimpleNameResolution ResolveSimpleNameInScope(INamespace scope, IElement if (candidates.Count == 1) { - return ReferenceEquals(candidates[0], target) + return accept(candidates[0]) ? SimpleNameResolution.Matched : SimpleNameResolution.Shadowed; } @@ -1088,11 +1457,218 @@ private SimpleNameResolution ResolveSimpleNameInScope(INamespace scope, IElement onlyLeaf = element; } - return leafCount == 1 && ReferenceEquals(onlyLeaf, target) + return leafCount == 1 && accept(onlyLeaf) ? SimpleNameResolution.Matched : SimpleNameResolution.Shadowed; } + /// + /// Determines whether binds to + /// through a Membership OTHER than , + /// the reference being emitted. + /// + /// The scope to inspect. + /// The simple-name lexical form. + /// The referenced element. + /// The Membership that IS the reference. + /// when the binding survives without the reference itself. + /// + /// Answers the question the element-keyed index cannot: first start; declares a Membership + /// naming the library Actions::Action::start in a scope that ALREADY inherits that same + /// element through an implied Specialization, so the parser resolves the bare name — while + /// member Foo::bar; in a scope with no other binding for bar does not. + /// Deliberately a live query rather than index provenance: it runs only when the probed scope + /// is the reference's own AND the name is bound there to nothing else, which is rare. It mirrors + /// the sources of and + /// ; a source it fails to cover only costs a longer name, never + /// an invalid one. + /// + private bool BindsTargetIndependently(INamespace scope, string rawName, IElement target, IMembership selfMembership) + { + return this.QueryBindingMemberships(scope, visibleOnly: false) + .Any(membership => !ReferenceEquals(membership, selfMembership) && BindsName(membership, rawName, target)); + } + + /// + /// Determines whether binds to + /// among its VISIBLE Memberships — the test the parser applies to every + /// segment of a qualified name after the first. + /// + /// The scope named by the preceding segment. + /// The segment's simple-name lexical form. + /// The element the segment must name. + /// when the segment resolves visibly to the target. + /// + /// Namespace::resolve resolves the FIRST segment with resolveLocal — the outward climb + /// over owned, imported and inherited Memberships of ANY visibility — but every following segment + /// with resolveVisible, i.e. visibleMemberships(Set{}, false, false), which is public + /// only (KerML §8.2.3.5.3). The simple-name index cannot answer this: it is built with the + /// visibility filter of the path that REACHED the scope, and within the model that admits + /// everything. Emitting A::b for a b that is private in A would produce a name + /// no conformant parser resolves. + /// + private bool BindsVisibly(INamespace scope, string rawName, IElement target) + { + return this.QueryBindingMemberships(scope, visibleOnly: true) + .Any(membership => BindsName(membership, rawName, target)); + } + + /// + /// Enumerates the Memberships that give its name bindings: owned, + /// imported, inherited, and those contributed by implied generals. + /// + /// The scope whose bindings are collected. + /// Whether to keep only the bindings visible OUTSIDE the scope. + /// The Memberships, with duplicates possible. + private List QueryBindingMemberships(INamespace scope, bool visibleOnly) + { + var memberships = new List(QueryOwnedMembershipsSafe(scope).Where(ownedMember => PassesVisibilityFilter(ownedMember, visibleOnly))); + + memberships.AddRange(QueryImportedMembershipsSafe(scope, visibleOnly)); + + if (scope is not IType type) + { + return memberships; + } + + memberships.AddRange(QueryInheritedMembershipsSafe(type).Where(inheritedMember => PassesVisibilityFilter(inheritedMember, visibleOnly))); + + var declaredSupertypes = QueryAllSupertypesSafe(type) + .Where(supertype => !ReferenceEquals(supertype, type)) + .ToList(); + + foreach (var impliedGeneral in this.QueryImpliedGeneralClosure(type, declaredSupertypes)) + { + memberships.AddRange(QueryOwnedMembershipsSafe(impliedGeneral) + .Where(ownedMember => ownedMember.Visibility != VisibilityKind.Private && PassesVisibilityFilter(ownedMember, visibleOnly))); + + memberships.AddRange(QueryInheritedMembershipsSafe(impliedGeneral).Where(inheritedMember => PassesVisibilityFilter(inheritedMember, visibleOnly))); + } + + return memberships; + } + + /// + /// Determines whether binds to + /// , under either lexical form. + /// + /// The Membership to test. + /// The simple-name lexical form. + /// The referenced element. + /// when the membership binds the name to the target. + private static bool BindsName(IMembership membership, string rawName, IElement target) + { + if (membership?.MemberElement == null || !ReferenceEquals(membership.MemberElement, target)) + { + return false; + } + + var (shortName, longName) = QueryMembershipNames(membership, target); + + return string.Equals(shortName, rawName, StringComparison.Ordinal) + || string.Equals(longName, rawName, StringComparison.Ordinal); + } + + /// + /// Returns 's ownedMembership, or an empty list when the derived + /// property is not implemented. + /// + /// The scope to query; must be non-null. + /// The owned memberships, possibly empty. + private static List QueryOwnedMembershipsSafe(INamespace scope) + { + try + { + return scope.ownedMembership; + } + catch (NotSupportedException) + { + return []; + } + } + + /// + /// Returns 's inheritedMembership, or an empty list when the derived + /// property is not implemented. + /// + /// The type to query; must be non-null. + /// The inherited memberships, possibly empty. + private static List QueryInheritedMembershipsSafe(IType type) + { + try + { + return type.inheritedMembership; + } + catch (NotSupportedException) + { + return []; + } + } + + /// + /// Returns the Memberships 's own Imports contribute, mirroring + /// minus its collision filter — a colliding import + /// cannot be the INDEPENDENT binding anyway, since the owned member it collides with is. + /// + /// The importing scope; must be non-null. + /// Whether to keep only PUBLIC imports, the ones that re-export. + /// The imported memberships, possibly empty. + private static List QueryImportedMembershipsSafe(INamespace scope, bool visibleOnly) + { + var imported = new List(); + + try + { + foreach (var ownedImport in scope.ownedImport.Where(ownedImport => PassesVisibilityFilter(ownedImport, visibleOnly))) + { + switch (ownedImport) + { + case IMembershipImport { ImportedMembership: { } importedMembership }: + imported.Add(importedMembership); + break; + case INamespaceImport { ImportedNamespace: not null } namespaceImport: + imported.AddRange(QueryVisibleMemberships(namespaceImport.ImportedNamespace, namespaceImport.IsImportAll, false, [scope])); + break; + } + } + } + catch (NotSupportedException) + { + // ownedImport, or a derivation behind one of the imported namespaces, is not implemented. + } + + return imported; + } + + /// + /// Builds the acceptance predicate for an ancestor ANCHOR in a partially-qualified name: the anchor + /// segment need not resolve to itself, as long as what it resolves to + /// binds the next segment to the same . + /// + /// The owner-chain ancestor being probed as an anchor. + /// The element named by the segment immediately below the anchor. + /// The predicate. + /// + /// A feature that redefines a Type binds that Type's members by inheritance, so it anchors a path + /// through them exactly as the Type does — subsystemA::subsystem1 where subsystemA + /// resolves to the redefining part :>> subsystemA rather than to the variation it redefines. + /// Identity of the anchor is therefore too strong a test; what matters is that the WHOLE path still + /// resolves to the target, which is verified here one segment at a time. + /// + private Func BuildAnchorAcceptance(IElement ancestor, IElement descendant) + { + var descendantRawName = QueryPreferredRawName(descendant); + + // Whatever the anchor segment resolves to is what the parser applies visible resolution to for + // the next segment, so the check runs against the CANDIDATE — for the anchor itself as much as + // for a Feature that redefines it. + return candidate => candidate is INamespace candidateScope + && !string.IsNullOrWhiteSpace(descendantRawName) + && this.BindsVisibly(candidateScope, descendantRawName, descendant) + && (ReferenceEquals(candidate, ancestor) + || this.ResolveSimpleNameInScope(candidateScope, descendant, descendantRawName, localRedefiner: null, selfBinding: null) == SimpleNameResolution.Matched); + } + /// /// Narrows to the directly-owned ones when every other candidate is /// only inherited into — an owned feature shadows a same-named inherited @@ -1281,7 +1857,7 @@ private void BuildOwnedAndImportedEntries(INamespace scope, Dictionary IsVisibleWhenGlobal(ownedMember, isGlobal))) + foreach (var ownedMember in scope.ownedMembership.Where(ownedMember => PassesVisibilityFilter(ownedMember, isGlobal))) { AddMembershipEntry(index, ownedMember, pending, isGlobal); this.RecordAliasIfDeclared(scope, ownedMember); @@ -1300,7 +1876,7 @@ private void BuildOwnedAndImportedEntries(INamespace scope, Dictionary IsVisibleWhenGlobal(ownedImport, isGlobal))) + foreach (var ownedImport in scope.ownedImport.Where(ownedImport => PassesVisibilityFilter(ownedImport, isGlobal))) { switch (ownedImport) { @@ -1321,7 +1897,13 @@ private void BuildOwnedAndImportedEntries(INamespace scope, Dictionary - /// The source scope chain (innermost first). + /// The reference site; its source POCO rejects the alias declaration itself. /// The element being referenced. - /// The reference site's source POCO — used to reject the alias declaration itself. - /// Feature to exclude from the scope buckets, or . - /// Scope whose binding of the target must be ignored, or . /// On a hit, the escaped alias name to emit. /// when an unambiguous in-scope alias was found. - private bool TryResolveViaAlias(IReadOnlyList chain, IElement target, IElement sourcePoco, IFeature localRedefiner, INamespace selfBindingScope, out string matched) + private bool TryResolveViaAlias(ReferenceSite site, IElement target, out string matched) { matched = null; - var candidateAliasNames = chain + var candidateAliasNames = site.Chain.Scopes .Where(scope => this.aliasIndex.ContainsKey(scope)) .SelectMany(scope => this.aliasIndex[scope].TryGetValue(target, out var aliasNames) ? aliasNames : Enumerable.Empty()) .Distinct(StringComparer.Ordinal) - .Where(aliasName => !DeclaresAlias(sourcePoco, target, aliasName)); + .Where(aliasName => !DeclaresAlias(site.SourcePoco, target, aliasName)); foreach (var aliasName in candidateAliasNames) { - if (this.TryResolveSimpleNameAcrossChain(chain, target, aliasName, Escape(aliasName), localRedefiner, selfBindingScope, out matched)) + if (this.TryResolveSimpleNameAcrossChain(site, target, aliasName, Escape(aliasName), accept: null, out matched)) { return true; } @@ -1531,16 +2110,18 @@ private void RecordDirectFacade(INamespace canonicalOwner, INamespace facade) } /// - /// When is set, admits only PUBLIC memberships and imports — the - /// global namespace contains only the visible memberships of other roots (KerML §8.2.3.5.2), so a - /// name bound privately there would not re-parse. Within the model itself everything is visible. + /// When is set, admits only PUBLIC memberships and imports — the + /// filter both the global namespace and visible resolution apply. The global namespace contains + /// only the visible memberships of other roots, and every segment of a qualified name after the + /// first resolves against the visible memberships of the preceding one (KerML §8.2.3.5.2–.3), so a + /// name bound privately there would not re-parse. Within a local scope everything is visible. /// /// The or considered. - /// Whether the owning scope is reached through the global namespace. + /// Whether only bindings visible OUTSIDE the owning scope are admitted. /// when the relationship may contribute a binding. - private static bool IsVisibleWhenGlobal(IRelationship relationship, bool isGlobal) + private static bool PassesVisibilityFilter(IRelationship relationship, bool publicOnly) { - if (!isGlobal) + if (!publicOnly) { return true; } @@ -1557,26 +2138,26 @@ private static bool IsVisibleWhenGlobal(IRelationship relationship, bool isGloba /// Indexes the entries inherited from 's transitive supertypes; namespace /// supertypes are enqueued as scopes in their own right. /// - /// KNOWN DIVERGENCE from Type::inheritedMembership (KerML §8.3.3.1.10): this walk flattens - /// the hierarchy and applies only removeRedefinedFeatures condition 2, at the leaf type - /// alone, so a membership an intermediate supertype redefined away still reaches this index. It - /// also admits private supertype members and misses their public/protected - /// imports. The SDK's type.inheritedMembership is spec-faithful on all three counts and is - /// verified for the transitive case by - /// TypeExtensionsTestFixture.VerifyComputeInheritedMembershipsOperation, so it is the - /// intended replacement, and delegating to it is a small, well-understood diff. It was attempted - /// and backed out for COST, not correctness: inheritedMembership recomputes the transitive - /// closure on every access (no memoisation) and this method runs once per indexed Type, which took - /// the textual-notation validation fixture from 17 s to over 4 minutes (measured back-to-back on an - /// otherwise idle machine). Delegating therefore needs a memoisation layer first — either inside - /// TypeExtensions or as a per-Type memo held by this cache. + /// Membership indexing delegates to Type::inheritedMembership (KerML §8.3.3.1.10) rather than + /// re-deriving it. An earlier flattened walk applied removeRedefinedFeatures condition 2 at + /// the leaf type only — so a membership an intermediate supertype redefined away still reached the + /// index — and it admitted private supertype members while missing their + /// public/protected imports. Delegating is spec-faithful on all three counts. + /// + /// + /// The delegation had previously been backed out for COST, when inheritedMembership + /// recomputed the transitive closure on every access and took the validation fixture from 17 s to + /// over 4 minutes. The per-query memoisation since added to TypeExtensions removes that + /// blow-up: the same fixture now runs in 40 s against 18 s for the flattened walk, measured + /// back-to-back. That remaining ~2x is the price of spec fidelity, and the corpus output is + /// unchanged by the switch. /// /// /// The type whose inherited memberships are indexed. /// The destination index. /// Queue of namespaces yet to be indexed. /// Whether the owning scope is reached through the global namespace. - private static void BuildInheritedEntries(IType type, Dictionary> index, Queue<(INamespace Scope, bool IsGlobal)> pending, bool isGlobal) + private void BuildInheritedEntries(IType type, Dictionary> index, Queue<(INamespace Scope, bool IsGlobal)> pending, bool isGlobal) { var inheritableSupertypes = QueryAllSupertypesSafe(type) .OfType() @@ -1588,75 +2169,200 @@ private static void BuildInheritedEntries(IType type, Dictionary inheritedMemberships; + + try + { + inheritedMemberships = type.inheritedMembership; + } + catch (NotSupportedException) + { + // Resolving inheritance is atomic: a derivation that is unimplemented ANYWHERE in this + // Type's transitive supertype closure costs the whole closure, not just the branch that + // raised. Names that would have resolved through an unaffected supertype then fall back + // to a longer — never an invalid — form. + return; + } - foreach (var supertype in inheritableSupertypes) + foreach (var inheritedMember in inheritedMemberships + .Where(inheritedMember => PassesVisibilityFilter(inheritedMember, isGlobal))) { - try + AddMembershipEntry(index, inheritedMember, pending, isGlobal); + } + + // Implied Specializations are DETACHED — the layer computing them never touches + // ownedRelationship, so inheritedMembership above cannot see them and a name inherited ONLY + // through an implied general never reaches the index. Resolution then walks past the scope that + // really binds the name and emits a needlessly qualified form. + // + // These entries are LOOKUP-ONLY. The implied general is deliberately NOT enqueued as a scope: + // `pending` drives traversal into further namespaces, and an implied general is a library Type, + // so enqueueing it would drag the model libraries into the walk. Only the members it contributes + // are indexed, so nothing here can reach the writer. + foreach (var impliedGeneral in this.QueryImpliedGeneralClosure(type, inheritableSupertypes)) + { + // `pending` feeds INDEX construction only — it is not the traversal that emits output — so + // indexing the general as a scope in its own right keeps the fix lookup-only while making + // the names it owns resolvable. + pending.Enqueue((impliedGeneral, isGlobal)); + + AddImpliedLookupEntries(impliedGeneral, index, isGlobal); + } + } + + /// + /// Translates a Type produced by the implied-relationship layer into this cache's OWN object graph. + /// + /// The general of an implied Specialization, possibly from a foreign graph. + /// The same-Id Type of the resolution graph, or null when the graph does not carry it. + /// + /// The implied layer may be wired against a SEPARATE library load — a full, model-independent one — + /// so the generals it returns can be different POCO instances than the ones this cache resolves + /// against, even for the same library element (same Id). Indexing a foreign instance is + /// worse than useless: it can never equal a resolution target by reference, so it answers + /// Shadowed and STOPS the outward walk that would otherwise have found the local instance. + /// Translating by Id keeps reference equality authoritative everywhere else. A general the + /// resolution graph does not carry is dropped: its members can never be targets here. + private IType TranslateToResolutionGraph(IType impliedGeneral) + { + if (impliedGeneral == null) + { + return null; + } + + this.resolutionGraphElementsById ??= this.BuildResolutionGraphIndex(); + + if (this.resolutionGraphElementsById.TryGetValue(impliedGeneral.Id, out var local)) + { + return local as IType; + } + + // The general may belong to THIS graph already — a hand-coded rule computing against the model + // itself returns resolution-graph instances, which the containment walk below indexes only for + // library namespaces. + return this.IsInResolutionGraph(impliedGeneral) ? impliedGeneral : null; + } + + /// + /// Builds the by-Id index of every Element reachable from the global namespaces. + /// + /// The index. + private Dictionary BuildResolutionGraphIndex() + { + var elementsById = new Dictionary(); + var pendingElements = new Queue(); + + foreach (var globalNamespace in this.globalNamespaces) + { + pendingElements.Enqueue(globalNamespace); + } + + while (pendingElements.Count > 0) + { + var current = pendingElements.Dequeue(); + + // First-wins on a duplicate Id: distinct libraries carry unique Ids, so a collision only + // occurs when the same library is loaded twice, and the copies are then interchangeable. + if (!elementsById.TryAdd(current.Id, current)) { - foreach (var ownedMember in supertype.ownedMembership - .Where(ownedMember => IsVisibleWhenGlobal(ownedMember, isGlobal)) - .Where(ownedMember => !IsRedefinedAway(ownedMember, featuresRedefinedByOwned))) - { - AddMembershipEntry(index, ownedMember, pending, isGlobal); - } + continue; } - catch (NotSupportedException) + + foreach (var owned in current.OwnedRelationship.SelectMany(relationship => relationship.OwnedRelatedElement)) { - // ownedMembership not implemented for this supertype; skip. + pendingElements.Enqueue(owned); } } + + return elementsById; } /// - /// Collects the features directly redefined by 's owned features — the - /// ownedFeature.redefinition.redefinedFeature set of removeRedefinedFeatures. + /// Asserts whether an Element belongs to this cache's own graph, by walking its owners to a known root. /// - /// The type whose owned redefinitions are collected. - /// The redefined features; empty when unavailable. - private static HashSet QueryFeaturesRedefinedByOwnedFeatures(IType type) + /// The Element to test. + /// True when an owner chain reaches the root or a global namespace. + private bool IsInResolutionGraph(IElement element) { - try - { - return [..type.ownedFeature - .SelectMany(ownedFeature => ownedFeature.OwnedRelationship.OfType()) - .Select(redefinition => (IElement)redefinition.RedefinedFeature) - .Where(redefined => redefined != null)]; - } - catch (NotSupportedException) + for (var current = element; current != null; current = current.owner) { - return []; + if (ReferenceEquals(current, this.RootNamespace) || this.globalNamespaces.Contains(current)) + { + return true; + } } + + return false; } /// - /// Applies condition 2 of Type::removeRedefinedFeatures: an inherited membership drops out of - /// the local scope when its member element — or anything that element redefines — is redefined by an - /// owned feature of the inheriting type. The redefinition's own target is still reachable through the - /// §8.2.3.5.1 supertype scope (see ). + /// Returns every Type reachable from a Type or its declared supertypes through implied + /// Specializations, transitively. /// - /// The candidate inherited membership. - /// Features redefined by the inheriting type's owned features. - /// when the membership must not be indexed. - private static bool IsRedefinedAway(IMembership membership, HashSet featuresRedefinedByOwned) + /// The Type whose implied generals are collected. + /// The declared supertypes, which carry implied Specializations of their own. + /// The implied generals, without duplicates. + private List QueryImpliedGeneralClosure(IType type, List declaredSupertypes) { - if (featuresRedefinedByOwned.Count == 0 || membership.MemberElement is not IFeature memberFeature) + var visited = new HashSet(); + var pendingTypes = new Queue(); + + pendingTypes.Enqueue(type); + + foreach (var declaredSupertype in declaredSupertypes) { - return false; + pendingTypes.Enqueue(declaredSupertype); } - if (featuresRedefinedByOwned.Contains(memberFeature)) + var impliedGenerals = new List(); + + while (pendingTypes.Count > 0) { - return true; + var current = pendingTypes.Dequeue(); + + foreach (var general in this.impliedRelationshipProvider.GetImpliedSpecializations(current) + .Select(specialization => this.TranslateToResolutionGraph(specialization.General)) + .Where(general => general != null && visited.Add(general))) + { + impliedGenerals.Add(general); + pendingTypes.Enqueue(general); + } + } + + return impliedGenerals; + } + + /// + /// Indexes, for lookup only, the members an implied general contributes. + /// + /// The Type reached through an implied Specialization. + /// The destination index. + /// Whether the owning scope is reached through the global namespace. + private static void AddImpliedLookupEntries(IType impliedGeneral, Dictionary> index, bool isGlobal) + { + if (impliedGeneral == null) + { + return; } + // Stricter than the declared-supertype walk on purpose: an implied general is reached without + // any authored relationship, so its private internals are never exposed, even in a non-global + // scope where PassesVisibilityFilter alone would admit them. + var contributed = new List(impliedGeneral.ownedMembership + .Where(ownedMember => ownedMember.Visibility != VisibilityKind.Private)); + try { - return memberFeature.AllRedefinedFeatures().Any(featuresRedefinedByOwned.Contains); + contributed.AddRange(impliedGeneral.inheritedMembership); } catch (NotSupportedException) { - return false; + // Same atomicity as above: an unimplemented derivation costs this general's contribution. + } + + foreach (var member in contributed.Where(member => PassesVisibilityFilter(member, isGlobal))) + { + AddLookupOnlyEntry(index, member); } } @@ -1687,6 +2393,29 @@ private static void AddMembershipEntry(Dictionary> ind } } + /// + /// Indexes a Membership for name lookup WITHOUT extending the namespace traversal. + /// + /// The destination index. + /// The Membership to index. + /// + /// The counterpart of , minus its pending enqueue. Used for + /// members reached through an IMPLIED Specialization: they must be resolvable by name, but the + /// library Types they come from must not be pulled into the walk that produces output. + /// + private static void AddLookupOnlyEntry(Dictionary> index, IMembership membership) + { + if (membership is not { MemberElement: { } target }) + { + return; + } + + var (shortName, longName) = QueryMembershipNames(membership, target); + + AddIndexEntry(index, shortName, target); + AddIndexEntry(index, longName, target); + } + /// /// Returns the two lexical forms a membership binds: the membership's explicit name overrides when /// present, else the member element's own names. diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/TextualNotationWriterContext.cs b/SysML2.NET.Serializer.TextualNotation/Writers/TextualNotationWriterContext.cs index c12b309c..059e8da2 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/TextualNotationWriterContext.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/TextualNotationWriterContext.cs @@ -25,6 +25,8 @@ namespace SysML2.NET.Serializer.TextualNotation.Writers using SysML2.NET.Core.POCO.Kernel.Functions; using SysML2.NET.Core.POCO.Root.Namespaces; + using SysML2.NET.Extensions; + using SysML2.NET.Semantics.Implied; /// /// Provides the serialization context for the textual notation builders. Carries the @@ -35,6 +37,12 @@ namespace SysML2.NET.Serializer.TextualNotation.Writers /// public class TextualNotationWriterContext : IDisposable { + /// + /// Shares inheritance resolution across the whole write, so the Types being written resolve the + /// library supertype chain they have in common once rather than once each. + /// + private readonly InheritanceScope inheritanceScope; + /// /// Initializes a new instance of the class. /// Eagerly builds the per-namespace simple-name index for every namespace reachable @@ -55,15 +63,45 @@ public class TextualNotationWriterContext : IDisposable /// Optional: when omitted, resolution is confined to 's own /// containment and import graph, which can only yield a longer — never an invalid — name. /// - public TextualNotationWriterContext(INamespace contextNamespace, IEnumerable globalNamespaces = null) + /// + /// The provider supplying the implied Relationships (KerML §8.4.2) that a model exported + /// without them omits. Optional: when omitted, a name reachable ONLY through an implied + /// Specialization degrades to a longer — never an invalid — form. + /// + public TextualNotationWriterContext(INamespace contextNamespace, IEnumerable globalNamespaces = null, IImpliedRelationshipProvider impliedRelationshipProvider = null) { - this.CursorCache = new CursorCache(); this.ContextNamespace = contextNamespace ?? throw new ArgumentNullException(nameof(contextNamespace)); - this.NameResolutionCache = new NameResolutionCache(contextNamespace, globalNamespaces); - this.OperatorContextStack = new Stack(); - this.EmitOperatorParentheses = true; + + // Opened before the name-resolution index is built, so the index and the write pass that + // follows it share one inheritance memo; closed by Dispose. + this.inheritanceScope = InheritanceScope.Begin(); + + // Building the index walks the whole reachable model and can therefore raise on a malformed + // one. A constructor that throws leaves the caller's `using` with nothing to dispose, so the + // scope has to be closed here or it would stay open on this thread for good. + try + { + this.CursorCache = new CursorCache(); + this.ImpliedRelationshipProvider = impliedRelationshipProvider ?? NullImpliedRelationshipProvider.Instance; + this.NameResolutionCache = new NameResolutionCache(contextNamespace, globalNamespaces, this.ImpliedRelationshipProvider); + this.OperatorContextStack = new Stack(); + this.EmitOperatorParentheses = true; + } + catch + { + this.CursorCache?.Dispose(); + this.inheritanceScope.Dispose(); + + throw; + } } + /// + /// Gets the provider supplying the implied Relationships (KerML §8.4.2) omitted by a model + /// exported without them; never null. + /// + public IImpliedRelationshipProvider ImpliedRelationshipProvider { get; } + /// /// Gets or sets a value indicating whether the writer should emit precedence-aware /// parentheses around operator-expression operands. Defaults to true, which @@ -121,6 +159,7 @@ public TextualNotationWriterContext(INamespace contextNamespace, IEnumerable +// +// 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 +// +// 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.Serializer.Xmi +{ + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + + using SysML2.NET.Core.POCO.Root.Namespaces; + + /// + /// Loads a complete set of model libraries from disk, independently of any user model. + /// + /// + /// Deserializing a user model yields only the libraries that model transitively references. The KerML + /// 8.4.2 semantic constraints need the whole library set regardless — every Class must specialize + /// Occurrences::Occurrence and every Feature Base::things, whether or not the model + /// mentions them — so a model-independent load is required to resolve them. + /// + public interface IModelLibraryLoader + { + /// + /// Loads every model library found beneath a directory. + /// + /// The root directory to search recursively. + /// The distinct root Namespaces of the loaded libraries. + /// Thrown when is null. + /// Thrown when the directory does not exist. + IReadOnlyCollection Load(string libraryDirectory); + + /// + /// Asynchronously loads every model library found beneath a directory. + /// + /// The root directory to search recursively. + /// The token used to cancel the load. + /// The distinct root Namespaces of the loaded libraries. + /// Thrown when is null. + /// Thrown when the directory does not exist. + Task> LoadAsync(string libraryDirectory, CancellationToken cancellationToken = default); + } +} diff --git a/SysML2.NET.Serializer.Xmi/ModelLibraryLoader.cs b/SysML2.NET.Serializer.Xmi/ModelLibraryLoader.cs new file mode 100644 index 00000000..d09d79f1 --- /dev/null +++ b/SysML2.NET.Serializer.Xmi/ModelLibraryLoader.cs @@ -0,0 +1,184 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Serializer.Xmi +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Logging.Abstractions; + + using SysML2.NET.Core.POCO.Root.Namespaces; + + /// + /// Loads model libraries by recursively deserializing every KerML and SysML interchange file beneath a + /// directory. + /// + /// + /// Both the root Namespace of each file AND the Namespaces it referenced are collected, so libraries + /// reachable only as a dependency of another are indexed too. A file that fails to deserialize is logged + /// and skipped rather than aborting the load, since one malformed library must not make every semantic + /// constraint unresolvable. + /// + public class ModelLibraryLoader : IModelLibraryLoader + { + /// + /// The search patterns identifying a model-library file. + /// + private static readonly string[] LibrarySearchPatterns = ["*.kermlx", "*.sysmlx"]; + + /// + /// The factory used to create loggers for the deserializer. + /// + private readonly ILoggerFactory loggerFactory; + + /// + /// The logger used to report skipped files. + /// + private readonly ILogger logger; + + /// + /// The service resolving references between library files. + /// + private readonly IExternalReferenceService externalReferenceService; + + /// + /// Initializes a new instance of the class. + /// + /// The injected factory used to set up logging. + /// + /// The service resolving href references between library files; optional. + /// + public ModelLibraryLoader(ILoggerFactory loggerFactory, IExternalReferenceService externalReferenceService = null) + { + this.loggerFactory = loggerFactory ?? NullLoggerFactory.Instance; + this.logger = this.loggerFactory.CreateLogger(); + this.externalReferenceService = externalReferenceService; + } + + /// + /// Loads every model library found beneath a directory. + /// + /// The root directory to search recursively. + /// The distinct root Namespaces of the loaded libraries. + /// Thrown when is null. + /// Thrown when the directory does not exist. + public IReadOnlyCollection Load(string libraryDirectory) + { + var deSerializer = new DeSerializer(this.loggerFactory, this.externalReferenceService); + var namespaces = new List(); + + foreach (var libraryFile in QueryLibraryFiles(libraryDirectory)) + { + try + { + Collect(deSerializer.DeSerialize(new Uri(libraryFile)), namespaces); + } + catch (Exception exception) + { + this.logger.LogWarning(exception, "The model library {LibraryFile} could not be loaded and was skipped.", libraryFile); + } + } + + return Distinct(namespaces); + } + + /// + /// Asynchronously loads every model library found beneath a directory. + /// + /// The root directory to search recursively. + /// The token used to cancel the load. + /// The distinct root Namespaces of the loaded libraries. + /// Thrown when is null. + /// Thrown when the directory does not exist. + public async Task> LoadAsync(string libraryDirectory, CancellationToken cancellationToken = default) + { + var deSerializer = new DeSerializer(this.loggerFactory, this.externalReferenceService); + var namespaces = new List(); + + foreach (var libraryFile in QueryLibraryFiles(libraryDirectory)) + { + cancellationToken.ThrowIfCancellationRequested(); + + try + { + Collect(await deSerializer.DeSerializeAsync(new Uri(libraryFile), cancellationToken), namespaces); + } + catch (Exception exception) when (exception is not OperationCanceledException) + { + this.logger.LogWarning(exception, "The model library {LibraryFile} could not be loaded and was skipped.", libraryFile); + } + } + + return Distinct(namespaces); + } + + /// + /// Returns the model-library files beneath a directory, in a stable order. + /// + /// The root directory to search recursively. + /// The absolute paths of the library files. + /// Thrown when is null. + /// Thrown when the directory does not exist. + private static IReadOnlyList QueryLibraryFiles(string libraryDirectory) + { + if (libraryDirectory == null) + { + throw new ArgumentNullException(nameof(libraryDirectory)); + } + + if (!Directory.Exists(libraryDirectory)) + { + throw new DirectoryNotFoundException($"The model-library directory '{libraryDirectory}' does not exist."); + } + + return [..LibrarySearchPatterns + .SelectMany(pattern => Directory.EnumerateFiles(libraryDirectory, pattern, SearchOption.AllDirectories)) + .OrderBy(libraryFile => libraryFile, StringComparer.Ordinal)]; + } + + /// + /// Removes duplicate Namespaces, keeping first-seen order. + /// + /// The collected Namespaces. + /// The distinct Namespaces. + private static IReadOnlyCollection Distinct(List namespaces) => [..namespaces.Distinct()]; + + /// + /// Adds the root and referenced Namespaces of one read result to the accumulator. + /// + /// The result of deserializing one library file. + /// The accumulator. + private static void Collect(XmiReadResult readResult, List namespaces) + { + if (readResult.RootNamespace != null) + { + namespaces.Add(readResult.RootNamespace); + } + + namespaces.AddRange(readResult.ReferencedNamespaces.Where(referenced => referenced != null)); + } + } +} diff --git a/SysML2.NET.Tests/Extend/NamespaceExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/NamespaceExtensionsTestFixture.cs index c343f316..8d977b24 100644 --- a/SysML2.NET.Tests/Extend/NamespaceExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/NamespaceExtensionsTestFixture.cs @@ -25,11 +25,15 @@ namespace SysML2.NET.Tests.Extend using NUnit.Framework; using SysML2.NET.Core.Root.Namespaces; + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Core.Types; using SysML2.NET.Core.POCO.Root.Elements; using SysML2.NET.Core.POCO.Root.Namespaces; using SysML2.NET.Core.POCO.Systems.DefinitionAndUsage; using SysML2.NET.Extensions; + using Type = SysML2.NET.Core.POCO.Core.Types.Type; + [TestFixture] public class NamespaceExtensionsTestFixture { @@ -83,6 +87,24 @@ public void VerifyComputeMembership() namespaceElement.AssignOwnership(membership, element); Assert.That(namespaceElement.ComputeMembership(), Is.EquivalentTo([membership])); + + // `membership` is a derived UNION and its subsets are ownedMembership, importedMembership and + // — when the Namespace is a Type — inheritedMembership (KerML §8.2.3.5.3: memberships "include + // owned, imported and (if the Namespace is a Type) inherited"). Omitting the inherited subset + // makes ResolveLocal, ComputeMember and NamesOf blind to everything a Type inherits. + var supertype = new Type(); + var inheritedFeature = new Feature { DeclaredName = "inherited" }; + var inheritedMembership = new FeatureMembership { Visibility = VisibilityKind.Public }; + supertype.AssignOwnership(inheritedMembership, inheritedFeature); + + var subtype = new Type(); + subtype.AssignOwnership(new Specialization { Specific = subtype, General = supertype }); + + var ownFeature = new Feature { DeclaredName = "own" }; + var ownMembership = new FeatureMembership { Visibility = VisibilityKind.Public }; + subtype.AssignOwnership(ownMembership, ownFeature); + + Assert.That(subtype.ComputeMembership(), Is.EquivalentTo([ownMembership, inheritedMembership])); } [Test] @@ -213,8 +235,56 @@ public void VerifyComputeVisibleMembershipsOperation() // recursive case: public ownedMemberships of the outer namespace, plus visible // memberships harvested from each public nested INamespace. - Assert.That(namespaceElement.ComputeVisibleMembershipsOperation([], true, false), Is.EquivalentTo(new[] { publicMembership, nestedOwning, nestedMembership })); + Assert.That(namespaceElement.ComputeVisibleMembershipsOperation([], true, false), Is.EquivalentTo([publicMembership, nestedOwning, nestedMembership])); } + + // Namespace::visibleMemberships is membershipsOfVisibility(...), NOT `membership`. For a Type + // the two differ: `membership` carries inheritedMembership, which Type::visibleMemberships adds + // back separately with `excluded->including(self)` threaded into the recursion. Sourcing the + // Namespace-level operation from `membership` would both double-count the inherited part and + // discard that cycle guard. + var supertype = new Type(); + var inheritedMembership = new FeatureMembership { Visibility = VisibilityKind.Public }; + supertype.AssignOwnership(inheritedMembership, new Feature { DeclaredName = "inherited" }); + + var subtype = new Type(); + subtype.AssignOwnership(new Specialization { Specific = subtype, General = supertype }); + + var ownMembership = new FeatureMembership { Visibility = VisibilityKind.Public }; + subtype.AssignOwnership(ownMembership, new Feature { DeclaredName = "own" }); + + using (Assert.EnterMultipleScope()) + { + Assert.That(subtype.ComputeVisibleMembershipsOperation([], false, true), Is.EquivalentTo([ownMembership])); + Assert.That(subtype.VisibleMemberships([], false, true), Is.EquivalentTo([ownMembership, inheritedMembership])); + } + + // The recursive branch must descend with `excluded->including(self)`. Here `inner` is a public + // owned Namespace of `outer` that imports `outer` back with `import all`, so without self in + // the excluded set the descent round-trips: outer's PRIVATE membership is re-imported into + // `inner` (an `import all` sees it) and climbs back out as one of outer's own VISIBLE + // memberships. KerML §8.2.3.5.1 makes this guard normative, not defensive. + var outerNamespace = new Namespace { DeclaredName = "outer" }; + var innerNamespace = new Namespace { DeclaredName = "inner" }; + var innerOwning = new OwningMembership { Visibility = VisibilityKind.Public }; + outerNamespace.AssignOwnership(innerOwning, innerNamespace); + + var secretMembership = new OwningMembership { Visibility = VisibilityKind.Private }; + outerNamespace.AssignOwnership(secretMembership, new Definition { DeclaredName = "secret" }); + + var innerMemberMembership = new OwningMembership { Visibility = VisibilityKind.Public }; + innerNamespace.AssignOwnership(innerMemberMembership, new Definition { DeclaredName = "innerMember" }); + + innerNamespace.AssignOwnership(new NamespaceImport + { + ImportedNamespace = outerNamespace, + IsImportAll = true, + Visibility = VisibilityKind.Public + }); + + Assert.That( + outerNamespace.ComputeVisibleMembershipsOperation([], true, false), + Is.EquivalentTo([innerOwning, innerMemberMembership])); } [Test] @@ -343,6 +413,19 @@ public void VerifyComputeResolveLocalOperation() Assert.That(childNamespace.ComputeResolveLocalOperation("myElement"), Is.EqualTo(membership)); Assert.That(childNamespace.ComputeResolveLocalOperation("nonExistent"), Is.Null); } + + // Local resolution is visibility-BLIND (KerML §8.2.3.5.3) — it searches every membership of + // the Namespace, so a private owned member resolves in its own Namespace. That is what + // distinguishes it from visible resolution, which admits public memberships only. + var privateElement = new Definition { DeclaredName = "hidden", DeclaredShortName = "h" }; + var privateMembership = new OwningMembership { Visibility = VisibilityKind.Private }; + childNamespace.AssignOwnership(privateMembership, privateElement); + + using (Assert.EnterMultipleScope()) + { + Assert.That(childNamespace.ComputeResolveLocalOperation("hidden"), Is.EqualTo(privateMembership)); + Assert.That(childNamespace.ComputeResolveLocalOperation("h"), Is.EqualTo(privateMembership)); + } } [Test] diff --git a/SysML2.NET.Tests/Extend/NamespaceImportExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/NamespaceImportExtensionsTestFixture.cs new file mode 100644 index 00000000..27ed4ada --- /dev/null +++ b/SysML2.NET.Tests/Extend/NamespaceImportExtensionsTestFixture.cs @@ -0,0 +1,63 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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.Root.Namespaces; + using SysML2.NET.Core.POCO.Root.Namespaces; + using SysML2.NET.Extensions; + + [TestFixture] + public class NamespaceImportExtensionsTestFixture + { + [Test] + public void VerifyComputeRedefinedImportedMembershipsOperation() + { + Assert.That(() => ((INamespaceImport)null).ComputeRedefinedImportedMembershipsOperation([]), Throws.TypeOf()); + + // No importedNamespace -> nothing to import. + Assert.That(new NamespaceImport().ComputeRedefinedImportedMembershipsOperation([]), Is.Empty); + + var importedNamespace = new Namespace { DeclaredName = "imported" }; + var visibleMembership = new OwningMembership { Visibility = VisibilityKind.Public }; + importedNamespace.AssignOwnership(visibleMembership, new Namespace { DeclaredName = "member" }); + + var subject = new NamespaceImport { ImportedNamespace = importedNamespace }; + + using (Assert.EnterMultipleScope()) + { + Assert.That(subject.ComputeRedefinedImportedMembershipsOperation([]), Is.EquivalentTo([visibleMembership])); + + // `if excluded->includes(importedNamespace) then Sequence{}` — the OCL's first branch. This + // is the circularity guard of KerML §8.2.3.5.1 ("an implementation must avoid re-processing + // a Namespace that has already been visited"), so it is load-bearing: without it a + // Namespace that imports one of its own ancestors re-enters that ancestor indefinitely. + Assert.That(subject.ComputeRedefinedImportedMembershipsOperation([importedNamespace]), Is.Empty); + + // An unrelated Namespace in the excluded set must not suppress the import. + Assert.That(subject.ComputeRedefinedImportedMembershipsOperation([new Namespace()]), Is.EquivalentTo([visibleMembership])); + } + } + } +} diff --git a/SysML2.NET.Tests/Extend/TypeExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/TypeExtensionsTestFixture.cs index c8a357e7..8cc3066f 100644 --- a/SysML2.NET.Tests/Extend/TypeExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/TypeExtensionsTestFixture.cs @@ -22,6 +22,7 @@ namespace SysML2.NET.Tests.Extend { using System; using System.Collections.Generic; + using System.Linq; using NUnit.Framework; @@ -787,6 +788,117 @@ public void VerifyComputeInheritedMembershipsOperation() supertype.AssignOwnership(publicMembership, publicElement); Assert.That(subject.ComputeInheritedMembershipsOperation(null, null, false), Does.Contain(publicMembership)); + + // Transitive hiding: `middle` owns a feature that REDEFINES grandparent::hidden under a + // different name, so grandparent::hidden is not a membership of `middle` and must not reach + // `leaf` either. RemoveRedefinedFeatures is applied at EVERY level of the recursion (KerML + // §8.3.3.1.10) — a flattened all-supertypes walk that filters only at the leaf would let it + // through, because `leaf` itself owns no redefinition. + var grandparent = new Type(); + var hiddenFeature = new Feature { DeclaredName = "hidden" }; + var hiddenMembership = new FeatureMembership { Visibility = VisibilityKind.Public }; + grandparent.AssignOwnership(hiddenMembership, hiddenFeature); + + var middle = new Type(); + middle.AssignOwnership(new Specialization { Specific = middle, General = grandparent }); + + var renamingFeature = new Feature { DeclaredName = "renamed" }; + renamingFeature.AssignOwnership(new Redefinition { RedefiningFeature = renamingFeature, RedefinedFeature = hiddenFeature }); + middle.AssignOwnership(new FeatureMembership { Visibility = VisibilityKind.Public }, renamingFeature); + + var leaf = new Type(); + leaf.AssignOwnership(new Specialization { Specific = leaf, General = middle }); + + // Positive control over the SAME two-level shape but with no redefinition, so the negative + // assertions below cannot pass vacuously: the grandparent membership must propagate two + // levels when nothing hides it. + var controlMiddle = new Type(); + controlMiddle.AssignOwnership(new Specialization { Specific = controlMiddle, General = grandparent }); + + var controlLeaf = new Type(); + controlLeaf.AssignOwnership(new Specialization { Specific = controlLeaf, General = controlMiddle }); + + using (Assert.EnterMultipleScope()) + { + Assert.That(controlLeaf.ComputeInheritedMembershipsOperation(null, null, false), Does.Contain(hiddenMembership)); + Assert.That(middle.ComputeInheritedMembershipsOperation(null, null, false), Does.Not.Contain(hiddenMembership)); + Assert.That(leaf.ComputeInheritedMembershipsOperation(null, null, false), Does.Not.Contain(hiddenMembership)); + } + } + + [Test] + public void VerifyComputeInheritedMembershipsOperationWithCircularSpecialization() + { + // KerML §8.2.3.5.1 makes circularity LEGAL for Specializations, so `excludedTypes` is a + // normative cycle guard rather than a defensive one, and `inheritedMemberships(T, eTs)` is + // genuinely PATH-DEPENDENT: the answer for a Type depends on which Types are already on the + // specialization path that reached it. Any memoisation keyed on the Type alone is unsound here. + var first = new Type(); + var second = new Type(); + + var firstMembership = new FeatureMembership { Visibility = VisibilityKind.Public }; + first.AssignOwnership(firstMembership, new Feature { DeclaredName = "first" }); + + var secondMembership = new FeatureMembership { Visibility = VisibilityKind.Public }; + second.AssignOwnership(secondMembership, new Feature { DeclaredName = "second" }); + + first.AssignOwnership(new Specialization { Specific = first, General = second }); + second.AssignOwnership(new Specialization { Specific = second, General = first }); + + using (Assert.EnterMultipleScope()) + { + // Terminates, and each Type inherits the other's membership but never its own: descending + // from `first` puts it on the path, so `second`'s Specialization back to it is skipped. + Assert.That(first.ComputeInheritedMembershipsOperation(null, null, false), Is.EqualTo([secondMembership])); + Assert.That(second.ComputeInheritedMembershipsOperation(null, null, false), Is.EqualTo([firstMembership])); + } + + // A Type specializing BOTH members of the cycle reaches each of them twice, by two paths that + // carry different excluded sets — and the two visits legitimately produce DIFFERENT results. + // Reached under path {root, first}, `second` contributes only its own membership because the + // step back to `first` is cut; reached under path {root, second}, `second` also contributes + // `first`'s. Memoising `second` on the first visit would drop the second contribution, so this + // is the assertion that distinguishes conditional memoisation from unconditional. + var root = new Type(); + root.AssignOwnership(new Specialization { Specific = root, General = first }); + root.AssignOwnership(new Specialization { Specific = root, General = second }); + + var rootInherited = root.ComputeInheritedMembershipsOperation(null, null, false); + + using (Assert.EnterMultipleScope()) + { + Assert.That(rootInherited.Count(membership => membership == firstMembership), Is.EqualTo(2)); + Assert.That(rootInherited.Count(membership => membership == secondMembership), Is.EqualTo(2)); + } + } + + [Test] + public void VerifyComputeInheritedMembershipsOperationWithDiamondSpecialization() + { + // The acyclic counterpart of the circular fixture: `top` is reached by two disjoint paths that + // never intersect its own supertype closure, so both visits MUST agree — this is the shape + // memoisation is allowed to collapse, and the shape that proves it collapses to the right value. + var top = new Type(); + var topMembership = new FeatureMembership { Visibility = VisibilityKind.Public }; + top.AssignOwnership(topMembership, new Feature { DeclaredName = "top" }); + + var left = new Type(); + var leftMembership = new FeatureMembership { Visibility = VisibilityKind.Public }; + left.AssignOwnership(leftMembership, new Feature { DeclaredName = "left" }); + left.AssignOwnership(new Specialization { Specific = left, General = top }); + + var right = new Type(); + var rightMembership = new FeatureMembership { Visibility = VisibilityKind.Public }; + right.AssignOwnership(rightMembership, new Feature { DeclaredName = "right" }); + right.AssignOwnership(new Specialization { Specific = right, General = top }); + + var bottom = new Type(); + bottom.AssignOwnership(new Specialization { Specific = bottom, General = left }); + bottom.AssignOwnership(new Specialization { Specific = bottom, General = right }); + + // `union` deduplicates only within a single nonPrivateMemberships call, so `top`'s membership + // legitimately arrives once per branch — collapsing it to one would be its own regression. + Assert.That(bottom.ComputeInheritedMembershipsOperation(null, null, false), Is.EqualTo([leftMembership, topMembership, rightMembership, topMembership])); } [Test] diff --git a/SysML2.NET.Tests/Extensions/InheritanceScopeTestFixture.cs b/SysML2.NET.Tests/Extensions/InheritanceScopeTestFixture.cs new file mode 100644 index 00000000..df681ff6 --- /dev/null +++ b/SysML2.NET.Tests/Extensions/InheritanceScopeTestFixture.cs @@ -0,0 +1,210 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// 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.Extensions +{ + using NUnit.Framework; + + using SysML2.NET.Core.Root.Namespaces; + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Core.Types; + using SysML2.NET.Core.POCO.Root.Elements; + using SysML2.NET.Core.POCO.Root.Namespaces; + using SysML2.NET.Extensions; + + [TestFixture] + public class InheritanceScopeTestFixture + { + [Test] + public void VerifyBegin() + { + Assert.That(InheritanceScope.Current, Is.Null, "no scope is open before the first Begin"); + + using (var outerScope = InheritanceScope.Begin()) + { + Assert.That(InheritanceScope.Current, Is.SameAs(outerScope)); + + // Scopes nest: the inner one takes over, and the outer is restored when it closes. + using (var innerScope = InheritanceScope.Begin()) + { + Assert.That(InheritanceScope.Current, Is.SameAs(innerScope)); + Assert.That(innerScope, Is.Not.SameAs(outerScope)); + } + + Assert.That(InheritanceScope.Current, Is.SameAs(outerScope)); + } + + Assert.That(InheritanceScope.Current, Is.Null); + } + + [Test] + public void VerifyDispose() + { + var scope = InheritanceScope.Begin(); + + var subject = new Type(); + subject.AssignOwnership(new Specialization { Specific = subject, General = BuildSupertypeWithPublicMembership(out _) }); + + Assert.That(subject.ComputeInheritedMembership(), Has.Count.EqualTo(1)); + Assert.That(scope.DefaultSignatureResults, Is.Not.Empty, "the resolved supertype is retained while the scope is open"); + + scope.Dispose(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(InheritanceScope.Current, Is.Null); + Assert.That(scope.DefaultSignatureResults, Is.Empty, "disposal releases the retained POCOs"); + } + + // Disposing twice is a no-op rather than clobbering whichever scope is current by then. + using (var replacementScope = InheritanceScope.Begin()) + { + scope.Dispose(); + + Assert.That(InheritanceScope.Current, Is.SameAs(replacementScope)); + } + + Assert.That(InheritanceScope.Current, Is.Null); + } + + [Test] + public void VerifyDisposeOutOfOrder() + { + var outerScope = InheritanceScope.Begin(); + var innerScope = InheritanceScope.Begin(); + + // Closing the ENCLOSING scope first must not strand the one still open: the inner scope stays + // current, so queries made through it keep sharing. + outerScope.Dispose(); + + Assert.That(InheritanceScope.Current, Is.SameAs(innerScope)); + + // And closing the inner one then returns to no-scope rather than to the closed outer scope. + innerScope.Dispose(); + + Assert.That(InheritanceScope.Current, Is.Null); + } + + [Test] + public void VerifyScopedResolutionMatchesUnscopedResolution() + { + // Two Types over a SHARED supertype: the case the scope exists to collapse, since both + // resolve the same supertype subtree. + var sharedSupertype = BuildSupertypeWithPublicMembership(out var sharedMembership); + + var firstSubtype = new Type(); + firstSubtype.AssignOwnership(new Specialization { Specific = firstSubtype, General = sharedSupertype }); + + var secondSubtype = new Type(); + secondSubtype.AssignOwnership(new Specialization { Specific = secondSubtype, General = sharedSupertype }); + + var unscopedFirst = firstSubtype.ComputeInheritedMembership(); + var unscopedSecond = secondSubtype.ComputeInheritedMembership(); + + using (InheritanceScope.Begin()) + { + using (Assert.EnterMultipleScope()) + { + // The second query reads the first's cached entry, and must still agree. + Assert.That(firstSubtype.ComputeInheritedMembership(), Is.EqualTo(unscopedFirst)); + Assert.That(secondSubtype.ComputeInheritedMembership(), Is.EqualTo(unscopedSecond)); + Assert.That(unscopedFirst, Is.EqualTo([sharedMembership])); + } + } + + // A non-default signature never shares, so an excluded supertype still drops out even though + // the default-signature answer for the same Type is already cached. + using (InheritanceScope.Begin()) + { + Assert.That(firstSubtype.ComputeInheritedMembership(), Is.EqualTo([sharedMembership])); + Assert.That(firstSubtype.ComputeInheritedMembershipsOperation(null, [sharedSupertype], false), Is.Empty); + } + } + + [Test] + public void VerifyScopedResolutionOfCircularSpecialization() + { + // KerML §8.2.3.5.1 makes circular Specializations LEGAL, which makes inheritance resolution + // PATH-DEPENDENT: a Type reached along a cycle must not be answered from a cache keyed on the + // Type alone. Widening the memo's lifetime to a scope must not weaken that guard. + var first = new Type(); + var second = new Type(); + + var firstMembership = new FeatureMembership { Visibility = VisibilityKind.Public }; + first.AssignOwnership(firstMembership, new Feature { DeclaredName = "first" }); + + var secondMembership = new FeatureMembership { Visibility = VisibilityKind.Public }; + second.AssignOwnership(secondMembership, new Feature { DeclaredName = "second" }); + + first.AssignOwnership(new Specialization { Specific = first, General = second }); + second.AssignOwnership(new Specialization { Specific = second, General = first }); + + using (InheritanceScope.Begin()) + { + using (Assert.EnterMultipleScope()) + { + // Identical to the unscoped answers: each Type inherits the other's membership and + // never its own, so neither is served a cached entry from the other's walk. + Assert.That(first.ComputeInheritedMembership(), Is.EqualTo([secondMembership])); + Assert.That(second.ComputeInheritedMembership(), Is.EqualTo([firstMembership])); + + // Repeating them inside the same scope stays stable. + Assert.That(first.ComputeInheritedMembership(), Is.EqualTo([secondMembership])); + Assert.That(second.ComputeInheritedMembership(), Is.EqualTo([firstMembership])); + } + } + + // A third Type specializing BOTH members of the cycle reaches them by a path neither reaches + // itself by, so it is the shape most likely to be served a wrongly-cached entry. Its answer + // must be the same whether or not the cycle members were queried first inside the same scope. + var descendant = new Type(); + descendant.AssignOwnership(new Specialization { Specific = descendant, General = first }); + descendant.AssignOwnership(new Specialization { Specific = descendant, General = second }); + + var unscopedDescendant = descendant.ComputeInheritedMembership(); + + using (InheritanceScope.Begin()) + { + first.ComputeInheritedMembership(); + second.ComputeInheritedMembership(); + + Assert.That(descendant.ComputeInheritedMembership(), Is.EqualTo(unscopedDescendant)); + + // Both cycle members contribute, each reached down both branches of the diamond. + Assert.That(unscopedDescendant, Does.Contain(firstMembership)); + Assert.That(unscopedDescendant, Does.Contain(secondMembership)); + } + } + + /// + /// Builds a Type carrying a single public Membership, which a subtype therefore inherits. + /// + /// The public Membership the returned Type owns. + /// The supertype. + private static Type BuildSupertypeWithPublicMembership(out IOwningMembership publicMembership) + { + var supertype = new Type(); + publicMembership = new OwningMembership { Visibility = VisibilityKind.Public }; + supertype.AssignOwnership(publicMembership, new Type()); + + return supertype; + } + } +} diff --git a/SysML2.NET.sln b/SysML2.NET.sln index da9f0b79..a279f683 100644 --- a/SysML2.NET.sln +++ b/SysML2.NET.sln @@ -74,6 +74,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SysML2.NET.Serializer.Textu EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SysML2.NET.Serializer.TextualNotation.Tests", "SysML2.NET.Serializer.TextualNotation.Tests\SysML2.NET.Serializer.TextualNotation.Tests.csproj", "{46EB73DF-D702-4FC2-BD1F-FA9FEEACE8A9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SysML2.NET.Semantics", "SysML2.NET.Semantics\SysML2.NET.Semantics.csproj", "{D42F33B8-63D6-485A-8335-E2084FE36812}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SysML2.NET.Semantics.Tests", "SysML2.NET.Semantics.Tests\SysML2.NET.Semantics.Tests.csproj", "{AE364357-591B-468D-9C39-9656B9A189C7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -348,6 +352,30 @@ Global {46EB73DF-D702-4FC2-BD1F-FA9FEEACE8A9}.Release|x64.Build.0 = Release|Any CPU {46EB73DF-D702-4FC2-BD1F-FA9FEEACE8A9}.Release|x86.ActiveCfg = Release|Any CPU {46EB73DF-D702-4FC2-BD1F-FA9FEEACE8A9}.Release|x86.Build.0 = Release|Any CPU + {D42F33B8-63D6-485A-8335-E2084FE36812}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D42F33B8-63D6-485A-8335-E2084FE36812}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D42F33B8-63D6-485A-8335-E2084FE36812}.Debug|x64.ActiveCfg = Debug|Any CPU + {D42F33B8-63D6-485A-8335-E2084FE36812}.Debug|x64.Build.0 = Debug|Any CPU + {D42F33B8-63D6-485A-8335-E2084FE36812}.Debug|x86.ActiveCfg = Debug|Any CPU + {D42F33B8-63D6-485A-8335-E2084FE36812}.Debug|x86.Build.0 = Debug|Any CPU + {D42F33B8-63D6-485A-8335-E2084FE36812}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D42F33B8-63D6-485A-8335-E2084FE36812}.Release|Any CPU.Build.0 = Release|Any CPU + {D42F33B8-63D6-485A-8335-E2084FE36812}.Release|x64.ActiveCfg = Release|Any CPU + {D42F33B8-63D6-485A-8335-E2084FE36812}.Release|x64.Build.0 = Release|Any CPU + {D42F33B8-63D6-485A-8335-E2084FE36812}.Release|x86.ActiveCfg = Release|Any CPU + {D42F33B8-63D6-485A-8335-E2084FE36812}.Release|x86.Build.0 = Release|Any CPU + {AE364357-591B-468D-9C39-9656B9A189C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE364357-591B-468D-9C39-9656B9A189C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE364357-591B-468D-9C39-9656B9A189C7}.Debug|x64.ActiveCfg = Debug|Any CPU + {AE364357-591B-468D-9C39-9656B9A189C7}.Debug|x64.Build.0 = Debug|Any CPU + {AE364357-591B-468D-9C39-9656B9A189C7}.Debug|x86.ActiveCfg = Debug|Any CPU + {AE364357-591B-468D-9C39-9656B9A189C7}.Debug|x86.Build.0 = Debug|Any CPU + {AE364357-591B-468D-9C39-9656B9A189C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE364357-591B-468D-9C39-9656B9A189C7}.Release|Any CPU.Build.0 = Release|Any CPU + {AE364357-591B-468D-9C39-9656B9A189C7}.Release|x64.ActiveCfg = Release|Any CPU + {AE364357-591B-468D-9C39-9656B9A189C7}.Release|x64.Build.0 = Release|Any CPU + {AE364357-591B-468D-9C39-9656B9A189C7}.Release|x86.ActiveCfg = Release|Any CPU + {AE364357-591B-468D-9C39-9656B9A189C7}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SysML2.NET/Extend/NamespaceExtensions.cs b/SysML2.NET/Extend/NamespaceExtensions.cs index 3991f1e5..b22f6785 100644 --- a/SysML2.NET/Extend/NamespaceExtensions.cs +++ b/SysML2.NET/Extend/NamespaceExtensions.cs @@ -26,6 +26,7 @@ namespace SysML2.NET.Core.POCO.Root.Namespaces using System.Text; using SysML2.NET.Core.Root.Namespaces; + using SysML2.NET.Core.POCO.Core.Types; using SysML2.NET.Core.POCO.Root.Annotations; using SysML2.NET.Core.POCO.Root.Elements; using SysML2.NET.Extensions; @@ -87,7 +88,20 @@ internal static List ComputeMember(this INamespace namespaceSubject) /// internal static List ComputeMembership(this INamespace namespaceSubject) { - return namespaceSubject == null ? throw new ArgumentNullException(nameof(namespaceSubject)) : [..namespaceSubject.ownedMembership.Union(namespaceSubject.ImportedMemberships([]))]; + if (namespaceSubject == null) + { + throw new ArgumentNullException(nameof(namespaceSubject)); + } + + // `membership` is a derived UNION; its subsets are ownedMembership, importedMembership and — + // only when the Namespace is a Type — inheritedMembership. KerML §8.2.3.5.3: memberships + // "include owned, imported and (if the Namespace is a Type) inherited". Each subset is taken + // from its own derived property rather than re-deriving it here. + var result = namespaceSubject.ownedMembership.Union(namespaceSubject.importedMembership); + + return namespaceSubject is IType typeSubject + ? [..result.Union(typeSubject.inheritedMembership)] + : [..result]; } /// @@ -295,38 +309,34 @@ internal static List ComputeVisibleMembershipsOperation(this INames throw new ArgumentNullException(nameof(namespaceSubject)); } - var result = new List(); + var safeExcluded = excluded ?? []; - if (includeAll) + // Sourced from membershipsOfVisibility — NOT from `membership`. For a Type the two differ: + // `membership` carries inheritedMembership, which Type::visibleMemberships adds back separately + // with the excluded set threaded in. Reading `membership` here would double-count the inherited + // part and drop that cycle guard. + var result = namespaceSubject.MembershipsOfVisibility(includeAll ? null : VisibilityKind.Public, safeExcluded); + + if (!isRecursive) { - result.AddRange(namespaceSubject.membership); + return result; } - else - { - result.AddRange(namespaceSubject.ownedMembership.Where(m => m.Visibility == VisibilityKind.Public)); - var excludedWithSelf = new List(excluded) { namespaceSubject }; + // `excluded->including(self)`: descending into a nested Namespace must not round-trip back into + // this one. KerML §8.2.3.5.1 makes that guard normative — a nested Namespace importing its own + // owner would otherwise re-export this Namespace's members, private ones included when the + // Import is `import all`. + var excludedWithSelf = new List(safeExcluded) { namespaceSubject }; - var publicImported = namespaceSubject.ImportedMemberships(excludedWithSelf) - .Where(m => namespaceSubject.VisibilityOf(m) == VisibilityKind.Public); + var nestedNamespaces = namespaceSubject.ownedMembership + .OfType() + .Where(mem => includeAll || mem.Visibility == VisibilityKind.Public) + .Select(mem => mem.ownedMemberElement) + .OfType(); - result.AddRange(publicImported); - } - - if (isRecursive) + foreach (var nestedNamespace in nestedNamespaces) { - var namespaceMemberships = includeAll - ? namespaceSubject.ownedMembership - : namespaceSubject.ownedMembership.Where(m => m.Visibility == VisibilityKind.Public); - - foreach (var mem in namespaceMemberships) - { - if (mem.MemberElement is INamespace nestedNamespace) - { - var nestedMemberships = nestedNamespace.VisibleMemberships(excluded, true, includeAll); - result.AddRange(nestedMemberships); - } - } + result.AddRange(nestedNamespace.VisibleMemberships(excludedWithSelf, true, includeAll)); } return result; @@ -410,23 +420,32 @@ internal static List ComputeMembershipsOfVisibilityOperation(this I throw new ArgumentNullException(nameof(namespaceSubject)); } - var excludedWithSelf = new List(excluded) { namespaceSubject }; - - if (visibility == null) - { - var result = new List(namespaceSubject.ownedMembership); - result.AddRange(namespaceSubject.ImportedMemberships(excludedWithSelf)); - return result; - } - - var filtered = new List(); - - filtered.AddRange(namespaceSubject.ownedMembership.Where(m => m.Visibility == visibility.Value)); + var safeExcluded = excluded ?? []; + + var result = new List( + namespaceSubject.ownedMembership.Where(mem => visibility == null || mem.Visibility == visibility.Value)); + + // `Namespace::importedMemberships` additionally drops Memberships with distinguishability + // collisions (KerML §8.2.3.5.1), which the terse OCL above does not spell out — so the imported + // side is taken from it and then narrowed to the Memberships contributed by Imports of the + // requested visibility. + // + // The visibility filter is applied to the IMPORTS, per the OCL, rather than by asking + // `visibilityOf` for each resulting Membership. The two readings agree — visibilityOf(mem) IS + // the visibility of the Import that produced mem — but only the import-side filter is usable + // here: visibilityOf falls back to `membership`, and for a Type `membership` includes + // inheritedMembership, whose derivation runs back through this very operation. visibilityOf + // also hard-codes Set{} where the excluded set has to be threaded through. + var excludedWithSelf = new List(safeExcluded) { namespaceSubject }; + + var membershipsOfVisibleImports = namespaceSubject.ownedImport + .Where(import => visibility == null || import.Visibility == visibility.Value) + .SelectMany(import => import.ImportedMemberships(excludedWithSelf)) + .ToHashSet(); - filtered.AddRange(namespaceSubject.ImportedMemberships(excludedWithSelf) - .Where(m => namespaceSubject.VisibilityOf(m) == visibility.Value)); + result.AddRange(namespaceSubject.ImportedMemberships(safeExcluded).Where(membershipsOfVisibleImports.Contains)); - return filtered; + return result; } /// @@ -582,14 +601,22 @@ internal static IMembership ComputeResolveLocalOperation(this INamespace namespa return null; } - if (namespaceSubject.owner == null) + if (namespaceSubject.owningNamespace == null) { return namespaceSubject.ResolveGlobal(name); } - var resolved = namespaceSubject.ResolveVisible(name); - - return resolved ?? namespaceSubject.owningNamespace?.ResolveLocal(name); + // Local resolution searches EVERY membership of this Namespace regardless of visibility, per + // the OCL above and KerML §8.2.3.5.3. Filtering to the visible ones (ResolveVisible) is the + // rule for a NON-FIRST segment of a qualified name, not for local resolution, and made every + // reference to a private owned member fail to resolve locally. `membership` carries a Type's + // INHERITED memberships too (§8.2.3.5.3), so an inherited feature is nameable from within the + // Type that inherits it. + var resolved = namespaceSubject.membership + .FirstOrDefault(membership => string.Equals(membership.MemberShortName, name, StringComparison.Ordinal) + || string.Equals(membership.MemberName, name, StringComparison.Ordinal)); + + return resolved ?? namespaceSubject.owningNamespace.ResolveLocal(name); } /// diff --git a/SysML2.NET/Extend/NamespaceImportExtensions.cs b/SysML2.NET/Extend/NamespaceImportExtensions.cs index 593cc168..b4c162cb 100644 --- a/SysML2.NET/Extend/NamespaceImportExtensions.cs +++ b/SysML2.NET/Extend/NamespaceImportExtensions.cs @@ -67,7 +67,15 @@ internal static List ComputeRedefinedImportedMembershipsOperation(t return []; } - return namespaceImportSubject.ImportedNamespace.VisibleMemberships(excluded, namespaceImportSubject.IsRecursive, namespaceImportSubject.IsImportAll); + // `if excluded->includes(importedNamespace) then Sequence{}` is the first branch of the OCL and + // is the circularity guard of KerML §8.2.3.5.1 — without it a Namespace that imports one of its + // own ancestors re-enters that ancestor indefinitely. + if (excluded != null && excluded.Contains(namespaceImportSubject.ImportedNamespace)) + { + return []; + } + + return namespaceImportSubject.ImportedNamespace.VisibleMemberships(excluded ?? [], namespaceImportSubject.IsRecursive, namespaceImportSubject.IsImportAll); } } } diff --git a/SysML2.NET/Extend/TypeExtensions.cs b/SysML2.NET/Extend/TypeExtensions.cs index 4baa046a..49a02a49 100644 --- a/SysML2.NET/Extend/TypeExtensions.cs +++ b/SysML2.NET/Extend/TypeExtensions.cs @@ -1,4 +1,4 @@ -// ------------------------------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------- // // // Copyright (C) 2022-2026 Starion Group S.A. @@ -30,6 +30,7 @@ namespace SysML2.NET.Core.POCO.Core.Types using SysML2.NET.Core.POCO.Root.Annotations; using SysML2.NET.Core.POCO.Root.Elements; using SysML2.NET.Core.POCO.Root.Namespaces; + using SysML2.NET.Extensions; /// /// The class provides extensions methods for @@ -621,11 +622,223 @@ internal static List ComputeInheritedMembershipsOperation(this ITyp throw new ArgumentNullException(nameof(typeSubject)); } - var inheritable = typeSubject.InheritableMemberships(excludedNamespaces ?? [], excludedTypes ?? [], excludeImplied); + var inheritable = new List(); + + CollectInheritableMemberships(typeSubject, new InheritanceQuery(excludedNamespaces, excludedTypes, excludeImplied), inheritable); return typeSubject.RemoveRedefinedFeatures(inheritable); } + /// + /// Carries the values that stay fixed for one top-level inheritance query, together with the + /// specialization path being walked and the results that may be reused across branches of it. + /// + /// + /// excludedNamespaces and excludeImplied are passed down the recursion unchanged, so + /// within one query the only thing that varies is the path — which is why + /// can be keyed on the Type alone. + /// The model is a mutable object graph, so the memo must not outlive a traversal of it. By + /// default it lives and dies with the query. A caller that knows it is traversing a model that will + /// not change can widen that lifetime to a whole traversal by opening an + /// , in which case queries carrying the default signature share one + /// memo instead of each rebuilding the supertype chain their Types have in common. + /// + private sealed class InheritanceQuery + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The Namespaces whose Imports are excluded, or null for none + /// + /// + /// The Types to seed the specialization path with, or null for none + /// + /// + /// Whether supertypes reached through implied Specializations are excluded + /// + internal InheritanceQuery(List excludedNamespaces, List excludedTypes, bool excludeImplied) + { + this.ExcludedNamespaces = excludedNamespaces ?? []; + this.PathTypes = [..excludedTypes ?? []]; + this.ExcludeImplied = excludeImplied; + + var isDefaultSignature = this.ExcludedNamespaces.Count == 0 && this.PathTypes.Count == 0 && !excludeImplied; + + this.PathIndependentResults = isDefaultSignature && InheritanceScope.Current != null + ? InheritanceScope.Current.DefaultSignatureResults + : []; + } + + /// + /// Gets the Namespaces whose Imports are excluded. + /// + internal List ExcludedNamespaces { get; } + + /// + /// Gets the Types on the specialization path currently being walked, mutated in place as the + /// recursion descends and ascends. + /// + internal HashSet PathTypes { get; } + + /// + /// Gets a value indicating whether supertypes reached through implied Specializations are excluded. + /// + internal bool ExcludeImplied { get; } + + /// + /// Gets the non-private Memberships of Types whose subtree never consulted the cycle guard, and + /// which are therefore the same no matter which path reaches them. + /// + /// + /// Shared with the open when this query carries the default + /// signature; private to the query otherwise. + /// + internal Dictionary> PathIndependentResults { get; } + } + + /// + /// Collects the inheritable Memberships of a Type into , and reports whether + /// the subtree it walked is independent of the path that reached it. + /// + /// + /// + /// This helper and carry the mutual recursion + /// inheritedMemberships -> inheritableMemberships -> nonPrivateMemberships -> + /// inheritedMemberships. The public operations keep their signatures, but + /// the recursion runs through these helpers so the excluded Types can be ONE set pushed on descent + /// and popped on return, rather than a fresh List allocated per supertype per level and searched + /// with an O(n) Contains. Calling the helpers directly bypasses POCO dispatch, which is safe + /// ONLY because none of those three operations is redefined by any subclass — every POCO wires all + /// three to the methods in this file. Introducing a redefinition means routing that arm back through + /// the POCO instance member. + /// + /// + /// The returned flag is false as soon as the cycle guard rejects a supertype anywhere below this + /// Type. It gates memoisation: in an ACYCLIC hierarchy every Type on the path to a Type T is a + /// subtype of T, so no path node can also be one of T's supertypes and the guard never fires — + /// making T's result reusable. The guard firing means the graph is circular through this Type, the + /// answer genuinely differs per path (KerML §8.2.3.5.1), and nothing here may be reused. + /// + /// + /// + /// The subject + /// + /// + /// The state of the top-level inheritance query + /// + /// + /// The accumulator the Memberships are appended to + /// + /// + /// True when no cycle guard fired in the walked subtree + /// + private static bool CollectInheritableMemberships(IType typeSubject, InheritanceQuery query, List result) + { + var addedSelf = query.PathTypes.Add(typeSubject); + + // Failing to add means this Type is ALREADY on the path, i.e. a cycle closes on the subject + // itself — path-dependent by construction. + var isPathIndependent = addedSelf; + + try + { + foreach (var supertype in typeSubject.Supertypes(query.ExcludeImplied).Where(supertype => supertype != null)) + { + if (query.PathTypes.Contains(supertype)) + { + isPathIndependent = false; + continue; + } + + result.AddRange(ResolveNonPrivateMemberships(supertype, query, out var supertypeIsPathIndependent)); + + isPathIndependent &= supertypeIsPathIndependent; + } + } + finally + { + if (addedSelf) + { + query.PathTypes.Remove(typeSubject); + } + } + + return isPathIndependent; + } + + /// + /// Returns the public, protected and inherited Memberships of a Type, reusing an earlier result when + /// that Type proved independent of the path reaching it. + /// + /// + /// See for why the recursion runs through these helpers + /// and what makes a result reusable. The returned list may be the memoised instance, so callers must + /// read from it and never mutate it. + /// + /// + /// The subject + /// + /// + /// The state of the top-level inheritance query + /// + /// + /// True when no cycle guard fired in the walked subtree + /// + /// + /// The collected + /// + private static List ResolveNonPrivateMemberships(IType typeSubject, InheritanceQuery query, out bool isPathIndependent) + { + if (query.PathIndependentResults.TryGetValue(typeSubject, out var reusable)) + { + isPathIndependent = true; + + return reusable; + } + + // The OCL joins the three parts with `union`, which deduplicates — but only within THIS call. + // inheritableMemberships deliberately concatenates its supertypes' results without deduplicating + // across them, so the seen-set must not outlive this invocation. + var seen = new HashSet(); + var result = new List(); + + AppendDistinct(result, seen, typeSubject.MembershipsOfVisibility(VisibilityKind.Public, query.ExcludedNamespaces)); + AppendDistinct(result, seen, typeSubject.MembershipsOfVisibility(VisibilityKind.Protected, query.ExcludedNamespaces)); + + var inheritable = new List(); + + isPathIndependent = CollectInheritableMemberships(typeSubject, query, inheritable); + + AppendDistinct(result, seen, typeSubject.RemoveRedefinedFeatures(inheritable)); + + if (isPathIndependent) + { + query.PathIndependentResults[typeSubject] = result; + } + + return result; + } + + /// + /// Appends the Memberships of that are not yet in . + /// + /// + /// The accumulator the Memberships are appended to + /// + /// + /// The set of Memberships already appended, extended in place + /// + /// + /// The Memberships to append + /// + private static void AppendDistinct(List target, HashSet seen, List source) + { + // `seen.Add` is the filter AND the record of what was taken; AddRange enumerates once, in order, + // so the side effect is well defined here. + target.AddRange(source.Where(seen.Add)); + } + /// /// Return all the non-private Memberships of all the supertypes of this Type, excluding any supertypes /// that are this Type or are in the given set of excludedTypes. If excludeImplied = true, then also @@ -661,22 +874,9 @@ internal static List ComputeInheritableMembershipsOperation(this IT throw new ArgumentNullException(nameof(typeSubject)); } - var safeExcludedNamespaces = excludedNamespaces ?? []; - var safeExcludedTypes = excludedTypes ?? []; - - var excludingSelf = new List(safeExcludedTypes) { typeSubject }; - var result = new List(); - foreach (var supertype in typeSubject.Supertypes(excludeImplied)) - { - if (supertype == null || excludingSelf.Contains(supertype)) - { - continue; - } - - result.AddRange(supertype.NonPrivateMemberships(safeExcludedNamespaces, excludingSelf, excludeImplied)); - } + CollectInheritableMemberships(typeSubject, new InheritanceQuery(excludedNamespaces, excludedTypes, excludeImplied), result); return result; } @@ -723,14 +923,7 @@ internal static List ComputeNonPrivateMembershipsOperation(this ITy throw new ArgumentNullException(nameof(typeSubject)); } - var safeExcludedNamespaces = excludedNamespaces ?? []; - var safeExcludedTypes = excludedTypes ?? []; - - var publicMemberships = typeSubject.MembershipsOfVisibility(VisibilityKind.Public, safeExcludedNamespaces); - var protectedMemberships = typeSubject.MembershipsOfVisibility(VisibilityKind.Protected, safeExcludedNamespaces); - var inheritedMemberships = typeSubject.InheritedMemberships(safeExcludedNamespaces, safeExcludedTypes, excludeImplied); - - return [..publicMemberships.Union(protectedMemberships).Union(inheritedMemberships)]; + return ResolveNonPrivateMemberships(typeSubject, new InheritanceQuery(excludedNamespaces, excludedTypes, excludeImplied), out _); } /// @@ -778,10 +971,29 @@ internal static List ComputeRemoveRedefinedFeaturesOperation(this I throw new ArgumentNullException(nameof(memberships)); } + // AllRedefinedFeaturesOf walks a redefinition chain, so it is computed ONCE per membership + // here rather than inside a nested loop: the previous form was O(n^2) calls over the whole + // inherited closure. Condition 1 then reduces to a set lookup — a membership is rejected when + // some OTHER membership in the same set redefines its memberElement. Because + // AllRedefinedFeaturesOf always includes the membership's own memberElement, "some other" + // is exactly "the feature appears in at least two memberships' redefined-sets". + // Memberships may arrive more than once (the same supertype membership is reachable by several + // paths through a branching hierarchy), so distinct memberships are counted — mirroring the + // original `other != current` guard, which likewise never let a membership reject itself. + var redefinedFeatureCounts = new Dictionary(); + + foreach (var redefinedFeature in memberships + .Distinct() + .SelectMany(membership => typeSubject.AllRedefinedFeaturesOf(membership).Distinct())) + { + redefinedFeatureCounts.TryGetValue(redefinedFeature, out var occurrences); + redefinedFeatureCounts[redefinedFeature] = occurrences + 1; + } + var reducedMemberships = memberships - .Where(currentMembership => !memberships.Any(otherMembership => - otherMembership != currentMembership - && typeSubject.AllRedefinedFeaturesOf(otherMembership).Contains(currentMembership.MemberElement as IFeature))) + .Where(currentMembership => currentMembership.MemberElement is not IFeature memberFeature + || !redefinedFeatureCounts.TryGetValue(memberFeature, out var occurrences) + || occurrences < 2) .ToList(); var redefinedFeatures = typeSubject.ownedFeature @@ -830,6 +1042,10 @@ internal static List ComputeAllRedefinedFeaturesOfOperation(this IType throw new ArgumentNullException(nameof(membership)); } + // `oclIsType(Feature)` is read as a KIND check (`is IFeature`), not as OCL's exact-type test. + // Feature is instantiated in practice only through its subclasses, so the literal exact-type + // reading would make redefinition hiding never fire on any real model — which cannot be the + // intent of a rule whose whole purpose is to remove redefined Features from inheritance. return membership.MemberElement is IFeature memberFeature ? memberFeature.AllRedefinedFeatures() : []; @@ -1026,22 +1242,23 @@ internal static List ComputeAllSupertypesOperation(this IType typeSubject throw new ArgumentNullException(nameof(typeSubject)); } - var visited = new List { typeSubject }; + // The result is an OrderedSet, so discovery order is kept in `ordered` while `visited` answers + // membership in O(1) — a List doing both made the BFS O(n^2). + var ordered = new List { typeSubject }; + var visited = new HashSet { typeSubject }; var queue = new Queue(); queue.Enqueue(typeSubject); while (queue.Count > 0) { - var current = queue.Dequeue(); - - foreach (var supertype in current.Supertypes(false).Where(supertype => supertype != null && !visited.Contains(supertype))) + foreach (var supertype in queue.Dequeue().Supertypes(false).Where(supertype => supertype != null && visited.Add(supertype))) { - visited.Add(supertype); + ordered.Add(supertype); queue.Enqueue(supertype); } } - return visited; + return ordered; } /// diff --git a/SysML2.NET/Extensions/InheritanceScope.cs b/SysML2.NET/Extensions/InheritanceScope.cs new file mode 100644 index 00000000..3e69deda --- /dev/null +++ b/SysML2.NET/Extensions/InheritanceScope.cs @@ -0,0 +1,166 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace SysML2.NET.Extensions +{ + using System; + using System.Collections.Generic; + + using SysML2.NET.Core.POCO.Core.Types; + using SysML2.NET.Core.POCO.Root.Namespaces; + + /// + /// Shares the intermediate results of inheritance resolution across every + /// query made while the scope is open. + /// + /// + /// Resolving walks a Type's transitive supertypes. Without a + /// scope each query starts from an empty cache, so every Type re-walks the library supertype chain that + /// all Types in a model share. Opening a scope around a bulk traversal collapses that repeated work. + /// Only the default query signature — no excluded Namespaces, no excluded Types and implied + /// Relationships included — is shared, because a different signature yields different results. The + /// shared entries are precisely the results the resolver already treats as independent of the path + /// taken to reach a Type, so sharing them across queries changes no outcome. + /// The scope caches against the model as it stands when each entry is produced, so it must not + /// remain open across a mutation of that model. Scope it to a single read-only traversal. + /// The current scope is tracked per thread, and scopes may nest: disposing one restores the scope + /// that was open before it. A scope must therefore be opened and disposed on the SAME thread — a + /// traversal that hands off to another thread simply resolves without sharing on that thread, but + /// disposing from one would leave the opening thread's scope open. + /// + /// + /// + /// using (InheritanceScope.Begin()) + /// { + /// foreach (var type in types) + /// { + /// Consume(type.inheritedMembership); + /// } + /// } + /// + /// + public sealed class InheritanceScope : IDisposable + { + /// + /// The scope currently open on this thread, if any. + /// + [ThreadStatic] + private static InheritanceScope current; + + /// + /// The scope that was open when this one began, restored on disposal, and repointed when a scope + /// it encloses is disposed before it. + /// + private InheritanceScope enclosingScope; + + /// + /// A value indicating whether this scope has already been disposed. + /// + private bool isDisposed; + + /// + /// Initializes a new instance of the class as a node of the + /// thread's scope chain. + /// + /// The scope open when this one begins, or null. + /// + /// Constructing a scope does NOT make it current: opening and closing are the two ends of one + /// operation on thread state, so both live in static members ( and + /// ) and the instance never writes the thread's scope pointer itself. + /// + private InheritanceScope(InheritanceScope enclosingScope) + { + this.enclosingScope = enclosingScope; + } + + /// + /// Gets the scope currently open on the calling thread, or null when there is none. + /// + internal static InheritanceScope Current => current; + + /// + /// Gets the results shared by the default query signature, keyed by the Type they were resolved for. + /// + internal Dictionary> DefaultSignatureResults { get; } = []; + + /// + /// Opens a new inheritance scope on the calling thread. + /// + /// The scope, which restores the previously open scope when disposed. + public static InheritanceScope Begin() + { + current = new InheritanceScope(current); + + return current; + } + + /// + /// Closes this scope, restores the scope that enclosed it and releases the shared results. + /// + /// + /// Scopes are expected to close in the order they opened, but a caller holding two overlapping + /// scopes may close them in any order, so a scope that is not the current one is spliced out of + /// the chain rather than allowed to overwrite whichever scope is current by then. Closing an + /// already-closed scope does nothing. + /// + public void Dispose() + { + if (this.isDisposed) + { + return; + } + + this.isDisposed = true; + + Detach(this); + + this.enclosingScope = null; + this.DefaultSignatureResults.Clear(); + } + + /// + /// Removes a scope from the calling thread's chain of open scopes. + /// + /// The scope to remove. + /// + /// The current scope is replaced by the one it encloses; a scope deeper in the chain is spliced + /// out of it, leaving whichever scope is current untouched. + /// + private static void Detach(InheritanceScope scope) + { + if (ReferenceEquals(current, scope)) + { + current = scope.enclosingScope; + + return; + } + + for (var openScope = current; openScope != null; openScope = openScope.enclosingScope) + { + if (ReferenceEquals(openScope.enclosingScope, scope)) + { + openScope.enclosingScope = scope.enclosingScope; + + return; + } + } + } + } +} diff --git a/SysML2.NET/SysML2.NET.csproj b/SysML2.NET/SysML2.NET.csproj index 2a54374f..497ed9d4 100644 --- a/SysML2.NET/SysML2.NET.csproj +++ b/SysML2.NET/SysML2.NET.csproj @@ -46,5 +46,6 @@ + \ No newline at end of file