diff --git a/uml4net.CodeGenerator.Tests/Generators/CorePocoGeneratorTestFixture.cs b/uml4net.CodeGenerator.Tests/Generators/CorePocoGeneratorTestFixture.cs index dae2549e..30f5c58b 100644 --- a/uml4net.CodeGenerator.Tests/Generators/CorePocoGeneratorTestFixture.cs +++ b/uml4net.CodeGenerator.Tests/Generators/CorePocoGeneratorTestFixture.cs @@ -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 { diff --git a/uml4net.HandleBars/GeneralizationHelper.cs b/uml4net.HandleBars/GeneralizationHelper.cs index dc6e871e..77063b90 100644 --- a/uml4net.HandleBars/GeneralizationHelper.cs +++ b/uml4net.HandleBars/GeneralizationHelper.cs @@ -24,8 +24,6 @@ namespace uml4net.HandleBars using System.Linq; using HandlebarsDotNet; - - using uml4net.POCO; using uml4net.POCO.StructuredClassifiers; /// diff --git a/uml4net.Tests/AssemblerTestFixture.cs b/uml4net.xmi.Tests/AssemblerTestFixture.cs similarity index 92% rename from uml4net.Tests/AssemblerTestFixture.cs rename to uml4net.xmi.Tests/AssemblerTestFixture.cs index 0371b192..a66bf679 100644 --- a/uml4net.Tests/AssemblerTestFixture.cs +++ b/uml4net.xmi.Tests/AssemblerTestFixture.cs @@ -18,33 +18,38 @@ // // ------------------------------------------------------------------------------------------------ -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 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()); + + this.assembler = new Assembler(loggerFactory.CreateLogger(), this.cache); } [TearDown] public void Teardown() { - this.cache.Clear(); + this.cache.GlobalCache.Clear(); } [Test] @@ -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(() => @@ -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(() => @@ -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(() => @@ -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)); @@ -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); } @@ -239,7 +243,7 @@ public void Synchronize_ShouldThrow_WhenElementNotIReferenceable() Assert.Multiple(() => { - Assert.Throws(() => this.assembler.Synchronize(this.cache)); + Assert.Throws(() => this.assembler.Synchronize()); Assert.That(classElement.OwnedComment, Is.Empty); Assert.That(classElement.NameExpression, Is.Null); }); diff --git a/uml4net.xmi.Tests/SysML2.XmiReaderTestFixture.cs b/uml4net.xmi.Tests/SysML2.XmiReaderTestFixture.cs index 96832058..3d7a9d11 100644 --- a/uml4net.xmi.Tests/SysML2.XmiReaderTestFixture.cs +++ b/uml4net.xmi.Tests/SysML2.XmiReaderTestFixture.cs @@ -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 { @@ -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)); diff --git a/uml4net.xmi.Tests/UMLXmiReaderTestFixture.cs b/uml4net.xmi.Tests/UMLXmiReaderTestFixture.cs index 8d9ab835..a48e2ad4 100644 --- a/uml4net.xmi.Tests/UMLXmiReaderTestFixture.cs +++ b/uml4net.xmi.Tests/UMLXmiReaderTestFixture.cs @@ -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 { @@ -44,11 +45,13 @@ 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)); @@ -56,14 +59,17 @@ public void Verify_that_UML_PrimitiveTypes__XMI_can_be_read() 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)); diff --git a/uml4net/Assembler.cs b/uml4net.xmireader/Cache/Assembler.cs similarity index 67% rename from uml4net/Assembler.cs rename to uml4net.xmireader/Cache/Assembler.cs index 5d13dc42..e93cac9b 100644 --- a/uml4net/Assembler.cs +++ b/uml4net.xmireader/Cache/Assembler.cs @@ -18,55 +18,56 @@ // // ------------------------------------------------------------------------------------------------ -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; /// /// The purpose of the Assembler is to resolve all the reference properties of the objects /// after deserialization to construct a complete object graph /// - public class Assembler + public class Assembler : IAssembler { /// - /// The (injected) used to setup logging + /// The used to log /// - private readonly ILoggerFactory loggerFactory; + private readonly ILogger logger; /// - /// The used to log + /// The /// - private readonly ILogger logger; + private readonly IXmiReaderCache cache; /// - /// Gets + /// Initializes a new /// - /// - public Assembler(ILoggerFactory loggerFactory) + /// The + /// The + public Assembler(ILogger logger, IXmiReaderCache cache) { - this.loggerFactory = loggerFactory; - this.logger = this.loggerFactory == null ? NullLogger.Instance : this.loggerFactory.CreateLogger(); + this.logger = logger; + this.cache = cache; } /// - /// Synchronizes the specified cache by assigning properties to elements. + /// Synchronizes the by assigning properties to elements. /// - /// - /// A dictionary containing the elements where the keys are their identifiers and the values are the corresponding instances. - /// - public void Synchronize(Dictionary 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); + } } } @@ -74,15 +75,13 @@ public void Synchronize(Dictionary cache) /// Resolves single and multi-value references for the given element using the provided cache. /// /// The element whose references are to be resolved. - /// The cache containing the referenced elements. - public void ResolveReferences(IXmiElement element, IDictionary 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; } @@ -106,7 +105,7 @@ public void ResolveReferences(IXmiElement element, IDictionary - /// 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. /// - /// The cache containing the referenced elements. - /// The key to the reference in the cache. - /// The name of the property referring to the element. - /// The resolved IXmiElement from the cache. - private IXmiElement GetReferencedElement(IDictionary cache, string reference, string key) + /// The key representing the reference ID. + /// When this method returns, contains the referenced element if found; otherwise, null. + /// true if the referenced element was successfully retrieved; otherwise, false. + 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); } /// @@ -151,7 +139,7 @@ private IXmiElement GetReferencedElement(IDictionary cache, /// The name of the property to find. /// The expected type of the property. If null, type checking is skipped. /// The of the found property, or null if no matching property is found. - private PropertyInfo? FindPropertyWithAttribute(IXmiElement element, string propertyName, Type? expectedType = null) + private PropertyInfo FindPropertyWithAttribute(IXmiElement element, string propertyName, Type? expectedType = null) { return element.GetType().GetProperties() .FirstOrDefault(x => Attribute.IsDefined(x, typeof(PropertyAttribute)) @@ -170,13 +158,13 @@ private IXmiElement GetReferencedElement(IDictionary cache, /// /// Thrown if a reference is not found in the cache or if the type of a referenced element does not match the expected type. /// - private List ResolveMultiValueReferences(IDictionary cache, IEnumerable propertyValues, string key, Type expectedType) + private List ResolveMultiValueReferences(IEnumerable propertyValues, string key, Type expectedType) { var resolvedReferences = new List(); 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; diff --git a/uml4net.xmireader/Cache/ExternalReferenceResolver.cs b/uml4net.xmireader/Cache/ExternalReferenceResolver.cs new file mode 100644 index 00000000..26be7bcf --- /dev/null +++ b/uml4net.xmireader/Cache/ExternalReferenceResolver.cs @@ -0,0 +1,164 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2019-2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, softwareUseCases +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace uml4net.xmi.Cache +{ + using Microsoft.Extensions.Logging; + using Settings; + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net.Http; + using System.Threading.Tasks; + + /// + /// Resolves external references for XMI elements using provided settings and cache. + /// + /// The cache containing XMI reader information. + /// The HTTP client used for making requests to external resources. + /// The settings for the XMI reader configuration. + /// The logger for logging information and errors. + public class ExternalReferenceResolver(IXmiReaderCache cache, HttpClient httpClient, IXmiReaderSettings settings, ILogger logger) + : IExternalReferenceResolver + { + /// + /// Asynchronously attempts to resolve external references and yields their context and stream. + /// + /// An asynchronous enumerable of tuples containing the context and stream of resolved references. + public async IAsyncEnumerable<(string Context, Stream Stream)> TryResolve() + { + foreach (var identifier in cache.Cache.Values + .SelectMany(cacheEntry => cacheEntry.SingleValueReferencePropertyIdentifiers.Values)) + { + if(await this.TryResolve(identifier) is { Stream: { Length: >0 } } reference) + { + yield return reference; + } + } + + foreach (var identifiers in cache.Cache.Values + .SelectMany(cacheEntry => cacheEntry.MultiValueReferencePropertyIdentifiers.Values)) + { + foreach (var identifier in identifiers) + { + if (await this.TryResolve(identifier) is { Stream: { Length: > 0 } } reference) + { + yield return reference; + } + } + } + } + + /// + /// Asynchronously attempts to resolve an external reference identified by the specified key. + /// + /// The key representing the external reference to resolve. + /// A task that represents the asynchronous operation, containing a tuple with the context and stream if resolved; otherwise, a default value. + private async Task<(string Context, Stream Stream)> TryResolve(string key) + { + if (string.IsNullOrEmpty(key) || key.StartsWith('#') || !key.Contains('#') || + !cache.TryResolveContext(key, out var resource)) + { + logger.LogInformation("Invalid external resource key [{key}]", key); + return default; + } + + if (cache.DoesContextExists(resource.Context, resource.ResourceId)) + { + logger.LogInformation("The resource {resource} was already parsed", resource.Context); + return default; + } + + try + { + var stream = default(Stream); + + if (Uri.TryCreate(key, UriKind.Absolute, out var uri)) + { + if (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps) + { + stream = await this.FetchRemoteXmi(uri); + } + if (uri.Scheme == Uri.UriSchemeFile) + { + stream = File.OpenRead(uri.LocalPath); + } + } + else if (key.StartsWith("pathmap://")) + { + stream = this.ResolvePathmapResource(key); + } + else if (File.Exists(resource.Context)) + { + stream = File.OpenRead(resource.Context); + } + else + { + logger.LogError("Unsupported external reference specified by {context}", resource.Context); + return default; + } + + return (resource.Context, stream); + } + catch (Exception exception) + { + logger.LogError(exception,"Error resolving key '{key}': {message}", key, exception.Message); + } + + return default; + } + + /// + /// Asynchronously fetches an XMI file from a remote URI. + /// + /// The URI of the remote XMI file to fetch. + /// A task that represents the asynchronous operation, containing a stream of the fetched XMI file if successful; otherwise, null. + private async Task FetchRemoteXmi(Uri uri) + { + try + { + var response = await httpClient.GetAsync(uri); + + if (response.IsSuccessStatusCode) + { + return await response.Content.ReadAsStreamAsync(); + } + } + catch (Exception exception) + { + logger.LogError(exception, "Error fetching remote XMI: {message}", exception.Message); + } + + return default; + } + + /// + /// Resolves the file path of a resource identified by the specified key and returns a stream for it. + /// + /// The key representing the resource path to resolve. + /// A stream of the resource if the file exists; otherwise, null. + private Stream ResolvePathmapResource(string key) + { + var mappedFilePath = key.Replace("pathmap://UML_PROFILES/", settings.UmlProfilesDirectoryPath); + return File.Exists(mappedFilePath) ? File.OpenRead(mappedFilePath) : null; + } + } +} diff --git a/uml4net.xmireader/Cache/IAssembler.cs b/uml4net.xmireader/Cache/IAssembler.cs new file mode 100644 index 00000000..760ec2dc --- /dev/null +++ b/uml4net.xmireader/Cache/IAssembler.cs @@ -0,0 +1,36 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2019-2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, softwareUseCases +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace uml4net.xmi.Cache +{ + using System.Collections.Generic; + using uml4net.POCO; + + /// + /// The is the interface definition for the + /// + public interface IAssembler + { + /// + /// Synchronizes the by assigning properties to elements. + /// + void Synchronize(); + } +} diff --git a/uml4net.xmireader/Cache/IExternalReferenceResolver.cs b/uml4net.xmireader/Cache/IExternalReferenceResolver.cs new file mode 100644 index 00000000..7d43fbba --- /dev/null +++ b/uml4net.xmireader/Cache/IExternalReferenceResolver.cs @@ -0,0 +1,37 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2019-2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, softwareUseCases +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace uml4net.xmi.Cache +{ + using System.Collections.Generic; + using System.IO; + + /// + /// Defines a contract for resolving external references associated with XMI elements. + /// + public interface IExternalReferenceResolver + { + /// + /// Asynchronously attempts to resolve external references and yields their context and stream. + /// + /// An asynchronous enumerable of tuples containing the context and stream of resolved references. + IAsyncEnumerable<(string Context, Stream Stream)> TryResolve(); + } +} diff --git a/uml4net.xmireader/Cache/IXmiReaderCache.cs b/uml4net.xmireader/Cache/IXmiReaderCache.cs new file mode 100644 index 00000000..32b9ebb5 --- /dev/null +++ b/uml4net.xmireader/Cache/IXmiReaderCache.cs @@ -0,0 +1,103 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2019-2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, softwareUseCases +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace uml4net.xmi.Cache +{ + using POCO; + + using System.Collections.Concurrent; + using System.Collections.Generic; + + /// + /// Represents a cache for storing and retrieving XMI elements during the reading process. + /// + public interface IXmiReaderCache + { + /// + /// Provides a cache for storing XMI elements with unique IDs, organized by context. + /// Each is stored in a nested dictionary keyed by its context + /// (representing the XMI file) and the element’s unique identifier within that context. + /// + ConcurrentDictionary> GlobalCache { get; } + + /// + /// Gets the collection of XMI elements associated with the current context. This + /// dictionary maps unique identifiers to their corresponding instances + /// within the active context. + /// + Dictionary Cache { get; } + + /// + /// Switches the current context to a new XMI file or section, allowing elements to be stored + /// under a distinct key in the cache. Initializes an empty dictionary in + /// for the specified context if it does not exist. + /// + /// The unique identifier for the new context, typically the XMI file name. + void SwitchContext(string context); + + /// + /// Adds the specified XMI element to the cache under the current context. + /// If the context does not already exist, the default one is used. It can be changed via . + /// + /// The unique identifier of the XMI element within the current context. + /// The XMI element to be added to the cache. + void Add(string id, IXmiElement element); + + /// + /// Checks whether the specified context exists in the cache and optionally verifies the existence of a specific key within that context. + /// + /// + /// The context name to check within the cache. + /// + /// + /// (Optional) The specific key to check within the specified context. If null, the method only verifies if the context exists + /// and contains any entries. + /// + /// + /// true if the context is missing, contains no entries, or if a specified key exists in the context with a non-null value; + /// otherwise, false. + /// + bool DoesContextExists(string context, string key = null); + + /// + /// Attempts to retrieve an instance from the cache + /// based on the specified and . + /// Returns true if both the context and key exist in the cache, otherwise false. + /// + /// The context in which the element is stored, typically representing an XMI file. + /// The unique identifier of the XMI element within the specified context. + /// + /// When this method returns, contains the associated with the specified + /// context and key, if found; otherwise, default. + /// + /// + /// true if the element is found in the cache with the specified context and key; otherwise, false. + /// + bool TryGetValue(string context, string key, out IXmiElement value); + + /// + /// Attempts to resolve the context and resource ID from the specified resource key. + /// + /// The key representing the resource, which may contain context and resource ID separated by '#'. + /// When this method returns, contains a tuple with the context and resource ID if resolved successfully; otherwise, (null, null). + /// true if the context and resource ID were successfully resolved; otherwise, false. + bool TryResolveContext(string resourceKey, out (string Context, string ResourceId) resolvedContextAndResource); + } +} diff --git a/uml4net.xmireader/Cache/XmiReaderCache.cs b/uml4net.xmireader/Cache/XmiReaderCache.cs new file mode 100644 index 00000000..69b84f01 --- /dev/null +++ b/uml4net.xmireader/Cache/XmiReaderCache.cs @@ -0,0 +1,166 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2019-2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, softwareUseCases +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace uml4net.xmi.Cache +{ + using Microsoft.Extensions.Logging; + using System.Collections.Generic; + + using POCO; + using System.Collections.Concurrent; + using System; + + /// + /// A cache specifically designed for XMI elements, organized by context, to facilitate + /// efficient lookups and storage during the reading of XMI files. This class provides methods + /// to switch contexts, manage external references, and store elements for each context. + /// + /// + /// Each context typically corresponds to an individual XMI file, enabling the cache to + /// organize elements on a per-file basis. Resolved external references are also tracked + /// to prevent repeated processing of the same references. + /// + internal class XmiReaderCache(ILogger logger) : IXmiReaderCache + { + private const string DefaultKey = "_"; + + /// + /// Provides a cache for storing XMI elements with unique IDs, organized by context. + /// Each is stored in a nested dictionary keyed by its context + /// (representing the XMI file) and the element’s unique identifier within that context. + /// + public ConcurrentDictionary> GlobalCache { get; } = []; + + /// + /// Gets the collection of XMI elements associated with the current context. This + /// dictionary maps unique identifiers to their corresponding instances + /// within the active context. + /// + public Dictionary Cache => this.GlobalCache.GetOrAdd(this.currentContext, []); + + /// + /// Holds the current context, typically representing the current XMI file being processed. + /// This context helps to group elements by their file of origin in the cache. + /// + private string currentContext = DefaultKey; + + /// + /// Checks whether the specified context exists in the cache and optionally verifies the existence of a specific key within that context. + /// + /// + /// The context name to check within the cache. + /// + /// + /// (Optional) The specific key to check within the specified context. If null, the method only verifies if the context exists + /// and contains any entries. + /// + /// + /// true if the context is missing, contains no entries, or if a specified key exists in the context with a non-null value; + /// otherwise, false. + /// + public bool DoesContextExists(string context, string key = null) + { + if (!this.GlobalCache.TryGetValue(context, out var contextDictionary) || contextDictionary.Count == 0) + { + return false; + } + + return key switch + { + null => true, + _ => contextDictionary.TryGetValue(key, out var value) && value != null + }; + } + + /// + /// Attempts to retrieve an instance from the cache + /// based on the specified and . + /// Returns true if both the context and key exist in the cache, otherwise false. + /// + /// The context in which the element is stored, typically representing an XMI file. + /// The unique identifier of the XMI element within the specified context. + /// + /// When this method returns, contains the associated with the specified + /// context and key, if found; otherwise, default. + /// + /// + /// true if the element is found in the cache with the specified context and key; otherwise, false. + /// + public bool TryGetValue(string context, string key, out IXmiElement value) + { + if (this.GlobalCache.TryGetValue(context, out var contextDictionary) + && contextDictionary.TryGetValue(key, out value)) + { + return true; + } + + value = default; + return false; + } + + /// + /// Switches the current context to a new XMI file or section, allowing elements to be stored + /// under a distinct key in the cache. Initializes an empty dictionary in + /// for the specified context if it does not exist. + /// + /// The unique identifier for the new context, typically the XMI file name. + public void SwitchContext(string context) + { + this.currentContext = context; + this.GlobalCache.GetOrAdd(context, []); + } + + /// + /// Adds the specified XMI element to the cache under the current context. + /// If the context does not already exist, it must be set via . + /// + /// The unique identifier of the XMI element within the current context. + /// The XMI element to be added to the cache. + public void Add(string id, IXmiElement element) + { + var result = this.Cache.TryAdd(id, element); + + if (!result) + { + logger.LogError("Failed to add element type [{element}] with id [{id}]", element.GetType().Name, id); + } + } + + /// + /// Attempts to resolve the context and resource ID from the specified resource key. + /// + /// The key representing the resource, which may contain context and resource ID separated by '#'. + /// When this method returns, contains a tuple with the context and resource ID if resolved successfully; otherwise, (null, null). + /// true if the context and resource ID were successfully resolved; otherwise, false. + public bool TryResolveContext(string resourceKey, out (string Context, string ResourceId) resolvedContextAndResource) + { + var referenceString = resourceKey.Split('#', StringSplitOptions.RemoveEmptyEntries); + + if (referenceString.Length == 2) + { + resolvedContextAndResource = (referenceString[0], referenceString[1]); + return true; + } + + resolvedContextAndResource = (null, null); + return false; + } + } +} diff --git a/uml4net.xmireader/IXmiReaderScope.cs b/uml4net.xmireader/IXmiReaderScope.cs new file mode 100644 index 00000000..273ad937 --- /dev/null +++ b/uml4net.xmireader/IXmiReaderScope.cs @@ -0,0 +1,29 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2019-2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, softwareUseCases +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace uml4net.xmi +{ + using System; + + /// + /// Represents a scope for managing the lifecycle of services used during the XMI reading process. + /// + public interface IXmiReaderScope : IDisposable; +} diff --git a/uml4net.xmireader/Classification/GeneralizationReader.cs b/uml4net.xmireader/Readers/Classification/GeneralizationReader.cs similarity index 76% rename from uml4net.xmireader/Classification/GeneralizationReader.cs rename to uml4net.xmireader/Readers/Classification/GeneralizationReader.cs index f3243de9..9b407fa5 100644 --- a/uml4net.xmireader/Classification/GeneralizationReader.cs +++ b/uml4net.xmireader/Readers/Classification/GeneralizationReader.cs @@ -18,41 +18,34 @@ // // ------------------------------------------------------------------------------------------------ -namespace uml4net.xmi.Classification +namespace uml4net.xmi.Readers.Classification { - using System.Collections.Generic; using System.Xml; using Microsoft.Extensions.Logging; - using Microsoft.Extensions.Logging.Abstractions; - - using uml4net.POCO; + using POCO; using uml4net.POCO.Classification; + using Cache; + using Readers; /// /// The purpose of the is to read an instance of /// from the XMI document /// - public class GeneralizationReader : XmiElementReader + public class GeneralizationReader : XmiElementReader, IXmiElementReader { - /// - /// The used to log - /// - private readonly ILogger logger; - /// /// Initializes a new instance of the class. /// /// /// The cache in which each > is stored /// - /// - /// The (injected) used to setup logging + /// + /// The /// - public GeneralizationReader(Dictionary cache, ILoggerFactory loggerFactory = null) - : base(cache, loggerFactory) + public GeneralizationReader(IXmiReaderCache cache, ILogger logger) + : base(cache, logger) { - this.logger = this.loggerFactory == null ? NullLogger.Instance : this.loggerFactory.CreateLogger(); } /// @@ -64,7 +57,7 @@ public GeneralizationReader(Dictionary cache, ILoggerFactor /// /// an instance of /// - public IGeneralization Read(XmlReader xmlReader) + public override IGeneralization Read(XmlReader xmlReader) { IGeneralization generalization = new Generalization(); @@ -81,7 +74,7 @@ public IGeneralization Read(XmlReader xmlReader) generalization.XmiId = xmlReader.GetAttribute("xmi:id"); - this.cache.Add(generalization.XmiId, generalization); + this.Cache.Add(generalization.XmiId, generalization); var isSubstitutable = xmlReader.GetAttribute("isSubstitutable"); if (!string.IsNullOrEmpty(isSubstitutable)) @@ -99,4 +92,4 @@ public IGeneralization Read(XmlReader xmlReader) return generalization; } } -} \ No newline at end of file +} diff --git a/uml4net.xmireader/Classification/PropertyReader.cs b/uml4net.xmireader/Readers/Classification/PropertyReader.cs similarity index 82% rename from uml4net.xmireader/Classification/PropertyReader.cs rename to uml4net.xmireader/Readers/Classification/PropertyReader.cs index 60fadff1..f455facf 100644 --- a/uml4net.xmireader/Classification/PropertyReader.cs +++ b/uml4net.xmireader/Readers/Classification/PropertyReader.cs @@ -18,32 +18,36 @@ // // ------------------------------------------------------------------------------------------------ -namespace uml4net.xmi.Classification +namespace uml4net.xmi.Readers.Classification { + using Cache; using System; using System.Collections.Generic; using System.Xml; using Microsoft.Extensions.Logging; - using Microsoft.Extensions.Logging.Abstractions; - - using uml4net.POCO; + using POCO.Values; + using POCO; using uml4net.POCO.Classification; using uml4net.POCO.CommonStructure; - - using uml4net.xmi.CommonStructure; - using uml4net.xmi.Values; + + using Readers; /// /// The purpose of the is to read an instance of /// from the XMI document /// - public class PropertyReader : XmiElementReader + public class PropertyReader : XmiCommentedElementReader, IXmiElementReader { /// - /// The used to log + /// The of + /// + private readonly IXmiElementReader literalIntegerReader; + + /// + /// The of /// - private readonly ILogger logger; + private readonly IXmiElementReader literalUnlimitedNaturalReader; /// /// Initializes a new instance of the class. @@ -51,13 +55,18 @@ public class PropertyReader : XmiElementReader /// /// The cache in which each > is stored /// - /// - /// The (injected) used to setup logging + /// + /// The (injected) used to setup logging /// - public PropertyReader(Dictionary cache, ILoggerFactory loggerFactory = null) - : base(cache, loggerFactory) + /// The of + /// The of + /// The of + public PropertyReader(IXmiReaderCache cache, ILogger logger, IXmiElementReader commentReader, + IXmiElementReader literalIntegerReader, IXmiElementReader literalUnlimitedNaturalReader) + : base(cache, logger, commentReader) { - this.logger = this.loggerFactory == null ? NullLogger.Instance : this.loggerFactory.CreateLogger(); + this.literalIntegerReader = literalIntegerReader; + this.literalUnlimitedNaturalReader = literalUnlimitedNaturalReader; } /// @@ -69,7 +78,7 @@ public PropertyReader(Dictionary cache, ILoggerFactory logg /// /// an instance of /// - public IProperty Read(XmlReader xmlReader) + public override IProperty Read(XmlReader xmlReader) { IProperty property = new Property(); @@ -86,7 +95,7 @@ public IProperty Read(XmlReader xmlReader) property.XmiId = xmlReader.GetAttribute("xmi:id"); - this.cache.Add(property.XmiId, property); + this.Cache.Add(property.XmiId, property); property.Name = xmlReader.GetAttribute("name"); @@ -141,7 +150,7 @@ public IProperty Read(XmlReader xmlReader) var aggregation = xmlReader.GetAttribute("aggregation"); if (!string.IsNullOrEmpty(aggregation)) { - property.Aggregation = (AggregationKind)Enum.Parse(typeof(AggregationKind), aggregation,true); + property.Aggregation = (AggregationKind)Enum.Parse(typeof(AggregationKind), aggregation, true); } var visibility = xmlReader.GetAttribute("visibility"); @@ -165,31 +174,28 @@ public IProperty Read(XmlReader xmlReader) case "ownedComment": using (var ownedCommentXmlReader = xmlReader.ReadSubtree()) { - var commentReader = new CommentReader(this.cache, this.loggerFactory); - var comment = commentReader.Read(ownedCommentXmlReader); + var comment = this.CommentReader.Read(ownedCommentXmlReader); property.OwnedComment.Add(comment); } break; case "lowerValue": using (var lowerValueXmlReader = xmlReader.ReadSubtree()) { - var literalIntegerReader = new LiteralIntegerReader(this.cache, this.loggerFactory); - var literalInteger = literalIntegerReader.Read(lowerValueXmlReader); + var literalInteger = this.literalIntegerReader.Read(lowerValueXmlReader); property.LowerValue = literalInteger; } break; case "upperValue": using (var upperValueXmlReader = xmlReader.ReadSubtree()) { - var literalUnlimitedNaturalReader = new LiteralUnlimitedNaturalReader(this.cache, this.loggerFactory); - var literalUnlimitedNatural = literalUnlimitedNaturalReader.Read(upperValueXmlReader); + var literalUnlimitedNatural = this.literalUnlimitedNaturalReader.Read(upperValueXmlReader); property.UpperValue = literalUnlimitedNatural; } break; case "nameExpression": using (var nameExpressionXmlReader = xmlReader.ReadSubtree()) { - this.logger.LogDebug("property:nameExpression not yet implemented"); + this.Logger.LogDebug("property:nameExpression not yet implemented"); } break; case "subsettedProperty": @@ -236,13 +242,13 @@ public IProperty Read(XmlReader xmlReader) case "defaultValue": using (var defaultValueXmlReader = xmlReader.ReadSubtree()) { - this.logger.LogDebug("property:defaultValueXmlReader not yet implemented"); + this.Logger.LogDebug("property:defaultValueXmlReader not yet implemented"); } break; case "redefinedProperty": using (var redefinedPropertyXmlReader = xmlReader.ReadSubtree()) { - this.logger.LogDebug("property:redefinedProperty not yet implemented"); + this.Logger.LogDebug("property:redefinedProperty not yet implemented"); } break; default: @@ -255,4 +261,4 @@ public IProperty Read(XmlReader xmlReader) return property; } } -} \ No newline at end of file +} diff --git a/uml4net.xmireader/CommonStructure/CommentReader.cs b/uml4net.xmireader/Readers/CommonStructure/CommentReader.cs similarity index 74% rename from uml4net.xmireader/CommonStructure/CommentReader.cs rename to uml4net.xmireader/Readers/CommonStructure/CommentReader.cs index 06fa39f7..518358f3 100644 --- a/uml4net.xmireader/CommonStructure/CommentReader.cs +++ b/uml4net.xmireader/Readers/CommonStructure/CommentReader.cs @@ -18,41 +18,35 @@ // // ------------------------------------------------------------------------------------------------ -namespace uml4net.xmi.CommonStructure +namespace uml4net.xmi.Readers.CommonStructure { - using System.Collections.Generic; using System.Xml; using Microsoft.Extensions.Logging; - using Microsoft.Extensions.Logging.Abstractions; - - using uml4net.POCO; + using POCO; + using uml4net.POCO.Classification; using uml4net.POCO.CommonStructure; + using Cache; + using Readers; /// /// The purpose of the is to read an instance of /// from the XMI document /// - public class CommentReader : XmiElementReader + public class CommentReader : XmiElementReader, IXmiElementReader { - /// - /// The used to log - /// - private readonly ILogger logger; - /// /// Initializes a new instance of the class. /// /// /// The cache in which each > is stored /// - /// - /// The (injected) used to setup logging + /// + /// The (injected) used to setup logging /// - public CommentReader(Dictionary cache, ILoggerFactory loggerFactory = null) - : base(cache, loggerFactory) + public CommentReader(IXmiReaderCache cache, ILogger logger) + : base(cache, logger) { - this.logger = this.loggerFactory == null ? NullLogger.Instance : this.loggerFactory.CreateLogger(); } /// @@ -64,7 +58,7 @@ public CommentReader(Dictionary cache, ILoggerFactory logge /// /// an instance of /// - public IComment Read(XmlReader xmlReader) + public override IComment Read(XmlReader xmlReader) { IComment comment = new Comment(); @@ -81,7 +75,7 @@ public IComment Read(XmlReader xmlReader) comment.XmiId = xmlReader.GetAttribute("xmi:id"); - this.cache.Add(comment.XmiId, comment); + this.Cache.Add(comment.XmiId, comment); comment.Body = xmlReader.GetAttribute("body"); } diff --git a/uml4net.xmireader/CommonStructure/ConstraintReader.cs b/uml4net.xmireader/Readers/CommonStructure/ConstraintReader.cs similarity index 74% rename from uml4net.xmireader/CommonStructure/ConstraintReader.cs rename to uml4net.xmireader/Readers/CommonStructure/ConstraintReader.cs index ba82cf2b..fabc3059 100644 --- a/uml4net.xmireader/CommonStructure/ConstraintReader.cs +++ b/uml4net.xmireader/Readers/CommonStructure/ConstraintReader.cs @@ -18,46 +18,45 @@ // // ------------------------------------------------------------------------------------------------ -namespace uml4net.xmi.CommonStructure +namespace uml4net.xmi.Readers.CommonStructure { + using Cache; using System; - using System.Collections.Generic; using System.Xml; using Microsoft.Extensions.Logging; - using Microsoft.Extensions.Logging.Abstractions; - - using uml4net.POCO; + using POCO.Values; + using POCO; using uml4net.POCO.CommonStructure; - using uml4net.xmi.Values; + using Readers; /// /// The purpose of the is to read an instance of /// from the XMI document /// - public class ConstraintReader : XmiElementReader + public class ConstraintReader : XmiCommentedElementReader, IXmiElementReader { /// - /// The used to log + /// The of /// - private readonly ILogger logger; + private readonly IXmiElementReader opaqueExpressionReader; /// /// Initializes a new instance of the class. /// /// - /// The cache in which each > is stored + /// The cache in which each > are stored /// - /// - /// The (injected) used to setup logging + /// + /// The /// - public ConstraintReader(Dictionary cache, ILoggerFactory loggerFactory = null) - : base(cache, loggerFactory) + /// The of + /// The of + public ConstraintReader(IXmiReaderCache cache, ILogger logger, IXmiElementReader commentReader, IXmiElementReader opaqueExpressionReader) + : base(cache, logger, commentReader) { - this.logger = this.loggerFactory == null - ? NullLogger.Instance - : this.loggerFactory.CreateLogger(); + this.opaqueExpressionReader = opaqueExpressionReader; } /// @@ -69,7 +68,7 @@ public ConstraintReader(Dictionary cache, ILoggerFactory lo /// /// an instance of /// - public IConstraint Read(XmlReader xmlReader) + public override IConstraint Read(XmlReader xmlReader) { IConstraint constraint = new Constraint(); @@ -86,7 +85,7 @@ public IConstraint Read(XmlReader xmlReader) constraint.XmiId = xmlReader.GetAttribute("xmi:id"); - this.cache.Add(constraint.XmiId, constraint); + this.Cache.Add(constraint.XmiId, constraint); while (xmlReader.Read()) { @@ -95,13 +94,12 @@ public IConstraint Read(XmlReader xmlReader) switch (xmlReader.LocalName) { case "constrainedElement": - this.logger.LogInformation("ConstraintReader.ownedRule not yet implemented"); + this.Logger.LogInformation("ConstraintReader.ownedRule not yet implemented"); break; case "ownedComment": using (var ownedCommentXmlReader = xmlReader.ReadSubtree()) { - var commentReader = new CommentReader(this.cache, this.loggerFactory); - var comment = commentReader.Read(ownedCommentXmlReader); + var comment = this.CommentReader.Read(ownedCommentXmlReader); constraint.OwnedComment.Add(comment); } break; @@ -136,8 +134,7 @@ private void ReadValueSpecification(IConstraint constraint, XmlReader xmlReader) case "uml:OpaqueExpression": using (var opaqueExpressionXmlReader = xmlReader.ReadSubtree()) { - var opaqueExpressionReader = new OpaqueExpressionReader(this.cache, this.loggerFactory); - var opaqueExpression = opaqueExpressionReader.Read(opaqueExpressionXmlReader); + var opaqueExpression = this.opaqueExpressionReader.Read(opaqueExpressionXmlReader); constraint.Specification = opaqueExpression; } break; diff --git a/uml4net.xmireader/CommonStructure/PackageImportReader.cs b/uml4net.xmireader/Readers/CommonStructure/PackageImportReader.cs similarity index 75% rename from uml4net.xmireader/CommonStructure/PackageImportReader.cs rename to uml4net.xmireader/Readers/CommonStructure/PackageImportReader.cs index 1ada1b58..fae9d94a 100644 --- a/uml4net.xmireader/CommonStructure/PackageImportReader.cs +++ b/uml4net.xmireader/Readers/CommonStructure/PackageImportReader.cs @@ -18,42 +18,35 @@ // // ------------------------------------------------------------------------------------------------ -namespace uml4net.xmi.CommonStructure +namespace uml4net.xmi.Readers.CommonStructure { + using Cache; using System; - using System.Collections.Generic; using System.Xml; using Microsoft.Extensions.Logging; - using Microsoft.Extensions.Logging.Abstractions; - - using uml4net.POCO; + using POCO; using uml4net.POCO.CommonStructure; + using Readers; /// /// The purpose of the is to read an instance of /// from the XMI document /// - public class PackageImportReader : XmiElementReader + public class PackageImportReader : XmiElementReader, IXmiElementReader { - /// - /// The used to log - /// - private readonly ILogger logger; - /// /// Initializes a new instance of the class. /// /// /// The cache in which each > is stored /// - /// - /// The (injected) used to setup logging + /// + /// The /// - public PackageImportReader(Dictionary cache, ILoggerFactory loggerFactory = null) - : base(cache, loggerFactory) + public PackageImportReader(IXmiReaderCache cache, ILogger logger) + : base(cache, logger) { - this.logger = this.loggerFactory == null ? NullLogger.Instance : this.loggerFactory.CreateLogger(); } /// @@ -65,8 +58,8 @@ public PackageImportReader(Dictionary cache, ILoggerFactory /// /// an instance of /// - public IPackageImport Read(XmlReader xmlReader) - { + public override IPackageImport Read(XmlReader xmlReader) + { var packageImport = new PackageImport(); if (xmlReader.MoveToContent() == XmlNodeType.Element) @@ -80,7 +73,7 @@ public IPackageImport Read(XmlReader xmlReader) } // TODO: figure out an algorithm to interpret how to set the "importingNamespace" property - this.logger.LogInformation("TODO: figure out an algorithm to interpret how to set the \"importingNamespace\" property"); + this.Logger.LogInformation("TODO: figure out an algorithm to interpret how to set the \"importingNamespace\" property"); packageImport.ImportingNamespace = null; string visibility = xmlReader.GetAttribute("visibility"); diff --git a/uml4net.xmireader/Readers/IXmiElementReader.cs b/uml4net.xmireader/Readers/IXmiElementReader.cs new file mode 100644 index 00000000..864dd3a2 --- /dev/null +++ b/uml4net.xmireader/Readers/IXmiElementReader.cs @@ -0,0 +1,44 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2019-2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, softwareUseCases +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace uml4net.xmi.Readers +{ + using POCO; + using System.Xml; + + /// + /// Defines a contract for reading objects + /// from their XML representation. + /// + /// The type of the XMI element to be read. + public interface IXmiElementReader where TXmiElement : IXmiElement + { + /// + /// Reads the object from its XML representation + /// + /// + /// an instance of + /// + /// + /// an instance of + /// + public TXmiElement Read(XmlReader xmlReader); + } +} diff --git a/uml4net.xmireader/IXmiReader.cs b/uml4net.xmireader/Readers/IXmiReader.cs similarity index 66% rename from uml4net.xmireader/IXmiReader.cs rename to uml4net.xmireader/Readers/IXmiReader.cs index d95b724f..b4a02e5f 100644 --- a/uml4net.xmireader/IXmiReader.cs +++ b/uml4net.xmireader/Readers/IXmiReader.cs @@ -18,39 +18,40 @@ // // ------------------------------------------------------------------------------------------------ -namespace uml4net.xmi +namespace uml4net.xmi.Readers { + using System; using System.Collections.Generic; using System.IO; - + using System.Threading.Tasks; using uml4net.POCO.Packages; /// /// The purpose of the is to provide a means to read (deserialize) /// an UML 2.5.1 model from XMI /// - public interface IXmiReader + public interface IXmiReader : IDisposable { /// - /// reads the content of a UML XMI 2.5.1 file + /// Reads the content of a UML XMI 2.5.1 file asynchronously. /// /// - /// the path to the XMI file + /// The URI of the XMI file to be read. /// /// - /// An + /// An representing the deserialized packages from the XMI file. /// - IEnumerable Read(string fileUri); - + Task> ReadAsync(string fileUri); + /// - /// reads the content of a UML XMI 2.5.1 stream + /// Reads the content of a UML XMI 2.5.1 stream asynchronously. /// /// - /// the that contains the XMI content + /// The that contains the XMI content to be read. /// /// - /// An + /// An representing the deserialized packages from the XMI stream. /// - IEnumerable Read(Stream stream); + Task> ReadAsync(Stream stream); } } \ No newline at end of file diff --git a/uml4net.xmireader/Packages/ModelReader.cs b/uml4net.xmireader/Readers/Packages/ModelReader.cs similarity index 73% rename from uml4net.xmireader/Packages/ModelReader.cs rename to uml4net.xmireader/Readers/Packages/ModelReader.cs index 803552d5..8408b0ba 100644 --- a/uml4net.xmireader/Packages/ModelReader.cs +++ b/uml4net.xmireader/Readers/Packages/ModelReader.cs @@ -18,28 +18,27 @@ // // ------------------------------------------------------------------------------------------------ -namespace uml4net.xmi.Packages +namespace uml4net.xmi.Readers.Packages { - using System.Collections.Generic; + using Cache; using System.Xml; using Microsoft.Extensions.Logging; - using Microsoft.Extensions.Logging.Abstractions; - - using uml4net.POCO; + using POCO.CommonStructure; + using POCO; using uml4net.POCO.Packages; - using uml4net.xmi.CommonStructure; + using Readers; /// /// The purpose of the is to read an instance of /// from the XMI document /// - public class ModelReader : XmiElementReader + public class ModelReader : XmiElementReader, IXmiElementReader { /// - /// The used to log + /// The of /// - private readonly ILogger logger; + private readonly IXmiElementReader packageImportReader; /// /// Initializes a new instance of the class. @@ -47,13 +46,14 @@ public class ModelReader : XmiElementReader /// /// The cache in which each > is stored /// - /// - /// The (injected) used to setup logging + /// + /// The (injected) used to setup logging /// - public ModelReader(Dictionary cache, ILoggerFactory loggerFactory = null) - : base(cache, loggerFactory) + /// The of + public ModelReader(IXmiReaderCache cache, ILogger logger, IXmiElementReader packageImportReader) + : base(cache, logger) { - this.logger = this.loggerFactory == null ? NullLogger.Instance : this.loggerFactory.CreateLogger(); + this.packageImportReader = packageImportReader; } /// @@ -65,15 +65,15 @@ public ModelReader(Dictionary cache, ILoggerFactory loggerF /// /// an instance of /// - public IModel Read(XmlReader xmlReader) - { + public override IModel Read(XmlReader xmlReader) + { IModel model = new Model(); if (xmlReader.MoveToContent() == XmlNodeType.Element) { var xmiType = xmlReader.GetAttribute("xmi:type"); - if (!string.IsNullOrEmpty(xmiType) && (xmiType != "uml:Model")) + if (!string.IsNullOrEmpty(xmiType) && xmiType != "uml:Model") { throw new XmlException($"The XmiType should be: uml:Model while it is {xmiType}"); } @@ -86,7 +86,7 @@ public IModel Read(XmlReader xmlReader) model.XmiId = xmlReader.GetAttribute("xmi:id"); - this.cache.Add(model.XmiId, model); + this.Cache.Add(model.XmiId, model); model.Name = xmlReader.GetAttribute("name"); @@ -100,15 +100,14 @@ public IModel Read(XmlReader xmlReader) case "packageImport": using (var packageImportXmlReader = xmlReader.ReadSubtree()) { - var packageImportReader = new PackageImportReader(this.cache, this.loggerFactory); - var packageImport = packageImportReader.Read(packageImportXmlReader); + var packageImport = this.packageImportReader.Read(packageImportXmlReader); model.PackageImport.Add(packageImport); } break; case "packagedElement": using (var packagedElementXmlReader = xmlReader.ReadSubtree()) { - this.logger.LogInformation("ModelReader.packagedElement not yet implemented"); + this.Logger.LogInformation("ModelReader.packagedElement not yet implemented"); } break; } diff --git a/uml4net.xmireader/Packages/PackageReader.cs b/uml4net.xmireader/Readers/Packages/PackageReader.cs similarity index 67% rename from uml4net.xmireader/Packages/PackageReader.cs rename to uml4net.xmireader/Readers/Packages/PackageReader.cs index bb295493..ce79ba1d 100644 --- a/uml4net.xmireader/Packages/PackageReader.cs +++ b/uml4net.xmireader/Readers/Packages/PackageReader.cs @@ -18,33 +18,47 @@ // // ------------------------------------------------------------------------------------------------ -namespace uml4net.xmi.Packages +namespace uml4net.xmi.Readers.Packages { + using Cache; using System; - using System.Collections.Generic; using System.Xml; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; - - using uml4net.POCO; + using POCO.SimpleClassifiers; + using POCO.StructuredClassifiers; + using POCO; using uml4net.POCO.CommonStructure; using uml4net.POCO.Packages; - using uml4net.xmi.CommonStructure; - using uml4net.xmi.SimpleClassifiers; - using uml4net.xmi.StructuredClassifiers; + using Readers; /// /// The purpose of the is to read an instance of /// from the XMI document /// - public class PackageReader : XmiElementReader + public class PackageReader : XmiCommentedElementReader, IXmiElementReader { /// - /// The used to log + /// The of + /// + private readonly IXmiElementReader classReader; + + /// + /// The of + /// + private readonly IXmiElementReader enumerationReader; + + /// + /// The of + /// + private readonly IXmiElementReader packageImportReader; + + /// + /// The of /// - private readonly ILogger logger; + private readonly IXmiElementReader primitiveReader; /// /// Initializes a new instance of the class. @@ -52,13 +66,22 @@ public class PackageReader : XmiElementReader /// /// The cache in which each > is stored /// - /// - /// The (injected) used to setup logging + /// + /// The (injected) used to setup logging /// - public PackageReader(Dictionary cache, ILoggerFactory loggerFactory = null) - : base(cache, loggerFactory) + /// The of + /// The of + /// The of + /// The of + /// The of + public PackageReader(IXmiReaderCache cache, ILogger logger, IXmiElementReader commentReader, IXmiElementReader classReader, + IXmiElementReader enumerationReader, IXmiElementReader packageImportReader, IXmiElementReader primitiveReader) + : base(cache, logger, commentReader) { - this.logger = this.loggerFactory == null ? NullLogger.Instance : this.loggerFactory.CreateLogger(); + this.classReader = classReader; + this.enumerationReader = enumerationReader; + this.packageImportReader = packageImportReader; + this.primitiveReader = primitiveReader; } /// @@ -70,7 +93,7 @@ public PackageReader(Dictionary cache, ILoggerFactory logge /// /// an instance of /// - public IPackage Read(XmlReader xmlReader) + public override IPackage Read(XmlReader xmlReader) { IPackage package = new Package(); @@ -87,7 +110,7 @@ public IPackage Read(XmlReader xmlReader) package.XmiId = xmlReader.GetAttribute("xmi:id"); - this.cache.Add(package.XmiId, package); + this.Cache.Add(package.XmiId, package); package.Name = xmlReader.GetAttribute("name"); @@ -106,35 +129,33 @@ public IPackage Read(XmlReader xmlReader) case "ownedComment": using (var ownedCommentXmlReader = xmlReader.ReadSubtree()) { - var commentReader = new CommentReader(this.cache, this.loggerFactory); - var comment = commentReader.Read(ownedCommentXmlReader); + var comment = this.CommentReader.Read(ownedCommentXmlReader); package.OwnedComment.Add(comment); } break; case "elementImport": using (var elementImportXmlReader = xmlReader.ReadSubtree()) { - this.logger.LogInformation("PackageReader.elementImport not yet implemented"); + this.Logger.LogInformation("PackageReader.elementImport not yet implemented"); } break; case "ownedRule": using (var ownedRuleXmlReader = xmlReader.ReadSubtree()) { - this.logger.LogInformation("PackageReader.ownedRule not yet implemented"); + this.Logger.LogInformation("PackageReader.ownedRule not yet implemented"); } break; case "packageImport": using (var packageImportXmlReader = xmlReader.ReadSubtree()) { - var packageImportReader = new PackageImportReader(this.cache, this.loggerFactory); - var packageImport = packageImportReader.Read(packageImportXmlReader); + var packageImport = this.packageImportReader.Read(packageImportXmlReader); package.PackageImport.Add(packageImport); } break; case "templateBinding": using (var templateBindingXmlReader = xmlReader.ReadSubtree()) { - this.logger.LogInformation("PackageReader.TemplateBinding not yet implemented"); + this.Logger.LogInformation("PackageReader.TemplateBinding not yet implemented"); } break; case "packagedElement": @@ -168,37 +189,35 @@ private void ReadPackagedElements(IPackage package, XmlReader xmlReader) case "uml:Association": using (var associationXmlReader = xmlReader.ReadSubtree()) { - this.logger.LogDebug("uml:Association not yet implements"); + this.Logger.LogDebug("uml:Association not yet implements"); } break; case "uml:Class": using (var classXmlReader = xmlReader.ReadSubtree()) { - var classReader = new ClassReader(this.cache, this.loggerFactory); - var packagedElement = classReader.Read(classXmlReader); + var packagedElement = this.classReader.Read(classXmlReader); package.PackagedElement.Add(packagedElement); } break; case "uml:Enumeration": using (var enumerationXmlReader = xmlReader.ReadSubtree()) { - var enumerationReader = new EnumerationReader(this.cache, this.loggerFactory); - var enumeration = enumerationReader.Read(enumerationXmlReader); + var enumeration = this.enumerationReader.Read(enumerationXmlReader); package.PackagedElement.Add(enumeration); } break; case "uml:Package": using (var packageXmlReader = xmlReader.ReadSubtree()) { - var packageReader = new PackageReader(this.cache, this.loggerFactory); - var packagedElement = packageReader.Read(packageXmlReader); + var packagedElement = this.Read(packageXmlReader); package.PackagedElement.Add(packagedElement); } break; case "uml:PrimitiveType": using (var primitiveTypeXmlReader = xmlReader.ReadSubtree()) { - this.logger.LogDebug("uml:PrimitiveType not yet implements"); + var primitive = this.primitiveReader.Read(primitiveTypeXmlReader); + package.PackagedElement.Add(primitive); } break; default: diff --git a/uml4net.xmireader/SimpleClassifiers/EnumerationLiteralReader.cs b/uml4net.xmireader/Readers/SimpleClassifiers/EnumerationLiteralReader.cs similarity index 74% rename from uml4net.xmireader/SimpleClassifiers/EnumerationLiteralReader.cs rename to uml4net.xmireader/Readers/SimpleClassifiers/EnumerationLiteralReader.cs index 0ecb739e..fc734be7 100644 --- a/uml4net.xmireader/SimpleClassifiers/EnumerationLiteralReader.cs +++ b/uml4net.xmireader/Readers/SimpleClassifiers/EnumerationLiteralReader.cs @@ -18,44 +18,38 @@ // // ------------------------------------------------------------------------------------------------ -namespace uml4net.xmi.SimpleClassifiers +namespace uml4net.xmi.Readers.SimpleClassifiers { + using Cache; using System; - using System.Collections.Generic; using System.Xml; using Microsoft.Extensions.Logging; - using Microsoft.Extensions.Logging.Abstractions; - - using uml4net.POCO; + using POCO.CommonStructure; + using POCO; using uml4net.POCO.SimpleClassifiers; - using uml4net.xmi.CommonStructure; + using Readers; /// /// The purpose of the is to read an instance of /// from the XMI document /// - public class EnumerationLiteralReader : XmiElementReader + public class EnumerationLiteralReader : XmiCommentedElementReader, IXmiElementReader { - /// - /// The used to log - /// - private readonly ILogger logger; - /// /// Initializes a new instance of the class. /// /// /// The cache in which each > is stored /// - /// - /// The (injected) used to setup logging + /// + /// The (injected) used to setup logging /// - public EnumerationLiteralReader(Dictionary cache, ILoggerFactory loggerFactory = null) - : base(cache, loggerFactory) + /// The of + public EnumerationLiteralReader(IXmiReaderCache cache, ILogger logger, IXmiElementReader commentReader) + : base(cache, logger, commentReader) { - this.logger = this.loggerFactory == null ? NullLogger.Instance : this.loggerFactory.CreateLogger(); } /// @@ -67,7 +61,7 @@ public EnumerationLiteralReader(Dictionary cache, ILoggerFa /// /// an instance of /// - public IEnumerationLiteral Read(XmlReader xmlReader) + public override IEnumerationLiteral Read(XmlReader xmlReader) { IEnumerationLiteral enumerationLiteral = new EnumerationLiteral(); @@ -84,7 +78,7 @@ public IEnumerationLiteral Read(XmlReader xmlReader) enumerationLiteral.XmiId = xmlReader.GetAttribute("xmi:id"); - this.cache.Add(enumerationLiteral.XmiId, enumerationLiteral); + this.Cache.Add(enumerationLiteral.XmiId, enumerationLiteral); enumerationLiteral.Name = xmlReader.GetAttribute("name"); @@ -97,8 +91,7 @@ public IEnumerationLiteral Read(XmlReader xmlReader) case "ownedComment": using (var ownedCommentXmlReader = xmlReader.ReadSubtree()) { - var commentReader = new CommentReader(this.cache, this.loggerFactory); - var comment = commentReader.Read(ownedCommentXmlReader); + var comment = this.CommentReader.Read(ownedCommentXmlReader); enumerationLiteral.OwnedComment.Add(comment); } break; @@ -112,4 +105,4 @@ public IEnumerationLiteral Read(XmlReader xmlReader) return enumerationLiteral; } } -} \ No newline at end of file +} diff --git a/uml4net.xmireader/SimpleClassifiers/EnumerationReader.cs b/uml4net.xmireader/Readers/SimpleClassifiers/EnumerationReader.cs similarity index 71% rename from uml4net.xmireader/SimpleClassifiers/EnumerationReader.cs rename to uml4net.xmireader/Readers/SimpleClassifiers/EnumerationReader.cs index 57a1ac34..b6793069 100644 --- a/uml4net.xmireader/SimpleClassifiers/EnumerationReader.cs +++ b/uml4net.xmireader/Readers/SimpleClassifiers/EnumerationReader.cs @@ -18,29 +18,28 @@ // // ------------------------------------------------------------------------------------------------ -namespace uml4net.xmi.SimpleClassifiers +namespace uml4net.xmi.Readers.SimpleClassifiers { using System; - using System.Collections.Generic; using System.Xml; using Microsoft.Extensions.Logging; - using Microsoft.Extensions.Logging.Abstractions; - - using uml4net.POCO; + using POCO; + using uml4net.POCO.CommonStructure; using uml4net.POCO.SimpleClassifiers; - using uml4net.xmi.CommonStructure; + using Cache; + using Readers; /// /// The purpose of the is to read an instance of /// from the XMI document /// - public class EnumerationReader : XmiElementReader + public class EnumerationReader : XmiCommentedElementReader, IXmiElementReader { /// - /// The used to log + /// The of /// - private readonly ILogger logger; + private readonly IXmiElementReader enumerationLiteralReader; /// /// Initializes a new instance of the class. @@ -48,13 +47,15 @@ public class EnumerationReader : XmiElementReader /// /// The cache in which each > is stored /// - /// - /// The (injected) used to setup logging + /// + /// The (injected) used to setup logging /// - public EnumerationReader(Dictionary cache, ILoggerFactory loggerFactory = null) - : base(cache, loggerFactory) + /// The of + /// The of + public EnumerationReader(IXmiReaderCache cache, ILogger logger, IXmiElementReader commentReader, IXmiElementReader enumerationLiteralReader) + : base(cache, logger, commentReader) { - this.logger = this.loggerFactory == null ? NullLogger.Instance : this.loggerFactory.CreateLogger(); + this.enumerationLiteralReader = enumerationLiteralReader; } /// @@ -66,7 +67,7 @@ public EnumerationReader(Dictionary cache, ILoggerFactory l /// /// an instance of /// - public IEnumeration Read(XmlReader xmlReader) + public override IEnumeration Read(XmlReader xmlReader) { IEnumeration enumeration = new Enumeration(); @@ -83,7 +84,7 @@ public IEnumeration Read(XmlReader xmlReader) enumeration.XmiId = xmlReader.GetAttribute("xmi:id"); - this.cache.Add(enumeration.XmiId, enumeration); + this.Cache.Add(enumeration.XmiId, enumeration); enumeration.Name = xmlReader.GetAttribute("name"); @@ -96,19 +97,17 @@ public IEnumeration Read(XmlReader xmlReader) case "ownedComment": using (var ownedCommentXmlReader = xmlReader.ReadSubtree()) { - var commentReader = new CommentReader(this.cache, this.loggerFactory); - var comment = commentReader.Read(ownedCommentXmlReader); + var comment = this.CommentReader.Read(ownedCommentXmlReader); enumeration.OwnedComment.Add(comment); } break; case "ownedLiteral": using (var ownedLiteralXmlReader = xmlReader.ReadSubtree()) { - var enumerationLiteralReader = new EnumerationLiteralReader(this.cache, this.loggerFactory); - var enumerationLiteral = enumerationLiteralReader.Read(ownedLiteralXmlReader); + var enumerationLiteral = this.enumerationLiteralReader.Read(ownedLiteralXmlReader); enumeration.OwnedLiteral.Add(enumerationLiteral); - this.logger.LogInformation("ClassReader.ownedRule not yet implemented"); + this.Logger.LogInformation("ClassReader.ownedRule not yet implemented"); } break; default: diff --git a/uml4net.xmireader/Readers/SimpleClassifiers/PrimitiveTypeReader.cs b/uml4net.xmireader/Readers/SimpleClassifiers/PrimitiveTypeReader.cs new file mode 100644 index 00000000..09761f32 --- /dev/null +++ b/uml4net.xmireader/Readers/SimpleClassifiers/PrimitiveTypeReader.cs @@ -0,0 +1,102 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2019-2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, softwareUseCases +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace uml4net.xmi.Readers.SimpleClassifiers +{ + using Microsoft.Extensions.Logging; + using uml4net.POCO.CommonStructure; + using uml4net.POCO.SimpleClassifiers; + using uml4net.xmi.Cache; + + using POCO; + using System.Xml; + using System; + + public class PrimitiveTypeReader : XmiCommentedElementReader, IXmiElementReader + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The cache in which each > is stored + /// + /// + /// The (injected) used to setup logging + /// + /// The of + public PrimitiveTypeReader(IXmiReaderCache cache, ILogger logger, IXmiElementReader commentReader) + : base(cache, logger, commentReader) + { + } + + /// + /// Reads the object from its XML representation + /// + /// + /// an instance of + /// + /// + /// an instance of + /// + public override IPrimitiveType Read(XmlReader xmlReader) + { + IPrimitiveType primitiveType = new PrimitiveType(); + + if (xmlReader.MoveToContent() == XmlNodeType.Element) + { + var xmiType = xmlReader.GetAttribute("xmi:type"); + + if (xmiType != "uml:PrimitiveType") + { + throw new XmlException($"The XmiType should be: uml:PrimitiveType while it is {xmiType}"); + } + + primitiveType.XmiType = xmiType; + + primitiveType.XmiId = xmlReader.GetAttribute("xmi:id"); + + this.Cache.Add(primitiveType.XmiId, primitiveType); + + primitiveType.Name = xmlReader.GetAttribute("name"); + + while (xmlReader.Read()) + { + if (xmlReader.NodeType == XmlNodeType.Element) + { + switch (xmlReader.LocalName) + { + case "ownedComment": + using (var ownedCommentXmlReader = xmlReader.ReadSubtree()) + { + var comment = this.CommentReader.Read(ownedCommentXmlReader); + primitiveType.OwnedComment.Add(comment); + } + break; + default: + throw new NotImplementedException($"PrimitiveTypeReader: {xmlReader.LocalName}"); + } + } + } + } + + return primitiveType; + } + } +} diff --git a/uml4net.xmireader/StructuredClassifiers/ClassReader.cs b/uml4net.xmireader/Readers/StructuredClassifiers/ClassReader.cs similarity index 66% rename from uml4net.xmireader/StructuredClassifiers/ClassReader.cs rename to uml4net.xmireader/Readers/StructuredClassifiers/ClassReader.cs index 53c41307..1634b7b4 100644 --- a/uml4net.xmireader/StructuredClassifiers/ClassReader.cs +++ b/uml4net.xmireader/Readers/StructuredClassifiers/ClassReader.cs @@ -18,34 +18,42 @@ // // ------------------------------------------------------------------------------------------------ -namespace uml4net.xmi.StructuredClassifiers +namespace uml4net.xmi.Readers.StructuredClassifiers { + using Cache; using System; - using System.Collections.Generic; using System.Xml; using Microsoft.Extensions.Logging; - using Microsoft.Extensions.Logging.Abstractions; - - using uml4net.POCO; + using POCO.Classification; + using POCO; using uml4net.POCO.CommonStructure; using uml4net.POCO.StructuredClassifiers; using uml4net.POCO.Packages; - using uml4net.xmi.Classification; - using uml4net.xmi.CommonStructure; - using uml4net.xmi.Packages; + using Packages; + using Readers; /// /// The purpose of the is to read an instance of /// from the XMI document /// - public class ClassReader : XmiElementReader + public class ClassReader : XmiCommentedElementReader, IXmiElementReader { /// - /// The used to log + /// The of + /// + private readonly IXmiElementReader constraintReader; + + /// + /// The of + /// + private readonly IXmiElementReader propertyReader; + + /// + /// The of /// - private readonly ILogger logger; + private readonly IXmiElementReader generalizationReader; /// /// Initializes a new instance of the class. @@ -53,13 +61,20 @@ public class ClassReader : XmiElementReader /// /// The cache in which each > is stored /// - /// - /// The (injected) used to setup logging + /// + /// The (injected) used to setup logging /// - public ClassReader(Dictionary cache, ILoggerFactory loggerFactory = null) - : base(cache, loggerFactory) + /// The of + /// The of + /// The of + /// The of + public ClassReader(IXmiReaderCache cache, ILogger logger, IXmiElementReader commentReader, + IXmiElementReader constraintReader, IXmiElementReader propertyReader, IXmiElementReader generatizationReader) + : base(cache, logger, commentReader) { - this.logger = this.loggerFactory == null ? NullLogger.Instance : this.loggerFactory.CreateLogger(); + this.constraintReader = constraintReader; + this.propertyReader = propertyReader; + this.generalizationReader = generatizationReader; } /// @@ -71,7 +86,7 @@ public ClassReader(Dictionary cache, ILoggerFactory loggerF /// /// an instance of /// - public IClass Read(XmlReader xmlReader) + public override IClass Read(XmlReader xmlReader) { IClass @class = new Class(); @@ -88,7 +103,7 @@ public IClass Read(XmlReader xmlReader) @class.XmiId = xmlReader.GetAttribute("xmi:id"); - this.cache.Add(@class.XmiId, @class); + this.Cache.Add(@class.XmiId, @class); @class.Name = xmlReader.GetAttribute("name"); @@ -107,38 +122,34 @@ public IClass Read(XmlReader xmlReader) case "ownedComment": using (var ownedCommentXmlReader = xmlReader.ReadSubtree()) { - var commentReader = new CommentReader(this.cache, this.loggerFactory); - var comment = commentReader.Read(ownedCommentXmlReader); + var comment = this.CommentReader.Read(ownedCommentXmlReader); @class.OwnedComment.Add(comment); } break; case "ownedRule": using (var ownedRuleXmlReader = xmlReader.ReadSubtree()) { - var constraintReader = new ConstraintReader(this.cache, this.loggerFactory); - var constraint = constraintReader.Read(ownedRuleXmlReader); + var constraint = this.constraintReader.Read(ownedRuleXmlReader); @class.OwnedRule.Add(constraint); } break; case "ownedAttribute": using (var ownedAttributeXmlReader = xmlReader.ReadSubtree()) { - var propertyReader = new PropertyReader(this.cache, this.loggerFactory); - var property = propertyReader.Read(ownedAttributeXmlReader); + var property = this.propertyReader.Read(ownedAttributeXmlReader); @class.OwnedAttribute.Add(property); } break; case "ownedOperation": using (var ownedOperationXmlReader = xmlReader.ReadSubtree()) { - this.logger.LogInformation("ClassReader.ownedOperation not yet implemented"); + this.Logger.LogInformation("ClassReader.ownedOperation not yet implemented"); } break; case "generalization": using (var generalizationXmlReader = xmlReader.ReadSubtree()) { - var generalizationReader = new GeneralizationReader(this.cache, this.loggerFactory); - var generalization = generalizationReader.Read(generalizationXmlReader); + var generalization = this.generalizationReader.Read(generalizationXmlReader); @class.Generalization.Add(generalization); } break; @@ -152,4 +163,4 @@ public IClass Read(XmlReader xmlReader) return @class; } } -} \ No newline at end of file +} diff --git a/uml4net.xmireader/Values/LiteralIntegerReader.cs b/uml4net.xmireader/Readers/Values/LiteralIntegerReader.cs similarity index 77% rename from uml4net.xmireader/Values/LiteralIntegerReader.cs rename to uml4net.xmireader/Readers/Values/LiteralIntegerReader.cs index 25674507..c753d253 100644 --- a/uml4net.xmireader/Values/LiteralIntegerReader.cs +++ b/uml4net.xmireader/Readers/Values/LiteralIntegerReader.cs @@ -18,8 +18,9 @@ // // ------------------------------------------------------------------------------------------------ -namespace uml4net.xmi.Values +namespace uml4net.xmi.Readers.Values { + using Cache; using System; using System.Collections.Generic; using System.Xml; @@ -27,35 +28,30 @@ namespace uml4net.xmi.Values using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; - using uml4net.POCO; + using POCO; using uml4net.POCO.CommonStructure; using uml4net.POCO.Values; - using uml4net.xmi.CommonStructure; + using Readers; /// /// The purpose of the is to read an instance of /// from the XMI document /// - public class LiteralIntegerReader : XmiElementReader + public class LiteralIntegerReader : XmiCommentedElementReader, IXmiElementReader { - /// - /// The used to log - /// - private readonly ILogger logger; - /// /// Initializes a new instance of the class. /// /// /// The cache in which each > is stored /// - /// - /// The (injected) used to setup logging + /// + /// The (injected) used to setup logging /// - public LiteralIntegerReader(Dictionary cache, ILoggerFactory loggerFactory = null) - : base(cache, loggerFactory) + /// The of + public LiteralIntegerReader(IXmiReaderCache cache, ILogger logger, IXmiElementReader commentReader) + : base(cache, logger, commentReader) { - this.logger = this.loggerFactory == null ? NullLogger.Instance : this.loggerFactory.CreateLogger(); } /// @@ -67,7 +63,7 @@ public LiteralIntegerReader(Dictionary cache, ILoggerFactor /// /// an instance of /// - public ILiteralInteger Read(XmlReader xmlReader) + public override ILiteralInteger Read(XmlReader xmlReader) { ILiteralInteger literalInteger = new LiteralInteger(); @@ -84,7 +80,7 @@ public ILiteralInteger Read(XmlReader xmlReader) literalInteger.XmiId = xmlReader.GetAttribute("xmi:id"); - this.cache.Add(literalInteger.XmiId, literalInteger); + this.Cache.Add(literalInteger.XmiId, literalInteger); var value = xmlReader.GetAttribute("value"); if (!string.IsNullOrEmpty(value)) @@ -101,8 +97,7 @@ public ILiteralInteger Read(XmlReader xmlReader) case "ownedComment": using (var ownedCommentXmlReader = xmlReader.ReadSubtree()) { - var commentReader = new CommentReader(this.cache, this.loggerFactory); - var comment = commentReader.Read(ownedCommentXmlReader); + var comment = this.CommentReader.Read(ownedCommentXmlReader); literalInteger.OwnedComment.Add(comment); } break; @@ -116,4 +111,4 @@ public ILiteralInteger Read(XmlReader xmlReader) return literalInteger; } } -} \ No newline at end of file +} diff --git a/uml4net.xmireader/Values/LiteralUnlimitedNaturalReader.cs b/uml4net.xmireader/Readers/Values/LiteralUnlimitedNaturalReader.cs similarity index 77% rename from uml4net.xmireader/Values/LiteralUnlimitedNaturalReader.cs rename to uml4net.xmireader/Readers/Values/LiteralUnlimitedNaturalReader.cs index 8fb93ae6..e896e45a 100644 --- a/uml4net.xmireader/Values/LiteralUnlimitedNaturalReader.cs +++ b/uml4net.xmireader/Readers/Values/LiteralUnlimitedNaturalReader.cs @@ -18,7 +18,7 @@ // // ------------------------------------------------------------------------------------------------ -namespace uml4net.xmi.Values +namespace uml4net.xmi.Readers.Values { using System; using System.Collections.Generic; @@ -27,35 +27,31 @@ namespace uml4net.xmi.Values using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; - using uml4net.POCO; + using POCO; using uml4net.POCO.CommonStructure; using uml4net.POCO.Values; - using uml4net.xmi.CommonStructure; + using Cache; + using Readers; /// /// The purpose of the is to read an instance of /// from the XMI document /// - public class LiteralUnlimitedNaturalReader : XmiElementReader + public class LiteralUnlimitedNaturalReader : XmiCommentedElementReader, IXmiElementReader { - /// - /// The used to log - /// - private readonly ILogger logger; - /// /// Initializes a new instance of the class. /// /// /// The cache in which each > is stored /// - /// - /// The (injected) used to setup logging - /// - public LiteralUnlimitedNaturalReader(Dictionary cache, ILoggerFactory loggerFactory = null) - : base(cache, loggerFactory) + /// + /// The (injected) used to setup logging + /// + /// The of + public LiteralUnlimitedNaturalReader(IXmiReaderCache cache, ILogger logger, IXmiElementReader commentReader) + : base(cache, logger, commentReader) { - this.logger = this.loggerFactory == null ? NullLogger.Instance : this.loggerFactory.CreateLogger(); } /// @@ -67,7 +63,7 @@ public LiteralUnlimitedNaturalReader(Dictionary cache, ILog /// /// an instance of /// - public ILiteralUnlimitedNatural Read(XmlReader xmlReader) + public override ILiteralUnlimitedNatural Read(XmlReader xmlReader) { ILiteralUnlimitedNatural literalUnlimitedNatural = new LiteralUnlimitedNatural(); @@ -84,7 +80,7 @@ public ILiteralUnlimitedNatural Read(XmlReader xmlReader) literalUnlimitedNatural.XmiId = xmlReader.GetAttribute("xmi:id"); - this.cache.Add(literalUnlimitedNatural.XmiId, literalUnlimitedNatural); + this.Cache.Add(literalUnlimitedNatural.XmiId, literalUnlimitedNatural); var value = xmlReader.GetAttribute("value"); @@ -106,8 +102,7 @@ public ILiteralUnlimitedNatural Read(XmlReader xmlReader) case "ownedComment": using (var ownedCommentXmlReader = xmlReader.ReadSubtree()) { - var commentReader = new CommentReader(this.cache, this.loggerFactory); - var comment = commentReader.Read(ownedCommentXmlReader); + var comment = this.CommentReader.Read(ownedCommentXmlReader); literalUnlimitedNatural.OwnedComment.Add(comment); } break; @@ -121,4 +116,4 @@ public ILiteralUnlimitedNatural Read(XmlReader xmlReader) return literalUnlimitedNatural; } } -} \ No newline at end of file +} diff --git a/uml4net.xmireader/Values/OpaqueExpressionReader.cs b/uml4net.xmireader/Readers/Values/OpaqueExpressionReader.cs similarity index 77% rename from uml4net.xmireader/Values/OpaqueExpressionReader.cs rename to uml4net.xmireader/Readers/Values/OpaqueExpressionReader.cs index 381c6110..7f652471 100644 --- a/uml4net.xmireader/Values/OpaqueExpressionReader.cs +++ b/uml4net.xmireader/Readers/Values/OpaqueExpressionReader.cs @@ -18,7 +18,7 @@ // // ------------------------------------------------------------------------------------------------ -namespace uml4net.xmi.Values +namespace uml4net.xmi.Readers.Values { using System; using System.Collections.Generic; @@ -27,35 +27,31 @@ namespace uml4net.xmi.Values using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; - using uml4net.POCO; + using POCO; using uml4net.POCO.CommonStructure; using uml4net.POCO.Values; - using uml4net.xmi.CommonStructure; + using Cache; + using Readers; /// /// The purpose of the is to read an instance of /// from the XMI document /// - public class OpaqueExpressionReader : XmiElementReader + public class OpaqueExpressionReader : XmiCommentedElementReader, IXmiElementReader { - /// - /// The used to log - /// - private readonly ILogger logger; - /// /// Initializes a new instance of the class. /// /// /// The cache in which each > is stored /// - /// - /// The (injected) used to setup logging - /// - public OpaqueExpressionReader(Dictionary cache, ILoggerFactory loggerFactory = null) - : base(cache, loggerFactory) + /// + /// The (injected) used to setup logging + /// + /// The of + public OpaqueExpressionReader(IXmiReaderCache cache, ILogger logger, IXmiElementReader commentReader) + : base(cache, logger, commentReader) { - this.logger = this.loggerFactory == null ? NullLogger.Instance : this.loggerFactory.CreateLogger(); } /// @@ -67,7 +63,7 @@ public OpaqueExpressionReader(Dictionary cache, ILoggerFact /// /// an instance of /// - public IOpaqueExpression Read(XmlReader xmlReader) + public override IOpaqueExpression Read(XmlReader xmlReader) { IOpaqueExpression opaqueExpression = new OpaqueExpression(); @@ -84,7 +80,7 @@ public IOpaqueExpression Read(XmlReader xmlReader) opaqueExpression.XmiId = xmlReader.GetAttribute("xmi:id"); - this.cache.Add(opaqueExpression.XmiId, opaqueExpression); + this.Cache.Add(opaqueExpression.XmiId, opaqueExpression); while (xmlReader.Read()) { @@ -101,8 +97,7 @@ public IOpaqueExpression Read(XmlReader xmlReader) case "ownedComment": using (var ownedCommentXmlReader = xmlReader.ReadSubtree()) { - var commentReader = new CommentReader(this.cache, this.loggerFactory); - var comment = commentReader.Read(ownedCommentXmlReader); + var comment = this.CommentReader.Read(ownedCommentXmlReader); opaqueExpression.OwnedComment.Add(comment); } break; diff --git a/uml4net.xmireader/Readers/XmiCommentedElementReader.cs b/uml4net.xmireader/Readers/XmiCommentedElementReader.cs new file mode 100644 index 00000000..7af97b7c --- /dev/null +++ b/uml4net.xmireader/Readers/XmiCommentedElementReader.cs @@ -0,0 +1,44 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2019-2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, softwareUseCases +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace uml4net.xmi.Readers +{ + using Microsoft.Extensions.Logging; + + using POCO; + using POCO.CommonStructure; + using Cache; + + /// + /// An abstract class for reading XMI elements that include comments. + /// + /// The type of the XMI element to be read, must inherit from . + /// An instance of for caching XMI elements. + /// An instance of for logging. + /// An instance of to read comments associated with the XMI elements. + public abstract class XmiCommentedElementReader(IXmiReaderCache cache, ILogger> logger, IXmiElementReader commentReader) + : XmiElementReader(cache, logger) where TXmiElement : IXmiElement + { + /// + /// The of + /// + protected readonly IXmiElementReader CommentReader = commentReader; + } +} diff --git a/uml4net.xmireader/XmiElementReader.cs b/uml4net.xmireader/Readers/XmiElementReader.cs similarity index 53% rename from uml4net.xmireader/XmiElementReader.cs rename to uml4net.xmireader/Readers/XmiElementReader.cs index c7937311..8664af56 100644 --- a/uml4net.xmireader/XmiElementReader.cs +++ b/uml4net.xmireader/Readers/XmiElementReader.cs @@ -18,42 +18,53 @@ // // ------------------------------------------------------------------------------------------------ -namespace uml4net.xmi +namespace uml4net.xmi.Readers { - using System.Collections.Generic; - + using Cache; using Microsoft.Extensions.Logging; - - using uml4net.POCO; + using System.Xml; + using POCO; /// /// The abstract super class from which eadh XMI reader needs to derive - /// - public abstract class XmiElementReader + /// + /// The type of the XMI element to be read. + public abstract class XmiElementReader where TXmiElement : IXmiElement { /// - /// The (injected) used to setup logging + /// The (injected) used to setup logging /// - protected readonly ILoggerFactory loggerFactory; + protected readonly ILogger> Logger; /// - /// The cache in which each > is stored + /// The (injected) used to setup logging /// - protected readonly Dictionary cache; + protected readonly IXmiReaderCache Cache; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The cache in which each > is stored /// - /// - /// The (injected) used to setup logging + /// + /// The (injected) used to setup logging /// - protected XmiElementReader(Dictionary cache, ILoggerFactory loggerFactory = null) + protected XmiElementReader(IXmiReaderCache cache, ILogger> logger) { - this.cache = cache; - this.loggerFactory = loggerFactory; + this.Cache = cache; + this.Logger = logger; } + + /// + /// Reads the object from its XML representation + /// + /// + /// an instance of + /// + /// + /// an instance of + /// + public abstract TXmiElement Read(XmlReader xmlReader); } } \ No newline at end of file diff --git a/uml4net.xmireader/Readers/XmiReader.cs b/uml4net.xmireader/Readers/XmiReader.cs new file mode 100644 index 00000000..63bff8fb --- /dev/null +++ b/uml4net.xmireader/Readers/XmiReader.cs @@ -0,0 +1,234 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2019-2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, softwareUseCases +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace uml4net.xmi.Readers +{ + using Cache; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Xml; + + using Microsoft.Extensions.Logging; + using System.Threading.Tasks; + using uml4net.POCO.Packages; + + using uml4net; + using xmi; + + /// + /// The purpose of the is to provide a means to read (deserialize) + /// an UML 2.5.1 model from XMI + /// + public class XmiReader : IXmiReader + { + /// + /// The + /// + private readonly IAssembler assembler; + + /// + /// The used to log + /// + private readonly ILogger logger; + + /// + /// The of + /// + private readonly IXmiElementReader packageReader; + + /// + /// The of + /// + private readonly IXmiElementReader modelReader; + + /// + /// The + /// + private readonly IExternalReferenceResolver externalReferenceResolver; + + /// + /// The + /// + private readonly IXmiReaderScope scope; + + /// + /// The + /// + private readonly IXmiReaderCache cache; + + /// + /// Initializes a new instance of the class. + /// + /// The + /// The + /// + /// The (injected) used to setup logging + /// + /// The of + /// The of + /// The + /// The + public XmiReader(IAssembler assembler, IXmiReaderCache cache, ILogger logger, IXmiElementReader packageReader, + IXmiElementReader modelReader, IExternalReferenceResolver externalReferenceResolver, IXmiReaderScope scope) + { + this.assembler = assembler; + this.cache = cache; + this.logger = logger; + this.packageReader = packageReader; + this.modelReader = modelReader; + this.externalReferenceResolver = externalReferenceResolver; + this.scope = scope; + } + + /// + /// Reads the content of a UML XMI 2.5.1 file asynchronously. + /// + /// + /// The URI of the XMI file to be read. + /// + /// + /// An representing the deserialized packages from the XMI file. + /// + public async Task> ReadAsync(string fileUri) + { + await using var fileStream = File.OpenRead(fileUri); + + var sw = Stopwatch.StartNew(); + + this.logger.LogTrace("start deserializing from {path}", fileUri); + + var result = await this.ReadAsync(fileStream); + + this.logger.LogTrace("File {path} deserialized in {time} [ms]", fileUri, sw.ElapsedMilliseconds); + + return result; + } + + /// + /// Reads the content of a UML XMI 2.5.1 stream asynchronously. + /// + /// + /// The that contains the XMI content to be read. + /// + /// + /// An representing the deserialized packages from the XMI stream. + /// + public async Task> ReadAsync(Stream stream) + { + return await this.Read(stream, true); + } + + /// + /// Reads the content of a UML XMI 2.5.1 stream asynchronously. + /// + /// + /// The that contains the XMI content to be read. + /// + /// + /// A value indicating whether the reading occurs on the root node. + /// + /// + /// An representing the deserialized packages from the XMI stream. + /// + private async Task> Read(Stream stream, bool isRoot) + { + var settings = new XmlReaderSettings() { Async = true }; + + stream.Seek(0, SeekOrigin.Begin); + + var sw = Stopwatch.StartNew(); + var packages = new List(); + + using (var xmlReader = XmlReader.Create(stream, settings)) + { + this.logger.LogTrace("starting to read xml"); + + while (await xmlReader.ReadAsync()) + { + if (xmlReader.NodeType == XmlNodeType.Element) + { + //TODO: this should probably be a full list of all kinds of UML classes, use codegen + switch (xmlReader.Name) + { + case "uml:Package": + using (var packageXmlReader = xmlReader.ReadSubtree()) + { + var package = this.packageReader.Read(packageXmlReader); + packages.Add(package); + } + + break; + case "uml:Model": + using (var modelXmlReader = xmlReader.ReadSubtree()) + { + var model = this.modelReader.Read(modelXmlReader); + packages.Add(model); + } + + break; + } + } + } + } + + var currentlyElapsedMilliseconds = sw.ElapsedMilliseconds; + this.logger.LogTrace("xml read in {time}", currentlyElapsedMilliseconds); + sw.Stop(); + + await this.TryResolveExternalReferences(); + + if (isRoot) + { + this.assembler.Synchronize(); + } + + return packages; + + } + + /// + /// Asynchronously resolves external references and updates the cache with the retrieved resources. + /// + /// A task that represents the asynchronous operation. + private async Task TryResolveExternalReferences() + { + var stopwatch = Stopwatch.StartNew(); + + await foreach (var (context, externalResource) in this.externalReferenceResolver.TryResolve()) + { + this.cache.SwitchContext(context); + this.Read(externalResource, false); + } + + this.logger.LogTrace("External eferences synchronized in {time}", stopwatch.ElapsedMilliseconds); + stopwatch.Stop(); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + this.cache.Cache.Clear(); + this.scope.Dispose(); + } + } +} diff --git a/uml4net.xmireader/Settings/DefaultSettings.cs b/uml4net.xmireader/Settings/DefaultSettings.cs new file mode 100644 index 00000000..86633f1a --- /dev/null +++ b/uml4net.xmireader/Settings/DefaultSettings.cs @@ -0,0 +1,30 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2019-2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, softwareUseCases +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace uml4net.xmi.Settings +{ + public class DefaultSettings : IXmiReaderSettings + { + /// + /// Gets or sets the UML_PROFILES value, as a local file path to resolve pathmap references + /// + public string UmlProfilesDirectoryPath { get; set; } = ""; + } +} diff --git a/uml4net.xmireader/Settings/IXmiReaderSettings.cs b/uml4net.xmireader/Settings/IXmiReaderSettings.cs new file mode 100644 index 00000000..e629d5f4 --- /dev/null +++ b/uml4net.xmireader/Settings/IXmiReaderSettings.cs @@ -0,0 +1,35 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2019-2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, softwareUseCases +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +using uml4net.xmi.Readers; + +namespace uml4net.xmi.Settings +{ + /// + /// The interface defines settings the requires in order to properly read + /// + public interface IXmiReaderSettings + { + /// + /// Gets or sets the UML_PROFILES value, as a local file path to resolve pathmap references + /// + string UmlProfilesDirectoryPath {get;set;} + } +} diff --git a/uml4net.xmireader/XmiReader.cs b/uml4net.xmireader/XmiReader.cs deleted file mode 100644 index e114c394..00000000 --- a/uml4net.xmireader/XmiReader.cs +++ /dev/null @@ -1,161 +0,0 @@ -// ------------------------------------------------------------------------------------------------- -// -// -// Copyright 2019-2024 Starion Group S.A. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, softwareUseCases -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// -// ------------------------------------------------------------------------------------------------ - -namespace uml4net.xmi -{ - using System.Collections.Generic; - using System.Diagnostics; - using System.IO; - using System.IO.Packaging; - using System.Xml; - - using Microsoft.Extensions.Logging; - using Microsoft.Extensions.Logging.Abstractions; - - using uml4net.POCO; - using uml4net.POCO.Packages; - - using uml4net.xmi.Packages; - - /// - /// The purpose of the is to provide a means to read (deserialize) - /// an UML 2.5.1 model from XMI - /// - public class XmiReader : IXmiReader - { - /// - /// The (injected) used to setup logging - /// - private readonly ILoggerFactory loggerFactory; - - /// - /// The used to log - /// - private readonly ILogger logger; - - /// - /// The cache in which each > is stored - /// - private readonly Dictionary cache; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The (injected) used to setup logging - /// - public XmiReader(ILoggerFactory loggerFactory = null) - { - this.loggerFactory = loggerFactory; - - this.logger = this.loggerFactory == null ? NullLogger.Instance : this.loggerFactory.CreateLogger(); - - this.cache = new Dictionary(); - } - - /// - /// reads the content of a UML XMI 2.5.1 file - /// - /// - /// the path to the XMI file - /// - /// - /// An - /// - public IEnumerable Read(string fileUri) - { - using (var fileStream = File.OpenRead(fileUri)) - { - var sw = Stopwatch.StartNew(); - - this.logger.LogTrace("start deserializing from {path}", fileUri); - - var result = this.Read(fileStream); - - this.logger.LogTrace("File {path} deserialized in {time} [ms]", fileUri, sw.ElapsedMilliseconds); - - return result; - } - } - - /// - /// reads the content of a UML XMI 2.5.1 stream - /// - /// - /// the that contains the XMI content - /// - /// - /// An - /// - public IEnumerable Read(Stream stream) - { - XmlReader xmlReader; - - var settings = new XmlReaderSettings(); - - stream.Seek(0, SeekOrigin.Begin); - - using (xmlReader = XmlReader.Create(stream, settings)) - { - var sw = Stopwatch.StartNew(); - - var packages = new List(); - - this.logger.LogTrace("starting to read xml"); - - while (xmlReader.Read()) - { - if (xmlReader.NodeType == XmlNodeType.Element) - { - //TODO: this should probably be a full list of all kinds of UML classes, use codegen - switch (xmlReader.Name) - { - case "uml:Package": - using (var packageXmlReader = xmlReader.ReadSubtree()) - { - var packageReader = new PackageReader(this.cache, this.loggerFactory); - var package = packageReader.Read(packageXmlReader); - packages.Add(package); - } - break; - case "uml:Model": - using (var modelXmlReader = xmlReader.ReadSubtree()) - { - var modelReader = new ModelReader(this.cache, this.loggerFactory); - var model = modelReader.Read(modelXmlReader); - packages.Add(model); - } - break; - } - } - } - - var currentlyElapsedMilliseconds = sw.ElapsedMilliseconds; - this.logger.LogTrace("xml read in {time}", currentlyElapsedMilliseconds); - - new Assembler(this.loggerFactory).Synchronize(this.cache); - this.logger.LogTrace("elements references synchronized in {time}", sw.ElapsedMilliseconds - currentlyElapsedMilliseconds); - sw.Stop(); - - return packages; - } - } - } -} diff --git a/uml4net.xmireader/XmiReaderBuilder.cs b/uml4net.xmireader/XmiReaderBuilder.cs new file mode 100644 index 00000000..14ad2440 --- /dev/null +++ b/uml4net.xmireader/XmiReaderBuilder.cs @@ -0,0 +1,101 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2019-2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, softwareUseCases +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace uml4net.xmi +{ + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Logging; + using Settings; + using System; + using System.Net.Http; + using Readers; + + /// + /// Provides builder methods to configure and create an instance of . + /// + public static class XmiReaderBuilder + { + /// + /// Creates a new instance of used to configure services for an XMI reader. + /// + /// + /// A new to configure and build the XMI reader. + /// + public static XmiReaderScope Create() + { + return new XmiReaderScope(); + } + + /// + /// Configures the to use the provided . + /// + /// The being configured. + /// The to be used by the XMI reader. + /// + /// The configured instance. + /// + public static XmiReaderScope UsingHttpClient(this XmiReaderScope scope, HttpClient client) + { + scope.ServiceCollection.AddScoped(_ => client); + return scope; + } + + /// + /// Configures the to use the provided instance. + /// + /// The being configured. + /// The to be used by the XMI reader. + /// + /// The configured instance. + /// + public static XmiReaderScope UsingSettings(this XmiReaderScope scope, IXmiReaderSettings settings) + { + scope.ServiceCollection.AddScoped(_ => settings); + return scope; + } + + /// + /// Configures the to use the provided for logging. + /// + /// The being configured. + /// The to be used for logging. + /// + /// The configured instance. + /// + public static XmiReaderScope WithLogger(this XmiReaderScope scope, ILoggerFactory loggerFactory) + { + scope.ServiceCollection.AddSingleton(loggerFactory); + return scope; + } + + /// + /// Builds and configures the based on the services added to the . + /// + /// The being used to build the XMI reader. + /// + /// A fully configured instance of . + /// + public static IXmiReader Build(this XmiReaderScope scope) + { + scope.CreateScope(); + return scope.Scope.ServiceProvider.GetRequiredService(); + } + } +} diff --git a/uml4net.xmireader/XmiReaderScope.cs b/uml4net.xmireader/XmiReaderScope.cs new file mode 100644 index 00000000..dbeed6b0 --- /dev/null +++ b/uml4net.xmireader/XmiReaderScope.cs @@ -0,0 +1,116 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright 2019-2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, softwareUseCases +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace uml4net.xmi +{ + using Cache; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Logging; + using POCO.Classification; + using POCO.CommonStructure; + using POCO.Packages; + using POCO.SimpleClassifiers; + using POCO.StructuredClassifiers; + using POCO.Values; + using System; + using System.Net.Http; + using Readers; + using Readers.Classification; + using Readers.CommonStructure; + using Readers.Packages; + using Readers.SimpleClassifiers; + using Readers.StructuredClassifiers; + using Readers.Values; + using Settings; + + /// + /// Represents the scope for configuring and managing services used by the XMI reader. + /// + public class XmiReaderScope : IXmiReaderScope + { + /// + /// Gets the service collection where all service registrations are stored. + /// + internal IServiceCollection ServiceCollection { get; } = new ServiceCollection(); + + /// + /// Gets the service provider responsible for resolving services. + /// + internal IServiceProvider ServiceProvider { get; private set; } + + /// + /// Gets the service scope which provides a scoped lifetime for services. + /// + internal IServiceScope Scope { get; private set; } + + /// + /// Builds the service provider and service scope from the configured service collection. + /// + internal void CreateScope() + { + this.ServiceProvider = this.ServiceCollection.BuildServiceProvider(); + this.Scope = this.ServiceProvider.CreateScope(); + } + + /// + /// Initializes a new instance of the class and configures default services. + /// + internal XmiReaderScope() + { + //Overridable services + this.ServiceCollection.AddScoped(_ => new HttpClient()); + this.ServiceCollection.AddScoped(); + this.ServiceCollection.AddSingleton(LoggerFactory.Create(builder => builder.AddConsole())); + + //Required services + this.ServiceCollection.AddScoped(typeof(ILogger<>), typeof(Logger<>)); + this.ServiceCollection.AddScoped(x => this); + this.ServiceCollection.AddScoped(); + this.ServiceCollection.AddScoped(); + this.ServiceCollection.AddScoped(); + this.ServiceCollection.AddScoped(); + + //Readers + this.ServiceCollection.AddScoped, GeneralizationReader>(); + this.ServiceCollection.AddScoped, PropertyReader>(); + this.ServiceCollection.AddScoped, CommentReader>(); + this.ServiceCollection.AddScoped, ConstraintReader>(); + this.ServiceCollection.AddScoped, PackageImportReader>(); + this.ServiceCollection.AddScoped, ModelReader>(); + this.ServiceCollection.AddScoped, PackageReader>(); + this.ServiceCollection.AddScoped, EnumerationLiteralReader>(); + this.ServiceCollection.AddScoped, EnumerationReader>(); + this.ServiceCollection.AddScoped, ClassReader>(); + this.ServiceCollection.AddScoped, LiteralIntegerReader>(); + this.ServiceCollection.AddScoped, LiteralUnlimitedNaturalReader>(); + this.ServiceCollection.AddScoped, OpaqueExpressionReader>(); + this.ServiceCollection.AddScoped, PrimitiveTypeReader>(); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + this.Scope.Dispose(); + this.ServiceCollection.Clear(); + } + } +} diff --git a/uml4net.xmireader/uml4net.xmi.csproj b/uml4net.xmireader/uml4net.xmi.csproj index d5394c99..bc51b1e1 100644 --- a/uml4net.xmireader/uml4net.xmi.csproj +++ b/uml4net.xmireader/uml4net.xmi.csproj @@ -1,8 +1,8 @@  - netstandard2.0 - 11.0 + net8.0 + 12.0 A .NET implementation of the OMG UML v2.5.1 specification. uml4net.xmi Starion Group S.A. @@ -23,8 +23,20 @@ + + + <_Parameter1>$(AssemblyName).Tests + + + + + + + - + + +