Skip to content

Commit a1e7971

Browse files
committed
[generator] Encapsulate and parallelize enum generation.
1 parent 644e759 commit a1e7971

File tree

8 files changed

+253
-65
lines changed

8 files changed

+253
-65
lines changed
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using static MonoDroid.Generation.EnumMappings;
6+
7+
namespace MonoDroid.Generation
8+
{
9+
class EnumGenerator
10+
{
11+
protected TextWriter sw;
12+
13+
public EnumGenerator (TextWriter writer)
14+
{
15+
sw = writer;
16+
}
17+
18+
public void WriteEnumeration (KeyValuePair<string, EnumDescription> enu, GenBase [] gens)
19+
{
20+
string ns = enu.Key.Substring (0, enu.Key.LastIndexOf ('.')).Trim ();
21+
string enoom = enu.Key.Substring (enu.Key.LastIndexOf ('.') + 1).Trim ();
22+
23+
sw.WriteLine ("namespace {0} {{", ns);
24+
if (enu.Value.BitField)
25+
sw.WriteLine (" [System.Flags]");
26+
sw.WriteLine (" public enum {0} {{", enoom);
27+
28+
foreach (var member in enu.Value.Members) {
29+
var managedMember = FindManagedMember (enu.Value, member.Key, gens);
30+
sw.WriteLine (" [global::Android.Runtime.IntDefinition (" + (managedMember != null ? "\"" + managedMember + "\"" : "null") + ", JniField = \"" + StripExtraInterfaceSpec (enu.Value.JniNames [member.Key]) + "\")]");
31+
sw.WriteLine (" {0} = {1},", member.Key.Trim (), member.Value.Trim ());
32+
}
33+
sw.WriteLine (" }");
34+
sw.WriteLine ("}");
35+
}
36+
37+
string FindManagedMember (EnumDescription desc, string enumFieldName, IEnumerable<GenBase> gens)
38+
{
39+
if (desc.FieldsRemoved)
40+
return null;
41+
42+
var jniMember = desc.JniNames [enumFieldName];
43+
if (string.IsNullOrWhiteSpace (jniMember)) {
44+
// enum values like "None" falls here.
45+
return null;
46+
}
47+
return FindManagedMember (jniMember, gens);
48+
}
49+
50+
WeakReference cache_found_class;
51+
52+
string FindManagedMember (string jniMember, IEnumerable<GenBase> gens)
53+
{
54+
string package, type, member;
55+
ParseJniMember (jniMember, out package, out type, out member);
56+
var fullJavaType = (string.IsNullOrEmpty (package) ? "" : package + ".") + type;
57+
58+
var cls = cache_found_class != null ? cache_found_class.Target as GenBase : null;
59+
if (cls == null || cls.JniName != fullJavaType) {
60+
cls = gens.FirstOrDefault (g => g.JavaName == fullJavaType);
61+
if (cls == null) {
62+
// The class was not found e.g. removed by metadata fixup.
63+
return null;
64+
}
65+
cache_found_class = new WeakReference (cls);
66+
}
67+
var fld = cls.Fields.FirstOrDefault (f => f.JavaName == member);
68+
if (fld == null) {
69+
// The field was not found e.g. removed by metadata fixup.
70+
return null;
71+
}
72+
return cls.FullName + "." + fld.Name;
73+
}
74+
75+
internal void ParseJniMember (string jniMember, out string package, out string type, out string member)
76+
{
77+
try {
78+
DoParseJniMember (jniMember, out package, out type, out member);
79+
} catch (Exception ex) {
80+
throw new Exception (string.Format ("failed to parse enum mapping: JNI member: {0}", jniMember, ex));
81+
}
82+
}
83+
84+
static void DoParseJniMember (string jniMember, out string package, out string type, out string member)
85+
{
86+
int endPackage = jniMember.LastIndexOf ('/');
87+
int endClass = jniMember.LastIndexOf ('.');
88+
89+
package = jniMember.Substring (0, endPackage).Replace ('/', '.');
90+
if (package.StartsWith ("I:"))
91+
package = package.Substring (2);
92+
93+
if (endClass >= 0) {
94+
type = jniMember.Substring (endPackage + 1, endClass - endPackage - 1).Replace ('$', '.');
95+
member = jniMember.Substring (endClass + 1);
96+
} else {
97+
type = jniMember.Substring (endPackage + 1).Replace ('$', '.');
98+
member = "";
99+
}
100+
}
101+
102+
string StripExtraInterfaceSpec (string jniFieldSpec)
103+
{
104+
return jniFieldSpec.StartsWith ("I:", StringComparison.Ordinal) ? jniFieldSpec.Substring (2) : jniFieldSpec;
105+
}
106+
}
107+
}

tools/generator/Java.Interop.Tools.Generator.Transformation/EnumMappings.cs

Lines changed: 14 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
using System;
1+
using System;
2+
using System.Collections.Concurrent;
23
using System.Collections.Generic;
34
using System.IO;
45
using System.Linq;
56
using System.Text;
7+
using System.Threading.Tasks;
68
using System.Xml;
79
using System.Xml.Linq;
810
using System.Xml.XPath;
@@ -139,75 +141,22 @@ internal List<string> WriteEnumerations (string output_dir, Dictionary<string, E
139141
if (!Directory.Exists (output_dir))
140142
Directory.CreateDirectory (output_dir);
141143

142-
var files = new List<string> ();
143-
foreach (var enu in enums) {
144-
var path = Path.Combine (output_dir, GetFileName (enu.Key, useShortFileNames) + ".cs");
145-
files.Add (path);
146-
using (StreamWriter sw = new StreamWriter (path, append: false)) {
147-
string ns = enu.Key.Substring (0, enu.Key.LastIndexOf ('.')).Trim ();
148-
string enoom = enu.Key.Substring (enu.Key.LastIndexOf ('.') + 1).Trim ();
149-
150-
sw.WriteLine ("namespace {0} {{", ns);
151-
if (enu.Value.BitField)
152-
sw.WriteLine (" [System.Flags]");
153-
sw.WriteLine (" public enum {0} {{", enoom);
154-
155-
foreach (var member in enu.Value.Members) {
156-
var managedMember = FindManagedMember (ns, enoom, enu.Value, member.Key, gens);
157-
sw.WriteLine (" [global::Android.Runtime.IntDefinition (" + (managedMember != null? "\"" + managedMember + "\"" : "null") + ", JniField = \"" + StripExtraInterfaceSpec (enu.Value.JniNames [member.Key]) + "\")]");
158-
sw.WriteLine (" {0} = {1},", member.Key.Trim (), member.Value.Trim ());
159-
}
160-
sw.WriteLine (" }");
161-
sw.WriteLine ("}");
162-
}
163-
}
164-
return files;
165-
}
166-
167-
string StripExtraInterfaceSpec (string jniFieldSpec)
168-
{
169-
return jniFieldSpec.StartsWith ("I:", StringComparison.Ordinal) ? jniFieldSpec.Substring (2) : jniFieldSpec;
170-
}
171-
172-
string FindManagedMember (string ns, string enumName, EnumDescription desc, string enumFieldName, IEnumerable<GenBase> gens)
173-
{
174-
if (desc.FieldsRemoved)
175-
return null;
176-
177-
var jniMember = desc.JniNames [enumFieldName];
178-
if (string.IsNullOrWhiteSpace (jniMember)) {
179-
// enum values like "None" falls here.
180-
return null;
181-
}
182-
return FindManagedMember (jniMember, gens);
183-
}
144+
var files = new ConcurrentBag<string> ();
184145

185-
WeakReference cache_found_class;
146+
Parallel.ForEach (enums, enu => {
147+
var path = Path.Combine (output_dir, GetFileName (enu.Key, useShortFileNames) + ".cs");
148+
files.Add (path);
186149

187-
string FindManagedMember (string jniMember, IEnumerable<GenBase> gens)
188-
{
189-
string package, type, member;
190-
ParseJniMember (jniMember, out package, out type, out member);
191-
var fullJavaType = (string.IsNullOrEmpty (package) ? "" : package + ".") + type;
192-
193-
var cls = cache_found_class != null ? cache_found_class.Target as GenBase : null;
194-
if (cls == null || cls.JniName != fullJavaType) {
195-
cls = gens.FirstOrDefault (g => g.JavaName == fullJavaType);
196-
if (cls == null) {
197-
// The class was not found e.g. removed by metadata fixup.
198-
return null;
150+
using (var sw = File.CreateText (path)) {
151+
var generator = new EnumGenerator (sw);
152+
generator.WriteEnumeration (enu, gens);
199153
}
200-
cache_found_class = new WeakReference (cls);
201-
}
202-
var fld = cls.Fields.FirstOrDefault (f => f.JavaName == member);
203-
if (fld == null) {
204-
// The field was not found e.g. removed by metadata fixup.
205-
return null;
206-
}
207-
return cls.FullName + "." + fld.Name;
154+
});
155+
156+
return files.ToList ();
208157
}
209158

210-
Dictionary<string,string> file_name_map = new Dictionary<string, string> ();
159+
ConcurrentDictionary<string,string> file_name_map = new ConcurrentDictionary<string, string> ();
211160

212161
string GetFileName (string file, bool useShortFileNames)
213162
{
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace Android.App {
2+
public enum RecentTaskFlags {
3+
[global::Android.Runtime.IntDefinition (null, JniField = "android/app/ActivityManager.RECENT_IGNORE_UNAVAILABLE")]
4+
WithExcluded = 1,
5+
[global::Android.Runtime.IntDefinition (null, JniField = "android/app/ActivityManager.RECENT_WITH_EXCLUDED")]
6+
IgnoreUnavailable = 2,
7+
}
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace Android.App {
2+
public enum RecentTaskFlags {
3+
[global::Android.Runtime.IntDefinition ("android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE", JniField = "android/app/ActivityManager.RECENT_IGNORE_UNAVAILABLE")]
4+
WithExcluded = 1,
5+
[global::Android.Runtime.IntDefinition (null, JniField = "android/app/ActivityManager.RECENT_WITH_EXCLUDED")]
6+
IgnoreUnavailable = 2,
7+
}
8+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace Android.App {
2+
[System.Flags]
3+
public enum RecentTaskFlags {
4+
[global::Android.Runtime.IntDefinition (null, JniField = "android/app/ActivityManager.RECENT_IGNORE_UNAVAILABLE")]
5+
WithExcluded = 1,
6+
[global::Android.Runtime.IntDefinition (null, JniField = "android/app/ActivityManager.RECENT_WITH_EXCLUDED")]
7+
IgnoreUnavailable = 2,
8+
}
9+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Reflection;
5+
using System.Text;
6+
using MonoDroid.Generation;
7+
using NUnit.Framework;
8+
using NUnit.Framework.Internal;
9+
10+
namespace generatortests.Unit_Tests
11+
{
12+
[TestFixture]
13+
class EnumGeneratorTests
14+
{
15+
protected EnumGenerator generator;
16+
protected StringBuilder builder;
17+
protected StringWriter writer;
18+
19+
[SetUp]
20+
public void SetUp ()
21+
{
22+
builder = new StringBuilder ();
23+
writer = new StringWriter (builder);
24+
25+
generator = new EnumGenerator (writer);
26+
}
27+
28+
[Test]
29+
public void WriteBasicEnum ()
30+
{
31+
var enu = CreateEnum ();
32+
enu.Value.FieldsRemoved = true;
33+
34+
generator.WriteEnumeration (enu, null);
35+
36+
Assert.AreEqual (GetExpected (nameof (WriteBasicEnum)), writer.ToString ().NormalizeLineEndings ());
37+
}
38+
39+
[Test]
40+
public void WriteFlagsEnum ()
41+
{
42+
var enu = CreateEnum ();
43+
enu.Value.BitField = true;
44+
enu.Value.FieldsRemoved = true;
45+
46+
generator.WriteEnumeration (enu, null);
47+
48+
Assert.AreEqual (GetExpected (nameof (WriteFlagsEnum)), writer.ToString ().NormalizeLineEndings ());
49+
}
50+
51+
[Test]
52+
public void WriteEnumWithGens ()
53+
{
54+
var enu = CreateEnum ();
55+
var gens = CreateGens ();
56+
57+
generator.WriteEnumeration (enu, gens);
58+
59+
Assert.AreEqual (GetExpected (nameof (WriteEnumWithGens)), writer.ToString ().NormalizeLineEndings ());
60+
}
61+
62+
protected string GetExpected (string testName)
63+
{
64+
var root = Path.GetDirectoryName (Assembly.GetExecutingAssembly ().Location);
65+
66+
return File.ReadAllText (Path.Combine (root, "Unit-Tests", "EnumGeneratorExpectedResults", $"{testName}.txt")).NormalizeLineEndings ();
67+
}
68+
69+
KeyValuePair<string, EnumMappings.EnumDescription> CreateEnum ()
70+
{
71+
var enu = new EnumMappings.EnumDescription {
72+
Members = new Dictionary<string, string> {
73+
{ "WithExcluded", "1" },
74+
{ "IgnoreUnavailable", "2" }
75+
},
76+
JniNames = new Dictionary<string, string> {
77+
{ "WithExcluded", "android/app/ActivityManager.RECENT_IGNORE_UNAVAILABLE" },
78+
{ "IgnoreUnavailable", "android/app/ActivityManager.RECENT_WITH_EXCLUDED" }
79+
},
80+
BitField = false,
81+
FieldsRemoved = false
82+
};
83+
84+
return new KeyValuePair<string, EnumMappings.EnumDescription> ("Android.App.RecentTaskFlags", enu);
85+
}
86+
87+
GenBase[] CreateGens ()
88+
{
89+
var klass = new TestClass (string.Empty, "android.app.ActivityManager");
90+
91+
klass.Fields.Add (new TestField ("int", "RECENT_IGNORE_UNAVAILABLE"));
92+
93+
return new [] { klass };
94+
}
95+
}
96+
}

tools/generator/Tests/generator-Tests.csproj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
<Compile Include="Unit-Tests\CodeGeneratorTests.cs" />
7474
<Compile Include="Unit-Tests\AdjusterTests.cs" />
7575
<Compile Include="Unit-Tests\DefaultInterfaceMethodsTests.cs" />
76+
<Compile Include="Unit-Tests\EnumGeneratorTests.cs" />
7677
<Compile Include="Unit-Tests\ManagedTests.cs" />
7778
<Compile Include="Unit-Tests\SupportTypes.cs" />
7879
<Compile Include="Unit-Tests\TestExtensions.cs" />
@@ -481,6 +482,15 @@
481482
<Content Include="Unit-Tests\CodeGeneratorExpectedResults\XamarinAndroid\WriteCtor.txt">
482483
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
483484
</Content>
485+
<Content Include="Unit-Tests\EnumGeneratorExpectedResults\WriteBasicEnum.txt">
486+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
487+
</Content>
488+
<Content Include="Unit-Tests\EnumGeneratorExpectedResults\WriteEnumWithGens.txt">
489+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
490+
</Content>
491+
<Content Include="Unit-Tests\EnumGeneratorExpectedResults\WriteFlagsEnum.txt">
492+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
493+
</Content>
484494
</ItemGroup>
485495
<ItemGroup>
486496
<Content Include="expected\**\*">

tools/generator/generator.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
</Compile>
6060
<Compile Include="CodeGenerationTarget.cs" />
6161
<Compile Include="CodeGeneratorOptions.cs" />
62+
<Compile Include="Java.Interop.Tools.Generator.CodeGeneration\EnumGenerator.cs" />
6263
<Compile Include="Java.Interop.Tools.Generator.ObjectModel\InterfaceExtensionInfo.cs" />
6364
<Compile Include="Java.Interop.Tools.Generator.Importers\Xml\InterfaceXmlGenBaseSupport.cs" />
6465
<Compile Include="Java.Interop.Tools.Generator.Transformation\ApiFixup.cs" />

0 commit comments

Comments
 (0)