Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -190,6 +190,7 @@ static string ToConstantFieldActionString (FieldAction value)
};

}

static ConstantAction FromConstantActionString (string value)
{
return value switch
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Java.Interop.Tools.Generator.Enumification
{
public enum MethodAction
{
None,
Ignore,
Enumify,
}
}
144 changes: 144 additions & 0 deletions src/Java.Interop.Tools.Generator/Enumification/MethodMapEntry.cs
Original file line number Diff line number Diff line change
@@ -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<MethodMapEntry> 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);
}
}
}
66 changes: 66 additions & 0 deletions src/Java.Interop.Tools.Generator/Enumification/MethodMapParser.cs
Original file line number Diff line number Diff line change
@@ -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<MethodMapEntry> FromMethodMapCsv (string filename)
{
using (var sr = new StreamReader (filename))
return FromMethodMapCsv (sr);
}

public static List<MethodMapEntry> FromMethodMapCsv (TextReader reader)
{
var entries = new List<MethodMapEntry> ();

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<MethodMapEntry> entries, string filename, bool version2)
{
using (var sw = new StreamWriter (filename))
SaveMethodMapCsv (entries, sw, version2);
}

public static void SaveMethodMapCsv (IEnumerable<MethodMapEntry> entries, TextWriter writer, bool version2)
{
foreach (var entry in entries.OrderBy (e => e.JavaSignature))
writer.WriteLine (version2 ? entry.ToVersion2String () : entry.ToVersion1String ());
}

public static List<MethodMapEntry> FromApiXml (string filename) => FromApiXml (XDocument.Load (filename));

public static List<MethodMapEntry> FromApiXml (XDocument doc)
{
var results = new List<MethodMapEntry> ();

// 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;
}
}
}
53 changes: 52 additions & 1 deletion src/Java.Interop.Tools.Generator/Utilities/NamingConverter.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Linq;
using System.Xml.Linq;

namespace Java.Interop.Tools.Generator
{
Expand All @@ -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 '<class>' element and not its children <method>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 ());
}
}
}