From 96bec4a504f4ffe77318cb4665260bff064e4af2 Mon Sep 17 00:00:00 2001 From: Jonathan Pobst Date: Fri, 8 May 2020 10:31:16 -0500 Subject: [PATCH 1/2] [generator] Create a v2 version of map.csv. --- Documentation/EnumMappingFile.md | 97 ++++++ Java.Interop.sln | 14 + Makefile | 3 +- build-tools/automation/azure-pipelines.yaml | 9 +- .../Enumification/ConstantAction.cs | 11 + .../Enumification/ConstantEntry.cs | 225 ++++++++++++++ .../Enumification/ConstantsParser.cs | 96 ++++++ .../Enumification/FieldAction.cs | 9 + .../Extensions/UtilityExtensions.cs | 27 ++ .../Java.Interop.Tools.Common.csproj | 8 + .../Utilities/CsvParser.cs | 27 ++ .../Utilities/NamingConverter.cs | 23 ++ .../Enumification/ConstantEntryTests.cs | 121 ++++++++ .../Java.Interop.Tools.Common-Tests.csproj | 24 ++ .../Unit-Tests/EnumMappingsTests.cs | 288 ++++++++++++++++++ .../EnumMappings.cs | 83 ++--- tools/generator/Utilities/CsvParser.cs | 31 ++ tools/generator/generator.csproj | 2 + 18 files changed, 1040 insertions(+), 58 deletions(-) create mode 100644 Documentation/EnumMappingFile.md create mode 100644 src/Java.Interop.Tools.Common/Enumification/ConstantAction.cs create mode 100644 src/Java.Interop.Tools.Common/Enumification/ConstantEntry.cs create mode 100644 src/Java.Interop.Tools.Common/Enumification/ConstantsParser.cs create mode 100644 src/Java.Interop.Tools.Common/Enumification/FieldAction.cs create mode 100644 src/Java.Interop.Tools.Common/Extensions/UtilityExtensions.cs create mode 100644 src/Java.Interop.Tools.Common/Java.Interop.Tools.Common.csproj create mode 100644 src/Java.Interop.Tools.Common/Utilities/CsvParser.cs create mode 100644 src/Java.Interop.Tools.Common/Utilities/NamingConverter.cs create mode 100644 tests/Java.Interop.Tools.Common-Tests/Enumification/ConstantEntryTests.cs create mode 100644 tests/Java.Interop.Tools.Common-Tests/Java.Interop.Tools.Common-Tests.csproj create mode 100644 tests/generator-Tests/Unit-Tests/EnumMappingsTests.cs create mode 100644 tools/generator/Utilities/CsvParser.cs diff --git a/Documentation/EnumMappingFile.md b/Documentation/EnumMappingFile.md new file mode 100644 index 000000000..63b9960d1 --- /dev/null +++ b/Documentation/EnumMappingFile.md @@ -0,0 +1,97 @@ +# 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. + +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 constants referenced *AFTER* this line will be removed from the bindings. + +## 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. + +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..d39d135ab 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.Common", "src\Java.Interop.Tools.Common\Java.Interop.Tools.Common.csproj", "{C2FD2F12-DE3B-4FB9-A0D3-FA3EF597DD04}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Java.Interop.Tools.Common-Tests", "tests\Java.Interop.Tools.Common-Tests\Java.Interop.Tools.Common-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..055dd7c5f 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.Common-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..7e85e20ed 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.Common-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: Common' + inputs: + command: test + arguments: bin\Test$(Build.Configuration)\Java.Interop.Tools.Common-Tests.dll + continueOnError: true + - task: DotNetCoreCLI@2 displayName: 'Tests: generator' inputs: diff --git a/src/Java.Interop.Tools.Common/Enumification/ConstantAction.cs b/src/Java.Interop.Tools.Common/Enumification/ConstantAction.cs new file mode 100644 index 000000000..964bdb505 --- /dev/null +++ b/src/Java.Interop.Tools.Common/Enumification/ConstantAction.cs @@ -0,0 +1,11 @@ +namespace Java.Interop.Tools.Common.Enumification +{ + public enum ConstantAction + { + None, + Ignore, + Enumify, + Add, + Remove + } +} diff --git a/src/Java.Interop.Tools.Common/Enumification/ConstantEntry.cs b/src/Java.Interop.Tools.Common/Enumification/ConstantEntry.cs new file mode 100644 index 000000000..48f55f164 --- /dev/null +++ b/src/Java.Interop.Tools.Common/Enumification/ConstantEntry.cs @@ -0,0 +1,225 @@ +using System; +using System.Xml.Linq; + +namespace Java.Interop.Tools.Common.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.Common/Enumification/ConstantsParser.cs b/src/Java.Interop.Tools.Common/Enumification/ConstantsParser.cs new file mode 100644 index 000000000..d13294861 --- /dev/null +++ b/src/Java.Interop.Tools.Common/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.Common.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.Common/Enumification/FieldAction.cs b/src/Java.Interop.Tools.Common/Enumification/FieldAction.cs new file mode 100644 index 000000000..fcf59eba8 --- /dev/null +++ b/src/Java.Interop.Tools.Common/Enumification/FieldAction.cs @@ -0,0 +1,9 @@ +namespace Java.Interop.Tools.Common.Enumification +{ + public enum FieldAction + { + None, + Remove, + Keep + } +} diff --git a/src/Java.Interop.Tools.Common/Extensions/UtilityExtensions.cs b/src/Java.Interop.Tools.Common/Extensions/UtilityExtensions.cs new file mode 100644 index 000000000..8cbf43957 --- /dev/null +++ b/src/Java.Interop.Tools.Common/Extensions/UtilityExtensions.cs @@ -0,0 +1,27 @@ +using System; + +namespace Java.Interop.Tools.Common +{ + 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.Common/Java.Interop.Tools.Common.csproj b/src/Java.Interop.Tools.Common/Java.Interop.Tools.Common.csproj new file mode 100644 index 000000000..c9252c92d --- /dev/null +++ b/src/Java.Interop.Tools.Common/Java.Interop.Tools.Common.csproj @@ -0,0 +1,8 @@ + + + + netstandard2.0 + 8.0 + + + diff --git a/src/Java.Interop.Tools.Common/Utilities/CsvParser.cs b/src/Java.Interop.Tools.Common/Utilities/CsvParser.cs new file mode 100644 index 000000000..62b0f4089 --- /dev/null +++ b/src/Java.Interop.Tools.Common/Utilities/CsvParser.cs @@ -0,0 +1,27 @@ +using System; + +namespace Java.Interop.Tools.Common +{ + 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.Common/Utilities/NamingConverter.cs b/src/Java.Interop.Tools.Common/Utilities/NamingConverter.cs new file mode 100644 index 000000000..cfa583559 --- /dev/null +++ b/src/Java.Interop.Tools.Common/Utilities/NamingConverter.cs @@ -0,0 +1,23 @@ +using System; + +namespace Java.Interop.Tools.Common +{ + 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.Common-Tests/Enumification/ConstantEntryTests.cs b/tests/Java.Interop.Tools.Common-Tests/Enumification/ConstantEntryTests.cs new file mode 100644 index 000000000..98733ee4b --- /dev/null +++ b/tests/Java.Interop.Tools.Common-Tests/Enumification/ConstantEntryTests.cs @@ -0,0 +1,121 @@ +using System; +using Java.Interop.Tools.Common.Enumification; +using NUnit.Framework; + +namespace Java.Interop.Tools.Common_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.Common-Tests/Java.Interop.Tools.Common-Tests.csproj b/tests/Java.Interop.Tools.Common-Tests/Java.Interop.Tools.Common-Tests.csproj new file mode 100644 index 000000000..cf9213f6d --- /dev/null +++ b/tests/Java.Interop.Tools.Common-Tests/Java.Interop.Tools.Common-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..7981b51a2 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.Common; +using Java.Interop.Tools.Common.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..3f49cc062 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 @@ + From ae9c61eaba11df76d44a2896a11d18a95b8cac4b Mon Sep 17 00:00:00 2001 From: Jonathan Pobst Date: Tue, 19 May 2020 10:33:59 -0500 Subject: [PATCH 2/2] Rename to Java.Interop.Tools.Generator.dll. --- Documentation/EnumMappingFile.md | 7 ++++--- Java.Interop.sln | 4 ++-- Makefile | 2 +- build-tools/automation/azure-pipelines.yaml | 6 +++--- .../Enumification/ConstantAction.cs | 2 +- .../Enumification/ConstantEntry.cs | 2 +- .../Enumification/ConstantsParser.cs | 2 +- .../Enumification/FieldAction.cs | 2 +- .../Extensions/UtilityExtensions.cs | 2 +- .../Java.Interop.Tools.Generator.csproj} | 0 .../Utilities/CsvParser.cs | 2 +- .../Utilities/NamingConverter.cs | 2 +- .../Enumification/ConstantEntryTests.cs | 4 ++-- .../Java.Interop.Tools.Generator-Tests.csproj} | 2 +- .../EnumMappings.cs | 4 ++-- tools/generator/generator.csproj | 2 +- 16 files changed, 23 insertions(+), 22 deletions(-) rename src/{Java.Interop.Tools.Common => Java.Interop.Tools.Generator}/Enumification/ConstantAction.cs (61%) rename src/{Java.Interop.Tools.Common => Java.Interop.Tools.Generator}/Enumification/ConstantEntry.cs (99%) rename src/{Java.Interop.Tools.Common => Java.Interop.Tools.Generator}/Enumification/ConstantsParser.cs (98%) rename src/{Java.Interop.Tools.Common => Java.Interop.Tools.Generator}/Enumification/FieldAction.cs (53%) rename src/{Java.Interop.Tools.Common => Java.Interop.Tools.Generator}/Extensions/UtilityExtensions.cs (93%) rename src/{Java.Interop.Tools.Common/Java.Interop.Tools.Common.csproj => Java.Interop.Tools.Generator/Java.Interop.Tools.Generator.csproj} (100%) rename src/{Java.Interop.Tools.Common => Java.Interop.Tools.Generator}/Utilities/CsvParser.cs (90%) rename src/{Java.Interop.Tools.Common => Java.Interop.Tools.Generator}/Utilities/NamingConverter.cs (92%) rename tests/{Java.Interop.Tools.Common-Tests => Java.Interop.Tools.Generator-Tests}/Enumification/ConstantEntryTests.cs (97%) rename tests/{Java.Interop.Tools.Common-Tests/Java.Interop.Tools.Common-Tests.csproj => Java.Interop.Tools.Generator-Tests/Java.Interop.Tools.Generator-Tests.csproj} (85%) diff --git a/Documentation/EnumMappingFile.md b/Documentation/EnumMappingFile.md index 63b9960d1..e5143ea38 100644 --- a/Documentation/EnumMappingFile.md +++ b/Documentation/EnumMappingFile.md @@ -26,7 +26,7 @@ The basic format since the beginning of Xamarin contains up to 6 fields: * **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. + `[Flags]` attribute. (Any member will `flags` will make the whole enum `[Flags]`.) Full example: ``` @@ -49,7 +49,8 @@ file can contain a line like this at any point: - ENTER TRANSIENT MODE - ``` -Any constants referenced *AFTER* this line will be removed from the bindings. +Any v1 constants referenced *AFTER* this line will be removed from the bindings. +(This will not affect v2 constants.) ## CSV Format v2 @@ -87,7 +88,7 @@ A "v2" line contains up to 8 fields: * `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. + `[Flags]` attribute. (Any member will `flags` will make the whole enum `[Flags]`.) Full example: ``` diff --git a/Java.Interop.sln b/Java.Interop.sln index d39d135ab..7ad9c60b7 100644 --- a/Java.Interop.sln +++ b/Java.Interop.sln @@ -87,9 +87,9 @@ 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.Common", "src\Java.Interop.Tools.Common\Java.Interop.Tools.Common.csproj", "{C2FD2F12-DE3B-4FB9-A0D3-FA3EF597DD04}" +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.Common-Tests", "tests\Java.Interop.Tools.Common-Tests\Java.Interop.Tools.Common-Tests.csproj", "{7F4828AB-3908-458C-B09F-33C74A1368F9}" +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 diff --git a/Makefile b/Makefile index 055dd7c5f..dbadfd6f6 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ TESTS = \ 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)/Java.Interop.Tools.Common-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 7e85e20ed..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;bin\Test$(Build.Configuration)\Java.Interop.Tools.Common-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 @@ -96,10 +96,10 @@ jobs: arguments: '-c $(Build.Configuration)' - task: DotNetCoreCLI@2 - displayName: 'Tests: Common' + displayName: 'Tests: Java.Interop.Tools.Generator' inputs: command: test - arguments: bin\Test$(Build.Configuration)\Java.Interop.Tools.Common-Tests.dll + arguments: bin\Test$(Build.Configuration)\Java.Interop.Tools.Generator-Tests.dll continueOnError: true - task: DotNetCoreCLI@2 diff --git a/src/Java.Interop.Tools.Common/Enumification/ConstantAction.cs b/src/Java.Interop.Tools.Generator/Enumification/ConstantAction.cs similarity index 61% rename from src/Java.Interop.Tools.Common/Enumification/ConstantAction.cs rename to src/Java.Interop.Tools.Generator/Enumification/ConstantAction.cs index 964bdb505..ab7c6a118 100644 --- a/src/Java.Interop.Tools.Common/Enumification/ConstantAction.cs +++ b/src/Java.Interop.Tools.Generator/Enumification/ConstantAction.cs @@ -1,4 +1,4 @@ -namespace Java.Interop.Tools.Common.Enumification +namespace Java.Interop.Tools.Generator.Enumification { public enum ConstantAction { diff --git a/src/Java.Interop.Tools.Common/Enumification/ConstantEntry.cs b/src/Java.Interop.Tools.Generator/Enumification/ConstantEntry.cs similarity index 99% rename from src/Java.Interop.Tools.Common/Enumification/ConstantEntry.cs rename to src/Java.Interop.Tools.Generator/Enumification/ConstantEntry.cs index 48f55f164..a0bcff11e 100644 --- a/src/Java.Interop.Tools.Common/Enumification/ConstantEntry.cs +++ b/src/Java.Interop.Tools.Generator/Enumification/ConstantEntry.cs @@ -1,7 +1,7 @@ using System; using System.Xml.Linq; -namespace Java.Interop.Tools.Common.Enumification +namespace Java.Interop.Tools.Generator.Enumification { /// /// This represents a Java int constant and/or a C# enum entry. diff --git a/src/Java.Interop.Tools.Common/Enumification/ConstantsParser.cs b/src/Java.Interop.Tools.Generator/Enumification/ConstantsParser.cs similarity index 98% rename from src/Java.Interop.Tools.Common/Enumification/ConstantsParser.cs rename to src/Java.Interop.Tools.Generator/Enumification/ConstantsParser.cs index d13294861..084de35ef 100644 --- a/src/Java.Interop.Tools.Common/Enumification/ConstantsParser.cs +++ b/src/Java.Interop.Tools.Generator/Enumification/ConstantsParser.cs @@ -4,7 +4,7 @@ using System.Xml.Linq; using System.Xml.XPath; -namespace Java.Interop.Tools.Common.Enumification +namespace Java.Interop.Tools.Generator.Enumification { public static class ConstantsParser { diff --git a/src/Java.Interop.Tools.Common/Enumification/FieldAction.cs b/src/Java.Interop.Tools.Generator/Enumification/FieldAction.cs similarity index 53% rename from src/Java.Interop.Tools.Common/Enumification/FieldAction.cs rename to src/Java.Interop.Tools.Generator/Enumification/FieldAction.cs index fcf59eba8..03e5c5ce2 100644 --- a/src/Java.Interop.Tools.Common/Enumification/FieldAction.cs +++ b/src/Java.Interop.Tools.Generator/Enumification/FieldAction.cs @@ -1,4 +1,4 @@ -namespace Java.Interop.Tools.Common.Enumification +namespace Java.Interop.Tools.Generator.Enumification { public enum FieldAction { diff --git a/src/Java.Interop.Tools.Common/Extensions/UtilityExtensions.cs b/src/Java.Interop.Tools.Generator/Extensions/UtilityExtensions.cs similarity index 93% rename from src/Java.Interop.Tools.Common/Extensions/UtilityExtensions.cs rename to src/Java.Interop.Tools.Generator/Extensions/UtilityExtensions.cs index 8cbf43957..c5fee4130 100644 --- a/src/Java.Interop.Tools.Common/Extensions/UtilityExtensions.cs +++ b/src/Java.Interop.Tools.Generator/Extensions/UtilityExtensions.cs @@ -1,6 +1,6 @@ using System; -namespace Java.Interop.Tools.Common +namespace Java.Interop.Tools.Generator { public static class UtilityExtensions { diff --git a/src/Java.Interop.Tools.Common/Java.Interop.Tools.Common.csproj b/src/Java.Interop.Tools.Generator/Java.Interop.Tools.Generator.csproj similarity index 100% rename from src/Java.Interop.Tools.Common/Java.Interop.Tools.Common.csproj rename to src/Java.Interop.Tools.Generator/Java.Interop.Tools.Generator.csproj diff --git a/src/Java.Interop.Tools.Common/Utilities/CsvParser.cs b/src/Java.Interop.Tools.Generator/Utilities/CsvParser.cs similarity index 90% rename from src/Java.Interop.Tools.Common/Utilities/CsvParser.cs rename to src/Java.Interop.Tools.Generator/Utilities/CsvParser.cs index 62b0f4089..66e2ecd8f 100644 --- a/src/Java.Interop.Tools.Common/Utilities/CsvParser.cs +++ b/src/Java.Interop.Tools.Generator/Utilities/CsvParser.cs @@ -1,6 +1,6 @@ using System; -namespace Java.Interop.Tools.Common +namespace Java.Interop.Tools.Generator { public class CsvParser { diff --git a/src/Java.Interop.Tools.Common/Utilities/NamingConverter.cs b/src/Java.Interop.Tools.Generator/Utilities/NamingConverter.cs similarity index 92% rename from src/Java.Interop.Tools.Common/Utilities/NamingConverter.cs rename to src/Java.Interop.Tools.Generator/Utilities/NamingConverter.cs index cfa583559..93f98f761 100644 --- a/src/Java.Interop.Tools.Common/Utilities/NamingConverter.cs +++ b/src/Java.Interop.Tools.Generator/Utilities/NamingConverter.cs @@ -1,6 +1,6 @@ using System; -namespace Java.Interop.Tools.Common +namespace Java.Interop.Tools.Generator { public static class NamingConverter { diff --git a/tests/Java.Interop.Tools.Common-Tests/Enumification/ConstantEntryTests.cs b/tests/Java.Interop.Tools.Generator-Tests/Enumification/ConstantEntryTests.cs similarity index 97% rename from tests/Java.Interop.Tools.Common-Tests/Enumification/ConstantEntryTests.cs rename to tests/Java.Interop.Tools.Generator-Tests/Enumification/ConstantEntryTests.cs index 98733ee4b..6a46b828e 100644 --- a/tests/Java.Interop.Tools.Common-Tests/Enumification/ConstantEntryTests.cs +++ b/tests/Java.Interop.Tools.Generator-Tests/Enumification/ConstantEntryTests.cs @@ -1,8 +1,8 @@ using System; -using Java.Interop.Tools.Common.Enumification; +using Java.Interop.Tools.Generator.Enumification; using NUnit.Framework; -namespace Java.Interop.Tools.Common_Tests +namespace Java.Interop.Tools.Generator_Tests { public class ConstantEntryTests { diff --git a/tests/Java.Interop.Tools.Common-Tests/Java.Interop.Tools.Common-Tests.csproj b/tests/Java.Interop.Tools.Generator-Tests/Java.Interop.Tools.Generator-Tests.csproj similarity index 85% rename from tests/Java.Interop.Tools.Common-Tests/Java.Interop.Tools.Common-Tests.csproj rename to tests/Java.Interop.Tools.Generator-Tests/Java.Interop.Tools.Generator-Tests.csproj index cf9213f6d..a556d2ff4 100644 --- a/tests/Java.Interop.Tools.Common-Tests/Java.Interop.Tools.Common-Tests.csproj +++ b/tests/Java.Interop.Tools.Generator-Tests/Java.Interop.Tools.Generator-Tests.csproj @@ -18,7 +18,7 @@ - + diff --git a/tools/generator/Java.Interop.Tools.Generator.Transformation/EnumMappings.cs b/tools/generator/Java.Interop.Tools.Generator.Transformation/EnumMappings.cs index 7981b51a2..663e46fee 100644 --- a/tools/generator/Java.Interop.Tools.Generator.Transformation/EnumMappings.cs +++ b/tools/generator/Java.Interop.Tools.Generator.Transformation/EnumMappings.cs @@ -9,8 +9,8 @@ using System.Xml; using System.Xml.Linq; using System.Xml.XPath; -using Java.Interop.Tools.Common; -using Java.Interop.Tools.Common.Enumification; +using Java.Interop.Tools.Generator; +using Java.Interop.Tools.Generator.Enumification; using Mono.Options; using Xamarin.Android.Tools; diff --git a/tools/generator/generator.csproj b/tools/generator/generator.csproj index 3f49cc062..e95c7c7d0 100644 --- a/tools/generator/generator.csproj +++ b/tools/generator/generator.csproj @@ -40,7 +40,7 @@ - +