diff --git a/Documentation/EnumMappingFile.md b/Documentation/EnumMappingFile.md
new file mode 100644
index 000000000..e5143ea38
--- /dev/null
+++ b/Documentation/EnumMappingFile.md
@@ -0,0 +1,98 @@
+# Enumeration Mapping File Documentation
+
+## Background
+
+In order to help with binding enumification (the process of converting groups
+of Java constant int fields into C# enums), a file can be created that defines
+a map between the fields and the enums to be created.
+
+There is a CSV format and an XML format of this file. In practice, users are
+guided to the XML format via our [documentation][0] and templates. The CSV format
+is mainly used internally for `Mono.Android.dll`, as it converts thousands of
+constants into hundreds of enums.
+
+## CSV Format
+
+The basic format since the beginning of Xamarin contains up to 6 fields:
+
+* **API Level** - This is generally only used by `Mono.Android.dll` to denote
+ the Android level the constant was introduced in. For other uses this
+ is generally `0`.
+* **Enum Type** - C# namespace and type of the enum to create. For example:
+ `Android.Views.WindowProgress`.
+* **Enum Member** - C# name of the enum to create. For example:
+ `Start`
+* **Enum Value** - The value of the enum. For example: `0`.
+* **JNI Signature** - The JNI signature of the Java constant to convert. For example:
+ `android/view/Window.PROGRESS_START`.
+* **Flags** - If this field contains `flags` the enum will be created with the
+ `[Flags]` attribute. (Any member will `flags` will make the whole enum `[Flags]`.)
+
+Full example:
+```
+10,Android.Views.WindowProgress,Start,android/view/Window.PROGRESS_START,0,flags
+```
+
+---
+**NOTE**
+
+Our CSV files also allow comments using `//`. Lines beginning with this
+sequence are ignored.
+
+---
+
+### Transient Mode
+
+By default, Java constants referenced in this format are kept. However the
+file can contain a line like this at any point:
+```
+- ENTER TRANSIENT MODE -
+```
+
+Any v1 constants referenced *AFTER* this line will be removed from the bindings.
+(This will not affect v2 constants.)
+
+## CSV Format v2
+
+Over time we have found some limitations to the format, such as being able
+to specify if the Java constant field should be removed. Additionally, since the
+format only specifies constants that *SHOULD* be mapped, we cannot use this
+to track constants that we have examined and determined *SHOULD NOT* be mapped.
+This has led to various blacklists and tooling of varying success to prevent
+us from needing to continually re-audit those constants.
+
+There is now a "v2" version of defining constants. This is a line-level change
+and you can mix "v1" and "v2" lines in the same file for backwards compatibility,
+but for consistency it's probably better to stick to one style.
+
+A "v2" line contains up to 8 fields:
+
+* **Action** - The action to perform. This is what denotes a "v2" line, if the first
+ character is not one of the following it will be treated as "v1".
+ * `E` - Create a C# enum from a Java constant
+ * `A` - Create a C# enum not mapped to a Java constant
+ * `R` - Remove a Java constant but do not create a C# enum
+ * `I` - Explicitly ignore this Java constant
+ * `?` - Unknown, an explicit action has not been decided yet, will be ignored
+* **API Level** - This is generally only used by `Mono.Android.dll` to denote
+ the Android level the constant was introduced in. For other uses this
+ is generally `0`.
+* **JNI Signature** - The JNI signature of the Java constant to convert. For example:
+ `android/view/Window.PROGRESS_START`.
+* **Enum Value** - The value of the enum. For example: `0`.
+* **Enum Type** - C# namespace and type of the enum to create. For example:
+ `Android.Views.WindowProgress`.
+* **Enum Member** - C# name of the enum to create. For example:
+ `Start`
+* **Field Action** - Action to take on the Java constant. (This replaces Transient mode.)
+ * `remove` - Remove the Java constant
+ * `keep` - Keeps the Java constant
+* **Flags** - If this field contains `flags` the enum will be created with the
+ `[Flags]` attribute. (Any member will `flags` will make the whole enum `[Flags]`.)
+
+Full example:
+```
+E,10,android/view/Window.PROGRESS_START,0,Android.Views.WindowProgress,Start,remove,flags
+```
+
+[0]: https://docs.microsoft.com/en-us/xamarin/android/platform/binding-java-library/customizing-bindings/java-bindings-metadata#enumfieldsxml-and-enummethodsxml
diff --git a/Java.Interop.sln b/Java.Interop.sln
index 499dcc396..7ad9c60b7 100644
--- a/Java.Interop.sln
+++ b/Java.Interop.sln
@@ -87,6 +87,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Java.Interop.Tools.JavaSour
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "param-name-importer", "tools\param-name-importer\param-name-importer.csproj", "{0E3AF6C1-7638-464D-9174-485D494499DC}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Java.Interop.Tools.Generator", "src\Java.Interop.Tools.Generator\Java.Interop.Tools.Generator.csproj", "{C2FD2F12-DE3B-4FB9-A0D3-FA3EF597DD04}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Java.Interop.Tools.Generator-Tests", "tests\Java.Interop.Tools.Generator-Tests\Java.Interop.Tools.Generator-Tests.csproj", "{7F4828AB-3908-458C-B09F-33C74A1368F9}"
+EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Java.Interop.NamingCustomAttributes\Java.Interop.NamingCustomAttributes.projitems*{58b564a1-570d-4da2-b02d-25bddb1a9f4f}*SharedItemsImports = 5
@@ -239,6 +243,14 @@ Global
{0E3AF6C1-7638-464D-9174-485D494499DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0E3AF6C1-7638-464D-9174-485D494499DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0E3AF6C1-7638-464D-9174-485D494499DC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C2FD2F12-DE3B-4FB9-A0D3-FA3EF597DD04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C2FD2F12-DE3B-4FB9-A0D3-FA3EF597DD04}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C2FD2F12-DE3B-4FB9-A0D3-FA3EF597DD04}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C2FD2F12-DE3B-4FB9-A0D3-FA3EF597DD04}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7F4828AB-3908-458C-B09F-33C74A1368F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7F4828AB-3908-458C-B09F-33C74A1368F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7F4828AB-3908-458C-B09F-33C74A1368F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7F4828AB-3908-458C-B09F-33C74A1368F9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -280,6 +292,8 @@ Global
{E34BCFA0-CAA4-412C-AA1C-75DB8D67D157} = {172B608B-E6F3-41CC-9949-203A76BA247C}
{093B5E94-7FB7-499F-9C11-30944BAFEE25} = {271C9F30-F679-4793-942B-0D9527CB3E2F}
{0E3AF6C1-7638-464D-9174-485D494499DC} = {C8F58966-94BF-407F-914A-8654F8B8AE3B}
+ {C2FD2F12-DE3B-4FB9-A0D3-FA3EF597DD04} = {0998E45F-8BCE-4791-A944-962CD54E2D80}
+ {7F4828AB-3908-458C-B09F-33C74A1368F9} = {271C9F30-F679-4793-942B-0D9527CB3E2F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {29204E0C-382A-49A0-A814-AD7FBF9774A5}
diff --git a/Makefile b/Makefile
index 69bdd34c3..dbadfd6f6 100644
--- a/Makefile
+++ b/Makefile
@@ -27,7 +27,8 @@ TESTS = \
bin/Test$(CONFIGURATION)/logcat-parse-Tests.dll \
bin/Test$(CONFIGURATION)/generator-Tests.dll \
bin/Test$(CONFIGURATION)/Xamarin.Android.Tools.ApiXmlAdjuster-Tests.dll \
- bin/Test$(CONFIGURATION)/Xamarin.Android.Tools.Bytecode-Tests.dll
+ bin/Test$(CONFIGURATION)/Xamarin.Android.Tools.Bytecode-Tests.dll \
+ bin/Test$(CONFIGURATION)/Java.Interop.Tools.Generator-Tests.dll
PTESTS = \
bin/Test$(CONFIGURATION)/Java.Interop-PerformanceTests.dll
diff --git a/build-tools/automation/azure-pipelines.yaml b/build-tools/automation/azure-pipelines.yaml
index ede29279e..6f42cbdc6 100644
--- a/build-tools/automation/azure-pipelines.yaml
+++ b/build-tools/automation/azure-pipelines.yaml
@@ -58,7 +58,7 @@ jobs:
inputs:
solution: build-tools/scripts/RunNUnitTests.targets
configuration: $(Build.Configuration)
- msbuildArguments: /p:TestAssembly="bin\Test$(Build.Configuration)\generator-Tests.dll;bin\Test$(Build.Configuration)\Java.Interop.Tools.JavaCallableWrappers-Tests.dll;bin\Test$(Build.Configuration)\logcat-parse-Tests.dll;bin\Test$(Build.Configuration)\Xamarin.Android.Tools.ApiXmlAdjuster-Tests.dll;bin\Test$(Build.Configuration)\Xamarin.Android.Tools.Bytecode-Tests.dll"
+ msbuildArguments: /p:TestAssembly="bin\Test$(Build.Configuration)\generator-Tests.dll;bin\Test$(Build.Configuration)\Java.Interop.Tools.JavaCallableWrappers-Tests.dll;bin\Test$(Build.Configuration)\logcat-parse-Tests.dll;bin\Test$(Build.Configuration)\Xamarin.Android.Tools.ApiXmlAdjuster-Tests.dll;bin\Test$(Build.Configuration)\Xamarin.Android.Tools.Bytecode-Tests.dll;bin\Test$(Build.Configuration)\Java.Interop.Tools.Generator-Tests.dll"
condition: succeededOrFailed()
- task: PublishTestResults@2
@@ -95,6 +95,13 @@ jobs:
projects: Java.Interop.sln
arguments: '-c $(Build.Configuration)'
+ - task: DotNetCoreCLI@2
+ displayName: 'Tests: Java.Interop.Tools.Generator'
+ inputs:
+ command: test
+ arguments: bin\Test$(Build.Configuration)\Java.Interop.Tools.Generator-Tests.dll
+ continueOnError: true
+
- task: DotNetCoreCLI@2
displayName: 'Tests: generator'
inputs:
diff --git a/src/Java.Interop.Tools.Generator/Enumification/ConstantAction.cs b/src/Java.Interop.Tools.Generator/Enumification/ConstantAction.cs
new file mode 100644
index 000000000..ab7c6a118
--- /dev/null
+++ b/src/Java.Interop.Tools.Generator/Enumification/ConstantAction.cs
@@ -0,0 +1,11 @@
+namespace Java.Interop.Tools.Generator.Enumification
+{
+ public enum ConstantAction
+ {
+ None,
+ Ignore,
+ Enumify,
+ Add,
+ Remove
+ }
+}
diff --git a/src/Java.Interop.Tools.Generator/Enumification/ConstantEntry.cs b/src/Java.Interop.Tools.Generator/Enumification/ConstantEntry.cs
new file mode 100644
index 000000000..a0bcff11e
--- /dev/null
+++ b/src/Java.Interop.Tools.Generator/Enumification/ConstantEntry.cs
@@ -0,0 +1,225 @@
+using System;
+using System.Xml.Linq;
+
+namespace Java.Interop.Tools.Generator.Enumification
+{
+ ///
+ /// This represents a Java int constant and/or a C# enum entry.
+ ///
+ public class ConstantEntry
+ {
+ public ConstantAction Action { get; set; }
+ public int ApiLevel { get; set; }
+ public string JavaSignature { get; set; }
+ public string Value { get; set; }
+ public string EnumFullType { get; set; }
+ public string EnumMember { get; set; }
+ public FieldAction FieldAction { get; set; }
+ public bool IsFlags { get; set; }
+
+ public string EnumNamespace {
+ get {
+ if (!EnumFullType.HasValue ())
+ return string.Empty;
+
+ var index = EnumFullType.LastIndexOf ('.');
+
+ // There is no namespace, only a type
+ if (index == -1)
+ return string.Empty;
+
+ return EnumFullType.Substring (0, index);
+ }
+ }
+
+ public string EnumType {
+ get {
+ if (!EnumFullType.HasValue ())
+ return string.Empty;
+
+ var index = EnumFullType.LastIndexOf ('.');
+
+ // There is no namespace, only a type
+ if (index == -1)
+ return EnumFullType;
+
+ return EnumFullType.Substring (index + 1);
+ }
+ }
+
+ public string JavaPackage {
+ get {
+ if (!JavaSignature.HasValue ())
+ return string.Empty;
+
+ var index = JavaSignature.LastIndexOf ('/');
+
+ // There is no namespace, only a type
+ if (index == -1)
+ return string.Empty;
+
+ return JavaSignature.Substring (0, index);
+ }
+ }
+
+ public string JavaType {
+ get {
+ if (!JavaSignature.HasValue ())
+ return string.Empty;
+
+ var index = JavaSignature.LastIndexOf ('/');
+ var dot_index = JavaSignature.LastIndexOf ('.');
+
+ return JavaSignature.Substring (index + 1, dot_index - index - 1);
+ }
+ }
+
+ public string JavaName {
+ get {
+ if (!JavaSignature.HasValue ())
+ return string.Empty;
+
+ var index = JavaSignature.LastIndexOf ('.');
+
+ return JavaSignature.Substring (index + 1);
+ }
+ }
+
+ public static ConstantEntry FromString (string line, bool transientMode = false)
+ {
+ var parser = new CsvParser (line);
+
+ if (parser.GetField (0).In ("?", "I", "E", "A", "R"))
+ return FromVersion2String (parser);
+
+ return FromVersion1String (parser, transientMode);
+ }
+
+ static ConstantEntry FromVersion1String (CsvParser parser, bool transientMode)
+ {
+ var entry = new ConstantEntry {
+ Action = ConstantAction.Enumify,
+ ApiLevel = parser.GetFieldAsInt (0),
+ EnumFullType = parser.GetField (1),
+ EnumMember = parser.GetField (2),
+ JavaSignature = parser.GetField (3),
+ Value = parser.GetField (4),
+ IsFlags = parser.GetField (5).ToLowerInvariant () == "flags",
+ FieldAction = transientMode ? FieldAction.Remove : FieldAction.Keep
+ };
+
+ if (!entry.EnumNamespace.HasValue ()) {
+ // This is a line that only deletes a const, not maps it to an enum
+ entry.Action = ConstantAction.Remove;
+ entry.FieldAction = FieldAction.Remove;
+ } else if (!entry.JavaSignature.HasValue ()) {
+ // This is a line that adds an unmapped enum member
+ entry.Action = ConstantAction.Add;
+ entry.FieldAction = transientMode ? FieldAction.Remove : FieldAction.None;
+ }
+
+ entry.NormalizeJavaSignature ();
+
+ return entry;
+ }
+
+ static ConstantEntry FromVersion2String (CsvParser parser)
+ {
+ var entry = new ConstantEntry {
+ Action = FromConstantActionString (parser.GetField (0)),
+ ApiLevel = parser.GetFieldAsInt (1),
+ JavaSignature = parser.GetField (2),
+ Value = parser.GetField (3),
+ EnumFullType = parser.GetField (4),
+ EnumMember = parser.GetField (5),
+ FieldAction = FromFieldActionString (parser.GetField (6)),
+ IsFlags = parser.GetField (7).ToLowerInvariant () == "flags"
+ };
+
+ entry.NormalizeJavaSignature ();
+
+ return entry;
+ }
+
+ public static ConstantEntry FromElement (XElement elem)
+ {
+ var entry = new ConstantEntry {
+ Action = ConstantAction.None,
+ ApiLevel = NamingConverter.ParseApiLevel (elem.Attribute ("merge.SourceFile")?.Value),
+ JavaSignature = elem.Parent.Parent.Attribute ("name").Value,
+ Value = elem.Attribute ("value")?.Value,
+ };
+
+ var java_package = elem.Parent.Parent.Attribute ("name").Value.Replace ('.', '/');
+ var java_type = elem.Parent.Attribute ("name").Value.Replace ('.', '$');
+ var java_member = elem.Attribute ("name").Value;
+
+ entry.JavaSignature = $"{java_package}/{java_type}.{java_member}".TrimStart ('/');
+
+ // Interfaces get an "I:" prefix
+ if (elem.Parent.Name.LocalName == "interface")
+ entry.JavaSignature = "I:" + entry.JavaSignature;
+
+ return entry;
+ }
+
+ public string ToVersion2String ()
+ {
+ var fields = new [] {
+ Action == ConstantAction.None ? "?" : Action.ToString ().Substring (0, 1),
+ ApiLevel.ToString (),
+ JavaSignature,
+ Value,
+ EnumFullType,
+ EnumMember,
+ ToConstantFieldActionString (FieldAction),
+ IsFlags ? "flags" : string.Empty
+ };
+
+ return string.Join (",", fields);
+ }
+
+ static string ToConstantFieldActionString (FieldAction value)
+ {
+ return value switch
+ {
+ FieldAction.None => string.Empty,
+ FieldAction.Remove => "remove",
+ FieldAction.Keep => "keep",
+ _ => string.Empty
+ };
+
+ }
+ static ConstantAction FromConstantActionString (string value)
+ {
+ return value switch
+ {
+ "?" => ConstantAction.None,
+ "I" => ConstantAction.Ignore,
+ "E" => ConstantAction.Enumify,
+ "A" => ConstantAction.Add,
+ "R" => ConstantAction.Remove,
+ _ => throw new ArgumentOutOfRangeException (nameof (value), $"Specified action '{value}' is not valid"),
+ };
+ }
+
+ static FieldAction FromFieldActionString (string value)
+ {
+ return value.ToLowerInvariant () switch
+ {
+ "" => FieldAction.None,
+ "remove" => FieldAction.Remove,
+ "keep" => FieldAction.Keep,
+ _ => throw new ArgumentOutOfRangeException (nameof (value), $"Specified field action '{value}' is not valid"),
+ };
+ }
+
+ void NormalizeJavaSignature ()
+ {
+ // Somehow we got a mix of using dollar signs and periods
+ // for nested classes. Normalize to dollar signs.
+ if (JavaSignature.HasValue ())
+ JavaSignature = $"{JavaPackage}/{JavaType.Replace ('.', '$')}.{JavaName}";
+ }
+ }
+}
diff --git a/src/Java.Interop.Tools.Generator/Enumification/ConstantsParser.cs b/src/Java.Interop.Tools.Generator/Enumification/ConstantsParser.cs
new file mode 100644
index 000000000..084de35ef
--- /dev/null
+++ b/src/Java.Interop.Tools.Generator/Enumification/ConstantsParser.cs
@@ -0,0 +1,96 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Xml.Linq;
+using System.Xml.XPath;
+
+namespace Java.Interop.Tools.Generator.Enumification
+{
+ public static class ConstantsParser
+ {
+ public static List FromEnumMapCsv (string filename)
+ {
+ using (var sr = new StreamReader (filename))
+ return FromEnumMapCsv (sr);
+ }
+
+ public static List FromEnumMapCsv (TextReader reader)
+ {
+ var constants = new List ();
+ var transient = false;
+
+ string s;
+
+ // Read the enum csv file
+ while ((s = reader.ReadLine ()) != null) {
+ // Skip empty lines and comments
+ if (string.IsNullOrEmpty (s) || s.StartsWith ("//"))
+ continue;
+
+ // Transient mode means remove the original field
+ if (s == "- ENTER TRANSIENT MODE -") {
+ transient = true;
+ continue;
+ }
+
+ constants.Add (ConstantEntry.FromString (s, transient));
+ }
+
+ return constants;
+ }
+
+ public static void SaveEnumMapCsv (List constants, string filename)
+ {
+ using (var sw = new StreamWriter (filename))
+ SaveEnumMapCsv (constants, sw);
+ }
+
+ public static void SaveEnumMapCsv (List constants, TextWriter writer)
+ {
+ foreach (var c in Sort (constants))
+ writer.WriteLine (c.ToVersion2String ());
+ }
+
+ public static List FromApiXml (string filename) => FromApiXml (XDocument.Load (filename));
+
+ public static List FromApiXml (XDocument doc)
+ {
+ var int_fields = doc.XPathSelectElements ("//field[@type='int']");
+
+ return int_fields.Select (f => ConstantEntry.FromElement (f)).Where (c => c.Value.HasValue ()).ToList ();
+ }
+
+ public static List Sort (List constants)
+ {
+ // We want a well-defined sort to reduce diffs, but it's not as easy as just
+ // using the JavaSignature, because there may be added enum members that do not
+ // have a JavaSignature, and we would like them to be with their other members.
+ // For example:
+ // - A,0,,0,Java.MyEnum,None
+ // - E,1,java/class/member1,1,Java.MyEnum,Member1
+ // - E,1,java/class/member2,2,Java.MyEnum,Member2
+ var sorted = constants.Where (c => c.JavaSignature.HasValue ()).OrderBy (c => c.JavaSignature).ToList ();
+
+ // Try to put members without signatures at the beginning of the section with
+ // their fellow enum members. If not, put them at the end of the list.
+ foreach (var c in constants.Where (c => !c.JavaSignature.HasValue ()).OrderBy (c => $"{c.EnumFullType}.{c.EnumMember}")) {
+ var sibling_index = sorted.FindIndex (c2 => c2.EnumFullType == c.EnumFullType);
+
+ if (sibling_index >= 0)
+ sorted.Insert (sibling_index, c);
+ else
+ sorted.Add (c);
+ }
+
+ return sorted;
+ }
+ }
+
+ public class JavaSignatureComparer : IEqualityComparer
+ {
+ public static JavaSignatureComparer Instance { get; } = new JavaSignatureComparer ();
+
+ public bool Equals (ConstantEntry x, ConstantEntry y) => x?.JavaSignature == y?.JavaSignature;
+ public int GetHashCode (ConstantEntry obj) => 0;
+ }
+}
diff --git a/src/Java.Interop.Tools.Generator/Enumification/FieldAction.cs b/src/Java.Interop.Tools.Generator/Enumification/FieldAction.cs
new file mode 100644
index 000000000..03e5c5ce2
--- /dev/null
+++ b/src/Java.Interop.Tools.Generator/Enumification/FieldAction.cs
@@ -0,0 +1,9 @@
+namespace Java.Interop.Tools.Generator.Enumification
+{
+ public enum FieldAction
+ {
+ None,
+ Remove,
+ Keep
+ }
+}
diff --git a/src/Java.Interop.Tools.Generator/Extensions/UtilityExtensions.cs b/src/Java.Interop.Tools.Generator/Extensions/UtilityExtensions.cs
new file mode 100644
index 000000000..c5fee4130
--- /dev/null
+++ b/src/Java.Interop.Tools.Generator/Extensions/UtilityExtensions.cs
@@ -0,0 +1,27 @@
+using System;
+
+namespace Java.Interop.Tools.Generator
+{
+ public static class UtilityExtensions
+ {
+ public static bool In (this T enumeration, params T [] values)
+ {
+ foreach (var en in values)
+ if (enumeration.Equals (en))
+ return true;
+
+ return false;
+ }
+
+ public static bool StartsWithAny (this string value, params string [] values)
+ {
+ foreach (var en in values)
+ if (value.StartsWith (en, StringComparison.OrdinalIgnoreCase))
+ return true;
+
+ return false;
+ }
+
+ public static bool HasValue (this string str) => !string.IsNullOrEmpty (str);
+ }
+}
diff --git a/src/Java.Interop.Tools.Generator/Java.Interop.Tools.Generator.csproj b/src/Java.Interop.Tools.Generator/Java.Interop.Tools.Generator.csproj
new file mode 100644
index 000000000..c9252c92d
--- /dev/null
+++ b/src/Java.Interop.Tools.Generator/Java.Interop.Tools.Generator.csproj
@@ -0,0 +1,8 @@
+
+
+
+ netstandard2.0
+ 8.0
+
+
+
diff --git a/src/Java.Interop.Tools.Generator/Utilities/CsvParser.cs b/src/Java.Interop.Tools.Generator/Utilities/CsvParser.cs
new file mode 100644
index 000000000..66e2ecd8f
--- /dev/null
+++ b/src/Java.Interop.Tools.Generator/Utilities/CsvParser.cs
@@ -0,0 +1,27 @@
+using System;
+
+namespace Java.Interop.Tools.Generator
+{
+ public class CsvParser
+ {
+ readonly string [] fields;
+
+ public CsvParser (string line)
+ {
+ fields = line.Split (',');
+ }
+
+ public string GetField (int index)
+ {
+ if (index >= fields.Length)
+ return string.Empty;
+
+ return fields [index].Trim ();
+ }
+
+ public int GetFieldAsInt (int index)
+ {
+ return int.Parse (GetField (index));
+ }
+ }
+}
diff --git a/src/Java.Interop.Tools.Generator/Utilities/NamingConverter.cs b/src/Java.Interop.Tools.Generator/Utilities/NamingConverter.cs
new file mode 100644
index 000000000..93f98f761
--- /dev/null
+++ b/src/Java.Interop.Tools.Generator/Utilities/NamingConverter.cs
@@ -0,0 +1,23 @@
+using System;
+
+namespace Java.Interop.Tools.Generator
+{
+ public static class NamingConverter
+ {
+ ///
+ /// Converts a 'merge.SourceFile' attribute to an API level. (ex. "..\..\bin\BuildDebug\api\api-28.xml.in")
+ ///
+ public static int ParseApiLevel (string value)
+ {
+ if (!value.HasValue ())
+ return 0;
+
+ var hyphen = value.IndexOf ('-');
+ var period = value.IndexOf ('.', hyphen);
+
+ var result = value.Substring (hyphen + 1, period - hyphen - 1);
+
+ return int.Parse (result == "R" ? "30" : result);
+ }
+ }
+}
diff --git a/tests/Java.Interop.Tools.Generator-Tests/Enumification/ConstantEntryTests.cs b/tests/Java.Interop.Tools.Generator-Tests/Enumification/ConstantEntryTests.cs
new file mode 100644
index 000000000..6a46b828e
--- /dev/null
+++ b/tests/Java.Interop.Tools.Generator-Tests/Enumification/ConstantEntryTests.cs
@@ -0,0 +1,121 @@
+using System;
+using Java.Interop.Tools.Generator.Enumification;
+using NUnit.Framework;
+
+namespace Java.Interop.Tools.Generator_Tests
+{
+ public class ConstantEntryTests
+ {
+ [Test]
+ public void ParseEnumMapV1 ()
+ {
+ var csv = "10,Org.XmlPull.V1.XmlPullParserNode,Cdsect,I:org/xmlpull/v1/XmlPullParser.CDSECT,5";
+ var entry = ConstantEntry.FromString (csv);
+
+ Assert.AreEqual (ConstantAction.Enumify, entry.Action);
+ Assert.AreEqual (10, entry.ApiLevel);
+ Assert.AreEqual ("I:org/xmlpull/v1/XmlPullParser.CDSECT", entry.JavaSignature);
+ Assert.AreEqual ("5", entry.Value);
+ Assert.AreEqual ("Org.XmlPull.V1.XmlPullParserNode", entry.EnumFullType);
+ Assert.AreEqual ("Cdsect", entry.EnumMember);
+ Assert.AreEqual (FieldAction.Keep, entry.FieldAction);
+ Assert.False (entry.IsFlags);
+ }
+
+ [Test]
+ public void ParseTransientEnumMapV1 ()
+ {
+ var csv = "10,Org.XmlPull.V1.XmlPullParserNode,Cdsect,I:org/xmlpull/v1/XmlPullParser.CDSECT,5";
+ var entry = ConstantEntry.FromString (csv, true);
+
+ Assert.AreEqual (ConstantAction.Enumify, entry.Action);
+ Assert.AreEqual (10, entry.ApiLevel);
+ Assert.AreEqual ("I:org/xmlpull/v1/XmlPullParser.CDSECT", entry.JavaSignature);
+ Assert.AreEqual ("5", entry.Value);
+ Assert.AreEqual ("Org.XmlPull.V1.XmlPullParserNode", entry.EnumFullType);
+ Assert.AreEqual ("Cdsect", entry.EnumMember);
+ Assert.AreEqual (FieldAction.Remove, entry.FieldAction);
+ Assert.False (entry.IsFlags);
+ }
+
+ [Test]
+ public void ParseAddEnumMapV1 ()
+ {
+ var csv = "10,Org.XmlPull.V1.XmlPullParserNode,Cdsect,,5";
+ var entry = ConstantEntry.FromString (csv);
+
+ Assert.AreEqual (ConstantAction.Add, entry.Action);
+ Assert.AreEqual (10, entry.ApiLevel);
+ Assert.AreEqual (string.Empty, entry.JavaSignature);
+ Assert.AreEqual ("5", entry.Value);
+ Assert.AreEqual ("Org.XmlPull.V1.XmlPullParserNode", entry.EnumFullType);
+ Assert.AreEqual ("Cdsect", entry.EnumMember);
+ Assert.AreEqual (FieldAction.None, entry.FieldAction);
+ Assert.False (entry.IsFlags);
+ }
+
+ [Test]
+ public void ParseRemoveEnumMapV1 ()
+ {
+ var csv = "10,,,I:org/xmlpull/v1/XmlPullParser.CDSECT,5";
+ var entry = ConstantEntry.FromString (csv);
+
+ Assert.AreEqual (ConstantAction.Remove, entry.Action);
+ Assert.AreEqual (10, entry.ApiLevel);
+ Assert.AreEqual ("I:org/xmlpull/v1/XmlPullParser.CDSECT", entry.JavaSignature);
+ Assert.AreEqual ("5", entry.Value);
+ Assert.AreEqual (string.Empty, entry.EnumFullType);
+ Assert.AreEqual (string.Empty, entry.EnumMember);
+ Assert.AreEqual (FieldAction.Remove, entry.FieldAction);
+ Assert.False (entry.IsFlags);
+ }
+
+ [Test]
+ public void ParseEnumMapV2 ()
+ {
+ var csv = "E,10,I:org/xmlpull/v1/XmlPullParser.CDSECT,5,Org.XmlPull.V1.XmlPullParserNode,Cdsect,keep";
+ var entry = ConstantEntry.FromString (csv);
+
+ Assert.AreEqual (ConstantAction.Enumify, entry.Action);
+ Assert.AreEqual (10, entry.ApiLevel);
+ Assert.AreEqual ("I:org/xmlpull/v1/XmlPullParser.CDSECT", entry.JavaSignature);
+ Assert.AreEqual ("5", entry.Value);
+ Assert.AreEqual ("Org.XmlPull.V1.XmlPullParserNode", entry.EnumFullType);
+ Assert.AreEqual ("Cdsect", entry.EnumMember);
+ Assert.AreEqual (FieldAction.Keep, entry.FieldAction);
+ Assert.False (entry.IsFlags);
+ }
+
+ [Test]
+ public void ParseAddEnumMapV2 ()
+ {
+ var csv = "A,10,,5,Org.XmlPull.V1.XmlPullParserNode,Cdsect";
+ var entry = ConstantEntry.FromString (csv);
+
+ Assert.AreEqual (ConstantAction.Add, entry.Action);
+ Assert.AreEqual (10, entry.ApiLevel);
+ Assert.AreEqual (string.Empty, entry.JavaSignature);
+ Assert.AreEqual ("5", entry.Value);
+ Assert.AreEqual ("Org.XmlPull.V1.XmlPullParserNode", entry.EnumFullType);
+ Assert.AreEqual ("Cdsect", entry.EnumMember);
+ Assert.AreEqual (FieldAction.None, entry.FieldAction);
+ Assert.False (entry.IsFlags);
+ }
+
+ [Test]
+ public void ParseRemoveEnumMapV2 ()
+ {
+ var csv = "R,10,I:org/xmlpull/v1/XmlPullParser.CDSECT,5,,,remove";
+ var entry = ConstantEntry.FromString (csv);
+
+ Assert.AreEqual (ConstantAction.Remove, entry.Action);
+ Assert.AreEqual (10, entry.ApiLevel);
+ Assert.AreEqual ("I:org/xmlpull/v1/XmlPullParser.CDSECT", entry.JavaSignature);
+ Assert.AreEqual ("5", entry.Value);
+ Assert.AreEqual (string.Empty, entry.EnumFullType);
+ Assert.AreEqual (string.Empty, entry.EnumMember);
+ Assert.AreEqual (FieldAction.Remove, entry.FieldAction);
+ Assert.False (entry.IsFlags);
+ }
+ }
+}
diff --git a/tests/Java.Interop.Tools.Generator-Tests/Java.Interop.Tools.Generator-Tests.csproj b/tests/Java.Interop.Tools.Generator-Tests/Java.Interop.Tools.Generator-Tests.csproj
new file mode 100644
index 000000000..a556d2ff4
--- /dev/null
+++ b/tests/Java.Interop.Tools.Generator-Tests/Java.Interop.Tools.Generator-Tests.csproj
@@ -0,0 +1,24 @@
+
+
+
+ net472
+ Java.Interop.Tools.Common_Tests
+ false
+
+
+
+ $(TestOutputFullPath)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/generator-Tests/Unit-Tests/EnumMappingsTests.cs b/tests/generator-Tests/Unit-Tests/EnumMappingsTests.cs
new file mode 100644
index 000000000..e3dcde232
--- /dev/null
+++ b/tests/generator-Tests/Unit-Tests/EnumMappingsTests.cs
@@ -0,0 +1,288 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MonoDroid.Generation;
+using NUnit.Framework;
+
+namespace generatortests
+{
+ [TestFixture]
+ public class EnumMappingsTests
+ {
+ [Test]
+ public void BasicEnumificationTest ()
+ {
+ // This should create a new enum and remove the field
+ var csv = "10,Org.XmlPull.V1.XmlPullParserNode,Cdsect,I:org/xmlpull/v1/XmlPullParser.CDSECT,5";
+ var mappings = new EnumMappings (string.Empty, string.Empty, "30", false);
+ var sr = new StringReader (csv);
+
+ var removes = new List> ();
+ var enums = mappings.ParseFieldMappings (sr, new string [0], 30, removes);
+
+ Assert.AreEqual ("[I:org/xmlpull/v1/XmlPullParser.CDSECT, ]", removes.Single ().ToString ());
+
+ Assert.AreEqual ("Org.XmlPull.V1.XmlPullParserNode", enums.Single ().Key);
+ Assert.AreEqual (false, enums.Single ().Value.BitField);
+ Assert.AreEqual (true, enums.Single ().Value.FieldsRemoved);
+ Assert.AreEqual ("[Cdsect, I:org/xmlpull/v1/XmlPullParser.CDSECT]", enums.First ().Value.JniNames.Single ().ToString ());
+ Assert.AreEqual ("[Cdsect, 5]", enums.First ().Value.Members.Single ().ToString ());
+ }
+
+ [Test]
+ public void RemoveFieldOnlyTest ()
+ {
+ // This should only remove the field
+ var csv = "10,,,I:org/xmlpull/v1/XmlPullParser.CDSECT,5";
+ var mappings = new EnumMappings (string.Empty, string.Empty, "30", false);
+ var sr = new StringReader (csv);
+
+ var removes = new List> ();
+ var enums = mappings.ParseFieldMappings (sr, new string [0], 30, removes);
+
+ Assert.AreEqual ("[I:org/xmlpull/v1/XmlPullParser.CDSECT, ]", removes.Single ().ToString ());
+
+ Assert.AreEqual (0, enums.Count);
+ }
+
+ [Test]
+ public void AddConstantOnlyTest ()
+ {
+ // This should only add an enum
+ var csv = "10,Org.XmlPull.V1.XmlPullParserNode,Cdsect,,5";
+ var mappings = new EnumMappings (string.Empty, string.Empty, "30", false);
+ var sr = new StringReader (csv);
+
+ var removes = new List> ();
+ var enums = mappings.ParseFieldMappings (sr, new string [0], 30, removes);
+
+ Assert.AreEqual (0, removes.Count);
+
+ Assert.AreEqual ("Org.XmlPull.V1.XmlPullParserNode", enums.Single ().Key);
+ Assert.AreEqual (false, enums.Single ().Value.BitField);
+ Assert.AreEqual (true, enums.Single ().Value.FieldsRemoved);
+ Assert.AreEqual ("[Cdsect, ]", enums.First ().Value.JniNames.Single ().ToString ());
+ Assert.AreEqual ("[Cdsect, 5]", enums.First ().Value.Members.Single ().ToString ());
+ }
+
+ [Test]
+ public void FlagsEnumerationTest ()
+ {
+ // This should create a new enum with [Flags] because of the ",Flags" field
+ var csv = "10,Org.XmlPull.V1.XmlPullParserNode,Cdsect,I:org/xmlpull/v1/XmlPullParser.CDSECT,5,Flags";
+ var mappings = new EnumMappings (string.Empty, string.Empty, "30", false);
+ var sr = new StringReader (csv);
+
+ var removes = new List> ();
+ var enums = mappings.ParseFieldMappings (sr, new string [0], 30, removes);
+
+ Assert.AreEqual (true, enums.Single ().Value.BitField);
+ }
+
+ [Test]
+ public void ExternalFlagsEnumerationTest ()
+ {
+ // This should create a new enum with [Flags] because of the "enumFlags" parameter
+ var csv = "10,Org.XmlPull.V1.XmlPullParserNode,Cdsect,I:org/xmlpull/v1/XmlPullParser.CDSECT,5";
+ var mappings = new EnumMappings (string.Empty, string.Empty, "30", false);
+ var sr = new StringReader (csv);
+
+ var removes = new List> ();
+ var enums = mappings.ParseFieldMappings (sr, new [] { "Org.XmlPull.V1.XmlPullParserNode" }, 30, removes);
+
+ Assert.AreEqual (true, enums.Single ().Value.BitField);
+ }
+
+ [Test]
+ public void ApiVersionExcludedTest ()
+ {
+ // This should be completely ignored because it's API=10 and we're looking for API=5
+ var csv = "10,Org.XmlPull.V1.XmlPullParserNode,Cdsect,I:org/xmlpull/v1/XmlPullParser.CDSECT,5,Flags";
+ var mappings = new EnumMappings (string.Empty, string.Empty, "5", false);
+ var sr = new StringReader (csv);
+
+ var removes = new List> ();
+ var enums = mappings.ParseFieldMappings (sr, new string [0], 5, removes);
+
+ Assert.AreEqual (0, removes.Count);
+ Assert.AreEqual (0, enums.Count);
+ }
+
+ [Test]
+ public void TransientEnumificationTest ()
+ {
+ // This should create a new enum and remove the field
+ var csv = $"- ENTER TRANSIENT MODE -{Environment.NewLine}10,Org.XmlPull.V1.XmlPullParserNode,Cdsect,I:org/xmlpull/v1/XmlPullParser.CDSECT,5";
+ var mappings = new EnumMappings (string.Empty, string.Empty, "30", false);
+ var sr = new StringReader (csv);
+
+ var removes = new List> ();
+ var enums = mappings.ParseFieldMappings (sr, new string [0], 30, removes);
+
+ Assert.AreEqual ("[I:org/xmlpull/v1/XmlPullParser.CDSECT, Org.XmlPull.V1.XmlPullParserNode]", removes.Single ().ToString ());
+
+ Assert.AreEqual ("Org.XmlPull.V1.XmlPullParserNode", enums.Single ().Key);
+ Assert.AreEqual (false, enums.Single ().Value.BitField);
+ Assert.AreEqual (false, enums.Single ().Value.FieldsRemoved);
+ Assert.AreEqual ("[Cdsect, I:org/xmlpull/v1/XmlPullParser.CDSECT]", enums.First ().Value.JniNames.Single ().ToString ());
+ Assert.AreEqual ("[Cdsect, 5]", enums.First ().Value.Members.Single ().ToString ());
+ }
+
+ [Test]
+ public void TransientRemoveFieldOnlyTest ()
+ {
+ // Transient has no effect here
+ var csv = $"- ENTER TRANSIENT MODE -{Environment.NewLine}10,,,I:org/xmlpull/v1/XmlPullParser.CDSECT,5";
+ var mappings = new EnumMappings (string.Empty, string.Empty, "30", false);
+ var sr = new StringReader (csv);
+
+ var removes = new List> ();
+ var enums = mappings.ParseFieldMappings (sr, new string [0], 30, removes);
+
+ Assert.AreEqual ("[I:org/xmlpull/v1/XmlPullParser.CDSECT, ]", removes.Single ().ToString ());
+
+ Assert.AreEqual (0, enums.Count);
+ }
+
+ [Test]
+ public void TransientAddConstantOnlyTest ()
+ {
+ // This should only add an enum
+ var csv = $"- ENTER TRANSIENT MODE -{Environment.NewLine}10,Org.XmlPull.V1.XmlPullParserNode,Cdsect,,5";
+ var mappings = new EnumMappings (string.Empty, string.Empty, "30", false);
+ var sr = new StringReader (csv);
+
+ var removes = new List> ();
+ var enums = mappings.ParseFieldMappings (sr, new string [0], 30, removes);
+
+ Assert.AreEqual (0, removes.Count);
+
+ Assert.AreEqual ("Org.XmlPull.V1.XmlPullParserNode", enums.Single ().Key);
+ Assert.AreEqual (false, enums.Single ().Value.BitField);
+ Assert.AreEqual (false, enums.Single ().Value.FieldsRemoved);
+ Assert.AreEqual ("[Cdsect, ]", enums.First ().Value.JniNames.Single ().ToString ());
+ Assert.AreEqual ("[Cdsect, 5]", enums.First ().Value.Members.Single ().ToString ());
+ }
+ [Test]
+ public void BasicEnumificationV2Test ()
+ {
+ // This should create a new enum and remove the field
+ var csv = "E,10,I:org/xmlpull/v1/XmlPullParser.CDSECT,5,Org.XmlPull.V1.XmlPullParserNode,Cdsect";
+ var mappings = new EnumMappings (string.Empty, string.Empty, "30", false);
+ var sr = new StringReader (csv);
+
+ var removes = new List> ();
+ var enums = mappings.ParseFieldMappings (sr, new string [0], 30, removes);
+
+ Assert.AreEqual ("[I:org/xmlpull/v1/XmlPullParser.CDSECT, ]", removes.Single ().ToString ());
+
+ Assert.AreEqual ("Org.XmlPull.V1.XmlPullParserNode", enums.Single ().Key);
+ Assert.AreEqual (false, enums.Single ().Value.BitField);
+ Assert.AreEqual (true, enums.Single ().Value.FieldsRemoved);
+ Assert.AreEqual ("[Cdsect, I:org/xmlpull/v1/XmlPullParser.CDSECT]", enums.First ().Value.JniNames.Single ().ToString ());
+ Assert.AreEqual ("[Cdsect, 5]", enums.First ().Value.Members.Single ().ToString ());
+ }
+
+ [Test]
+ public void RemoveFieldOnlyV2Test ()
+ {
+ // This should only remove the field
+ var csv = "R,10,I:org/xmlpull/v1/XmlPullParser.CDSECT,5";
+ var mappings = new EnumMappings (string.Empty, string.Empty, "30", false);
+ var sr = new StringReader (csv);
+
+ var removes = new List> ();
+ var enums = mappings.ParseFieldMappings (sr, new string [0], 30, removes);
+
+ Assert.AreEqual ("[I:org/xmlpull/v1/XmlPullParser.CDSECT, ]", removes.Single ().ToString ());
+
+ Assert.AreEqual (0, enums.Count);
+ }
+
+ [Test]
+ public void AddConstantOnlyV2Test ()
+ {
+ // This should only add an enum
+ var csv = "A,10,,5,Org.XmlPull.V1.XmlPullParserNode,Cdsect";
+ var mappings = new EnumMappings (string.Empty, string.Empty, "30", false);
+ var sr = new StringReader (csv);
+
+ var removes = new List> ();
+ var enums = mappings.ParseFieldMappings (sr, new string [0], 30, removes);
+
+ Assert.AreEqual (0, removes.Count);
+
+ Assert.AreEqual ("Org.XmlPull.V1.XmlPullParserNode", enums.Single ().Key);
+ Assert.AreEqual (false, enums.Single ().Value.BitField);
+ Assert.AreEqual (true, enums.Single ().Value.FieldsRemoved);
+ Assert.AreEqual ("[Cdsect, ]", enums.First ().Value.JniNames.Single ().ToString ());
+ Assert.AreEqual ("[Cdsect, 5]", enums.First ().Value.Members.Single ().ToString ());
+ }
+
+ [Test]
+ public void FlagsEnumerationV2Test ()
+ {
+ // This should create a new enum with [Flags] because of the ",Flags" field
+ var csv = "E,10,I:org/xmlpull/v1/XmlPullParser.CDSECT,5,Org.XmlPull.V1.XmlPullParserNode,Cdsect,remove,Flags";
+ var mappings = new EnumMappings (string.Empty, string.Empty, "30", false);
+ var sr = new StringReader (csv);
+
+ var removes = new List> ();
+ var enums = mappings.ParseFieldMappings (sr, new string [0], 30, removes);
+
+ Assert.AreEqual (true, enums.Single ().Value.BitField);
+ }
+
+ [Test]
+ public void ExternalFlagsEnumerationV2Test ()
+ {
+ // This should create a new enum with [Flags] because of the "enumFlags" parameter
+ var csv = "E,10,I:org/xmlpull/v1/XmlPullParser.CDSECT,5,Org.XmlPull.V1.XmlPullParserNode,Cdsect";
+ var mappings = new EnumMappings (string.Empty, string.Empty, "30", false);
+ var sr = new StringReader (csv);
+
+ var removes = new List> ();
+ var enums = mappings.ParseFieldMappings (sr, new [] { "Org.XmlPull.V1.XmlPullParserNode" }, 30, removes);
+
+ Assert.AreEqual (true, enums.Single ().Value.BitField);
+ }
+
+ [Test]
+ public void ApiVersionExcludedV2Test ()
+ {
+ // This should be completely ignored because it's API=10 and we're looking for API=5
+ var csv = "E,10,I:org/xmlpull/v1/XmlPullParser.CDSECT,5,Org.XmlPull.V1.XmlPullParserNode,Cdsect";
+ var mappings = new EnumMappings (string.Empty, string.Empty, "5", false);
+ var sr = new StringReader (csv);
+
+ var removes = new List> ();
+ var enums = mappings.ParseFieldMappings (sr, new string [0], 5, removes);
+
+ Assert.AreEqual (0, removes.Count);
+ Assert.AreEqual (0, enums.Count);
+ }
+
+ [Test]
+ public void TransientEnumificationV2Test ()
+ {
+ // This should create a new enum and remove the field
+ var csv = "E,10,I:org/xmlpull/v1/XmlPullParser.CDSECT,5,Org.XmlPull.V1.XmlPullParserNode,Cdsect,remove";
+ var mappings = new EnumMappings (string.Empty, string.Empty, "30", false);
+ var sr = new StringReader (csv);
+
+ var removes = new List> ();
+ var enums = mappings.ParseFieldMappings (sr, new string [0], 30, removes);
+
+ Assert.AreEqual ("[I:org/xmlpull/v1/XmlPullParser.CDSECT, Org.XmlPull.V1.XmlPullParserNode]", removes.Single ().ToString ());
+
+ Assert.AreEqual ("Org.XmlPull.V1.XmlPullParserNode", enums.Single ().Key);
+ Assert.AreEqual (false, enums.Single ().Value.BitField);
+ Assert.AreEqual (false, enums.Single ().Value.FieldsRemoved);
+ Assert.AreEqual ("[Cdsect, I:org/xmlpull/v1/XmlPullParser.CDSECT]", enums.First ().Value.JniNames.Single ().ToString ());
+ Assert.AreEqual ("[Cdsect, 5]", enums.First ().Value.Members.Single ().ToString ());
+ }
+ }
+}
diff --git a/tools/generator/Java.Interop.Tools.Generator.Transformation/EnumMappings.cs b/tools/generator/Java.Interop.Tools.Generator.Transformation/EnumMappings.cs
index 174b643b5..663e46fee 100644
--- a/tools/generator/Java.Interop.Tools.Generator.Transformation/EnumMappings.cs
+++ b/tools/generator/Java.Interop.Tools.Generator.Transformation/EnumMappings.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
@@ -8,6 +9,8 @@
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
+using Java.Interop.Tools.Generator;
+using Java.Interop.Tools.Generator.Enumification;
using Mono.Options;
using Xamarin.Android.Tools;
@@ -43,68 +46,36 @@ internal Dictionary ParseFieldMappings (string csv, str
internal Dictionary ParseFieldMappings (TextReader source, string [] enumFlags, int filter_version, IList> remove_nodes)
{
var enums = new Dictionary ();
+
if (source == null)
return enums;
- bool transient = false;
- string s;
- string last_enum = null;
- EnumDescription enumDescription = null;
+ var constants = ConstantsParser.FromEnumMapCsv (source);
- while ((s = source.ReadLine ()) != null) {
- try {
- if (string.IsNullOrEmpty (s) || s.StartsWith ("//"))
- continue;
- if (s == "- ENTER TRANSIENT MODE -") {
- transient = true;
- continue;
- }
-
- string[] pieces = s.Split (',');
-
- string verstr = pieces[0].Trim ();
- string enu = pieces[1].Trim ();
- string member = pieces[2].Trim ();
- string java_name = pieces[3].Trim ();
- string value = pieces[4].Trim ();
-
- if (filter_version > 0 && filter_version < int.Parse (verstr))
- continue;
-
- if (!string.IsNullOrEmpty (java_name.Trim ()))
- remove_nodes.Add (new KeyValuePair (java_name.Trim (), transient ? enu : null));
-
- // This is a line that only deletes a const, not maps it to an enum
- if (string.IsNullOrEmpty (enu))
- continue;
-
- // If this is a new enum, add the old one and reset things
- if (last_enum != enu) {
- if (last_enum != null)
- enums.Add (last_enum, enumDescription);
-
- last_enum = enu;
- enumDescription = new EnumDescription () { FieldsRemoved = !transient };
- }
-
- if (pieces.Length > 5)
- enumDescription.BitField = pieces [5].Trim () == "Flags";
-
- if (enumFlags != null && enumFlags.Contains (enu))
- enumDescription.BitField = true;
-
- // Add this member to the enum
- enumDescription.Members.Add (member, value);
- enumDescription.JniNames.Add (member, java_name);
- } catch (Exception ex) {
- Report.Error (Report.ErrorEnumMapping + 0, "ERROR at parsing enum " + s, ex);
- throw;
+ // Discard ignored constants
+ constants = constants.Where (c =>
+ c.Action.In (ConstantAction.Enumify, ConstantAction.Add, ConstantAction.Remove) &&
+ (c.ApiLevel == 0 || c.ApiLevel <= filter_version)).ToList ();
+
+ // Find the constant fields we need remove
+ foreach (var constant in constants.Where (c => c.Action.In (ConstantAction.Enumify, ConstantAction.Remove)))
+ remove_nodes.Add (new KeyValuePair (constant.JavaSignature, constant.FieldAction == FieldAction.Remove && constant.EnumFullType.HasValue () ? constant.EnumFullType : null));
+
+ // Find the enumerations that we need to create
+ foreach (var group in constants.Where (c => c.Action.In (ConstantAction.Enumify, ConstantAction.Add)).GroupBy (c => c.EnumFullType)) {
+
+ var desc = new EnumDescription {
+ FieldsRemoved = group.Any (c => c.FieldAction != FieldAction.Remove),
+ BitField = group.Any (c => c.IsFlags) || enumFlags?.Contains (group.Key) == true
+ };
+
+ foreach (var c in group) {
+ desc.Members.Add (c.EnumMember, c.Value);
+ desc.JniNames.Add (c.EnumMember, c.JavaSignature);
}
- }
- // Make sure the last enum gets added to the list
- if (last_enum != null)
- enums.Add (last_enum, enumDescription);
+ enums.Add (group.Key, desc);
+ }
return enums;
}
diff --git a/tools/generator/Utilities/CsvParser.cs b/tools/generator/Utilities/CsvParser.cs
new file mode 100644
index 000000000..b2c5d134e
--- /dev/null
+++ b/tools/generator/Utilities/CsvParser.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MonoDroid.Generation
+{
+ public class CsvParser
+ {
+ readonly string [] fields;
+
+ public CsvParser (string line)
+ {
+ fields = line.Split (',');
+ }
+
+ public string GetField (int index)
+ {
+ if (index >= fields.Length)
+ return string.Empty;
+
+ return fields [index].Trim ();
+ }
+
+ public int GetFieldAsInt (int index)
+ {
+ return int.Parse (GetField (index));
+ }
+ }
+}
diff --git a/tools/generator/generator.csproj b/tools/generator/generator.csproj
index dcddb1f9d..e95c7c7d0 100644
--- a/tools/generator/generator.csproj
+++ b/tools/generator/generator.csproj
@@ -4,6 +4,7 @@
net472;netcoreapp3.1
Exe
$(DefineConstants);GENERATOR;HAVE_CECIL;JCW_ONLY_TYPE_NAMES
+ 8.0
@@ -39,6 +40,7 @@
+