diff --git a/uml4net.Extensions.Tests/ClassExtensionsTestFixture.cs b/uml4net.Extensions.Tests/ClassExtensionsTestFixture.cs index 1128c5ab..5e7f3f08 100644 --- a/uml4net.Extensions.Tests/ClassExtensionsTestFixture.cs +++ b/uml4net.Extensions.Tests/ClassExtensionsTestFixture.cs @@ -584,5 +584,135 @@ public void Verify_that_QueryAllDescendantSpecializations_returns_expected_resul Assert.That(descendants.Any(x => x.Name == "Activity"), Is.True); } } + + [Test] + public void Verify_that_QueryAllNonDerivedProperties_returns_the_properties_that_are_not_derived() + { + var dependency = this.QueryClass("CommonStructure", "Dependency"); + + var properties = dependency.QueryAllNonDerivedProperties(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(properties, Is.Not.Empty); + Assert.That(properties.Any(x => x.IsDerived), Is.False); + Assert.That(properties, Is.EqualTo(dependency.QueryAllProperties().Where(x => !x.IsDerived))); + Assert.That(properties.Select(x => x.XmiId), Does.Contain("Dependency-client")); + Assert.That(properties.Select(x => x.XmiId), Does.Not.Contain("Element-ownedElement"), "ownedElement is a derived union"); + Assert.That(() => ClassExtensions.QueryAllNonDerivedProperties(null), Throws.ArgumentNullException); + } + } + + [Test] + public void Verify_that_QueryAllNonDerivedNonRedefinedProperties_leaves_out_the_properties_that_are_redefined_in_the_class() + { + var @class = this.QueryClass("StructuredClassifiers", "Class"); + + var nonDerived = @class.QueryAllNonDerivedProperties().Select(x => x.XmiId).ToList(); + var nonDerivedNonRedefined = @class.QueryAllNonDerivedNonRedefinedProperties().Select(x => x.XmiId).ToList(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(nonDerived, Does.Contain("NamedElement-visibility")); + Assert.That(nonDerivedNonRedefined, Does.Not.Contain("NamedElement-visibility"), "redefined by PackageableElement::visibility"); + Assert.That(nonDerivedNonRedefined, Does.Contain("PackageableElement-visibility")); + Assert.That(nonDerivedNonRedefined, Is.SubsetOf(nonDerived)); + Assert.That(() => ClassExtensions.QueryAllNonDerivedNonRedefinedProperties(null), Throws.ArgumentNullException); + } + } + + [Test] + public void Verify_that_QueryIsSubclassOf_returns_expected_result() + { + var dependency = this.QueryClass("CommonStructure", "Dependency"); + var packageableElement = this.QueryClass("CommonStructure", "PackageableElement"); + var element = this.QueryClass("CommonStructure", "Element"); + var comment = this.QueryClass("CommonStructure", "Comment"); + + using (Assert.EnterMultipleScope()) + { + Assert.That(dependency.QueryIsSubclassOf(packageableElement), Is.True, "a direct general class"); + Assert.That(dependency.QueryIsSubclassOf(element), Is.True, "an indirect general class"); + Assert.That(dependency.QueryIsSubclassOf(dependency), Is.False, "a class is not a subclass of itself"); + Assert.That(dependency.QueryIsSubclassOf(comment), Is.False); + Assert.That(element.QueryIsSubclassOf(dependency), Is.False); + + Assert.That(dependency.QueryIsSubclassOf("PackageableElement"), Is.True); + Assert.That(dependency.QueryIsSubclassOf("Element"), Is.True); + Assert.That(dependency.QueryIsSubclassOf("Dependency"), Is.False, "a class is not a subclass of itself"); + Assert.That(dependency.QueryIsSubclassOf("Comment"), Is.False); + Assert.That(dependency.QueryIsSubclassOf("element"), Is.False, "the name is case-sensitive"); + } + } + + [Test] + public void Verify_that_QueryIsSubclassOf_throws_for_missing_arguments() + { + var dependency = new Class { Name = "Dependency" }; + + using (Assert.EnterMultipleScope()) + { + Assert.That(() => ClassExtensions.QueryIsSubclassOf(null, dependency), Throws.ArgumentNullException); + Assert.That(() => dependency.QueryIsSubclassOf((IClass)null), Throws.ArgumentNullException); + Assert.That(() => ClassExtensions.QueryIsSubclassOf(null, "Element"), Throws.ArgumentNullException); + Assert.That(() => dependency.QueryIsSubclassOf((string)null), Throws.ArgumentException); + Assert.That(() => dependency.QueryIsSubclassOf(" "), Throws.ArgumentException); + } + } + + [Test] + public void Verify_that_QueryReferencingProperties_returns_the_properties_that_are_typed_by_the_class() + { + var comment = this.QueryClass("CommonStructure", "Comment"); + + var referencingProperties = comment.QueryReferencingProperties(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(referencingProperties, Is.Not.Empty); + Assert.That(referencingProperties.Select(x => x.Type), Is.All.SameAs(comment)); + Assert.That(referencingProperties.Select(x => x.XmiId), Does.Contain("Element-ownedComment")); + Assert.That(referencingProperties, Is.Unique); + + Assert.That(new Class { Name = "Detached" }.QueryReferencingProperties(), Is.Empty, "a class without a root package"); + Assert.That(() => ClassExtensions.QueryReferencingProperties(null), Throws.ArgumentNullException); + } + } + + [Test] + public void Verify_that_QueryOwnedManyToManyProperties_returns_expected_result() + { + var dependency = this.QueryClass("CommonStructure", "Dependency"); + var comment = this.QueryClass("CommonStructure", "Comment"); + + using (Assert.EnterMultipleScope()) + { + Assert.That(dependency.QueryOwnedManyToManyProperties().Select(x => x.Name), Is.EqualTo(new[] { "client", "supplier" })); + Assert.That(comment.QueryOwnedManyToManyProperties().Select(x => x.Name), Is.EqualTo(new[] { "annotatedElement" }), "body is not a reference property"); + Assert.That(dependency.QueryOwnedManyToManyProperties().All(x => x.QueryIsMemberOfManyToMany()), Is.True); + Assert.That(() => ClassExtensions.QueryOwnedManyToManyProperties(null), Throws.ArgumentNullException); + } + } + + /// + /// Queries the with the specified name from the specified package of the UML metamodel + /// + /// + /// The name of the package that contains the class + /// + /// + /// The name of the class + /// + /// + /// The + /// + private IClass QueryClass(string packageName, string className) + { + var root = this.xmiReaderResult.QueryRoot(xmiId: "_0", name: "UML"); + + var package = root.NestedPackage.Single(x => x.Name == packageName); + + return package.PackagedElement.OfType().Single(x => x.Name == className); + } } } diff --git a/uml4net.Extensions.Tests/ClassifierExtensionsTestFixture.cs b/uml4net.Extensions.Tests/ClassifierExtensionsTestFixture.cs index f83e0504..c593098a 100644 --- a/uml4net.Extensions.Tests/ClassifierExtensionsTestFixture.cs +++ b/uml4net.Extensions.Tests/ClassifierExtensionsTestFixture.cs @@ -126,5 +126,39 @@ public void Verify_that_QueryContainers_throws_when_classifier_is_null() { Assert.That(() => ClassifierExtensions.QueryContainers(null), Throws.ArgumentNullException); } + + [Test] + public void Verify_that_QueryConformsTo_returns_expected_result() + { + var root = this.xmiReaderResult.QueryRoot(xmiId: "_0", name: "UML"); + + var commonStructurePackage = root.NestedPackage.Single(x => x.Name == "CommonStructure"); + + var dependency = commonStructurePackage.PackagedElement.OfType().Single(x => x.Name == "Dependency"); + var packageableElement = commonStructurePackage.PackagedElement.OfType().Single(x => x.Name == "PackageableElement"); + var element = commonStructurePackage.PackagedElement.OfType().Single(x => x.Name == "Element"); + var comment = commonStructurePackage.PackagedElement.OfType().Single(x => x.Name == "Comment"); + + using (Assert.EnterMultipleScope()) + { + Assert.That(dependency.QueryConformsTo(dependency), Is.True, "a classifier conforms to itself"); + Assert.That(dependency.QueryConformsTo(packageableElement), Is.True, "a direct general classifier"); + Assert.That(dependency.QueryConformsTo(element), Is.True, "an indirect general classifier"); + Assert.That(dependency.QueryConformsTo(comment), Is.False); + Assert.That(element.QueryConformsTo(dependency), Is.False); + } + } + + [Test] + public void Verify_that_QueryConformsTo_throws_for_missing_arguments() + { + var classifier = new Class { Name = "Classifier" }; + + using (Assert.EnterMultipleScope()) + { + Assert.That(() => ClassifierExtensions.QueryConformsTo(null, classifier), Throws.ArgumentNullException); + Assert.That(() => classifier.QueryConformsTo(null), Throws.ArgumentNullException); + } + } } } diff --git a/uml4net.Extensions/ClassExtensions.cs b/uml4net.Extensions/ClassExtensions.cs index 943071af..bbeeaa91 100644 --- a/uml4net.Extensions/ClassExtensions.cs +++ b/uml4net.Extensions/ClassExtensions.cs @@ -366,5 +366,184 @@ public static ReadOnlyCollection QueryAllOperations(this IClass @cla return result.Distinct().OrderBy(x => x.Name).ToList().AsReadOnly(); } + + /// + /// Queries all the properties of the , including the inherited ones, that are not derived + /// + /// + /// The from which to query the properties + /// + /// + /// The properties of for which Property::isDerived is false, in the + /// same order + /// + public static ReadOnlyCollection QueryAllNonDerivedProperties(this IClass @class) + { + if (@class == null) + { + throw new ArgumentNullException(nameof(@class)); + } + + return @class.QueryAllProperties() + .Where(x => !x.IsDerived) + .ToList() + .AsReadOnly(); + } + + /// + /// Queries all the properties of the , including the inherited ones, that are not derived + /// and that are not redefined in the context of the + /// + /// + /// The from which to query the properties + /// + /// + /// The properties of for which + /// finds no redefining property in the + /// , in the same order + /// + public static ReadOnlyCollection QueryAllNonDerivedNonRedefinedProperties(this IClass @class) + { + if (@class == null) + { + throw new ArgumentNullException(nameof(@class)); + } + + return @class.QueryAllNonDerivedProperties() + .Where(x => !x.TryQueryRedefinedByProperty(@class, out _)) + .ToList() + .AsReadOnly(); + } + + /// + /// Queries whether the is a direct or indirect subclass of the + /// + /// + /// The subject + /// + /// + /// The that is expected to be a direct or indirect general class + /// + /// + /// true when the is one of the direct or indirect general classifiers of the + /// , false otherwise. A class is not a subclass of itself; use + /// for the test that includes the class itself. + /// + public static bool QueryIsSubclassOf(this IClass @class, IClass superClass) + { + if (@class == null) + { + throw new ArgumentNullException(nameof(@class)); + } + + if (superClass == null) + { + throw new ArgumentNullException(nameof(superClass)); + } + + return !ReferenceEquals(@class, superClass) && @class.QueryAllGeneralClassifiers().Contains(superClass); + } + + /// + /// Queries whether the is a direct or indirect subclass of a class with the specified name + /// + /// + /// The subject + /// + /// + /// The name of the that is expected to be a direct or indirect general class + /// + /// + /// true when one of the direct or indirect general classes of the has the + /// as name, false otherwise. The itself is not + /// taken into account. + /// + /// + /// The comparison is on the name only, it is case-sensitive and does not take the package into account. Two + /// classes that have the same name in different packages are not told apart; use the overload that takes an + /// when that matters. + /// + public static bool QueryIsSubclassOf(this IClass @class, string superClassName) + { + if (@class == null) + { + throw new ArgumentNullException(nameof(@class)); + } + + if (string.IsNullOrWhiteSpace(superClassName)) + { + throw new ArgumentException("The name of the super class may not be null or empty", nameof(superClassName)); + } + + return @class.QueryAllGeneralClassifiers() + .OfType() + .Any(x => !ReferenceEquals(x, @class) && x.Name == superClassName); + } + + /// + /// Queries the properties in the model that refer to the : the properties that have the + /// as type + /// + /// + /// The subject + /// + /// + /// The owned attributes that are typed by the , of the classes that are packaged + /// elements of the root package of the , of its nested packages and of the packages + /// that these import (see ). An empty + /// collection when the has no root package. + /// + /// + /// Only the owned attributes of classes that are directly contained by a package are searched. An association + /// end that is owned by the association, and an attribute of a nested class, an interface or a data type, are + /// not returned. + /// + public static ReadOnlyCollection QueryReferencingProperties(this IClass @class) + { + if (@class == null) + { + throw new ArgumentNullException(nameof(@class)); + } + + var rootPackage = @class.QueryRootPackage(); + + if (rootPackage == null) + { + return new List().AsReadOnly(); + } + + return rootPackage.QueryAllNestedAndImportedPackages() + .SelectMany(x => x.PackagedElement.OfType()) + .SelectMany(x => x.OwnedAttribute) + .Where(x => ReferenceEquals(x.Type, @class)) + .Distinct() + .ToList() + .AsReadOnly(); + } + + /// + /// Queries the owned attributes of the that take part in a many-to-many relationship + /// + /// + /// The subject + /// + /// + /// The owned attributes for which is true, ordered + /// by name + /// + public static ReadOnlyCollection QueryOwnedManyToManyProperties(this IClass @class) + { + if (@class == null) + { + throw new ArgumentNullException(nameof(@class)); + } + + return @class.OwnedAttribute + .Where(x => x.QueryIsMemberOfManyToMany()) + .OrderBy(x => x.Name, StringComparer.Ordinal) + .Distinct() + .ToList() + .AsReadOnly(); + } } } diff --git a/uml4net.Extensions/ClassifierExtensions.cs b/uml4net.Extensions/ClassifierExtensions.cs index 074e5b69..17a83de6 100644 --- a/uml4net.Extensions/ClassifierExtensions.cs +++ b/uml4net.Extensions/ClassifierExtensions.cs @@ -26,6 +26,7 @@ namespace uml4net.Extensions using System.Linq; using uml4net.Classification; + using uml4net.CommonStructure; /// /// The class provides extensions methods for the @@ -124,5 +125,35 @@ public static ReadOnlyCollection QueryContainers(this IClassifier s .ToList() .AsReadOnly(); } + + /// + /// Queries whether the conforms to the , + /// as specified by the operation Classifier::conformsTo of UML 2.5.1 + /// + /// + /// The subject + /// + /// + /// The to which the is expected to conform + /// + /// + /// true when the is the itself or one of its direct or + /// indirect general classifiers; false otherwise, and when the is not an + /// + /// + public static bool QueryConformsTo(this IClassifier classifier, IType other) + { + if (classifier == null) + { + throw new ArgumentNullException(nameof(classifier)); + } + + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + return other is IClassifier otherClassifier && classifier.QueryAllGeneralClassifiers().Contains(otherClassifier); + } } } diff --git a/uml4net.HandleBars.Tests/ClassHelperTestFixture.cs b/uml4net.HandleBars.Tests/ClassHelperTestFixture.cs index 381804a0..85138768 100644 --- a/uml4net.HandleBars.Tests/ClassHelperTestFixture.cs +++ b/uml4net.HandleBars.Tests/ClassHelperTestFixture.cs @@ -345,5 +345,93 @@ public void Verify_that_WriteDerivedPropertyCasesForXmiReader_throws_when_not_pr Assert.That(() => actionWithoutArguments(new Class()), Throws.InstanceOf()); } + + [Test] + public void Verify_that_QueryAllNonDerivedNonRedefinedProperties_returns_expected_result() + { + var template = "{{#each (#Class.QueryAllNonDerivedNonRedefinedProperties this) as | property |}}{{ property.XmiId }};{{/each}}"; + + var action = this.handlebarsContext.Compile(template); + + var root = this.xmiReaderResult.QueryRoot(xmiId: "_0", name: "UML"); + + var @class = root.NestedPackage.Single(x => x.Name == "StructuredClassifiers").PackagedElement.OfType().Single(x => x.Name == "Class"); + + var result = action(@class); + + var names = @class.QueryAllNonDerivedNonRedefinedProperties().OrderBy(x => x.Name).Select(x => x.XmiId); + + using (Assert.EnterMultipleScope()) + { + Assert.That(result, Is.EqualTo(string.Concat(names.Select(x => $"{x};"))), "ordered by name"); + Assert.That(result, Does.Contain("PackageableElement-visibility;")); + Assert.That(result, Does.Not.Contain("NamedElement-visibility;")); + Assert.That(() => action(new Dependency()), Throws.ArgumentException); + } + } + + [Test] + public void Verify_that_QueryIsSubclassOf_returns_expected_result() + { + var template = "{{#if (#Class.QueryIsSubclassOf this.Class this.SuperClass)}}subclass{{else}}not a subclass{{/if}}"; + + var action = this.handlebarsContext.Compile(template); + + var root = this.xmiReaderResult.QueryRoot(xmiId: "_0", name: "UML"); + + var commonStructurePackage = root.NestedPackage.Single(x => x.Name == "CommonStructure"); + var dependency = commonStructurePackage.PackagedElement.OfType().Single(x => x.Name == "Dependency"); + var element = commonStructurePackage.PackagedElement.OfType().Single(x => x.Name == "Element"); + + using (Assert.EnterMultipleScope()) + { + Assert.That(action(new { Class = dependency, SuperClass = element }), Is.EqualTo("subclass")); + Assert.That(action(new { Class = element, SuperClass = dependency }), Is.EqualTo("not a subclass")); + Assert.That(action(new { Class = dependency, SuperClass = dependency }), Is.EqualTo("not a subclass")); + Assert.That(action(new { Class = dependency, SuperClass = "Element" }), Is.EqualTo("subclass")); + Assert.That(action(new { Class = dependency, SuperClass = "Comment" }), Is.EqualTo("not a subclass")); + + Assert.That(() => action(new { Class = "Dependency", SuperClass = element }), Throws.ArgumentException); + Assert.That(() => action(new { Class = dependency, SuperClass = 1 }), Throws.ArgumentException); + Assert.That(() => this.handlebarsContext.Compile("{{#if (#Class.QueryIsSubclassOf this)}}subclass{{/if}}")(dependency), Throws.TypeOf()); + } + } + + [Test] + public void Verify_that_QueryReferencingProperties_returns_expected_result() + { + var template = "{{#each (#Class.QueryReferencingProperties this) as | property |}}{{ property.XmiId }};{{/each}}"; + + var action = this.handlebarsContext.Compile(template); + + var root = this.xmiReaderResult.QueryRoot(xmiId: "_0", name: "UML"); + + var comment = root.NestedPackage.Single(x => x.Name == "CommonStructure").PackagedElement.OfType().Single(x => x.Name == "Comment"); + + using (Assert.EnterMultipleScope()) + { + Assert.That(action(comment), Does.Contain("Element-ownedComment;")); + Assert.That(action(new Class { Name = "Detached" }), Is.Empty); + Assert.That(() => action(new Dependency()), Throws.ArgumentException); + } + } + + [Test] + public void Verify_that_QueryOwnedManyToManyProperties_returns_expected_result() + { + var template = "{{#each (#Class.QueryOwnedManyToManyProperties this) as | property |}}{{ property.Name }};{{/each}}"; + + var action = this.handlebarsContext.Compile(template); + + var root = this.xmiReaderResult.QueryRoot(xmiId: "_0", name: "UML"); + + var dependency = root.NestedPackage.Single(x => x.Name == "CommonStructure").PackagedElement.OfType().Single(x => x.Name == "Dependency"); + + using (Assert.EnterMultipleScope()) + { + Assert.That(action(dependency), Is.EqualTo("client;supplier;")); + Assert.That(() => action(new Dependency()), Throws.ArgumentException); + } + } } } diff --git a/uml4net.HandleBars.Tests/ClassifierHelperTestFixture.cs b/uml4net.HandleBars.Tests/ClassifierHelperTestFixture.cs index 9e99bfb9..25870713 100644 --- a/uml4net.HandleBars.Tests/ClassifierHelperTestFixture.cs +++ b/uml4net.HandleBars.Tests/ClassifierHelperTestFixture.cs @@ -106,5 +106,30 @@ public void Verify_that_QueryContainers_throws_when_context_is_invalid() Assert.That(() => action(5), Throws.ArgumentException); } + + [Test] + public void Verify_that_QueryConformsTo_returns_expected_result() + { + var template = "{{#if (#Classifier.QueryConformsTo this.Classifier this.Other)}}conforms{{else}}does not conform{{/if}}"; + + var action = this.handlebarsContext.Compile(template); + + var root = this.xmiReaderResult.QueryRoot(xmiId: "_0", name: "UML"); + + var commonStructurePackage = root.NestedPackage.Single(x => x.Name == "CommonStructure"); + var dependency = commonStructurePackage.PackagedElement.OfType().Single(x => x.Name == "Dependency"); + var element = commonStructurePackage.PackagedElement.OfType().Single(x => x.Name == "Element"); + + using (Assert.EnterMultipleScope()) + { + Assert.That(action(new { Classifier = dependency, Other = element }), Is.EqualTo("conforms")); + Assert.That(action(new { Classifier = dependency, Other = dependency }), Is.EqualTo("conforms")); + Assert.That(action(new { Classifier = element, Other = dependency }), Is.EqualTo("does not conform")); + + Assert.That(() => action(new { Classifier = 5, Other = element }), Throws.ArgumentException); + Assert.That(() => action(new { Classifier = dependency, Other = "Element" }), Throws.ArgumentException); + Assert.That(() => this.handlebarsContext.Compile("{{#if (#Classifier.QueryConformsTo this)}}conforms{{/if}}")(dependency), Throws.TypeOf()); + } + } } } diff --git a/uml4net.HandleBars/ClassHelper.cs b/uml4net.HandleBars/ClassHelper.cs index e859428c..a703da2b 100644 --- a/uml4net.HandleBars/ClassHelper.cs +++ b/uml4net.HandleBars/ClassHelper.cs @@ -134,13 +134,74 @@ public static void RegisterClassHelper(this IHandlebars handlebars) throw new ArgumentException("supposed to be IClass"); } - var properties = @class.QueryAllProperties() - .Where(x => !x.IsDerived) + var properties = @class.QueryAllNonDerivedProperties() .OrderBy(x => x.Name); return properties; }); + // Queries all the properties of the class, including the inherited ones, that are not derived and that are + // not redefined in the context of the class + handlebars.RegisterHelper("Class.QueryAllNonDerivedNonRedefinedProperties", (context, _) => + { + if (!(context.Value is IClass @class)) + { + throw new ArgumentException("supposed to be IClass"); + } + + var properties = @class.QueryAllNonDerivedNonRedefinedProperties() + .OrderBy(x => x.Name); + + return properties; + }); + + // Queries whether the class is a direct or indirect subclass of the class, or of the class with the name, + // that is provided as second argument + handlebars.RegisterHelper("Class.QueryIsSubclassOf", (_, arguments) => + { + if (arguments.Length != 2) + { + throw new HandlebarsException("{{#Class.QueryIsSubclassOf}} helper must have exactly two arguments"); + } + + if (!(arguments[0] is IClass @class)) + { + throw new ArgumentException("{{#Class.QueryIsSubclassOf}} - the first argument is supposed to be an IClass"); + } + + switch (arguments[1]) + { + case IClass superClass: + return @class.QueryIsSubclassOf(superClass); + case string superClassName: + return @class.QueryIsSubclassOf(superClassName); + default: + throw new ArgumentException("{{#Class.QueryIsSubclassOf}} - the second argument is supposed to be an IClass or the name of a class"); + } + }); + + // Queries the properties in the model that have the class as type + handlebars.RegisterHelper("Class.QueryReferencingProperties", (context, _) => + { + if (!(context.Value is IClass @class)) + { + throw new ArgumentException("supposed to be IClass"); + } + + return @class.QueryReferencingProperties(); + }); + + // Queries the owned attributes of the class that take part in a many-to-many relationship + handlebars.RegisterHelper("Class.QueryOwnedManyToManyProperties", (context, _) => + { + if (!(context.Value is IClass @class)) + { + throw new ArgumentException("supposed to be IClass"); + } + + return @class.QueryOwnedManyToManyProperties(); + }); + handlebars.RegisterHelper("Class.QueryAllNonDerivedNonReadOnlyProperties", (context, _) => { if (!(context.Value is IClass @class)) diff --git a/uml4net.HandleBars/ClassifierHelper.cs b/uml4net.HandleBars/ClassifierHelper.cs index 62cea21b..9b1b18f9 100644 --- a/uml4net.HandleBars/ClassifierHelper.cs +++ b/uml4net.HandleBars/ClassifierHelper.cs @@ -25,6 +25,7 @@ namespace uml4net.HandleBars using Classification; using HandlebarsDotNet; + using uml4net.CommonStructure; using uml4net.Extensions; using uml4net.StructuredClassifiers; @@ -53,6 +54,28 @@ public static void RegisterClassifierHelper(this IHandlebars handlebars) return containers; }); + + // Queries whether the classifier conforms to the type that is provided as second argument: whether that + // type is the classifier itself or one of its direct or indirect general classifiers + handlebars.RegisterHelper("Classifier.QueryConformsTo", (_, arguments) => + { + if (arguments.Length != 2) + { + throw new HandlebarsException("{{#Classifier.QueryConformsTo}} helper must have exactly two arguments"); + } + + if (!(arguments[0] is IClassifier classifier)) + { + throw new ArgumentException("{{#Classifier.QueryConformsTo}} - the first argument is supposed to be an IClassifier"); + } + + if (!(arguments[1] is IType other)) + { + throw new ArgumentException("{{#Classifier.QueryConformsTo}} - the second argument is supposed to be an IType"); + } + + return classifier.QueryConformsTo(other); + }); } } }