Skip to content

Commit 880a500

Browse files
Fix #70: XMI DeSerilize ASYNC flow
1 parent 0e9a730 commit 880a500

177 files changed

Lines changed: 71769 additions & 98 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.

SysML2.NET.CodeGenerator/HandleBarHelpers/SafeContextHelper.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,18 @@ public static void RegisterSafeContextHelper(this IHandlebars handlebars)
4646

4747
options.Template(output, model);
4848
});
49+
50+
handlebars.RegisterHelper("withPropertyClassContextAndAsyncState", (output, options, context, arguments) =>
51+
{
52+
var model = new
53+
{
54+
property = arguments[0] as IProperty,
55+
classContext = arguments[1] as IClass,
56+
asyncState = (bool)arguments[2]
57+
};
58+
59+
options.Template(output, model);
60+
});
4961
}
5062
}
5163
}

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ case"{{String.LowerCaseFirstLetter property.Name}}":
1616
}
1717
else
1818
{
19-
var {{String.LowerCaseFirstLetter property.Name}}Value = (I{{property.Type.Name}})this.XmiDataReaderFacade.QueryXmiData(xmiReader, this.Cache, currentLocation, this.ExternalReferenceService, this.LoggerFactory);
19+
{{#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);
21+
{{else}}
22+
var {{String.LowerCaseFirstLetter property.Name}}Value = (I{{property.Type.Name}})this.XmiDataReaderFacade.QueryXmiData(xmiReader, this.Cache, currentLocation, this.ExternalReferenceService, this.LoggerFactory);
23+
{{/if}}
2024

2125
{{#if (Property.QueryIsEnumerable property)}}
2226
poco.{{Property.WritePropertyName property}}.Add({{String.LowerCaseFirstLetter property.Name}}Value);
@@ -25,7 +29,11 @@ case"{{String.LowerCaseFirstLetter property.Name}}":
2529
{{/if}}
2630
}
2731
{{else}}
28-
var {{String.LowerCaseFirstLetter property.Name}}Value = xmiReader.ReadElementContentAsString();
32+
{{#if asyncState}}
33+
var {{String.LowerCaseFirstLetter property.Name}}Value = await xmiReader.ReadElementContentAsStringAsync();
34+
{{else}}
35+
var {{String.LowerCaseFirstLetter property.Name}}Value = xmiReader.ReadElementContentAsString();
36+
{{/if}}
2937

3038
if(!string.IsNullOrWhiteSpace({{String.LowerCaseFirstLetter property.Name}}Value))
3139
{

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

Lines changed: 78 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ namespace SysML2.NET.Serializer.Xmi.Readers
2727
using System;
2828
using System.CodeDom.Compiler;
2929
using System.Collections.Generic;
30+
using System.Threading.Tasks;
3031
using System.Xml;
3132
3233
using Microsoft.Extensions.Logging;
@@ -45,6 +46,12 @@ namespace SysML2.NET.Serializer.Xmi.Readers
4546
/// </summary>
4647
private readonly Dictionary<string, Func<IXmiDataCache, IExternalReferenceService, ILoggerFactory, XmlReader, Uri, IData>> readerCache;
4748
49+
/// <summary>
50+
/// 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
51+
/// and a provided <see cref="IXmiDataCache"/>, <see cref="IExternalReferenceService" />, <see cref="ILoggerFactory"/> and <see cref="XmlReader"/>
52+
/// </summary>
53+
private readonly Dictionary<string, Func<IXmiDataCache, IExternalReferenceService, ILoggerFactory, XmlReader, Uri, Task<IData>>> readerAsyncCache;
54+
4855
/// <summary>
4956
/// Initializes a new instance of the <see cref="XmiDataReaderFacade"/>
5057
/// </summary>
@@ -61,6 +68,18 @@ namespace SysML2.NET.Serializer.Xmi.Readers
6168
},
6269
{{/each}}
6370
};
71+
72+
this.readerAsyncCache = new Dictionary<string, Func<IXmiDataCache, IExternalReferenceService,ILoggerFactory, XmlReader, Uri, Task<IData>>>
73+
{
74+
{{ #each this as | class | }}
75+
["sysml:{{ class.Name }}"] = async (cache, externalReferenceService, loggerFactory, xmlReader, currentLocation) =>
76+
{
77+
using var subXmlReader = xmlReader.ReadSubtree();
78+
var {{ String.LowerCaseFirstLetter class.Name }}Reader = new {{ class.Name }}Reader(cache, this, externalReferenceService, loggerFactory);
79+
return await {{ String.LowerCaseFirstLetter class.Name }}Reader.ReadAsync(subXmlReader, currentLocation);
80+
},
81+
{{/each}}
82+
};
6483
}
6584
6685
/// <summary>
@@ -78,37 +97,83 @@ namespace SysML2.NET.Serializer.Xmi.Readers
7897
/// <exception cref="InvalidOperationException">If the xsi:type is not supported</exception>
7998
public IData QueryXmiData(XmlReader xmiReader, IXmiDataCache xmiDataCache, Uri currentLocation, IExternalReferenceService externalReferenceService, ILoggerFactory loggerFactory, string explicitTypeName = "")
8099
{
81-
if (xmiReader == null)
82-
{
83-
throw new ArgumentNullException(nameof(xmiReader));
84-
}
100+
AssertValidQueryXmiDataParameters(xmiReader, xmiDataCache, currentLocation);
85101
86-
if (xmiDataCache == null)
102+
var xsiType = xmiReader.GetAttribute("xsi:type");
103+
104+
if (xsiType == null && string.IsNullOrEmpty(explicitTypeName))
87105
{
88-
throw new ArgumentNullException(nameof(xmiDataCache));
106+
throw new InvalidOperationException($"The xsi:type is not specified");
89107
}
108+
109+
xsiType ??= explicitTypeName;
90110
91-
if (currentLocation == null)
111+
if (this.readerCache.TryGetValue(xsiType, out var readerFactory))
92112
{
93-
throw new ArgumentNullException(nameof(currentLocation));
113+
return readerFactory(xmiDataCache, externalReferenceService, loggerFactory, xmiReader, currentLocation);
94114
}
115+
116+
throw new InvalidOperationException($"No reader found for xsi:type - {xsiType}");
117+
}
95118
119+
/// <summary>
120+
/// Queries asynchronously an instance of an <see cref="IData" /> based on the position of the <see cref="XmlReader"/>.
121+
/// When the reader is at an XML Element and has an attribute of xsi:type, the value of that attribute is used to select the appropriate
122+
/// XmiElementReader from which the <see cref="IData"/> is returned.
123+
/// </summary>
124+
/// <param name="xmiReader">The current <see cref="XmlReader"/></param>
125+
/// <param name="xmiDataCache">The <see cref="IXmiDataCache"/> to register and query read <see cref="IData"/></param>
126+
/// <param name="currentLocation">The <see cref="Uri"/> that keep tracks of the current location</param>
127+
/// <param name="externalReferenceService">The <see cref="IExternalReferenceService"/> used to register and process external references</param>
128+
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/> used to set up logging</param>
129+
/// <param name="explicitTypeName">The explicit type name to resolve, in case of un-specified xsi:type</param>
130+
/// <returns>An awaitable <see cref="Task" /> with the instance of the read <see cref="IData"/></returns>
131+
/// <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 = "")
133+
{
134+
AssertValidQueryXmiDataParameters(xmiReader, xmiDataCache, currentLocation);
135+
96136
var xsiType = xmiReader.GetAttribute("xsi:type");
97-
137+
98138
if (xsiType == null && string.IsNullOrEmpty(explicitTypeName))
99139
{
100140
throw new InvalidOperationException($"The xsi:type is not specified");
101141
}
102-
142+
103143
xsiType ??= explicitTypeName;
104-
105-
if (this.readerCache.TryGetValue(xsiType, out var readerFactory))
144+
145+
if (this.readerAsyncCache.TryGetValue(xsiType, out var readerFactory))
106146
{
107147
return readerFactory(xmiDataCache, externalReferenceService, loggerFactory, xmiReader, currentLocation);
108148
}
109-
149+
110150
throw new InvalidOperationException($"No reader found for xsi:type - {xsiType}");
111151
}
152+
153+
/// <summary>
154+
/// Asserts that parameters provided to QueryXmiData and QueryXmiDataAsync methods are valid
155+
/// </summary>
156+
/// <param name="xmiReader">The current <see cref="XmlReader"/></param>
157+
/// <param name="xmiDataCache">The <see cref="IXmiDataCache"/> to register and query read <see cref="IData"/></param>
158+
/// <param name="currentLocation">The <see cref="Uri"/> that keep tracks of the current location</param>
159+
/// <exception cref="ArgumentNullException">If one of the parameter is null</exception>
160+
private static void AssertValidQueryXmiDataParameters(XmlReader xmiReader, IXmiDataCache xmiDataCache, Uri currentLocation)
161+
{
162+
if (xmiReader == null)
163+
{
164+
throw new ArgumentNullException(nameof(xmiReader));
165+
}
166+
167+
if (xmiDataCache == null)
168+
{
169+
throw new ArgumentNullException(nameof(xmiDataCache));
170+
}
171+
172+
if (currentLocation == null)
173+
{
174+
throw new ArgumentNullException(nameof(currentLocation));
175+
}
176+
}
112177
}
113178
}
114179

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

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ namespace SysML2.NET.Serializer.Xmi.Readers
2626
{
2727
using System;
2828
using System.Collections.Generic;
29+
using System.Threading.Tasks;
2930
using System.Xml;
3031
3132
using Microsoft.Extensions.Logging;
@@ -129,9 +130,9 @@ namespace SysML2.NET.Serializer.Xmi.Readers
129130
{{#unless this.IsTransient}}
130131
{{#unless this.IsDerived}}
131132
{{#unless (Property.IsPropertyRedefinedInClass this class)}}
132-
{{#withPropertyClassContext property class}}
133+
{{#withPropertyClassContextAndAsyncState property class false}}
133134
{{> core-xmi-reader-partial-for-element-template this}}
134-
{{/withPropertyClassContext}}
135+
{{/withPropertyClassContextAndAsyncState}}
135136
{{/unless}}
136137
{{/unless}}
137138
{{/unless}}
@@ -144,6 +145,87 @@ namespace SysML2.NET.Serializer.Xmi.Readers
144145
145146
return poco;
146147
}
148+
149+
/// <summary>
150+
/// Reads asynchronously the <see cref="I{{this.Name}}" /> object from its XML representation
151+
/// </summary>
152+
/// <param name="xmiReader">An instance of <see cref="XmlReader" /></param>
153+
/// <param name="currentLocation">The <see cref="Uri" /> that keep tracks of the current location</param>
154+
/// <returns>An awaitable <see cref="Task{TResult}"/> with the read <see cref="I{{this.Name}}" /></returns>
155+
public override async Task<I{{this.Name}}> ReadAsync(XmlReader xmiReader, Uri currentLocation)
156+
{
157+
if (xmiReader == null)
158+
{
159+
throw new ArgumentNullException(nameof(xmiReader));
160+
}
161+
162+
var xmlLineInfo = xmiReader as IXmlLineInfo;
163+
164+
I{{this.Name}} poco = new SysML2.NET.Core.POCO.{{ #NamedElement.WriteFullyQualifiedNameSpace this }}.{{this.Name}}();
165+
166+
if (await xmiReader.MoveToContentAsync() == XmlNodeType.Element)
167+
{
168+
this.logger.LogTrace("reading {{this.Name}} at line:position {LineNumber}:{LinePosition}", xmlLineInfo?.LineNumber, xmlLineInfo?.LinePosition);
169+
var xsiType = xmiReader.GetAttribute("xsi:type");
170+
171+
if (!string.IsNullOrEmpty(xsiType) && xsiType != "sysml:{{this.Name}}")
172+
{
173+
throw new InvalidOperationException($"The xsi:type {xsiType} is not supported by the {{this.Name}}Reader");
174+
}
175+
176+
var xmiId = xmiReader.GetAttribute("xmi:id");
177+
178+
if (!Guid.TryParse(xmiId, out var guid))
179+
{
180+
throw new InvalidOperationException($"The xmi:id {xmiId} could not be parsed");
181+
}
182+
183+
poco.Id = guid;
184+
185+
if (!this.Cache.TryAdd(poco) && this.logger.IsEnabled(LogLevel.Critical))
186+
{
187+
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);
188+
}
189+
190+
{{#with this as |class| }}
191+
{{ #each (Class.QueryAllProperties this) as | property | }}
192+
{{#unless this.IsTransient}}
193+
{{#unless this.IsDerived}}
194+
{{#unless (Property.IsPropertyRedefinedInClass this class)}}
195+
{{#withPropertyClassContext property class}}
196+
{{> core-xmi-reader-partial-for-attribute-template this}}
197+
{{/withPropertyClassContext}}
198+
{{/unless}}
199+
{{/unless}}
200+
{{/unless}}
201+
{{/each}}
202+
{{/with}}
203+
while(await xmiReader.ReadAsync())
204+
{
205+
if(xmiReader.NodeType == XmlNodeType.Element)
206+
{
207+
switch(xmiReader.LocalName)
208+
{
209+
{{#with this as |class| }}
210+
{{ #each (Class.QueryAllProperties this) as | property | }}
211+
{{#unless this.IsTransient}}
212+
{{#unless this.IsDerived}}
213+
{{#unless (Property.IsPropertyRedefinedInClass this class)}}
214+
{{#withPropertyClassContextAndAsyncState property class true}}
215+
{{> core-xmi-reader-partial-for-element-template this}}
216+
{{/withPropertyClassContextAndAsyncState}}
217+
{{/unless}}
218+
{{/unless}}
219+
{{/unless}}
220+
{{/each}}
221+
{{/with}}
222+
}
223+
}
224+
}
225+
}
226+
227+
return poco;
228+
}
147229
}
148230
}
149231

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ namespace SysML2.NET.Serializer.Xmi.Tests
2222
{
2323
using System;
2424
using System.IO;
25+
using System.Threading;
26+
using System.Threading.Tasks;
2527

2628
using Microsoft.Extensions.DependencyInjection;
2729
using Microsoft.Extensions.Logging;
@@ -53,5 +55,20 @@ public void VerifyCanReadXmlLibrary()
5355
var filePath = Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "Domain Libraries", "Quantities and Units", "Quantities.sysmlx");
5456
Assert.That(() => this.deSerializer.DeSerialize(new Uri(filePath)), Throws.Nothing);
5557
}
58+
59+
[Test]
60+
public async Task VerifyCanReadXmlLibraryAsync()
61+
{
62+
var filePath = Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "Domain Libraries", "Quantities and Units", "Quantities.sysmlx");
63+
await Assert.ThatAsync(() => this.deSerializer.DeSerializeAsync(new Uri(filePath)), Throws.Nothing);
64+
}
65+
66+
[Test]
67+
public async Task VerifyCanCancelReadXmlLibraryAsync()
68+
{
69+
using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMilliseconds(500));
70+
var filePath = Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "Domain Libraries", "Quantities and Units", "Quantities.sysmlx");
71+
await Assert.ThatAsync(() => this.deSerializer.DeSerializeAsync(new Uri(filePath), cancellationTokenSource.Token), Throws.InstanceOf<OperationCanceledException>());
72+
}
5673
}
5774
}

0 commit comments

Comments
 (0)