Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions uml4net.Extensions.Tests/PropertyExtensionsTestFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ namespace uml4net.Extensions.Tests

using Serilog;

using uml4net.CommonStructure;
using uml4net.Packages;
using uml4net.SimpleClassifiers;
using uml4net.StructuredClassifiers;
Expand Down Expand Up @@ -798,5 +799,91 @@ public void Verify_that_QueryHasBeenRedefined_returns_expected_result()

Assert.That(result, Is.False);
}

[Test]
public void Verify_that_QueryIsEnumPropertyWithDefaultValue_returns_expected_result()
{
var root = this.xmiReaderResult.QueryRoot(xmiId: "_0", name: "UML");

var commonStructurePackage = root.NestedPackage.Single(x => x.Name == "CommonStructure");

var packageableElement = commonStructurePackage.PackagedElement.OfType<IClass>().Single(x => x.Name == "PackageableElement");
var namedElement = commonStructurePackage.PackagedElement.OfType<IClass>().Single(x => x.Name == "NamedElement");

var visibilityWithDefault = packageableElement.OwnedAttribute.Single(x => x.Name == "visibility");
var visibilityWithoutDefault = namedElement.OwnedAttribute.Single(x => x.Name == "visibility");
var name = namedElement.OwnedAttribute.Single(x => x.Name == "name");

var enumeration = new Enumeration { Name = "Colour" };
var withLiteralStringAsDefault = new Property { Name = "colour", Type = enumeration };
withLiteralStringAsDefault.DefaultValue.Add(new LiteralString { Value = "red" });

using (Assert.EnterMultipleScope())
{
Assert.That(visibilityWithDefault.QueryIsEnumPropertyWithDefaultValue(), Is.True, "the default is the literal public");
Assert.That(visibilityWithoutDefault.QueryIsEnumPropertyWithDefaultValue(), Is.False, "an enumeration without default");
Assert.That(name.QueryIsEnumPropertyWithDefaultValue(), Is.False, "not typed by an enumeration");
Assert.That(withLiteralStringAsDefault.QueryIsEnumPropertyWithDefaultValue(), Is.False, "the default is not an InstanceValue");
Assert.That(() => PropertyExtensions.QueryIsEnumPropertyWithDefaultValue(null), Throws.ArgumentNullException);
}
}

[Test]
public void Verify_that_QueryIsPartOfNonDerivedCompositeAggregation_returns_expected_result()
{
var root = this.xmiReaderResult.QueryRoot(xmiId: "_0", name: "UML");

var commonStructurePackage = root.NestedPackage.Single(x => x.Name == "CommonStructure");
var activitiesPackage = root.NestedPackage.Single(x => x.Name == "Activities");

var element = commonStructurePackage.PackagedElement.OfType<IClass>().Single(x => x.Name == "Element");
var comment = commonStructurePackage.PackagedElement.OfType<IClass>().Single(x => x.Name == "Comment");
var activity = activitiesPackage.PackagedElement.OfType<IClass>().Single(x => x.Name == "Activity");
var activityNode = activitiesPackage.PackagedElement.OfType<IClass>().Single(x => x.Name == "ActivityNode");

using (Assert.EnterMultipleScope())
{
Assert.That(element.OwnedAttribute.Single(x => x.Name == "ownedComment").QueryIsPartOfNonDerivedCompositeAggregation(), Is.True, "composite and not derived");
Assert.That(element.OwnedAttribute.Single(x => x.Name == "ownedElement").QueryIsPartOfNonDerivedCompositeAggregation(), Is.False, "composite and derived");
Assert.That(activity.OwnedAttribute.Single(x => x.Name == "node").QueryIsPartOfNonDerivedCompositeAggregation(), Is.True, "the end that refers to the contained elements");
Assert.That(activityNode.OwnedAttribute.Single(x => x.Name == "activity").QueryIsPartOfNonDerivedCompositeAggregation(), Is.True, "the end that refers to the container");
Assert.That(comment.OwnedAttribute.Single(x => x.Name == "annotatedElement").QueryIsPartOfNonDerivedCompositeAggregation(), Is.False, "a reference that is not part of a composition");
Assert.That(comment.OwnedAttribute.Single(x => x.Name == "body").QueryIsPartOfNonDerivedCompositeAggregation(), Is.False);
Assert.That(() => PropertyExtensions.QueryIsPartOfNonDerivedCompositeAggregation(null), Throws.ArgumentNullException);
}
}

[Test]
public void Verify_that_QueryConstraints_returns_the_rules_of_the_owner_that_constrain_the_property()
{
var @class = new Class { Name = "Tank" };

var volume = new Property { Name = "volume" };
var mass = new Property { Name = "mass" };
@class.OwnedAttribute.Add(volume);
@class.OwnedAttribute.Add(mass);

var volumeIsPositive = new Constraint { Name = "volumeIsPositive" };
volumeIsPositive.ConstrainedElement.Add(volume);

var volumeAndMass = new Constraint { Name = "volumeAndMass" };
volumeAndMass.ConstrainedElement.Add(mass);
volumeAndMass.ConstrainedElement.Add(volume);

var classRule = new Constraint { Name = "classRule" };
classRule.ConstrainedElement.Add(@class);

@class.OwnedRule.Add(volumeIsPositive);
@class.OwnedRule.Add(classRule);
@class.OwnedRule.Add(volumeAndMass);

using (Assert.EnterMultipleScope())
{
Assert.That(volume.QueryConstraints(), Is.EqualTo(new[] { volumeIsPositive, volumeAndMass }), "in the order of the owned rules");
Assert.That(mass.QueryConstraints(), Is.EqualTo(new[] { volumeAndMass }));
Assert.That(new Property { Name = "detached" }.QueryConstraints(), Is.Empty, "a property without owner");
Assert.That(() => PropertyExtensions.QueryConstraints(null), Throws.ArgumentNullException);
}
}
}
}
104 changes: 104 additions & 0 deletions uml4net.Extensions/PropertyExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ namespace uml4net.Extensions
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using uml4net.Classification;
using uml4net.CommonStructure;
Expand Down Expand Up @@ -572,5 +573,108 @@ public static bool QueryIsMemberOfManyToMany(this IProperty property)

return false;
}

/// <summary>
/// Queries whether the <see cref="IProperty"/> is typed by an <see cref="IEnumeration"/> and has one of the
/// literals of an enumeration as default value
/// </summary>
/// <param name="property">
/// The subject <see cref="IProperty"/>
/// </param>
/// <returns>
/// true when the type of the <paramref name="property"/> is an <see cref="IEnumeration"/> and its default
/// value is an <see cref="IInstanceValue"/> that refers to an <see cref="IEnumerationLiteral"/>, false
/// otherwise
/// </returns>
/// <remarks>
/// UML 2.5.1 specifies the default value of a property that is typed by an enumeration as an InstanceValue
/// that refers to the EnumerationLiteral. A default value of another kind, such as a LiteralString that
/// holds the name of the literal, is not taken into account.
/// </remarks>
public static bool QueryIsEnumPropertyWithDefaultValue(this IProperty property)
{
if (property == null)
{
throw new ArgumentNullException(nameof(property));
}

if (!property.QueryIsEnum())
{
return false;
}

return property.DefaultValue.FirstOrDefault() is IInstanceValue instanceValue
&& instanceValue.Instance is IEnumerationLiteral;
}

/// <summary>
/// Queries whether the <see cref="IProperty"/> is one of the two ends of a composite aggregation that is not
/// derived
/// </summary>
/// <param name="property">
/// The subject <see cref="IProperty"/>
/// </param>
/// <returns>
/// true when the <paramref name="property"/> is composite and not derived (the end that refers to the
/// contained elements), or when its <see cref="IProperty.Opposite"/> is composite and not derived (the end
/// that refers to the container); false otherwise
/// </returns>
/// <remarks>
/// <see cref="IProperty.Opposite"/> only has a value when both ends of the association are owned by the
/// classifiers at its ends. The end that refers to the container is therefore not recognized when the
/// composite end, or that end itself, is owned by the association.
/// </remarks>
public static bool QueryIsPartOfNonDerivedCompositeAggregation(this IProperty property)
{
if (property == null)
{
throw new ArgumentNullException(nameof(property));
}

if (property.IsComposite && !property.IsDerived)
{
return true;
}

var opposite = property.Opposite;

return opposite != null && opposite.IsComposite && !opposite.IsDerived;
}

/// <summary>
/// Queries the constraints that constrain the <see cref="IProperty"/>: the rules that are owned by the
/// namespace that owns the <see cref="IProperty"/> and that have the <see cref="IProperty"/> as one of
/// their constrained elements
/// </summary>
/// <param name="property">
/// The subject <see cref="IProperty"/>
/// </param>
/// <returns>
/// The <see cref="IConstraint"/>s of <c>Namespace::ownedRule</c> of the owner of the
/// <paramref name="property"/> for which <c>Constraint::constrainedElement</c> holds the
/// <paramref name="property"/>, in the order of the owned rules. An empty collection when the owner of the
/// <paramref name="property"/> is not an <see cref="INamespace"/>.
/// </returns>
/// <remarks>
/// A constraint that concerns the <paramref name="property"/> but has another element, such as the owning
/// class, as constrained element is not returned.
/// </remarks>
public static ReadOnlyCollection<IConstraint> QueryConstraints(this IProperty property)
{
if (property == null)
{
throw new ArgumentNullException(nameof(property));
}

if (!(property.Owner is INamespace owner))
{
return new List<IConstraint>().AsReadOnly();
}

return owner.OwnedRule
.Where(x => x.ConstrainedElement.Contains(property))
.ToList()
.AsReadOnly();
}
}
}
55 changes: 55 additions & 0 deletions uml4net.HandleBars.Tests/PropertyHelperTestFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,61 @@ public void Verify_that_NameEqualsEnclosingType_helper_only_writes_the_block_whe
}
}

[Test]
public void Verify_that_QueryIsEnumPropertyWithDefaultValue_helper_returns_expected_result()
{
var template = this.handlebarsContext.Compile("{{#if (#Property.QueryIsEnumPropertyWithDefaultValue this)}}enum with default{{else}}other{{/if}}");

var visibilityWithDefault = this.QueryClass("CommonStructure", "PackageableElement").OwnedAttribute.Single(x => x.Name == "visibility");
var visibilityWithoutDefault = this.QueryClass("CommonStructure", "NamedElement").OwnedAttribute.Single(x => x.Name == "visibility");

using (Assert.EnterMultipleScope())
{
Assert.That(template(visibilityWithDefault), Is.EqualTo("enum with default"));
Assert.That(template(visibilityWithoutDefault), Is.EqualTo("other"));
Assert.That(() => template(this.QueryClass("CommonStructure", "NamedElement")), Throws.TypeOf<ArgumentException>());
Assert.That(() => this.handlebarsContext.Compile("{{#if (#Property.QueryIsEnumPropertyWithDefaultValue this this)}}x{{/if}}")(visibilityWithDefault), Throws.TypeOf<HandlebarsException>());
}
}

[Test]
public void Verify_that_QueryIsPartOfNonDerivedCompositeAggregation_helper_returns_expected_result()
{
var template = this.handlebarsContext.Compile("{{#if (#Property.QueryIsPartOfNonDerivedCompositeAggregation this)}}composition{{else}}other{{/if}}");

var element = this.QueryClass("CommonStructure", "Element");

using (Assert.EnterMultipleScope())
{
Assert.That(template(element.OwnedAttribute.Single(x => x.Name == "ownedComment")), Is.EqualTo("composition"));
Assert.That(template(element.OwnedAttribute.Single(x => x.Name == "ownedElement")), Is.EqualTo("other"));
Assert.That(() => template(element), Throws.TypeOf<ArgumentException>());
Assert.That(() => this.handlebarsContext.Compile("{{#if (#Property.QueryIsPartOfNonDerivedCompositeAggregation this this)}}x{{/if}}")(element), Throws.TypeOf<HandlebarsException>());
}
}

[Test]
public void Verify_that_QueryConstraints_helper_returns_expected_result()
{
var template = this.handlebarsContext.Compile("{{#each (#Property.QueryConstraints this) as | constraint |}}{{ constraint.Name }};{{/each}}");

var @class = new Class { Name = "Tank" };
var volume = new Property { Name = "volume" };
@class.OwnedAttribute.Add(volume);

var volumeIsPositive = new Constraint { Name = "volumeIsPositive" };
volumeIsPositive.ConstrainedElement.Add(volume);
@class.OwnedRule.Add(volumeIsPositive);
@class.OwnedRule.Add(new Constraint { Name = "classRule" });

using (Assert.EnterMultipleScope())
{
Assert.That(template(volume), Is.EqualTo("volumeIsPositive;"));
Assert.That(() => template(@class), Throws.TypeOf<ArgumentException>());
Assert.That(() => this.handlebarsContext.Compile("{{#each (#Property.QueryConstraints this this)}}x{{/each}}")(volume), Throws.TypeOf<HandlebarsException>());
}
}

/// </returns>
private IClass QueryClass(string packageName, string className)
{
Expand Down
48 changes: 48 additions & 0 deletions uml4net.HandleBars/PropertyHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,54 @@
return property.QueryIsSubsetted();
});

// Queries whether the Property is typed by an Enumeration and has an EnumerationLiteral as default value
handlebars.RegisterHelper("Property.QueryIsEnumPropertyWithDefaultValue", (_, arguments) =>
{
if (arguments.Length != 1)
{
throw new HandlebarsException("{{#Property.QueryIsEnumPropertyWithDefaultValue}} helper must have exactly one argument");
}

if (!(arguments[0] is IProperty property))
{
throw new ArgumentException("{{#Property.QueryIsEnumPropertyWithDefaultValue}} - supposed to be IProperty");
}

return property.QueryIsEnumPropertyWithDefaultValue();
});

// Queries whether the Property is one of the two ends of a composite aggregation that is not derived
handlebars.RegisterHelper("Property.QueryIsPartOfNonDerivedCompositeAggregation", (_, arguments) =>
{
if (arguments.Length != 1)
{
throw new HandlebarsException("{{#Property.QueryIsPartOfNonDerivedCompositeAggregation}} helper must have exactly one argument");
}

if (!(arguments[0] is IProperty property))
{
throw new ArgumentException("{{#Property.QueryIsPartOfNonDerivedCompositeAggregation}} - supposed to be IProperty");
}

return property.QueryIsPartOfNonDerivedCompositeAggregation();
});

// Queries the constraints of the owner of the Property that have the Property as constrained element
handlebars.RegisterHelper("Property.QueryConstraints", (_, arguments) =>
{
if (arguments.Length != 1)
{
throw new HandlebarsException("{{#Property.QueryConstraints}} helper must have exactly one argument");
}

if (!(arguments[0] is IProperty property))
{
throw new ArgumentException("{{#Property.QueryConstraints}} - supposed to be IProperty");
}

return property.QueryConstraints();
});

// Queries whether the Property is Derived, DerivedUnion or ReadOnly
handlebars.RegisterHelper("Property.QueryIsDerivedOrDerivedUnionOrReadOnly", (_, arguments) =>
{
Expand Down Expand Up @@ -857,7 +905,7 @@
switch (cSharpTypeName)
{
case "bool":
case "double":

Check warning on line 908 in uml4net.HandleBars/PropertyHelper.cs

View workflow job for this annotation

GitHub Actions / Build

Define a constant instead of using this literal 'double' 9 times.
case "int":
sb.AppendLine($"var {property.Name}XmlAttribute = xmlReader.GetAttribute(\"{property.Name}\") ?? xmlReader.GetAttribute(\"{property.Name}\", this.NameSpaceResolver.UmlNameSpace);");
sb.AppendLine($"{Environment.NewLine}if (!string.IsNullOrWhiteSpace({property.Name}XmlAttribute))");
Expand Down
Loading