Skip to content

Commit

Permalink
[ADD] Resolving External Xmi
Browse files Browse the repository at this point in the history
  • Loading branch information
nathanael smiechowski committed Oct 25, 2024
1 parent c96fb8d commit b450631
Show file tree
Hide file tree
Showing 37 changed files with 1,652 additions and 569 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,8 @@

namespace uml4net.CodeGenerator.Tests.Generators
{
using System.IO;
using System.Threading.Tasks;

using ECoreNetto;

using NUnit.Framework;

using uml4net.CodeGenerator.Generators;

[TestFixture]
public class CorePocoGeneratorTestFixture
{
Expand Down
2 changes: 0 additions & 2 deletions uml4net.HandleBars/GeneralizationHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ namespace uml4net.HandleBars
using System.Linq;

using HandlebarsDotNet;

using uml4net.POCO;
using uml4net.POCO.StructuredClassifiers;

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,38 @@
// </copyright>
// ------------------------------------------------------------------------------------------------

namespace uml4net.Tests
namespace uml4net.xmi.Tests
{
using Cache;
using Microsoft.Extensions.Logging;
using NUnit.Framework;
using System;
using POCO.CommonStructure;
using POCO.StructuredClassifiers;
using POCO.Values;
using System.Collections.Generic;
using uml4net.POCO;
using Microsoft.Extensions.Logging;
using uml4net.POCO.CommonStructure;
using uml4net.POCO.StructuredClassifiers;
using uml4net.POCO.Values;

[TestFixture]
public class AssemblerTestFixture
{
private Assembler assembler;
private readonly Dictionary<string, IXmiElement> cache = [];
private IXmiReaderCache cache;

[SetUp]
public void Setup()
{
this.assembler = new Assembler(LoggerFactory.Create(builder => builder.AddConsole()));
var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());

this.cache = new XmiReaderCache(loggerFactory.CreateLogger<XmiReaderCache>());

this.assembler = new Assembler(loggerFactory.CreateLogger<Assembler>(), this.cache);
}

[TearDown]
public void Teardown()
{
this.cache.Clear();
this.cache.GlobalCache.Clear();
}

[Test]
Expand All @@ -66,7 +71,7 @@ public void Synchronize_ShouldSetSingleValueReference()

Assert.That(classElement.NameExpression, Is.Null);

this.assembler.Synchronize(this.cache);
this.assembler.Synchronize();

// Assert
Assert.Multiple(() =>
Expand Down Expand Up @@ -97,7 +102,7 @@ public void Synchronize_ShouldSetMultiValueReference()
this.cache.Add(comment1.XmiId, comment1);
this.cache.Add(comment2.XmiId, comment2);

this.assembler.Synchronize(this.cache);
this.assembler.Synchronize();

// Assert
Assert.Multiple(() =>
Expand Down Expand Up @@ -135,7 +140,7 @@ public void Synchronize_ShouldSetSingleValueReferenceAndMultipleValuesReference(
Assert.That(classElement.NameExpression, Is.Null);
Assert.That(classElement.OwnedComment.Count, Is.Zero);

this.assembler.Synchronize(this.cache);
this.assembler.Synchronize();

// Assert
Assert.Multiple(() =>
Expand Down Expand Up @@ -187,12 +192,11 @@ public void Synchronize_ShouldSetExpectedReferenceToExpectedReference()
Assert.That(classElement0.OwnedComment.Count, Is.Zero);
Assert.That(classElement1.OwnedComment.Count, Is.Zero);

this.assembler.Synchronize(this.cache);
this.assembler.Synchronize();

// Assert
Assert.Multiple(() =>
{

Assert.That(classElement0.NameExpression, Is.SameAs(stringExpression));
Assert.That(classElement1.NameExpression, Is.Null);
Assert.That(classElement0.OwnedComment.Count, Is.EqualTo(1));
Expand All @@ -217,7 +221,7 @@ public void Synchronize_ShouldThrow_WhenReferenceNotFound()
classElement.SingleValueReferencePropertyIdentifiers.Add("NameExpression", "NonExistentReference");
this.cache.Add(classElement.XmiId, classElement);

this.assembler.Synchronize(this.cache);
this.assembler.Synchronize();

Assert.That(classElement.NameExpression, Is.Not.Null);
}
Expand All @@ -239,7 +243,7 @@ public void Synchronize_ShouldThrow_WhenElementNotIReferenceable()

Assert.Multiple(() =>
{
Assert.Throws<InvalidOperationException>(() => this.assembler.Synchronize(this.cache));
Assert.Throws<InvalidOperationException>(() => this.assembler.Synchronize());
Assert.That(classElement.OwnedComment, Is.Empty);
Assert.That(classElement.NameExpression, Is.Null);
});
Expand Down
9 changes: 5 additions & 4 deletions uml4net.xmi.Tests/SysML2.XmiReaderTestFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ namespace uml4net.xmi.Tests
using Microsoft.Extensions.Logging;

using NUnit.Framework;

using System.Threading.Tasks;
using uml4net.POCO.Packages;
using uml4net.xmi;

public class SysML2XmiReaderTestFixture
{
Expand All @@ -40,11 +41,11 @@ public void SetUp()
}

[Test]
public void Verify_that_SysML_XMI_can_be_read()
public async Task Verify_that_SysML_XMI_can_be_read()
{
var reader = new XmiReader(this.loggerFactory);
using var reader = XmiReaderBuilder.Create().WithLogger(this.loggerFactory).Build();

var packages = reader.Read(Path.Combine(TestContext.CurrentContext.TestDirectory, "TestData", "SysML.uml"));
var packages = await reader.ReadAsync(Path.Combine(TestContext.CurrentContext.TestDirectory, "TestData", "SysML.uml"));

Assert.That(packages.Count(), Is.EqualTo(1));

Expand Down
22 changes: 14 additions & 8 deletions uml4net.xmi.Tests/UMLXmiReaderTestFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@ namespace uml4net.xmi.Tests
using Microsoft.Extensions.Logging;

using NUnit.Framework;

using System.Threading.Tasks;
using uml4net.POCO.Values;
using uml4net.POCO.Packages;
using uml4net.POCO.SimpleClassifiers;
using uml4net.POCO.StructuredClassifiers;

using uml4net.xmi;

[TestFixture]
public class UMLXmiReaderTestFixture
{
Expand All @@ -44,26 +45,31 @@ public void SetUp()
}

[Test]
public void Verify_that_UML_PrimitiveTypes__XMI_can_be_read()
public async Task Verify_that_UML_PrimitiveTypes__XMI_can_be_read()
{
var reader = new XmiReader(this.loggerFactory);
using var reader = XmiReaderBuilder.Create()
.WithLogger(this.loggerFactory)
.Build();

var packages = reader.Read(Path.Combine(TestContext.CurrentContext.TestDirectory, "TestData", "PrimitiveTypes.xmi.xml"));
var packages = await reader.ReadAsync(Path.Combine(TestContext.CurrentContext.TestDirectory, "TestData", "PrimitiveTypes.xmi.xml"));

Assert.That(packages.Count(), Is.EqualTo(1));

var package = packages.First();

Assert.That(package.XmiId, Is.EqualTo("_0"));
Assert.That(package.Name, Is.EqualTo("PrimitiveTypes"));
Assert.That(package.PackagedElement.Count, Is.EqualTo(5));
}

[Test]
public void Verify_that_UML_XMI_can_be_read()
public async Task Verify_that_UML_XMI_can_be_read()
{
var reader = new XmiReader(this.loggerFactory);
using var reader = XmiReaderBuilder.Create()
.WithLogger(this.loggerFactory)
.Build();

var packages = reader.Read(Path.Combine(TestContext.CurrentContext.TestDirectory, "TestData", "UML.xmi.xml"));
var packages = await reader.ReadAsync(Path.Combine(TestContext.CurrentContext.TestDirectory, "TestData", "UML.xmi.xml"));

Assert.That(packages.Count(), Is.EqualTo(1));

Expand Down
88 changes: 38 additions & 50 deletions uml4net/Assembler.cs → uml4net.xmireader/Cache/Assembler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,71 +18,70 @@
// </copyright>
// ------------------------------------------------------------------------------------------------

namespace uml4net
namespace uml4net.xmi.Cache
{
using Decorators;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using POCO;
using System.Reflection;
using Microsoft.Extensions.Logging.Abstractions;
using Decorators;
using POCO;
using DocumentFormat.OpenXml.InkML;

/// <summary>
/// The purpose of the Assembler is to resolve all the reference properties of the objects
/// after deserialization to construct a complete object graph
/// </summary>
public class Assembler
public class Assembler : IAssembler
{
/// <summary>
/// The (injected) <see cref="ILoggerFactory"/> used to setup logging
/// The <see cref="ILogger"/> used to log
/// </summary>
private readonly ILoggerFactory loggerFactory;
private readonly ILogger<Assembler> logger;

/// <summary>
/// The <see cref="ILogger"/> used to log
/// The <see cref="IXmiReaderCache"/>
/// </summary>
private readonly ILogger<Assembler> logger;
private readonly IXmiReaderCache cache;

/// <summary>
/// Gets
/// Initializes a new <see cref="Assembler"/>
/// </summary>
/// <param name="loggerFactory"></param>
public Assembler(ILoggerFactory loggerFactory)
/// <param name="logger">The <see cref="ILogger{T}"/></param>
/// <param name="cache">The <see cref="IXmiReaderCache"/></param>
public Assembler(ILogger<Assembler> logger, IXmiReaderCache cache)
{
this.loggerFactory = loggerFactory;
this.logger = this.loggerFactory == null ? NullLogger<Assembler>.Instance : this.loggerFactory.CreateLogger<Assembler>();
this.logger = logger;
this.cache = cache;
}

/// <summary>
/// Synchronizes the specified cache by assigning properties to elements.
/// Synchronizes the <see cref="IXmiReaderCache"/> by assigning properties to elements.
/// </summary>
/// <param name="cache">
/// A dictionary containing the elements where the keys are their identifiers and the values are the corresponding <see cref="IXmiElement"/> instances.
/// </param>
public void Synchronize(Dictionary<string, IXmiElement> cache)
public void Synchronize()
{
foreach (var element in cache.Values)
foreach (var contextEntries in this.cache.GlobalCache)
{
this.ResolveReferences(element, cache);
foreach (var element in contextEntries.Value)
{
this.ResolveReferences(element.Value);
}
}
}

/// <summary>
/// Resolves single and multi-value references for the given element using the provided cache.
/// </summary>
/// <param name="element">The element whose references are to be resolved.</param>
/// <param name="cache">The cache containing the referenced elements.</param>
public void ResolveReferences(IXmiElement element, IDictionary<string, IXmiElement> cache)
public void ResolveReferences(IXmiElement element)
{
foreach (var property in element.SingleValueReferencePropertyIdentifiers)
{
var referencedElement = this.GetReferencedElement(cache, property.Value, property.Key);

if (referencedElement is null)
if (!this.TryGetReferencedElement(property.Value, out var referencedElement))
{
this.logger.LogWarning("The reference to [{reference}] for property [{key}] on element type [{element}] with id [{id}] was not found in the cache, probably because its type is not supported.", property.Value, property.Key, element.XmiType, element.XmiId);
continue;
}

Expand All @@ -106,7 +105,7 @@ public void ResolveReferences(IXmiElement element, IDictionary<string, IXmiEleme
throw new NullReferenceException($"The target property {property.Key} was not found on {element.GetType().Name} or the type is null");
}

var resolvedReferences = this.ResolveMultiValueReferences(cache, property.Value, property.Key, underlyingType);
var resolvedReferences = this.ResolveMultiValueReferences(property.Value, property.Key, underlyingType);

if (targetProperty.GetValue(element) is not IList list)
{
Expand All @@ -121,27 +120,16 @@ public void ResolveReferences(IXmiElement element, IDictionary<string, IXmiEleme
}

/// <summary>
/// Retrieves a referenced element from the cache based on the reference key.
/// Attempts to retrieve the referenced element associated with the specified reference ID key.
/// </summary>
/// <param name="cache">The cache containing the referenced elements.</param>
/// <param name="reference">The key to the reference in the cache.</param>
/// <param name="key">The name of the property referring to the element.</param>
/// <returns>The resolved IXmiElement from the cache.</returns>
private IXmiElement GetReferencedElement(IDictionary<string, IXmiElement> cache, string reference, string key)
/// <param name="referenceIdKey">The key representing the reference ID.</param>
/// <param name="element">When this method returns, contains the referenced element if found; otherwise, <c>null</c>.</param>
/// <returns><c>true</c> if the referenced element was successfully retrieved; otherwise, <c>false</c>.</returns>
private bool TryGetReferencedElement(string referenceIdKey, out IXmiElement element)
{
if (reference.Contains('#'))
{
this.logger.LogWarning("Referencing external type is not yet supported");
return null;
}

if (cache.TryGetValue(reference, out var referencedElement))
{
return referencedElement;
}

this.logger.LogWarning("The reference with the id [{reference}] to [{key}] was not found in the cache, probably because its type is not supported.", reference, key);
return null;
return this.cache.TryResolveContext(referenceIdKey, out var resolvedContextAndResource)
? this.cache.TryGetValue(resolvedContextAndResource.Context, resolvedContextAndResource.ResourceId, out element)
: this.cache.Cache.TryGetValue(referenceIdKey, out element);
}

/// <summary>
Expand All @@ -151,7 +139,7 @@ private IXmiElement GetReferencedElement(IDictionary<string, IXmiElement> cache,
/// <param name="propertyName">The name of the property to find.</param>
/// <param name="expectedType">The expected type of the property. If null, type checking is skipped.</param>
/// <returns>The <see cref="PropertyInfo"/> of the found property, or null if no matching property is found.</returns>
private PropertyInfo? FindPropertyWithAttribute(IXmiElement element, string propertyName, Type? expectedType = null)
private PropertyInfo FindPropertyWithAttribute(IXmiElement element, string propertyName, Type? expectedType = null)

Check warning on line 142 in uml4net.xmireader/Cache/Assembler.cs

View workflow job for this annotation

GitHub Actions / CodeQL-Build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 142 in uml4net.xmireader/Cache/Assembler.cs

View workflow job for this annotation

GitHub Actions / CodeQL-Build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
return element.GetType().GetProperties()
.FirstOrDefault(x => Attribute.IsDefined(x, typeof(PropertyAttribute))
Expand All @@ -170,13 +158,13 @@ private IXmiElement GetReferencedElement(IDictionary<string, IXmiElement> cache,
/// <exception cref="InvalidOperationException">
/// Thrown if a reference is not found in the cache or if the type of a referenced element does not match the expected type.
/// </exception>
private List<IXmiElement> ResolveMultiValueReferences(IDictionary<string, IXmiElement> cache, IEnumerable<string> propertyValues, string key, Type expectedType)
private List<IXmiElement> ResolveMultiValueReferences(IEnumerable<string> propertyValues, string key, Type expectedType)
{
var resolvedReferences = new List<IXmiElement>();

foreach (var propertyValue in propertyValues)
{
if (!cache.TryGetValue(propertyValue, out var referencedElement) || !expectedType.IsAssignableFrom(referencedElement.GetType()))
if (!this.TryGetReferencedElement(propertyValue, out var referencedElement) || !expectedType.IsAssignableFrom(referencedElement.GetType()))
{
this.logger.LogWarning("The reference with the id [{key}] to [{propertyValue}] was not found in the cache, probably because its type is not supported.", key, propertyValue);
continue;
Expand Down
Loading

0 comments on commit b450631

Please sign in to comment.