Skip to content

Commit d1686a7

Browse files
[Refactor] xmiwriter to generated classes
1 parent 2040638 commit d1686a7

179 files changed

Lines changed: 86156 additions & 426 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.

CLAUDE.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ Test framework: **NUnit**. Test classes use `[TestFixture]` and `[Test]` attribu
3131

3232
## Architecture
3333

34+
### Code Generation
35+
36+
- favour duplicated code in codegeneration to have staticaly defined methods that provide performance over reflection based code.
37+
- code generation is done by processing the UML model and creating handlebars templates
38+
3439
### Code Generation Pipeline
3540

3641
Most code in this repo is **auto-generated** — files marked `THIS IS AN AUTOMATICALLY GENERATED FILE. ANY MANUAL CHANGES WILL BE OVERWRITTEN!` must not be edited directly.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// -------------------------------------------------------------------------------------------------
2+
// <copyright file="UmlCoreXmiWriterGeneratorTestFixture.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.Tests.Generators.UmlHandleBarsGenerators
22+
{
23+
using System.IO;
24+
using System.Threading.Tasks;
25+
26+
using NUnit.Framework;
27+
28+
using SysML2.NET.CodeGenerator.Generators.UmlHandleBarsGenerators;
29+
30+
[TestFixture]
31+
public class UmlCoreXmiWriterGeneratorTestFixture
32+
{
33+
private DirectoryInfo umlXmiWriterDirectoryInfo;
34+
private UmlCoreXmiWriterGenerator umlCoreXmiWriterGenerator;
35+
36+
[OneTimeSetUp]
37+
public void OneTimeSetup()
38+
{
39+
var directoryInfo = new DirectoryInfo(TestContext.CurrentContext.TestDirectory);
40+
41+
var path = Path.Combine("UML", "_SysML2.NET.Serializer.Xmi.AutoGenWriters");
42+
43+
this.umlXmiWriterDirectoryInfo = directoryInfo.CreateSubdirectory(path);
44+
this.umlCoreXmiWriterGenerator = new UmlCoreXmiWriterGenerator();
45+
}
46+
47+
[Test]
48+
public async Task VerifyXmiWritersAreGenerated()
49+
{
50+
await Assert.ThatAsync(() => this.umlCoreXmiWriterGenerator.GenerateAsync(GeneratorSetupFixture.XmiReaderResult, this.umlXmiWriterDirectoryInfo), Throws.Nothing);
51+
}
52+
}
53+
}

SysML2.NET.CodeGenerator/Generators/UmlHandleBarsGenerators/UmlCoreXmiWriterGenerator.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ public class UmlCoreXmiWriterGenerator : UmlHandleBarsGenerator
4242
/// </summary>
4343
private const string XmiWriterTemplateName = "core-xmi-writer-template";
4444

45+
/// <summary>
46+
/// Gets the name of the Xmi Writer Facade template
47+
/// </summary>
48+
private const string XmiWriterFacadeTemplateName = "core-xmi-writer-facade-template";
49+
4550
/// <summary>
4651
/// Generates code specific to the concrete implementation
4752
/// </summary>
@@ -57,6 +62,7 @@ public class UmlCoreXmiWriterGenerator : UmlHandleBarsGenerator
5762
public override async Task GenerateAsync(XmiReaderResult xmiReaderResult, DirectoryInfo outputDirectory)
5863
{
5964
await this.GenerateXmiWriters(xmiReaderResult, outputDirectory);
65+
await this.GenerateXmiWriterFacade(xmiReaderResult, outputDirectory);
6066
}
6167

6268
/// <summary>
@@ -111,6 +117,53 @@ private async Task GenerateXmiWritersInternal(XmiReaderResult xmiReaderResult, D
111117
}
112118
}
113119

120+
/// <summary>
121+
/// Generates XMI Writer facade class for all concrete <see cref="IClass" />
122+
/// </summary>
123+
/// <param name="xmiReaderResult">the <see cref="XmiReaderResult" /> that contains the UML model to generate from</param>
124+
/// <param name="outputDirectory">
125+
/// The target <see cref="DirectoryInfo" />
126+
/// </param>
127+
/// <returns>
128+
/// an awaitable <see cref="Task" />
129+
/// </returns>
130+
/// <exception cref="ArgumentNullException">
131+
/// In case of null value for <paramref name="xmiReaderResult" /> or
132+
/// <paramref name="outputDirectory" />
133+
/// </exception>
134+
private Task GenerateXmiWriterFacade(XmiReaderResult xmiReaderResult, DirectoryInfo outputDirectory)
135+
{
136+
ArgumentNullException.ThrowIfNull(xmiReaderResult);
137+
ArgumentNullException.ThrowIfNull(outputDirectory);
138+
139+
return this.GenerateXmiWriterFacadeInternal(xmiReaderResult, outputDirectory);
140+
}
141+
142+
/// <summary>
143+
/// Generates XMI Writer facade class for all concrete <see cref="IClass" />
144+
/// </summary>
145+
/// <param name="xmiReaderResult">the <see cref="XmiReaderResult" /> that contains the UML model to generate from</param>
146+
/// <param name="outputDirectory">
147+
/// The target <see cref="DirectoryInfo" />
148+
/// </param>
149+
/// <returns>
150+
/// an awaitable <see cref="Task" />
151+
/// </returns>
152+
private async Task GenerateXmiWriterFacadeInternal(XmiReaderResult xmiReaderResult, DirectoryInfo outputDirectory)
153+
{
154+
var template = this.Templates[XmiWriterFacadeTemplateName];
155+
156+
var classes = xmiReaderResult.QueryContainedAndImported("SysML")
157+
.SelectMany(x => x.PackagedElement.OfType<IClass>())
158+
.Where(x => !x.IsAbstract)
159+
.OrderBy(x => x.Name)
160+
.ToList();
161+
162+
var generatedFacade = template(classes);
163+
generatedFacade = this.CodeCleanup(generatedFacade);
164+
await WriteAsync(generatedFacade, outputDirectory, "XmiDataWriterFacade.cs");
165+
}
166+
114167
/// <summary>
115168
/// Register the custom helpers
116169
/// </summary>
@@ -131,7 +184,9 @@ protected override void RegisterHelpers()
131184
protected override void RegisterTemplates()
132185
{
133186
this.RegisterTemplate(XmiWriterTemplateName);
187+
this.RegisterTemplate(XmiWriterFacadeTemplateName);
134188
this.RegisterPartialTemplate("core-xmi-writer-partial-for-attribute-template");
189+
this.RegisterPartialTemplate("core-xmi-writer-partial-for-element-template");
135190
}
136191
}
137192
}

SysML2.NET.CodeGenerator/SysML2.NET.CodeGenerator.csproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,18 @@
211211
<None Update="Templates\Uml\core-poco-reference-resolve-extension-facade-template.hbs">
212212
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
213213
</None>
214+
<None Update="Templates\Uml\core-xmi-writer-template.hbs">
215+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
216+
</None>
217+
<None Update="Templates\Uml\core-xmi-writer-facade-template.hbs">
218+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
219+
</None>
220+
<None Update="Templates\Uml\Partials\core-xmi-writer-partial-for-attribute-template.hbs">
221+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
222+
</None>
223+
<None Update="Templates\Uml\Partials\core-xmi-writer-partial-for-element-template.hbs">
224+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
225+
</None>
214226
</ItemGroup>
215227
<ItemGroup>
216228
<Folder Include="Resources\HtmlDocs\" />
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{{#if (Property.QueryIsEnumerable property)}}
2+
if(poco.{{Property.WritePropertyName property}} != null)
3+
{
4+
foreach(var item in poco.{{Property.WritePropertyName property}})
5+
{
6+
{{#if property.IsComposite}}
7+
xmiWriterFacade.WriteContainedElement(xmlWriter, (IData)item, "{{String.LowerCaseFirstLetter property.Name}}", includeDerivedProperties, elementOriginMap, currentFileUri);
8+
{{else}}
9+
xmiWriterFacade.WriteReferenceElement(xmlWriter, (IData)item, "{{String.LowerCaseFirstLetter property.Name}}", elementOriginMap, currentFileUri);
10+
{{/if}}
11+
}
12+
}
13+
{{else}}
14+
if(poco.{{Property.WritePropertyName property}} != null)
15+
{
16+
{{#if property.IsComposite}}
17+
xmiWriterFacade.WriteContainedElement(xmlWriter, (IData)poco.{{Property.WritePropertyName property}}, "{{String.LowerCaseFirstLetter property.Name}}", includeDerivedProperties, elementOriginMap, currentFileUri);
18+
{{else}}
19+
xmiWriterFacade.WriteReferenceElement(xmlWriter, (IData)poco.{{Property.WritePropertyName property}}, "{{String.LowerCaseFirstLetter property.Name}}", elementOriginMap, currentFileUri);
20+
{{/if}}
21+
}
22+
{{/if}}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// -------------------------------------------------------------------------------------------------
2+
// <copyright file="XmiDataWriterFacade.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+
// ------------------------------------------------------------------------------------------------
22+
// --------THIS IS AN AUTOMATICALLY GENERATED FILE. ANY MANUAL CHANGES WILL BE OVERWRITTEN!--------
23+
// ------------------------------------------------------------------------------------------------
24+
25+
namespace SysML2.NET.Serializer.Xmi.Writers
26+
{
27+
using System;
28+
using System.Collections.Generic;
29+
using System.Xml;
30+
31+
using SysML2.NET.Common;
32+
using SysML2.NET.Core.POCO.Root.Elements;
33+
34+
/// <summary>
35+
/// The purpose of the <see cref="XmiDataWriterFacade"/> is to dispatch writing of <see cref="IData"/> instances
36+
/// to the appropriate per-type static writer class
37+
/// </summary>
38+
public class XmiDataWriterFacade : IXmiDataWriterFacade
39+
{
40+
/// <summary>
41+
/// A dictionary that contains actions that write <see cref="IData"/> based on a key that represents the POCO type name
42+
/// </summary>
43+
private readonly Dictionary<string, Action<XmlWriter, IData, string, bool, IXmiDataWriterFacade, IXmiElementOriginMap, Uri>> writerCache;
44+
45+
/// <summary>
46+
/// Initializes a new instance of the <see cref="XmiDataWriterFacade"/>
47+
/// </summary>
48+
public XmiDataWriterFacade()
49+
{
50+
this.writerCache = new Dictionary<string, Action<XmlWriter, IData, string, bool, IXmiDataWriterFacade, IXmiElementOriginMap, Uri>>
51+
{
52+
{{ #each this as | class | }}
53+
["{{ class.Name }}"] = (xmlWriter, data, elementName, includeDerived, facade, originMap, uri) =>
54+
{{ class.Name }}Writer.Write(xmlWriter, (Core.POCO.{{ #NamedElement.WriteFullyQualifiedNameSpace class }}.I{{ class.Name }})data, elementName, includeDerived, facade, originMap, uri),
55+
{{/each}}
56+
};
57+
}
58+
59+
/// <summary>
60+
/// Writes the specified <see cref="IData"/> as an XMI element by dispatching to the appropriate per-type writer
61+
/// </summary>
62+
/// <param name="xmlWriter">The target <see cref="XmlWriter"/></param>
63+
/// <param name="data">The <see cref="IData"/> to write</param>
64+
/// <param name="elementName">The XML element name to use</param>
65+
/// <param name="includeDerivedProperties">Whether to include derived properties</param>
66+
/// <param name="elementOriginMap">The optional <see cref="IXmiElementOriginMap"/> for href reconstruction</param>
67+
/// <param name="currentFileUri">The <see cref="Uri"/> of the current output file for relative href computation</param>
68+
public void Write(XmlWriter xmlWriter, IData data, string elementName, bool includeDerivedProperties, IXmiElementOriginMap elementOriginMap = null, Uri currentFileUri = null)
69+
{
70+
var typeName = data.GetType().Name;
71+
72+
if (this.writerCache.TryGetValue(typeName, out var writer))
73+
{
74+
writer(xmlWriter, data, elementName, includeDerivedProperties, this, elementOriginMap, currentFileUri);
75+
}
76+
else
77+
{
78+
throw new InvalidOperationException($"No writer found for type {typeName}");
79+
}
80+
}
81+
82+
/// <summary>
83+
/// Writes a contained child element, checking origin map for cross-file href
84+
/// </summary>
85+
/// <param name="xmlWriter">The target <see cref="XmlWriter"/></param>
86+
/// <param name="childData">The child <see cref="IData"/> to write</param>
87+
/// <param name="elementName">The XML element name to use</param>
88+
/// <param name="includeDerivedProperties">Whether to include derived properties</param>
89+
/// <param name="elementOriginMap">The optional <see cref="IXmiElementOriginMap"/> for href reconstruction</param>
90+
/// <param name="currentFileUri">The <see cref="Uri"/> of the current output file for relative href computation</param>
91+
public void WriteContainedElement(XmlWriter xmlWriter, IData childData, string elementName, bool includeDerivedProperties, IXmiElementOriginMap elementOriginMap, Uri currentFileUri)
92+
{
93+
if (elementOriginMap != null && currentFileUri != null)
94+
{
95+
var childSourceFile = elementOriginMap.GetSourceFile(childData.Id);
96+
97+
if (childSourceFile != null && childSourceFile != currentFileUri)
98+
{
99+
WriteHrefElement(xmlWriter, childData, elementName, childSourceFile, currentFileUri);
100+
return;
101+
}
102+
}
103+
104+
this.Write(xmlWriter, childData, elementName, includeDerivedProperties, elementOriginMap, currentFileUri);
105+
}
106+
107+
/// <summary>
108+
/// Writes a reference child element, checking origin map for cross-file href
109+
/// </summary>
110+
/// <param name="xmlWriter">The target <see cref="XmlWriter"/></param>
111+
/// <param name="childData">The child <see cref="IData"/> to write</param>
112+
/// <param name="elementName">The XML element name to use</param>
113+
/// <param name="elementOriginMap">The optional <see cref="IXmiElementOriginMap"/> for href reconstruction</param>
114+
/// <param name="currentFileUri">The <see cref="Uri"/> of the current output file for relative href computation</param>
115+
public void WriteReferenceElement(XmlWriter xmlWriter, IData childData, string elementName, IXmiElementOriginMap elementOriginMap, Uri currentFileUri)
116+
{
117+
if (elementOriginMap != null && currentFileUri != null)
118+
{
119+
var childSourceFile = elementOriginMap.GetSourceFile(childData.Id);
120+
121+
if (childSourceFile != null && childSourceFile != currentFileUri)
122+
{
123+
WriteHrefElement(xmlWriter, childData, elementName, childSourceFile, currentFileUri);
124+
return;
125+
}
126+
}
127+
128+
xmlWriter.WriteStartElement(elementName);
129+
xmlWriter.WriteAttributeString("xmi", "idref", null, childData.Id.ToString());
130+
xmlWriter.WriteEndElement();
131+
}
132+
133+
/// <summary>
134+
/// Writes an href element for cross-file references
135+
/// </summary>
136+
private static void WriteHrefElement(XmlWriter xmlWriter, IData childData, string elementName, Uri targetFile, Uri currentFile)
137+
{
138+
var relativePath = currentFile.MakeRelativeUri(targetFile);
139+
var href = $"{Uri.UnescapeDataString(relativePath.ToString())}#{childData.Id}";
140+
141+
xmlWriter.WriteStartElement(elementName);
142+
xmlWriter.WriteAttributeString("href", href);
143+
xmlWriter.WriteEndElement();
144+
}
145+
}
146+
}
147+
148+
// ------------------------------------------------------------------------------------------------
149+
// --------THIS IS AN AUTOMATICALLY GENERATED FILE. ANY MANUAL CHANGES WILL BE OVERWRITTEN!--------
150+
// ------------------------------------------------------------------------------------------------

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

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ namespace SysML2.NET.Serializer.Xmi.Writers
4848
/// <param name="poco">The <see cref="I{{this.Name}}" /> to write</param>
4949
/// <param name="elementName">The XML element name</param>
5050
/// <param name="includeDerivedProperties">Whether to include derived properties</param>
51-
/// <param name="xmiWriter">The <see cref="IXmiWriter" /> for writing child elements</param>
51+
/// <param name="xmiWriterFacade">The <see cref="IXmiDataWriterFacade" /> for writing child elements</param>
5252
/// <param name="elementOriginMap">The optional <see cref="IXmiElementOriginMap" /> for href reconstruction</param>
5353
/// <param name="currentFileUri">The optional <see cref="Uri" /> of the current output file</param>
54-
public static void Write(XmlWriter xmlWriter, I{{this.Name}} poco, string elementName, bool includeDerivedProperties, IXmiWriter xmiWriter, IXmiElementOriginMap elementOriginMap = null, Uri currentFileUri = null)
54+
public static void Write(XmlWriter xmlWriter, I{{this.Name}} poco, string elementName, bool includeDerivedProperties, IXmiDataWriterFacade xmiWriterFacade, IXmiElementOriginMap elementOriginMap = null, Uri currentFileUri = null)
5555
{
5656
xmlWriter.WriteStartElement(elementName);
5757
xmlWriter.WriteAttributeString("xsi", "type", null, "sysml:{{this.Name}}");
@@ -97,14 +97,32 @@ namespace SysML2.NET.Serializer.Xmi.Writers
9797
{{#unless this.IsDerived}}
9898
{{#if (Property.QueryIsReferenceProperty this)}}
9999
{{#unless (Property.IsPropertyRedefinedInClass this class)}}
100-
// {{Property.WritePropertyName this}} child elements handled by reflection-based XmiWriter
100+
{{> core-xmi-writer-partial-for-element-template this}}
101101
{{/unless}}
102102
{{/if}}
103103
{{/unless}}
104104
{{/unless}}
105105
{{/each}}
106106
{{/with}}
107107
108+
// Derived reference/containment properties as child elements
109+
if(includeDerivedProperties)
110+
{
111+
{{#with this as |class| }}
112+
{{ #each (Class.QueryAllProperties this) as | property | }}
113+
{{#unless this.IsTransient}}
114+
{{#if this.IsDerived}}
115+
{{#if (Property.QueryIsReferenceProperty this)}}
116+
{{#unless (Property.IsPropertyRedefinedInClass this class)}}
117+
{{> core-xmi-writer-partial-for-element-template this}}
118+
{{/unless}}
119+
{{/if}}
120+
{{/if}}
121+
{{/unless}}
122+
{{/each}}
123+
{{/with}}
124+
}
125+
108126
xmlWriter.WriteEndElement();
109127
}
110128
}

0 commit comments

Comments
 (0)