Skip to content

Commit aae8f25

Browse files
authored
[Java.Interop.Tools.Generator] Add some enumification helper methods (#866)
Context: https://github.com/jpobst/BindingStudio Context: https://github.com/xamarin/xamarin-android/blob/main/src/Mono.Android/methodmap.csv Add a few helper methods used for dealing with the enumification process. The main function is a reader/writer for "Method Maps", e.g. <https://github.com/xamarin/xamarin-android/blob/main/src/Mono.Android/methodmap.csv>. Implemented to allow sharing logic between `generator` and [BindingStudio][0]. Currently `generator` does not use this logic, but the idea is to eventually standardize it on this common implementation. [0]: https://github.com/jpobst/BindingStudio
1 parent 111ebca commit aae8f25

File tree

5 files changed

+273
-2
lines changed

5 files changed

+273
-2
lines changed

src/Java.Interop.Tools.Generator/Enumification/ConstantEntry.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ public static ConstantEntry FromElement (XElement elem)
145145
{
146146
var entry = new ConstantEntry {
147147
Action = ConstantAction.None,
148-
ApiLevel = NamingConverter.ParseApiLevel (elem.Attribute ("merge.SourceFile")?.Value),
148+
ApiLevel = NamingConverter.ParseApiLevel (elem),
149149
JavaSignature = elem.Parent.Parent.Attribute ("name").Value,
150150
Value = elem.Attribute ("value")?.Value,
151151
};
@@ -190,6 +190,7 @@ static string ToConstantFieldActionString (FieldAction value)
190190
};
191191

192192
}
193+
193194
static ConstantAction FromConstantActionString (string value)
194195
{
195196
return value switch
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace Java.Interop.Tools.Generator.Enumification
2+
{
3+
public enum MethodAction
4+
{
5+
None,
6+
Ignore,
7+
Enumify,
8+
}
9+
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Xml.Linq;
4+
using Xamarin.Android.Tools;
5+
6+
namespace Java.Interop.Tools.Generator.Enumification
7+
{
8+
public class MethodMapEntry
9+
{
10+
public MethodAction Action { get; set; }
11+
public int ApiLevel { get; set; }
12+
public string? JavaPackage { get; set; }
13+
public string? JavaType { get; set; }
14+
public string? JavaName { get; set; }
15+
public string? ParameterName { get; set; }
16+
public string? EnumFullType { get; set; }
17+
public string JavaSignature => $"{JavaPackage}/{JavaType}.{JavaName}.{ParameterName}";
18+
public bool IsInterface { get; set; }
19+
20+
public static IEnumerable<MethodMapEntry> FromXml (XElement element)
21+
{
22+
// Handle fields first
23+
if (element.Name == "field") {
24+
yield return FromElement (element, element.XGetAttribute ("name")!);
25+
yield break;
26+
}
27+
28+
// Now methods and constructors
29+
// There could be multiple entries, from the return type and multiple parameters
30+
if (element.XGetAttribute ("return") == "int")
31+
yield return FromElement (element, "return");
32+
33+
foreach (var p in element.Elements ("parameter"))
34+
if (p.XGetAttribute ("type") == "int")
35+
yield return FromElement (element, p.XGetAttribute ("name")!);
36+
}
37+
38+
static MethodMapEntry FromElement (XElement element, string parameterName)
39+
{
40+
var entry = new MethodMapEntry {
41+
JavaPackage = element.Parent.Parent.XGetAttribute ("name")?.Replace ('.', '/'),
42+
JavaType = element.Parent.XGetAttribute ("name")?.Replace ('.', '$'),
43+
JavaName = element.XGetAttribute ("name"),
44+
ParameterName = parameterName,
45+
ApiLevel = NamingConverter.ParseApiLevel (element),
46+
IsInterface = element.Parent.Name == "interface"
47+
};
48+
49+
if (element.Name == "constructor")
50+
entry.JavaName = "ctor";
51+
52+
return entry;
53+
}
54+
55+
public static MethodMapEntry FromString (string line)
56+
{
57+
var parser = new CsvParser (line);
58+
59+
if (parser.GetField (0).In ("?", "I", "E"))
60+
return FromVersion2String (parser);
61+
62+
return FromVersion1String (parser);
63+
}
64+
65+
static MethodMapEntry FromVersion1String (CsvParser parser)
66+
{
67+
var entry = new MethodMapEntry {
68+
Action = MethodAction.Enumify,
69+
ApiLevel = parser.GetFieldAsInt (0),
70+
JavaPackage = parser.GetField (1),
71+
JavaType = parser.GetField (2),
72+
JavaName = parser.GetField (3),
73+
ParameterName = parser.GetField (4),
74+
EnumFullType = parser.GetField (5)
75+
};
76+
77+
if (entry.JavaType.StartsWith ("[Interface]", StringComparison.Ordinal)) {
78+
entry.IsInterface = true;
79+
entry.JavaType = entry.JavaType.Substring ("[Interface]".Length);
80+
}
81+
82+
return entry;
83+
}
84+
85+
static MethodMapEntry FromVersion2String (CsvParser parser)
86+
{
87+
var entry = new MethodMapEntry {
88+
Action = FromMethodActionString (parser.GetField (0)),
89+
ApiLevel = parser.GetFieldAsInt (1),
90+
JavaPackage = parser.GetField (2),
91+
JavaType = parser.GetField (3),
92+
JavaName = parser.GetField (4),
93+
ParameterName = parser.GetField (5),
94+
EnumFullType = parser.GetField (6)
95+
};
96+
97+
if (entry.JavaType.StartsWith ("[Interface]", StringComparison.Ordinal)) {
98+
entry.IsInterface = true;
99+
entry.JavaType = entry.JavaType.Substring ("[Interface]".Length);
100+
}
101+
102+
return entry;
103+
}
104+
105+
static MethodAction FromMethodActionString (string value)
106+
{
107+
return value switch {
108+
"?" => MethodAction.None,
109+
"I" => MethodAction.Ignore,
110+
"E" => MethodAction.Enumify,
111+
_ => throw new ArgumentOutOfRangeException (nameof (value), $"Specified action '{value}' is not valid"),
112+
};
113+
}
114+
115+
public string ToVersion1String ()
116+
{
117+
var fields = new [] {
118+
ApiLevel.ToString (),
119+
JavaPackage,
120+
(IsInterface ? "I:" : string.Empty) + JavaType,
121+
JavaName,
122+
ParameterName,
123+
EnumFullType,
124+
};
125+
126+
return string.Join (",", fields);
127+
}
128+
129+
public string ToVersion2String ()
130+
{
131+
var fields = new [] {
132+
Action == MethodAction.None ? "?" : Action.ToString ().Substring (0, 1),
133+
ApiLevel.ToString (),
134+
JavaPackage,
135+
(IsInterface ? "[Interface]" : string.Empty) + JavaType,
136+
JavaName,
137+
ParameterName,
138+
EnumFullType,
139+
};
140+
141+
return string.Join (",", fields);
142+
}
143+
}
144+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Xml.Linq;
6+
using System.Xml.XPath;
7+
8+
namespace Java.Interop.Tools.Generator.Enumification
9+
{
10+
public class MethodMapParser
11+
{
12+
public static List<MethodMapEntry> FromMethodMapCsv (string filename)
13+
{
14+
using (var sr = new StreamReader (filename))
15+
return FromMethodMapCsv (sr);
16+
}
17+
18+
public static List<MethodMapEntry> FromMethodMapCsv (TextReader reader)
19+
{
20+
var entries = new List<MethodMapEntry> ();
21+
22+
string s;
23+
24+
// Read the enum csv file
25+
while ((s = reader.ReadLine ()) != null) {
26+
// Skip empty lines and comments
27+
if (string.IsNullOrEmpty (s) || s.StartsWith ("//", StringComparison.Ordinal))
28+
continue;
29+
30+
entries.Add (MethodMapEntry.FromString (s));
31+
}
32+
33+
return entries;
34+
}
35+
36+
public static void SaveMethodMapCsv (IEnumerable<MethodMapEntry> entries, string filename, bool version2)
37+
{
38+
using (var sw = new StreamWriter (filename))
39+
SaveMethodMapCsv (entries, sw, version2);
40+
}
41+
42+
public static void SaveMethodMapCsv (IEnumerable<MethodMapEntry> entries, TextWriter writer, bool version2)
43+
{
44+
foreach (var entry in entries.OrderBy (e => e.JavaSignature))
45+
writer.WriteLine (version2 ? entry.ToVersion2String () : entry.ToVersion1String ());
46+
}
47+
48+
public static List<MethodMapEntry> FromApiXml (string filename) => FromApiXml (XDocument.Load (filename));
49+
50+
public static List<MethodMapEntry> FromApiXml (XDocument doc)
51+
{
52+
var results = new List<MethodMapEntry> ();
53+
54+
// Methods that return int or have an int parameter
55+
results.AddRange (doc.XPathSelectElements ("//method[@return='int'] | //method[parameter/@type='int']").SelectMany (x => MethodMapEntry.FromXml (x)));
56+
57+
// Constructors with an int parameter
58+
results.AddRange (doc.XPathSelectElements ("//constructor[parameter/@type='int']").SelectMany (x => MethodMapEntry.FromXml (x)));
59+
60+
// Fields that are a non-constant int
61+
results.AddRange (doc.XPathSelectElements ("//field[@type='int' and @final='false']").SelectMany (x => MethodMapEntry.FromXml (x)));
62+
63+
return results;
64+
}
65+
}
66+
}

src/Java.Interop.Tools.Generator/Utilities/NamingConverter.cs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System;
2+
using System.Linq;
3+
using System.Xml.Linq;
24

35
namespace Java.Interop.Tools.Generator
46
{
@@ -17,7 +19,56 @@ public static int ParseApiLevel (string? value)
1719

1820
var result = value.Substring (hyphen + 1, period - hyphen - 1);
1921

20-
return int.Parse (result == "R" ? "30" : result);
22+
return result switch {
23+
"R" => 30,
24+
"S" => 31,
25+
_ => int.Parse (result)
26+
};
27+
}
28+
29+
// The 'merge.SourceFile' attribute may be on the element, or only on its parent. For example,
30+
// a new 'class' added will only put the attribute on the '<class>' element and not its children <method>s.
31+
public static int ParseApiLevel (XElement element)
32+
{
33+
var loop = element;
34+
35+
while (loop != null) {
36+
if (loop.Attribute ("merge.SourceFile") is XAttribute attr)
37+
return ParseApiLevel (attr.Value);
38+
39+
loop = loop.Parent;
40+
}
41+
42+
return 0;
43+
}
44+
45+
public static string ConvertNamespaceToCSharp (string v)
46+
{
47+
return string.Join (".", v.Split ('.').Select (s => Capitalize (s)));
48+
}
49+
50+
public static string ConvertClassToCSharp (string javaType)
51+
{
52+
return javaType;
53+
}
54+
55+
public static string ConvertFieldToCSharp (string javaName)
56+
{
57+
// EX: FOREGROUND_SERVICE_IMMEDIATE
58+
return string.Join ("", javaName.Split ('_').Select (s => SentenceCase (s)));
59+
}
60+
61+
public static string Capitalize (this string value)
62+
{
63+
if (value.Length < 1)
64+
return value;
65+
66+
return char.ToUpperInvariant (value[0]) + value.Substring (1);
67+
}
68+
69+
public static string SentenceCase (this string value)
70+
{
71+
return Capitalize (value.ToLowerInvariant ());
2172
}
2273
}
2374
}

0 commit comments

Comments
 (0)