diff --git a/src/Java.Interop.Tools.Generator/Enumification/ConstantEntry.cs b/src/Java.Interop.Tools.Generator/Enumification/ConstantEntry.cs index 4a35f259a..0891121a0 100644 --- a/src/Java.Interop.Tools.Generator/Enumification/ConstantEntry.cs +++ b/src/Java.Interop.Tools.Generator/Enumification/ConstantEntry.cs @@ -145,7 +145,7 @@ public static ConstantEntry FromElement (XElement elem) { var entry = new ConstantEntry { Action = ConstantAction.None, - ApiLevel = NamingConverter.ParseApiLevel (elem.Attribute ("merge.SourceFile")?.Value), + ApiLevel = NamingConverter.ParseApiLevel (elem), JavaSignature = elem.Parent.Parent.Attribute ("name").Value, Value = elem.Attribute ("value")?.Value, }; @@ -190,6 +190,7 @@ static string ToConstantFieldActionString (FieldAction value) }; } + static ConstantAction FromConstantActionString (string value) { return value switch diff --git a/src/Java.Interop.Tools.Generator/Enumification/MethodAction.cs b/src/Java.Interop.Tools.Generator/Enumification/MethodAction.cs new file mode 100644 index 000000000..05af02ae9 --- /dev/null +++ b/src/Java.Interop.Tools.Generator/Enumification/MethodAction.cs @@ -0,0 +1,9 @@ +namespace Java.Interop.Tools.Generator.Enumification +{ + public enum MethodAction + { + None, + Ignore, + Enumify, + } +} diff --git a/src/Java.Interop.Tools.Generator/Enumification/MethodMapEntry.cs b/src/Java.Interop.Tools.Generator/Enumification/MethodMapEntry.cs new file mode 100644 index 000000000..cde4d7e37 --- /dev/null +++ b/src/Java.Interop.Tools.Generator/Enumification/MethodMapEntry.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Xml.Linq; +using Xamarin.Android.Tools; + +namespace Java.Interop.Tools.Generator.Enumification +{ + public class MethodMapEntry + { + public MethodAction Action { get; set; } + public int ApiLevel { get; set; } + public string? JavaPackage { get; set; } + public string? JavaType { get; set; } + public string? JavaName { get; set; } + public string? ParameterName { get; set; } + public string? EnumFullType { get; set; } + public string JavaSignature => $"{JavaPackage}/{JavaType}.{JavaName}.{ParameterName}"; + public bool IsInterface { get; set; } + + public static IEnumerable FromXml (XElement element) + { + // Handle fields first + if (element.Name == "field") { + yield return FromElement (element, element.XGetAttribute ("name")!); + yield break; + } + + // Now methods and constructors + // There could be multiple entries, from the return type and multiple parameters + if (element.XGetAttribute ("return") == "int") + yield return FromElement (element, "return"); + + foreach (var p in element.Elements ("parameter")) + if (p.XGetAttribute ("type") == "int") + yield return FromElement (element, p.XGetAttribute ("name")!); + } + + static MethodMapEntry FromElement (XElement element, string parameterName) + { + var entry = new MethodMapEntry { + JavaPackage = element.Parent.Parent.XGetAttribute ("name")?.Replace ('.', '/'), + JavaType = element.Parent.XGetAttribute ("name")?.Replace ('.', '$'), + JavaName = element.XGetAttribute ("name"), + ParameterName = parameterName, + ApiLevel = NamingConverter.ParseApiLevel (element), + IsInterface = element.Parent.Name == "interface" + }; + + if (element.Name == "constructor") + entry.JavaName = "ctor"; + + return entry; + } + + public static MethodMapEntry FromString (string line) + { + var parser = new CsvParser (line); + + if (parser.GetField (0).In ("?", "I", "E")) + return FromVersion2String (parser); + + return FromVersion1String (parser); + } + + static MethodMapEntry FromVersion1String (CsvParser parser) + { + var entry = new MethodMapEntry { + Action = MethodAction.Enumify, + ApiLevel = parser.GetFieldAsInt (0), + JavaPackage = parser.GetField (1), + JavaType = parser.GetField (2), + JavaName = parser.GetField (3), + ParameterName = parser.GetField (4), + EnumFullType = parser.GetField (5) + }; + + if (entry.JavaType.StartsWith ("[Interface]", StringComparison.Ordinal)) { + entry.IsInterface = true; + entry.JavaType = entry.JavaType.Substring ("[Interface]".Length); + } + + return entry; + } + + static MethodMapEntry FromVersion2String (CsvParser parser) + { + var entry = new MethodMapEntry { + Action = FromMethodActionString (parser.GetField (0)), + ApiLevel = parser.GetFieldAsInt (1), + JavaPackage = parser.GetField (2), + JavaType = parser.GetField (3), + JavaName = parser.GetField (4), + ParameterName = parser.GetField (5), + EnumFullType = parser.GetField (6) + }; + + if (entry.JavaType.StartsWith ("[Interface]", StringComparison.Ordinal)) { + entry.IsInterface = true; + entry.JavaType = entry.JavaType.Substring ("[Interface]".Length); + } + + return entry; + } + + static MethodAction FromMethodActionString (string value) + { + return value switch { + "?" => MethodAction.None, + "I" => MethodAction.Ignore, + "E" => MethodAction.Enumify, + _ => throw new ArgumentOutOfRangeException (nameof (value), $"Specified action '{value}' is not valid"), + }; + } + + public string ToVersion1String () + { + var fields = new [] { + ApiLevel.ToString (), + JavaPackage, + (IsInterface ? "I:" : string.Empty) + JavaType, + JavaName, + ParameterName, + EnumFullType, + }; + + return string.Join (",", fields); + } + + public string ToVersion2String () + { + var fields = new [] { + Action == MethodAction.None ? "?" : Action.ToString ().Substring (0, 1), + ApiLevel.ToString (), + JavaPackage, + (IsInterface ? "[Interface]" : string.Empty) + JavaType, + JavaName, + ParameterName, + EnumFullType, + }; + + return string.Join (",", fields); + } + } +} diff --git a/src/Java.Interop.Tools.Generator/Enumification/MethodMapParser.cs b/src/Java.Interop.Tools.Generator/Enumification/MethodMapParser.cs new file mode 100644 index 000000000..5168ed71b --- /dev/null +++ b/src/Java.Interop.Tools.Generator/Enumification/MethodMapParser.cs @@ -0,0 +1,66 @@ +using System; +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 class MethodMapParser + { + public static List FromMethodMapCsv (string filename) + { + using (var sr = new StreamReader (filename)) + return FromMethodMapCsv (sr); + } + + public static List FromMethodMapCsv (TextReader reader) + { + var entries = new List (); + + string s; + + // Read the enum csv file + while ((s = reader.ReadLine ()) != null) { + // Skip empty lines and comments + if (string.IsNullOrEmpty (s) || s.StartsWith ("//", StringComparison.Ordinal)) + continue; + + entries.Add (MethodMapEntry.FromString (s)); + } + + return entries; + } + + public static void SaveMethodMapCsv (IEnumerable entries, string filename, bool version2) + { + using (var sw = new StreamWriter (filename)) + SaveMethodMapCsv (entries, sw, version2); + } + + public static void SaveMethodMapCsv (IEnumerable entries, TextWriter writer, bool version2) + { + foreach (var entry in entries.OrderBy (e => e.JavaSignature)) + writer.WriteLine (version2 ? entry.ToVersion2String () : entry.ToVersion1String ()); + } + + public static List FromApiXml (string filename) => FromApiXml (XDocument.Load (filename)); + + public static List FromApiXml (XDocument doc) + { + var results = new List (); + + // Methods that return int or have an int parameter + results.AddRange (doc.XPathSelectElements ("//method[@return='int'] | //method[parameter/@type='int']").SelectMany (x => MethodMapEntry.FromXml (x))); + + // Constructors with an int parameter + results.AddRange (doc.XPathSelectElements ("//constructor[parameter/@type='int']").SelectMany (x => MethodMapEntry.FromXml (x))); + + // Fields that are a non-constant int + results.AddRange (doc.XPathSelectElements ("//field[@type='int' and @final='false']").SelectMany (x => MethodMapEntry.FromXml (x))); + + return results; + } + } +} diff --git a/src/Java.Interop.Tools.Generator/Utilities/NamingConverter.cs b/src/Java.Interop.Tools.Generator/Utilities/NamingConverter.cs index 5a5fcd99d..0e17dbd8e 100644 --- a/src/Java.Interop.Tools.Generator/Utilities/NamingConverter.cs +++ b/src/Java.Interop.Tools.Generator/Utilities/NamingConverter.cs @@ -1,4 +1,6 @@ using System; +using System.Linq; +using System.Xml.Linq; namespace Java.Interop.Tools.Generator { @@ -17,7 +19,56 @@ public static int ParseApiLevel (string? value) var result = value.Substring (hyphen + 1, period - hyphen - 1); - return int.Parse (result == "R" ? "30" : result); + return result switch { + "R" => 30, + "S" => 31, + _ => int.Parse (result) + }; + } + + // The 'merge.SourceFile' attribute may be on the element, or only on its parent. For example, + // a new 'class' added will only put the attribute on the '' element and not its children s. + public static int ParseApiLevel (XElement element) + { + var loop = element; + + while (loop != null) { + if (loop.Attribute ("merge.SourceFile") is XAttribute attr) + return ParseApiLevel (attr.Value); + + loop = loop.Parent; + } + + return 0; + } + + public static string ConvertNamespaceToCSharp (string v) + { + return string.Join (".", v.Split ('.').Select (s => Capitalize (s))); + } + + public static string ConvertClassToCSharp (string javaType) + { + return javaType; + } + + public static string ConvertFieldToCSharp (string javaName) + { + // EX: FOREGROUND_SERVICE_IMMEDIATE + return string.Join ("", javaName.Split ('_').Select (s => SentenceCase (s))); + } + + public static string Capitalize (this string value) + { + if (value.Length < 1) + return value; + + return char.ToUpperInvariant (value[0]) + value.Substring (1); + } + + public static string SentenceCase (this string value) + { + return Capitalize (value.ToLowerInvariant ()); } } }