Skip to content

Commit 2040638

Browse files
[Add] XmiElementOriginMap to track from which xmi file an IElement was sourced
[Add] first implementatino of the XMI serializer
1 parent 66d01b9 commit 2040638

187 files changed

Lines changed: 4121 additions & 1908 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// -------------------------------------------------------------------------------------------------
2+
// <copyright file="UmlCoreXmiWriterGenerator.cs" company="Starion Group S.A.">
3+
//
4+
// Copyright 2022-2026 Starion Group S.A.
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
//
18+
// </copyright>
19+
// ------------------------------------------------------------------------------------------------
20+
21+
namespace SysML2.NET.CodeGenerator.Generators.UmlHandleBarsGenerators
22+
{
23+
using System;
24+
using System.IO;
25+
using System.Linq;
26+
using System.Threading.Tasks;
27+
28+
using SysML2.NET.CodeGenerator.Extensions;
29+
using SysML2.NET.CodeGenerator.HandleBarHelpers;
30+
31+
using uml4net.Extensions;
32+
using uml4net.StructuredClassifiers;
33+
using uml4net.xmi.Readers;
34+
35+
/// <summary>
36+
/// A UML Handlebars based XMI Writer code generator
37+
/// </summary>
38+
public class UmlCoreXmiWriterGenerator : UmlHandleBarsGenerator
39+
{
40+
/// <summary>
41+
/// Gets the name of the Xmi Writer template
42+
/// </summary>
43+
private const string XmiWriterTemplateName = "core-xmi-writer-template";
44+
45+
/// <summary>
46+
/// Generates code specific to the concrete implementation
47+
/// </summary>
48+
/// <param name="xmiReaderResult">
49+
/// the <see cref="XmiReaderResult" /> that contains the UML model to generate from
50+
/// </param>
51+
/// <param name="outputDirectory">
52+
/// The target <see cref="DirectoryInfo" />
53+
/// </param>
54+
/// <returns>
55+
/// an awaitable <see cref="Task" />
56+
/// </returns>
57+
public override async Task GenerateAsync(XmiReaderResult xmiReaderResult, DirectoryInfo outputDirectory)
58+
{
59+
await this.GenerateXmiWriters(xmiReaderResult, outputDirectory);
60+
}
61+
62+
/// <summary>
63+
/// Generates XMI Writer classes for all concrete <see cref="IClass" />
64+
/// </summary>
65+
/// <param name="xmiReaderResult">the <see cref="XmiReaderResult" /> that contains the UML model to generate from</param>
66+
/// <param name="outputDirectory">
67+
/// The target <see cref="DirectoryInfo" />
68+
/// </param>
69+
/// <returns>
70+
/// an awaitable <see cref="Task" />
71+
/// </returns>
72+
/// <exception cref="ArgumentNullException">
73+
/// In case of null value for <paramref name="xmiReaderResult" /> or
74+
/// <paramref name="outputDirectory" />
75+
/// </exception>
76+
private Task GenerateXmiWriters(XmiReaderResult xmiReaderResult, DirectoryInfo outputDirectory)
77+
{
78+
ArgumentNullException.ThrowIfNull(xmiReaderResult);
79+
ArgumentNullException.ThrowIfNull(outputDirectory);
80+
81+
return this.GenerateXmiWritersInternal(xmiReaderResult, outputDirectory);
82+
}
83+
84+
/// <summary>
85+
/// Generates XMI Writer classes for all concrete <see cref="IClass" />
86+
/// </summary>
87+
/// <param name="xmiReaderResult">the <see cref="XmiReaderResult" /> that contains the UML model to generate from</param>
88+
/// <param name="outputDirectory">
89+
/// The target <see cref="DirectoryInfo" />
90+
/// </param>
91+
/// <returns>
92+
/// an awaitable <see cref="Task" />
93+
/// </returns>
94+
private async Task GenerateXmiWritersInternal(XmiReaderResult xmiReaderResult, DirectoryInfo outputDirectory)
95+
{
96+
var template = this.Templates[XmiWriterTemplateName];
97+
98+
var classes = xmiReaderResult.QueryContainedAndImported("SysML")
99+
.SelectMany(x => x.PackagedElement.OfType<IClass>())
100+
.Where(x => !x.IsAbstract)
101+
.ToList();
102+
103+
foreach (var umlClass in classes)
104+
{
105+
var generatedXmiWriter = template(umlClass);
106+
107+
generatedXmiWriter = this.CodeCleanup(generatedXmiWriter);
108+
109+
var fileName = $"{umlClass.Name.CapitalizeFirstLetter()}Writer.cs";
110+
await WriteAsync(generatedXmiWriter, outputDirectory, fileName);
111+
}
112+
}
113+
114+
/// <summary>
115+
/// Register the custom helpers
116+
/// </summary>
117+
protected override void RegisterHelpers()
118+
{
119+
this.Handlebars.RegisterNamedElementHelper();
120+
this.Handlebars.RegisterSafeContextHelper();
121+
this.Handlebars.RegisterClassHelper();
122+
SysML2.NET.CodeGenerator.HandleBarHelpers.PropertyHelper.RegisterPropertyHelper(this.Handlebars);
123+
uml4net.HandleBars.StringHelper.RegisterStringHelper(this.Handlebars);
124+
uml4net.HandleBars.ClassHelper.RegisterClassHelper(this.Handlebars);
125+
uml4net.HandleBars.PropertyHelper.RegisterPropertyHelper(this.Handlebars);
126+
}
127+
128+
/// <summary>
129+
/// Register the code templates
130+
/// </summary>
131+
protected override void RegisterTemplates()
132+
{
133+
this.RegisterTemplate(XmiWriterTemplateName);
134+
this.RegisterPartialTemplate("core-xmi-writer-partial-for-attribute-template");
135+
}
136+
}
137+
}

SysML2.NET.CodeGenerator/Templates/Uml/Partials/core-xmi-reader-partial-for-element-template.hbs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ case"{{String.LowerCaseFirstLetter property.Name}}":
1717
else
1818
{
1919
{{#if asyncState}}
20-
var {{String.LowerCaseFirstLetter property.Name}}Value = (I{{property.Type.Name}})await this.XmiDataReaderFacade.QueryXmiDataAsync(xmiReader, this.Cache, currentLocation, this.ExternalReferenceService, this.LoggerFactory);
20+
var {{String.LowerCaseFirstLetter property.Name}}Value = (I{{property.Type.Name}})await this.XmiDataReaderFacade.QueryXmiDataAsync(xmiReader, this.Cache, currentLocation, this.ExternalReferenceService, this.LoggerFactory, elementOriginMap: this.ElementOriginMap);
2121
{{else}}
22-
var {{String.LowerCaseFirstLetter property.Name}}Value = (I{{property.Type.Name}})this.XmiDataReaderFacade.QueryXmiData(xmiReader, this.Cache, currentLocation, this.ExternalReferenceService, this.LoggerFactory);
22+
var {{String.LowerCaseFirstLetter property.Name}}Value = (I{{property.Type.Name}})this.XmiDataReaderFacade.QueryXmiData(xmiReader, this.Cache, currentLocation, this.ExternalReferenceService, this.LoggerFactory, elementOriginMap: this.ElementOriginMap);
2323
{{/if}}
2424

2525
{{#if (Property.QueryIsEnumerable property)}}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{{#if (Property.QueryIsEnumerable property)}}
2+
{{#if (Property.QueryIsBool property)}}
3+
if(poco.{{Property.WritePropertyName property}} != null && poco.{{Property.WritePropertyName property}}.Count > 0)
4+
{
5+
xmlWriter.WriteAttributeString("{{String.LowerCaseFirstLetter property.Name}}", string.Join(" ", poco.{{Property.WritePropertyName property}}));
6+
}
7+
{{else}}
8+
if(poco.{{Property.WritePropertyName property}} != null && poco.{{Property.WritePropertyName property}}.Count > 0)
9+
{
10+
xmlWriter.WriteAttributeString("{{String.LowerCaseFirstLetter property.Name}}", string.Join(" ", poco.{{Property.WritePropertyName property}}));
11+
}
12+
{{/if}}
13+
{{else}}
14+
{{#if (Property.QueryIsBool property)}}
15+
if(poco.{{Property.WritePropertyName property}})
16+
{
17+
xmlWriter.WriteAttributeString("{{String.LowerCaseFirstLetter property.Name}}", "true");
18+
}
19+
{{else if (Property.QueryIsEnum property)}}
20+
xmlWriter.WriteAttributeString("{{String.LowerCaseFirstLetter property.Name}}", poco.{{Property.WritePropertyName property}}.ToString());
21+
{{else if (Property.QueryIsNumeric property)}}
22+
xmlWriter.WriteAttributeString("{{String.LowerCaseFirstLetter property.Name}}", poco.{{Property.WritePropertyName property}}.ToString(CultureInfo.InvariantCulture));
23+
{{else}}
24+
if(!string.IsNullOrWhiteSpace(poco.{{Property.WritePropertyName property}}))
25+
{
26+
xmlWriter.WriteAttributeString("{{String.LowerCaseFirstLetter property.Name}}", poco.{{Property.WritePropertyName property}});
27+
}
28+
{{/if}}
29+
{{/if}}

SysML2.NET.CodeGenerator/Templates/Uml/core-xmi-reader-facade-template.hbs

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -44,38 +44,38 @@ namespace SysML2.NET.Serializer.Xmi.Readers
4444
/// A dictionary that contains functions that return <see cref="IData"/> based a key that represents the xsi Type
4545
/// and a provided <see cref="IXmiDataCache"/>, <see cref="IExternalReferenceService" />, <see cref="ILoggerFactory"/> and <see cref="XmlReader"/>
4646
/// </summary>
47-
private readonly Dictionary<string, Func<IXmiDataCache, IExternalReferenceService, ILoggerFactory, XmlReader, Uri, IData>> readerCache;
47+
private readonly Dictionary<string, Func<IXmiDataCache, IExternalReferenceService, ILoggerFactory, XmlReader, Uri, IXmiElementOriginMap, IData>> readerCache;
4848
4949
/// <summary>
5050
/// A dictionary that contains functions that return an awaitable <see cref="Task" /> with the <see cref="IData"/> based a key that represents the xsi Type
5151
/// and a provided <see cref="IXmiDataCache"/>, <see cref="IExternalReferenceService" />, <see cref="ILoggerFactory"/> and <see cref="XmlReader"/>
5252
/// </summary>
53-
private readonly Dictionary<string, Func<IXmiDataCache, IExternalReferenceService, ILoggerFactory, XmlReader, Uri, Task<IData>>> readerAsyncCache;
53+
private readonly Dictionary<string, Func<IXmiDataCache, IExternalReferenceService, ILoggerFactory, XmlReader, Uri, IXmiElementOriginMap, Task<IData>>> readerAsyncCache;
5454
5555
/// <summary>
5656
/// Initializes a new instance of the <see cref="XmiDataReaderFacade"/>
5757
/// </summary>
5858
public XmiDataReaderFacade()
5959
{
60-
this.readerCache = new Dictionary<string, Func<IXmiDataCache, IExternalReferenceService,ILoggerFactory, XmlReader, Uri, IData>>
60+
this.readerCache = new Dictionary<string, Func<IXmiDataCache, IExternalReferenceService, ILoggerFactory, XmlReader, Uri, IXmiElementOriginMap, IData>>
6161
{
6262
{{ #each this as | class | }}
63-
["sysml:{{ class.Name }}"] = (cache, externalReferenceService, loggerFactory, xmlReader, currentLocation) =>
63+
["sysml:{{ class.Name }}"] = (cache, externalReferenceService, loggerFactory, xmlReader, currentLocation, elementOriginMap) =>
6464
{
6565
using var subXmlReader = xmlReader.ReadSubtree();
66-
var {{ String.LowerCaseFirstLetter class.Name }}Reader = new {{ class.Name }}Reader(cache, this, externalReferenceService, loggerFactory);
66+
var {{ String.LowerCaseFirstLetter class.Name }}Reader = new {{ class.Name }}Reader(cache, this, externalReferenceService, loggerFactory, elementOriginMap);
6767
return {{ String.LowerCaseFirstLetter class.Name }}Reader.Read(subXmlReader, currentLocation);
6868
},
6969
{{/each}}
7070
};
7171
72-
this.readerAsyncCache = new Dictionary<string, Func<IXmiDataCache, IExternalReferenceService,ILoggerFactory, XmlReader, Uri, Task<IData>>>
72+
this.readerAsyncCache = new Dictionary<string, Func<IXmiDataCache, IExternalReferenceService, ILoggerFactory, XmlReader, Uri, IXmiElementOriginMap, Task<IData>>>
7373
{
7474
{{ #each this as | class | }}
75-
["sysml:{{ class.Name }}"] = async (cache, externalReferenceService, loggerFactory, xmlReader, currentLocation) =>
75+
["sysml:{{ class.Name }}"] = async (cache, externalReferenceService, loggerFactory, xmlReader, currentLocation, elementOriginMap) =>
7676
{
7777
using var subXmlReader = xmlReader.ReadSubtree();
78-
var {{ String.LowerCaseFirstLetter class.Name }}Reader = new {{ class.Name }}Reader(cache, this, externalReferenceService, loggerFactory);
78+
var {{ String.LowerCaseFirstLetter class.Name }}Reader = new {{ class.Name }}Reader(cache, this, externalReferenceService, loggerFactory, elementOriginMap);
7979
return await {{ String.LowerCaseFirstLetter class.Name }}Reader.ReadAsync(subXmlReader, currentLocation);
8080
},
8181
{{/each}}
@@ -95,7 +95,7 @@ namespace SysML2.NET.Serializer.Xmi.Readers
9595
/// <param name="explicitTypeName">The explicit type name to resolve, in case of un-specified xsi:type</param>
9696
/// <returns>An instance of the read <see cref="IData"/></returns>
9797
/// <exception cref="InvalidOperationException">If the xsi:type is not supported</exception>
98-
public IData QueryXmiData(XmlReader xmiReader, IXmiDataCache xmiDataCache, Uri currentLocation, IExternalReferenceService externalReferenceService, ILoggerFactory loggerFactory, string explicitTypeName = "")
98+
public IData QueryXmiData(XmlReader xmiReader, IXmiDataCache xmiDataCache, Uri currentLocation, IExternalReferenceService externalReferenceService, ILoggerFactory loggerFactory, string explicitTypeName = "", IXmiElementOriginMap elementOriginMap = null)
9999
{
100100
AssertValidQueryXmiDataParameters(xmiReader, xmiDataCache, currentLocation);
101101
@@ -107,12 +107,12 @@ namespace SysML2.NET.Serializer.Xmi.Readers
107107
}
108108
109109
xsiType ??= explicitTypeName;
110-
110+
111111
if (this.readerCache.TryGetValue(xsiType, out var readerFactory))
112112
{
113-
return readerFactory(xmiDataCache, externalReferenceService, loggerFactory, xmiReader, currentLocation);
113+
return readerFactory(xmiDataCache, externalReferenceService, loggerFactory, xmiReader, currentLocation, elementOriginMap);
114114
}
115-
115+
116116
throw new InvalidOperationException($"No reader found for xsi:type - {xsiType}");
117117
}
118118
@@ -129,24 +129,24 @@ namespace SysML2.NET.Serializer.Xmi.Readers
129129
/// <param name="explicitTypeName">The explicit type name to resolve, in case of un-specified xsi:type</param>
130130
/// <returns>An awaitable <see cref="Task" /> with the instance of the read <see cref="IData"/></returns>
131131
/// <exception cref="InvalidOperationException">If the xsi:type is not supported</exception>
132-
public Task<IData> QueryXmiDataAsync(XmlReader xmiReader, IXmiDataCache xmiDataCache, Uri currentLocation, IExternalReferenceService externalReferenceService, ILoggerFactory loggerFactory, string explicitTypeName = "")
132+
public Task<IData> QueryXmiDataAsync(XmlReader xmiReader, IXmiDataCache xmiDataCache, Uri currentLocation, IExternalReferenceService externalReferenceService, ILoggerFactory loggerFactory, string explicitTypeName = "", IXmiElementOriginMap elementOriginMap = null)
133133
{
134134
AssertValidQueryXmiDataParameters(xmiReader, xmiDataCache, currentLocation);
135-
135+
136136
var xsiType = xmiReader.GetAttribute("xsi:type");
137-
137+
138138
if (xsiType == null && string.IsNullOrEmpty(explicitTypeName))
139139
{
140140
throw new InvalidOperationException($"The xsi:type is not specified");
141141
}
142-
142+
143143
xsiType ??= explicitTypeName;
144-
144+
145145
if (this.readerAsyncCache.TryGetValue(xsiType, out var readerFactory))
146146
{
147-
return readerFactory(xmiDataCache, externalReferenceService, loggerFactory, xmiReader, currentLocation);
147+
return readerFactory(xmiDataCache, externalReferenceService, loggerFactory, xmiReader, currentLocation, elementOriginMap);
148148
}
149-
149+
150150
throw new InvalidOperationException($"No reader found for xsi:type - {xsiType}");
151151
}
152152

SysML2.NET.CodeGenerator/Templates/Uml/core-xmi-reader-template.hbs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ namespace SysML2.NET.Serializer.Xmi.Readers
6161
/// </param>
6262
/// <param name="externalReferenceService">The injected <see cref="IExternalReferenceService"/> used to register and process external references</param>
6363
/// <param name="loggerFactory">The injected <see cref="ILoggerFactory" /> used to set up logging</param>
64-
public {{this.Name}}Reader(IXmiDataCache cache, IXmiDataReaderFacade xmiDataReaderFacade, IExternalReferenceService externalReferenceService, ILoggerFactory loggerFactory) : base(cache, xmiDataReaderFacade, externalReferenceService, loggerFactory)
64+
/// <param name="elementOriginMap">The optional <see cref="IXmiElementOriginMap"/> used to track element-to-file associations</param>
65+
public {{this.Name}}Reader(IXmiDataCache cache, IXmiDataReaderFacade xmiDataReaderFacade, IExternalReferenceService externalReferenceService, ILoggerFactory loggerFactory, IXmiElementOriginMap elementOriginMap = null) : base(cache, xmiDataReaderFacade, externalReferenceService, loggerFactory, elementOriginMap)
6566
{
6667
this.logger = loggerFactory == null ? NullLogger<{{this.Name}}Reader>.Instance : loggerFactory.CreateLogger<{{this.Name}}Reader>();
6768
}
@@ -107,6 +108,8 @@ namespace SysML2.NET.Serializer.Xmi.Readers
107108
this.logger.LogCritical("Failed to add element type [{Poco}] with id [{Id}] as it was already in the Cache. The XMI document seems to have duplicate xmi:id values", "{{this.Name}}", poco.Id);
108109
}
109110
111+
this.ElementOriginMap?.Register(poco.Id, currentLocation);
112+
110113
{{#with this as |class| }}
111114
{{ #each (Class.QueryAllProperties this) as | property | }}
112115
{{#unless this.IsTransient}}
@@ -187,6 +190,8 @@ namespace SysML2.NET.Serializer.Xmi.Readers
187190
{
188191
this.logger.LogCritical("Failed to add element type [{Poco}] with id [{Id}] as it was already in the Cache. The XMI document seems to have duplicate xmi:id values", "{{this.Name}}", poco.Id);
189192
}
193+
194+
this.ElementOriginMap?.Register(poco.Id, currentLocation);
190195
191196
{{#with this as |class| }}
192197
{{ #each (Class.QueryAllProperties this) as | property | }}

0 commit comments

Comments
 (0)