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 0f0077d commit 2c1b083
Show file tree
Hide file tree
Showing 37 changed files with 1,655 additions and 569 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// -------------------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------------------
// <copyright file="CorePocoGeneratorTestFixture.cs" company="Starion Group S.A.">
//
// Copyright 2019-2024 Starion Group S.A.
Expand All @@ -20,13 +20,8 @@

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

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");

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

View workflow job for this annotation

GitHub Actions / Build

'System.NullReferenceException' should not be thrown by user code. (https://rules.sonarsource.com/csharp/RSPEC-112)
}

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.

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

View workflow job for this annotation

GitHub Actions / 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.

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()))

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

View workflow job for this annotation

GitHub Actions / Build

Use the 'IsInstanceOfType()' method instead. (https://rules.sonarsource.com/csharp/RSPEC-2219)
{
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 2c1b083

Please sign in to comment.