Skip to content

Commit f05a5b7

Browse files
#1 - Unsafe Parse() → TryParse() in element template (core-xmi-reader-partial-for-element-template.hbs)
- Replaced bool.Parse(), Enum.Parse(), int.Parse(), double.Parse() with TryParse() patterns - Replaced Guid.Parse() for href references with Guid.TryParse() - All failures now log warnings with property name, value, and element ID #2 - Missing default case in reader switch (core-xmi-reader-template.hbs) - Added default: case to both sync and async switch(xmiReader.LocalName) blocks - Logs warning with unexpected element name and line/position info - Calls xmiReader.Skip() / xmiReader.SkipAsync() to explicitly advance past unrecognized elements #3 - Writer instance caching (core-xmi-writer-facade-template.hbs) - Writer instances are now created once in the constructor (168 local variables) - Captured by lambda closures — no new allocations per element dispatch - Removed the loggerFactory field (no longer needed after construction) #6 - Silent reference loss logging (core-xmi-reader-partial-for-attribute-template.hbs) - Added else branches with logger.LogWarning() for both single and multi-reference Guid.TryParse failures #4 - FindNamespaceById optimization (Serializer.cs) - Replaced recursive FindNamespaceById() with BuildNamespaceIndex() that builds a Dictionary<Guid, INamespace> once before the loop - O(1) lookups instead of O(N) tree walks per source file #8 - DI for IXmiDataWriterFacade (Serializer.cs, tests) - Serializer constructor now accepts IXmiDataWriterFacade instead of creating XmiDataWriterFacade directly - Updated SerializerTestFixture.cs and RoundTripTestFixture.cs to pass facade #9 - Mutable elementOriginMap field (DeSerializer.cs) - Removed mutable IXmiElementOriginMap elementOriginMap instance field - Threaded elementOriginMap as a parameter through the entire method chain: DeSerialize → Read/ReadAsync → ResolveExternalReference/ResolveExternalReferenceAsync - DeSerializer instances are now safely reusable across calls with different origin maps
1 parent 0ab30b3 commit f05a5b7

File tree

176 files changed

+35855
-5447
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

176 files changed

+35855
-5447
lines changed

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@ if(!string.IsNullOrWhiteSpace({{String.LowerCaseFirstLetter property.Name}}XmlAt
55
{{#if (Property.QueryIsReferenceProperty property)}}
66
{{#if (Property.QueryIsEnumerable property)}}
77
var {{String.LowerCaseFirstLetter property.Name}}XmlAttributeReferences = new List<Guid>();
8-
8+
99
foreach(var {{String.LowerCaseFirstLetter property.Name}}XmlAttributeValue in {{String.LowerCaseFirstLetter property.Name}}XmlAttribute.Split(SplitMultiReference, StringSplitOptions.RemoveEmptyEntries))
1010
{
1111
if(Guid.TryParse({{String.LowerCaseFirstLetter property.Name}}XmlAttributeValue, out var {{String.LowerCaseFirstLetter property.Name}}XmlAttributeReference))
1212
{
1313
{{String.LowerCaseFirstLetter property.Name}}XmlAttributeReferences.Add({{String.LowerCaseFirstLetter property.Name}}XmlAttributeReference);
1414
}
15+
else
16+
{
17+
this.logger.LogWarning("Failed to parse GUID reference value '{Value}' for property '{{String.LowerCaseFirstLetter property.Name}}' on element {ElementId}", {{String.LowerCaseFirstLetter property.Name}}XmlAttributeValue, poco.Id);
18+
}
1519
}
16-
20+
1721
if({{String.LowerCaseFirstLetter property.Name}}XmlAttributeReferences.Count != 0)
1822
{
1923
this.Cache.AddMultipleValueReferencePropertyIdentifiers(poco.Id, "{{String.LowerCaseFirstLetter property.Name}}", {{String.LowerCaseFirstLetter property.Name}}XmlAttributeReferences);
@@ -23,6 +27,10 @@ if(!string.IsNullOrWhiteSpace({{String.LowerCaseFirstLetter property.Name}}XmlAt
2327
{
2428
this.Cache.AddSingleValueReferencePropertyIdentifier(poco.Id, "{{String.LowerCaseFirstLetter property.Name}}", {{String.LowerCaseFirstLetter property.Name}}XmlAttributeReference);
2529
}
30+
else
31+
{
32+
this.logger.LogWarning("Failed to parse GUID reference value '{Value}' for property '{{String.LowerCaseFirstLetter property.Name}}' on element {ElementId}", {{String.LowerCaseFirstLetter property.Name}}XmlAttribute, poco.Id);
33+
}
2634
{{/if}}
2735
{{else}}
2836
{{#if (Property.QueryIsEnumerable property)}}

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

Lines changed: 78 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,23 @@ case"{{String.LowerCaseFirstLetter property.Name}}":
22
{
33
{{#if (Property.QueryIsReferenceProperty property)}}
44
var hrefAttribute = xmiReader.GetAttribute("href");
5-
5+
66
if(!string.IsNullOrWhiteSpace(hrefAttribute))
77
{
88
var hrefSplit = hrefAttribute.Split('#');
99
this.ExternalReferenceService.AddExternalReferenceToProcess(currentLocation, hrefSplit[0]);
10-
var {{String.LowerCaseFirstLetter property.Name}}Id = Guid.Parse(hrefSplit[1]);
11-
{{#if (Property.QueryIsEnumerable property)}}
12-
this.Cache.AddMultipleValueReferencePropertyIdentifiers(poco.Id, "{{String.LowerCaseFirstLetter property.Name}}", {{String.LowerCaseFirstLetter property.Name}}Id);
13-
{{else}}
14-
this.Cache.AddSingleValueReferencePropertyIdentifier(poco.Id, "{{String.LowerCaseFirstLetter property.Name}}", {{String.LowerCaseFirstLetter property.Name}}Id);
15-
{{/if}}
10+
if(Guid.TryParse(hrefSplit[1], out var {{String.LowerCaseFirstLetter property.Name}}Id))
11+
{
12+
{{#if (Property.QueryIsEnumerable property)}}
13+
this.Cache.AddMultipleValueReferencePropertyIdentifiers(poco.Id, "{{String.LowerCaseFirstLetter property.Name}}", {{String.LowerCaseFirstLetter property.Name}}Id);
14+
{{else}}
15+
this.Cache.AddSingleValueReferencePropertyIdentifier(poco.Id, "{{String.LowerCaseFirstLetter property.Name}}", {{String.LowerCaseFirstLetter property.Name}}Id);
16+
{{/if}}
17+
}
18+
else
19+
{
20+
this.logger.LogWarning("Failed to parse href GUID value '{HrefValue}' for property '{{String.LowerCaseFirstLetter property.Name}}' on element {ElementId}", hrefSplit[1], poco.Id);
21+
}
1622
}
1723
else
1824
{
@@ -42,19 +48,47 @@ case"{{String.LowerCaseFirstLetter property.Name}}":
4248
{{else}}
4349
var {{String.LowerCaseFirstLetter property.Name}}Value = xmiReader.ReadElementContentAsString();
4450
{{/if}}
45-
51+
4652
if(!string.IsNullOrWhiteSpace({{String.LowerCaseFirstLetter property.Name}}Value))
4753
{
4854
{{#if (Property.QueryIsEnumerable property)}}
4955
{{#if (Property.QueryIsBool property )}}
50-
poco.{{Property.WritePropertyName property}}.Add(bool.Parse({{String.LowerCaseFirstLetter property.Name}}Value));
56+
if(bool.TryParse({{String.LowerCaseFirstLetter property.Name}}Value, out var {{String.LowerCaseFirstLetter property.Name}}ValueAsBool))
57+
{
58+
poco.{{Property.WritePropertyName property}}.Add({{String.LowerCaseFirstLetter property.Name}}ValueAsBool);
59+
}
60+
else
61+
{
62+
this.logger.LogWarning("Failed to parse bool value '{Value}' for property '{{String.LowerCaseFirstLetter property.Name}}' on element {ElementId}", {{String.LowerCaseFirstLetter property.Name}}Value, poco.Id);
63+
}
5164
{{else if (Property.QueryIsEnum property )}}
52-
poco.{{Property.WritePropertyName property}}.Add(({{property.Type.Name}})Enum.Parse(typeof({{property.Type.Name}}), {{String.LowerCaseFirstLetter property.Name}}Value, true));
65+
if(Enum.TryParse(typeof({{property.Type.Name}}), {{String.LowerCaseFirstLetter property.Name}}Value, true, out var {{String.LowerCaseFirstLetter property.Name}}ValueAsEnum))
66+
{
67+
poco.{{Property.WritePropertyName property}}.Add(({{property.Type.Name}}){{String.LowerCaseFirstLetter property.Name}}ValueAsEnum);
68+
}
69+
else
70+
{
71+
this.logger.LogWarning("Failed to parse enum value '{Value}' for property '{{String.LowerCaseFirstLetter property.Name}}' on element {ElementId}", {{String.LowerCaseFirstLetter property.Name}}Value, poco.Id);
72+
}
5373
{{else if (Property.QueryIsNumeric property )}}
5474
{{#if (Property.QueryIsInteger property) }}
55-
poco.{{Property.WritePropertyName property}}.Add(int.Parse({{String.LowerCaseFirstLetter property.Name}}Value));
75+
if(int.TryParse({{String.LowerCaseFirstLetter property.Name}}Value, out var {{String.LowerCaseFirstLetter property.Name}}ValueAsInt))
76+
{
77+
poco.{{Property.WritePropertyName property}}.Add({{String.LowerCaseFirstLetter property.Name}}ValueAsInt);
78+
}
79+
else
80+
{
81+
this.logger.LogWarning("Failed to parse int value '{Value}' for property '{{String.LowerCaseFirstLetter property.Name}}' on element {ElementId}", {{String.LowerCaseFirstLetter property.Name}}Value, poco.Id);
82+
}
5683
{{else if (Property.QueryIsDouble property) }}
57-
poco.{{Property.WritePropertyName property}}.Add(double.Parse({{String.LowerCaseFirstLetter property.Name}}Value));
84+
if(double.TryParse({{String.LowerCaseFirstLetter property.Name}}Value, out var {{String.LowerCaseFirstLetter property.Name}}ValueAsDouble))
85+
{
86+
poco.{{Property.WritePropertyName property}}.Add({{String.LowerCaseFirstLetter property.Name}}ValueAsDouble);
87+
}
88+
else
89+
{
90+
this.logger.LogWarning("Failed to parse double value '{Value}' for property '{{String.LowerCaseFirstLetter property.Name}}' on element {ElementId}", {{String.LowerCaseFirstLetter property.Name}}Value, poco.Id);
91+
}
5892
{{ else }}
5993
new NotImplementedException("{{ classContext.Name }}.{{property.Name}} is not yet supported");
6094
{{/if}}
@@ -63,14 +97,42 @@ case"{{String.LowerCaseFirstLetter property.Name}}":
6397
{{/if}}
6498
{{else}}
6599
{{#if (Property.QueryIsBool property )}}
66-
poco.{{Property.WritePropertyName property}} = bool.Parse({{String.LowerCaseFirstLetter property.Name}}Value);
100+
if(bool.TryParse({{String.LowerCaseFirstLetter property.Name}}Value, out var {{String.LowerCaseFirstLetter property.Name}}ValueAsBool))
101+
{
102+
poco.{{Property.WritePropertyName property}} = {{String.LowerCaseFirstLetter property.Name}}ValueAsBool;
103+
}
104+
else
105+
{
106+
this.logger.LogWarning("Failed to parse bool value '{Value}' for property '{{String.LowerCaseFirstLetter property.Name}}' on element {ElementId}", {{String.LowerCaseFirstLetter property.Name}}Value, poco.Id);
107+
}
67108
{{else if (Property.QueryIsEnum property )}}
68-
poco.{{Property.WritePropertyName property}} = ({{property.Type.Name}})Enum.Parse(typeof({{property.Type.Name}}), {{String.LowerCaseFirstLetter property.Name}}Value, true);
109+
if(Enum.TryParse(typeof({{property.Type.Name}}), {{String.LowerCaseFirstLetter property.Name}}Value, true, out var {{String.LowerCaseFirstLetter property.Name}}ValueAsEnum))
110+
{
111+
poco.{{Property.WritePropertyName property}} = ({{property.Type.Name}}){{String.LowerCaseFirstLetter property.Name}}ValueAsEnum;
112+
}
113+
else
114+
{
115+
this.logger.LogWarning("Failed to parse enum value '{Value}' for property '{{String.LowerCaseFirstLetter property.Name}}' on element {ElementId}", {{String.LowerCaseFirstLetter property.Name}}Value, poco.Id);
116+
}
69117
{{else if (Property.QueryIsNumeric property )}}
70118
{{#if (Property.QueryIsInteger property) }}
71-
poco.{{Property.WritePropertyName property}} = int.Parse({{String.LowerCaseFirstLetter property.Name}}Value);
119+
if(int.TryParse({{String.LowerCaseFirstLetter property.Name}}Value, out var {{String.LowerCaseFirstLetter property.Name}}ValueAsInt))
120+
{
121+
poco.{{Property.WritePropertyName property}} = {{String.LowerCaseFirstLetter property.Name}}ValueAsInt;
122+
}
123+
else
124+
{
125+
this.logger.LogWarning("Failed to parse int value '{Value}' for property '{{String.LowerCaseFirstLetter property.Name}}' on element {ElementId}", {{String.LowerCaseFirstLetter property.Name}}Value, poco.Id);
126+
}
72127
{{else if (Property.QueryIsDouble property) }}
73-
poco.{{Property.WritePropertyName property}} = double.Parse({{String.LowerCaseFirstLetter property.Name}}Value);
128+
if(double.TryParse({{String.LowerCaseFirstLetter property.Name}}Value, out var {{String.LowerCaseFirstLetter property.Name}}ValueAsDouble))
129+
{
130+
poco.{{Property.WritePropertyName property}} = {{String.LowerCaseFirstLetter property.Name}}ValueAsDouble;
131+
}
132+
else
133+
{
134+
this.logger.LogWarning("Failed to parse double value '{Value}' for property '{{String.LowerCaseFirstLetter property.Name}}' on element {ElementId}", {{String.LowerCaseFirstLetter property.Name}}Value, poco.Id);
135+
}
74136
{{ else }}
75137
new NotImplementedException("{{ classContext.Name }}.{{property.Name}} is not yet supported");
76138
{{/if}}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ namespace SysML2.NET.Serializer.Xmi.Readers
142142
{{/unless}}
143143
{{/each}}
144144
{{/with}}
145+
default:
146+
this.logger.LogWarning("Unexpected element '{LocalName}' encountered while reading {{this.Name}} at line:position {LineNumber}:{LinePosition}", xmiReader.LocalName, xmlLineInfo?.LineNumber, xmlLineInfo?.LinePosition);
147+
xmiReader.Skip();
148+
break;
145149
}
146150
}
147151
}
@@ -225,6 +229,10 @@ namespace SysML2.NET.Serializer.Xmi.Readers
225229
{{/unless}}
226230
{{/each}}
227231
{{/with}}
232+
default:
233+
this.logger.LogWarning("Unexpected element '{LocalName}' encountered while reading {{this.Name}} at line:position {LineNumber}:{LinePosition}", xmiReader.LocalName, xmlLineInfo?.LineNumber, xmlLineInfo?.LinePosition);
234+
await xmiReader.SkipAsync();
235+
break;
228236
}
229237
}
230238
}

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

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,6 @@ namespace SysML2.NET.Serializer.Xmi.Writers
4040
/// </summary>
4141
public class XmiDataWriterFacade : IXmiDataWriterFacade
4242
{
43-
/// <summary>
44-
/// The <see cref="ILoggerFactory"/> used to set up logging for writer instances
45-
/// </summary>
46-
private readonly ILoggerFactory loggerFactory;
47-
4843
/// <summary>
4944
/// A dictionary that contains actions that write <see cref="IData"/> based on a key that represents the POCO type name
5045
/// </summary>
@@ -61,15 +56,16 @@ namespace SysML2.NET.Serializer.Xmi.Writers
6156
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/> used to set up logging</param>
6257
public XmiDataWriterFacade(ILoggerFactory loggerFactory)
6358
{
64-
this.loggerFactory = loggerFactory;
59+
{{ #each this as | class | }}
60+
var {{ String.LowerCaseFirstLetter class.Name }}Writer = new {{ class.Name }}Writer(this, loggerFactory);
61+
{{/each}}
6562
6663
this.writerCache = new Dictionary<string, Action<XmlWriter, IData, string, XmiWriterOptions, IXmiElementOriginMap, Uri>>
6764
{
6865
{{ #each this as | class | }}
6966
["{{ class.Name }}"] = (xmlWriter, data, elementName, writerOptions, originMap, uri) =>
7067
{
71-
var writer = new {{ class.Name }}Writer(this, this.loggerFactory);
72-
writer.Write(xmlWriter, (Core.POCO.{{ #NamedElement.WriteFullyQualifiedNameSpace class }}.I{{ class.Name }})data, elementName, writerOptions, originMap, uri);
68+
{{ String.LowerCaseFirstLetter class.Name }}Writer.Write(xmlWriter, (Core.POCO.{{ #NamedElement.WriteFullyQualifiedNameSpace class }}.I{{ class.Name }})data, elementName, writerOptions, originMap, uri);
7369
},
7470
{{/each}}
7571
};
@@ -79,8 +75,7 @@ namespace SysML2.NET.Serializer.Xmi.Writers
7975
{{ #each this as | class | }}
8076
["{{ class.Name }}"] = async (xmlWriter, data, elementName, writerOptions, originMap, uri) =>
8177
{
82-
var writer = new {{ class.Name }}Writer(this, this.loggerFactory);
83-
await writer.WriteAsync(xmlWriter, (Core.POCO.{{ #NamedElement.WriteFullyQualifiedNameSpace class }}.I{{ class.Name }})data, elementName, writerOptions, originMap, uri);
78+
await {{ String.LowerCaseFirstLetter class.Name }}Writer.WriteAsync(xmlWriter, (Core.POCO.{{ #NamedElement.WriteFullyQualifiedNameSpace class }}.I{{ class.Name }})data, elementName, writerOptions, originMap, uri);
8479
},
8580
{{/each}}
8681
};

SysML2.NET.Serializer.Xmi.Tests/RoundTripTestFixture.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public void Verify_that_serialized_output_is_well_formed_xml_with_correct_root()
103103
Assert.That(originalNamespace, Is.Not.Null);
104104

105105
// Step 2: Serialize to stream
106-
var serializer = new Serializer(this.loggerFactory);
106+
var serializer = new Serializer(new XmiDataWriterFacade(this.loggerFactory), this.loggerFactory);
107107
var memoryStream = new MemoryStream();
108108

109109
serializer.Serialize(originalNamespace, this.writerOptions, memoryStream);
@@ -177,7 +177,7 @@ public void VerifySerializedXmlCorrespondToOriginalFile()
177177

178178
var quantityNamespace = deSerializer.DeSerialize(fileUri, originMap);
179179

180-
var serializer = new Serializer(this.loggerFactory);
180+
var serializer = new Serializer(new XmiDataWriterFacade(this.loggerFactory), this.loggerFactory);
181181
var outputFile = Path.Combine(TestContext.CurrentContext.TestDirectory, "SerializedQuantities.sysmlx");
182182

183183
var fileStream = new FileStream(outputFile, FileMode.Create);

SysML2.NET.Serializer.Xmi.Tests/SerializerTestFixture.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public void Setup()
5959

6060
this.ReadAndAssemblePopulationFromXmiFile();
6161

62-
this.serializer = new Serializer(serviceProvider.GetRequiredService<ILoggerFactory>());
62+
this.serializer = new Serializer(new XmiDataWriterFacade(serviceProvider.GetRequiredService<ILoggerFactory>()), serviceProvider.GetRequiredService<ILoggerFactory>());
6363
}
6464

6565
[Test]

0 commit comments

Comments
 (0)