Skip to content

Commit f4e68b5

Browse files
authored
[generator] Separate metadata fixup step from parsing step. (#822)
Context: #789 One issue with the proposal to flip the order of `ApiXmlAdjuster` and `metadata` in Issue #789 is that the "apply metadata" step is intertwined with the "parse API xml document into POCO's" step. This means that the adjuster step cannot be inserted between them, as the code is currently written. Separate out the two steps so that they can be reordered in the future. Additionally, refactor the metadata fixup step so that it can be unit tested more easily by moving it to `Java.Interop.Tools.Generator.dll`. Unit tests for applying metadata have been added.
1 parent f9faaab commit f4e68b5

File tree

29 files changed

+591
-223
lines changed

29 files changed

+591
-223
lines changed

src/Java.Interop.Tools.Generator/Extensions/UtilityExtensions.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System;
22
using System.Diagnostics.CodeAnalysis;
3+
using System.Xml;
4+
using System.Xml.Linq;
35

46
namespace Java.Interop.Tools.Generator
57
{
@@ -27,5 +29,17 @@ public static bool StartsWithAny (this string value, params string [] values)
2729
}
2830

2931
public static bool HasValue ([NotNullWhen (true)]this string? str) => !string.IsNullOrEmpty (str);
32+
33+
public static XDocument? LoadXmlDocument (string filename)
34+
{
35+
try {
36+
return XDocument.Load (filename, LoadOptions.SetBaseUri | LoadOptions.SetLineInfo);
37+
} catch (XmlException e) {
38+
Report.Verbose (0, "Exception: {0}", e);
39+
Report.LogCodedWarning (0, Report.WarningInvalidXmlFile, e, filename, e.Message);
40+
}
41+
42+
return null;
43+
}
3044
}
3145
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System;
2+
using System.Globalization;
3+
using System.Xml.Linq;
4+
using System.Xml.XPath;
5+
6+
namespace Xamarin.Android.Tools
7+
{
8+
static class XmlExtensions
9+
{
10+
public static string? XGetAttribute (this XElement element, string name)
11+
=> element.Attribute (name)?.Value.Trim ();
12+
13+
public static string? XGetAttribute (this XPathNavigator nav, string name, string ns)
14+
=> nav.GetAttribute (name, ns)?.Trim ();
15+
16+
public static int? XGetAttributeAsInt (this XElement element, string name)
17+
{
18+
var value = element.XGetAttribute (name);
19+
20+
if (int.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
21+
return result;
22+
23+
return null;
24+
}
25+
}
26+
}

src/Java.Interop.Tools.Generator/Java.Interop.Tools.Generator.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,8 @@
1515
<Compile Include="..\utils\NullableAttributes.cs" />
1616
</ItemGroup>
1717

18+
<ItemGroup>
19+
<ProjectReference Include="..\Java.Interop.Localization\Java.Interop.Localization.csproj" />
20+
</ItemGroup>
21+
1822
</Project>
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
using System;
2+
using System.Linq;
3+
using System.Xml.XPath;
4+
using System.Xml.Linq;
5+
6+
using Xamarin.Android.Tools;
7+
8+
namespace Java.Interop.Tools.Generator
9+
{
10+
public class FixupXmlDocument
11+
{
12+
public XDocument FixupDocument { get; }
13+
14+
public FixupXmlDocument (XDocument fixupDocument)
15+
{
16+
FixupDocument = fixupDocument;
17+
}
18+
19+
public static FixupXmlDocument? Load (string filename)
20+
{
21+
if (UtilityExtensions.LoadXmlDocument (filename) is XDocument doc)
22+
return new FixupXmlDocument (doc);
23+
24+
return null;
25+
}
26+
27+
public void Apply (ApiXmlDocument apiDocument, string apiLevelString, int productVersion)
28+
{
29+
// Defaulting to 0 here is fine
30+
int.TryParse (apiLevelString, out var apiLevel);
31+
32+
var metadataChildren = FixupDocument.XPathSelectElements ("/metadata/*");
33+
34+
string? prev_path = null;
35+
XElement? attr_last_cache = null;
36+
37+
foreach (var metaitem in metadataChildren) {
38+
if (ShouldSkip (metaitem, apiLevel, productVersion))
39+
continue;
40+
if (!ShouldApply (metaitem, apiDocument))
41+
continue;
42+
43+
var path = metaitem.XGetAttribute ("path");
44+
45+
if (path != prev_path)
46+
attr_last_cache = null;
47+
48+
prev_path = path;
49+
50+
switch (metaitem.Name.LocalName) {
51+
case "remove-node":
52+
try {
53+
var nodes = apiDocument.ApiDocument.XPathSelectElements (path).ToArray ();
54+
55+
if (nodes.Any ())
56+
foreach (var node in nodes)
57+
node.Remove ();
58+
else
59+
// BG8A00
60+
Report.LogCodedWarning (0, Report.WarningRemoveNodeMatchedNoNodes, null, metaitem, $"<remove-node path=\"{path}\" />");
61+
} catch (XPathException e) {
62+
// BG4301
63+
Report.LogCodedError (Report.ErrorRemoveNodeInvalidXPath, e, metaitem, path);
64+
}
65+
break;
66+
case "add-node":
67+
try {
68+
var nodes = apiDocument.ApiDocument.XPathSelectElements (path);
69+
70+
if (!nodes.Any ())
71+
// BG8A01
72+
Report.LogCodedWarning (0, Report.WarningAddNodeMatchedNoNodes, null, metaitem, $"<add-node path=\"{path}\" />");
73+
else {
74+
foreach (var node in nodes)
75+
node.Add (metaitem.Nodes ());
76+
}
77+
} catch (XPathException e) {
78+
// BG4302
79+
Report.LogCodedError (Report.ErrorAddNodeInvalidXPath, e, metaitem, path);
80+
}
81+
break;
82+
case "change-node":
83+
try {
84+
var nodes = apiDocument.ApiDocument.XPathSelectElements (path);
85+
var matched = false;
86+
87+
foreach (var node in nodes) {
88+
var newChild = new XElement (metaitem.Value);
89+
newChild.Add (node.Attributes ());
90+
newChild.Add (node.Nodes ());
91+
node.ReplaceWith (newChild);
92+
matched = true;
93+
}
94+
95+
if (!matched)
96+
// BG8A03
97+
Report.LogCodedWarning (0, Report.WarningChangeNodeTypeMatchedNoNodes, null, metaitem, $"<change-node-type path=\"{path}\" />");
98+
} catch (XPathException e) {
99+
// BG4303
100+
Report.LogCodedError (Report.ErrorChangeNodeInvalidXPath, e, metaitem, path);
101+
}
102+
break;
103+
case "attr":
104+
try {
105+
var attr_name = metaitem.XGetAttribute ("name");
106+
107+
if (string.IsNullOrEmpty (attr_name))
108+
// BG4307
109+
Report.LogCodedError (Report.ErrorMissingAttrName, null, metaitem, path);
110+
var nodes = attr_last_cache != null ? new XElement [] { attr_last_cache } : apiDocument.ApiDocument.XPathSelectElements (path);
111+
var attr_matched = 0;
112+
113+
foreach (var n in nodes) {
114+
n.SetAttributeValue (attr_name, metaitem.Value);
115+
attr_matched++;
116+
}
117+
if (attr_matched == 0)
118+
// BG8A04
119+
Report.LogCodedWarning (0, Report.WarningAttrMatchedNoNodes, null, metaitem, $"<attr path=\"{path}\" />");
120+
if (attr_matched != 1)
121+
attr_last_cache = null;
122+
} catch (XPathException e) {
123+
// BG4304
124+
Report.LogCodedError (Report.ErrorAttrInvalidXPath, e, metaitem, path);
125+
}
126+
break;
127+
case "move-node":
128+
try {
129+
var parent = metaitem.Value;
130+
var parents = apiDocument.ApiDocument.XPathSelectElements (parent);
131+
var matched = false;
132+
133+
foreach (var parent_node in parents) {
134+
var nodes = parent_node.XPathSelectElements (path).ToArray ();
135+
foreach (var node in nodes)
136+
node.Remove ();
137+
parent_node.Add (nodes);
138+
matched = true;
139+
}
140+
if (!matched)
141+
// BG8A05
142+
Report.LogCodedWarning (0, Report.WarningMoveNodeMatchedNoNodes, null, metaitem, $"<move-node path=\"{path}\" />");
143+
} catch (XPathException e) {
144+
// BG4305
145+
Report.LogCodedError (Report.ErrorMoveNodeInvalidXPath, e, metaitem, path);
146+
}
147+
break;
148+
case "remove-attr":
149+
try {
150+
var name = metaitem.XGetAttribute ("name");
151+
var nodes = apiDocument.ApiDocument.XPathSelectElements (path);
152+
var matched = false;
153+
154+
foreach (var node in nodes) {
155+
node.RemoveAttributes ();
156+
matched = true;
157+
}
158+
159+
if (!matched)
160+
// BG8A06
161+
Report.LogCodedWarning (0, Report.WarningRemoveAttrMatchedNoNodes, null, metaitem, $"<remove-attr path=\"{path}\" />");
162+
} catch (XPathException e) {
163+
// BG4306
164+
Report.LogCodedError (Report.ErrorRemoveAttrInvalidXPath, e, metaitem, path);
165+
}
166+
break;
167+
}
168+
}
169+
}
170+
171+
bool ShouldSkip (XElement node, int apiLevel, int productVersion)
172+
{
173+
if (apiLevel > 0) {
174+
var since = node.XGetAttributeAsInt ("api-since");
175+
var until = node.XGetAttributeAsInt ("api-until");
176+
177+
if (since is int since_int && since_int > apiLevel)
178+
return true;
179+
else if (until is int until_int && until_int < apiLevel)
180+
return true;
181+
}
182+
183+
if (productVersion > 0) {
184+
var product_version = node.XGetAttributeAsInt ("product-version");
185+
186+
if (product_version is int version && version > productVersion)
187+
return true;
188+
189+
}
190+
return false;
191+
}
192+
193+
bool ShouldApply (XElement node, ApiXmlDocument apiDocument)
194+
{
195+
if (apiDocument.ApiSource.HasValue ()) {
196+
var targetsource = node.XGetAttribute ("api-source");
197+
198+
if (!targetsource.HasValue ())
199+
return true;
200+
201+
return targetsource == apiDocument.ApiSource;
202+
}
203+
204+
return true;
205+
}
206+
}
207+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System;
2+
using System.Xml;
3+
using System.Xml.Linq;
4+
using Xamarin.Android.Tools;
5+
6+
namespace Java.Interop.Tools.Generator
7+
{
8+
public class ApiXmlDocument
9+
{
10+
public XDocument ApiDocument { get; }
11+
public string ApiLevel { get; }
12+
public int ProductVersion { get; }
13+
14+
public string? ApiSource => ApiDocument.Root?.XGetAttribute ("api-source");
15+
16+
public ApiXmlDocument (XDocument document, string apiLevel, int productVersion)
17+
{
18+
ApiDocument = document;
19+
ApiLevel = apiLevel;
20+
ProductVersion = productVersion;
21+
}
22+
23+
public static ApiXmlDocument? Load (string filename, string apiLevel, int productVersion)
24+
{
25+
if (UtilityExtensions.LoadXmlDocument (filename) is XDocument doc)
26+
return new ApiXmlDocument (doc, apiLevel, productVersion);
27+
28+
return null;
29+
}
30+
31+
public void ApplyFixupFile (string filename)
32+
{
33+
if (FixupXmlDocument.Load (filename) is FixupXmlDocument fixup)
34+
ApplyFixupFile (fixup);
35+
}
36+
37+
public void ApplyFixupFile (FixupXmlDocument fixup)
38+
{
39+
try {
40+
fixup.Apply (this, ApiLevel, ProductVersion);
41+
} catch (XmlException ex) {
42+
// BG4200
43+
Report.LogCodedError (Report.ErrorFailedToProcessMetadata, ex.Message);
44+
}
45+
}
46+
}
47+
}
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Text;
5-
using System.Threading.Tasks;
62

7-
namespace MonoDroid.Generation
3+
namespace Java.Interop.Tools.Generator
84
{
95
public interface ISourceLineInfo
106
{

0 commit comments

Comments
 (0)