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
130 changes: 130 additions & 0 deletions uml4net.Extensions.Tests/ClassExtensionsTestFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

/// <summary>
/// Queries the <see cref="IClass"/> with the specified name from the specified package of the UML metamodel
/// </summary>
/// <param name="packageName">
/// The name of the package that contains the class
/// </param>
/// <param name="className">
/// The name of the class
/// </param>
/// <returns>
/// The <see cref="IClass"/>
/// </returns>
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<IClass>().Single(x => x.Name == className);
}
}
}
34 changes: 34 additions & 0 deletions uml4net.Extensions.Tests/ClassifierExtensionsTestFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<IClass>().Single(x => x.Name == "Dependency");
var packageableElement = commonStructurePackage.PackagedElement.OfType<IClass>().Single(x => x.Name == "PackageableElement");
var element = commonStructurePackage.PackagedElement.OfType<IClass>().Single(x => x.Name == "Element");
var comment = commonStructurePackage.PackagedElement.OfType<IClass>().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);
}
}
}
}
179 changes: 179 additions & 0 deletions uml4net.Extensions/ClassExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -366,5 +366,184 @@ public static ReadOnlyCollection<IOperation> QueryAllOperations(this IClass @cla

return result.Distinct().OrderBy(x => x.Name).ToList().AsReadOnly();
}

/// <summary>
/// Queries all the properties of the <see cref="IClass"/>, including the inherited ones, that are not derived
/// </summary>
/// <param name="class">
/// The <see cref="IClass"/> from which to query the properties
/// </param>
/// <returns>
/// The properties of <see cref="QueryAllProperties"/> for which <c>Property::isDerived</c> is false, in the
/// same order
/// </returns>
public static ReadOnlyCollection<IProperty> QueryAllNonDerivedProperties(this IClass @class)
{
if (@class == null)
{
throw new ArgumentNullException(nameof(@class));
}

return @class.QueryAllProperties()
.Where(x => !x.IsDerived)
.ToList()
.AsReadOnly();
}

/// <summary>
/// Queries all the properties of the <see cref="IClass"/>, including the inherited ones, that are not derived
/// and that are not redefined in the context of the <see cref="IClass"/>
/// </summary>
/// <param name="class">
/// The <see cref="IClass"/> from which to query the properties
/// </param>
/// <returns>
/// The properties of <see cref="QueryAllNonDerivedProperties"/> for which
/// <see cref="PropertyExtensions.TryQueryRedefinedByProperty"/> finds no redefining property in the
/// <paramref name="class"/>, in the same order
/// </returns>
public static ReadOnlyCollection<IProperty> QueryAllNonDerivedNonRedefinedProperties(this IClass @class)
{
if (@class == null)
{
throw new ArgumentNullException(nameof(@class));
}

return @class.QueryAllNonDerivedProperties()
.Where(x => !x.TryQueryRedefinedByProperty(@class, out _))
.ToList()
.AsReadOnly();
}

/// <summary>
/// Queries whether the <see cref="IClass"/> is a direct or indirect subclass of the <paramref name="superClass"/>
/// </summary>
/// <param name="class">
/// The subject <see cref="IClass"/>
/// </param>
/// <param name="superClass">
/// The <see cref="IClass"/> that is expected to be a direct or indirect general class
/// </param>
/// <returns>
/// true when the <paramref name="superClass"/> is one of the direct or indirect general classifiers of the
/// <paramref name="class"/>, false otherwise. A class is not a subclass of itself; use
/// <see cref="ClassifierExtensions.QueryConformsTo"/> for the test that includes the class itself.
/// </returns>
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);
}

/// <summary>
/// Queries whether the <see cref="IClass"/> is a direct or indirect subclass of a class with the specified name
/// </summary>
/// <param name="class">
/// The subject <see cref="IClass"/>
/// </param>
/// <param name="superClassName">
/// The name of the <see cref="IClass"/> that is expected to be a direct or indirect general class
/// </param>
/// <returns>
/// true when one of the direct or indirect general classes of the <paramref name="class"/> has the
/// <paramref name="superClassName"/> as name, false otherwise. The <paramref name="class"/> itself is not
/// taken into account.
/// </returns>
/// <remarks>
/// 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
/// <see cref="IClass"/> when that matters.
/// </remarks>
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<IClass>()
.Any(x => !ReferenceEquals(x, @class) && x.Name == superClassName);
}

/// <summary>
/// Queries the properties in the model that refer to the <see cref="IClass"/>: the properties that have the
/// <see cref="IClass"/> as type
/// </summary>
/// <param name="class">
/// The subject <see cref="IClass"/>
/// </param>
/// <returns>
/// The owned attributes that are typed by the <paramref name="class"/>, of the classes that are packaged
/// elements of the root package of the <paramref name="class"/>, of its nested packages and of the packages
/// that these import (see <see cref="PackageExtensions.QueryAllNestedAndImportedPackages"/>). An empty
/// collection when the <paramref name="class"/> has no root package.
/// </returns>
/// <remarks>
/// 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.
/// </remarks>
public static ReadOnlyCollection<IProperty> QueryReferencingProperties(this IClass @class)
{
if (@class == null)
{
throw new ArgumentNullException(nameof(@class));
}

var rootPackage = @class.QueryRootPackage();

if (rootPackage == null)
{
return new List<IProperty>().AsReadOnly();
}

return rootPackage.QueryAllNestedAndImportedPackages()
.SelectMany(x => x.PackagedElement.OfType<IClass>())
.SelectMany(x => x.OwnedAttribute)
.Where(x => ReferenceEquals(x.Type, @class))
.Distinct()
.ToList()
.AsReadOnly();
}

/// <summary>
/// Queries the owned attributes of the <see cref="IClass"/> that take part in a many-to-many relationship
/// </summary>
/// <param name="class">
/// The subject <see cref="IClass"/>
/// </param>
/// <returns>
/// The owned attributes for which <see cref="PropertyExtensions.QueryIsMemberOfManyToMany"/> is true, ordered
/// by name
/// </returns>
public static ReadOnlyCollection<IProperty> 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();
}
}
}
Loading
Loading