Skip to content

Commit 8f37133

Browse files
committed
[ApiAdjuster] Use different data model structures for better performance.
1 parent 99897b2 commit 8f37133

17 files changed

+149
-91
lines changed

src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/JavaStubParser.cs

+12-3
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,12 @@ public JavaStubGrammar ()
315315
identifier.AstConfig.NodeCreator = (ctx, node) => node.AstNode = node.Token.ValueString;
316316
compile_unit.AstConfig.NodeCreator = (ctx, node) => {
317317
ProcessChildren (ctx, node);
318-
node.AstNode = new JavaPackage (null) { Name = (string)node.ChildNodes [0].AstNode, Types = ((IEnumerable<JavaType>)node.ChildNodes [2].AstNode).ToList () };
318+
var pkg = new JavaPackage (null) { Name = (string) node.ChildNodes [0].AstNode };
319+
320+
foreach (var t in (IEnumerable<JavaType>) node.ChildNodes [2].AstNode)
321+
pkg.AddType (t.Name!, t);
322+
323+
node.AstNode = pkg;
319324
};
320325
opt_package_decl.AstConfig.NodeCreator = SelectSingleChild;
321326
package_decl.AstConfig.NodeCreator = SelectChildValueAt (1);
@@ -637,9 +642,13 @@ public JavaStubParser ()
637642
void FlattenNestedTypes (JavaPackage package)
638643
{
639644
var results = new List<JavaType> ();
640-
foreach (var t in package.Types)
645+
foreach (var t in package.AllTypes)
641646
Flatten (results, t);
642-
package.Types = results.ToList ();
647+
648+
package.ClearTypes ();
649+
650+
foreach (var t in results)
651+
package.AddType (t.Name!, t);
643652

644653
void Flatten (List<JavaType> list, JavaType t)
645654
{

src/Xamarin.Android.Tools.ApiXmlAdjuster/JavaApi.XmlModel.cs

+57-9
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ public partial class JavaApi
1010
{
1111
public JavaApi ()
1212
{
13-
Packages = new List<JavaPackage> ();
13+
Packages = new Dictionary<string, JavaPackage> (StringComparer.OrdinalIgnoreCase);
1414
}
1515

1616
public string? ExtendedApiSource { get; set; }
1717
public string? Platform { get; set; }
18-
public IList<JavaPackage> Packages { get; set; }
18+
public IDictionary<string, JavaPackage> Packages { get; }
19+
20+
public ICollection<JavaPackage> AllPackages => Packages.Values;
1921
}
2022

2123
public partial class JavaPackage
@@ -24,15 +26,61 @@ public JavaPackage (JavaApi? parent)
2426
{
2527
Parent = parent;
2628

27-
Types = new List<JavaType> ();
29+
Types = new Dictionary<string, List<JavaType>> (StringComparer.OrdinalIgnoreCase);
2830
}
2931

3032
public JavaApi? Parent { get; private set; }
3133

3234
public string? Name { get; set; }
3335
public string? JniName { get; set; }
34-
public IList<JavaType> Types { get; set; }
35-
36+
37+
// Yes, there can be multiple types with the same *Java* name.
38+
// For example:
39+
// - MyInterface
40+
// - MyInterfaceConsts
41+
// It's debatable whether we handle this "properly", as most callers just
42+
// do `First ()`, but it's been working for years so I'm not changing it.
43+
// Exposes an IReadOnlyDictionary so caller cannot bypass our AddType/RemoveType code.
44+
public IReadOnlyDictionary<string, List<JavaType>> Types { get; }
45+
46+
// Use this for a flat list of *all* types
47+
public IEnumerable<JavaType> AllTypes => Types.Values.SelectMany (v => v);
48+
49+
public void AddType (string name, JavaType type)
50+
{
51+
// If this is a duplicate key, add it to existing list
52+
if (Types.TryGetValue (name, out var list)) {
53+
list.Add (type);
54+
return;
55+
}
56+
57+
// Add to a new list
58+
var new_list = new List<JavaType> ();
59+
new_list.Add (type);
60+
61+
((IDictionary<string, List<JavaType>>)Types).Add (name, new_list);
62+
}
63+
64+
public void RemoveType (string name, JavaType type)
65+
{
66+
if (!Types.TryGetValue (name, out var list))
67+
return;
68+
69+
// Remove 1 type from list if it contains multiple types
70+
if (list.Count > 1) {
71+
list.Remove (type);
72+
return;
73+
}
74+
75+
// Remove the whole dictionary entry
76+
((IDictionary<string, List<JavaType>>) Types).Remove (name);
77+
}
78+
79+
public void ClearTypes ()
80+
{
81+
((IDictionary<string, List<JavaType>>) Types).Clear ();
82+
}
83+
3684
// Content of this value is not stable.
3785
public override string ToString ()
3886
{
@@ -123,14 +171,14 @@ static ManagedType ()
123171
dummy_system_package = new JavaPackage (null) { Name = "System" };
124172
system_object = new ManagedType (dummy_system_package) { Name = "Object" };
125173
system_exception = new ManagedType (dummy_system_package) { Name = "Exception" };
126-
dummy_system_package.Types.Add (system_object);
127-
dummy_system_package.Types.Add (system_exception);
174+
dummy_system_package.AddType (system_object.Name, system_object);
175+
dummy_system_package.AddType (system_exception.Name, system_exception);
128176
dummy_system_io_package = new JavaPackage (null) { Name = "System.IO" };
129177
system_io_stream = new ManagedType (dummy_system_io_package) { Name = "Stream" };
130-
dummy_system_io_package.Types.Add (system_io_stream);
178+
dummy_system_io_package.AddType (system_io_stream.Name, system_io_stream);
131179
dummy_system_xml_package = new JavaPackage (null) { Name = "System.Xml" };
132180
system_xml_xmlreader = new ManagedType (dummy_system_xml_package) { Name = "XmlReader" };
133-
dummy_system_io_package.Types.Add (system_xml_xmlreader);
181+
dummy_system_io_package.AddType (system_xml_xmlreader.Name, system_xml_xmlreader);
134182
}
135183

136184
public static IEnumerable<JavaPackage> DummyManagedPackages {

src/Xamarin.Android.Tools.ApiXmlAdjuster/JavaApiDefectFinderExtensions.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ public static class JavaApiDefectFinderExtensions
77
{
88
public static void FindDefects (this JavaApi api)
99
{
10-
foreach (var type in api.Packages.SelectMany (p => p.Types).Where (t => !t.IsReferenceOnly))
10+
foreach (var type in api.AllPackages.SelectMany (p => p.AllTypes).Where (t => !t.IsReferenceOnly))
1111
type.FindDefects ();
1212
}
1313

src/Xamarin.Android.Tools.ApiXmlAdjuster/JavaApiGenericInheritanceMapperExtensions.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public static class JavaApiGenericInheritanceMapperExtensions
88
{
99
public static void CreateGenericInheritanceMapping (this JavaApi api)
1010
{
11-
foreach (var kls in api.Packages.SelectMany (p => p.Types).OfType<JavaClass> ())
11+
foreach (var kls in api.AllPackages.SelectMany (p => p.AllTypes).OfType<JavaClass> ())
1212
kls.PrepareGenericInheritanceMapping ();
1313
}
1414

src/Xamarin.Android.Tools.ApiXmlAdjuster/JavaApiNonBindableStripper.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public static class JavaApiNonBindableStripper
99
public static void StripNonBindables (this JavaApi api)
1010
{
1111
var invalids = new List<JavaMember> ();
12-
foreach (var member in api.Packages.SelectMany (p => p.Types)
12+
foreach (var member in api.AllPackages.SelectMany (p => p.AllTypes)
1313
.SelectMany (t => t.Members).Where (m => m.Name != null && m.Name.Contains ('$')))
1414
invalids.Add (member);
1515
foreach (var invalid in invalids)

src/Xamarin.Android.Tools.ApiXmlAdjuster/JavaApiOverrideMarkerExtensions.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public static void MarkOverrides (this JavaApi api)
1414

1515
public static void MarkOverrides (this JavaApi api, HashSet<JavaClass> doneList)
1616
{
17-
foreach (var kls in api.Packages.SelectMany (p => p.Types).OfType<JavaClass> ())
17+
foreach (var kls in api.AllPackages.SelectMany (p => p.AllTypes).OfType<JavaClass> ())
1818
kls.MarkOverrides (doneList);
1919
}
2020

src/Xamarin.Android.Tools.ApiXmlAdjuster/JavaApiTypeResolverExtensions.cs

+29-28
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,31 @@ static JavaTypeReference JavaTypeNameToReference (this JavaApi api, JavaTypeName
3939

4040
public static JavaType FindNonGenericType (this JavaApi api, string? name)
4141
{
42-
var ret = FindPackages (api, name ?? "")
43-
.SelectMany (p => p.Types)
44-
.FirstOrDefault (t => name == (t.Parent?.Name != null ? t.Parent.Name + "." : "") + t.Name);
45-
if (ret == null)
46-
ret = ManagedType.DummyManagedPackages
47-
.SelectMany (p => p.Types)
48-
.FirstOrDefault (t => t.FullName == name);
42+
// Given a type name like 'android.graphics.BitmapFactory.Options'
43+
// We're going to search for:
44+
// - Pkg: android.graphics.BitmapFactory Type: Options
45+
// - Pkg: android.graphics Type: BitmapFactory.Options
46+
// - Pkg: android Type: graphics.BitmapFactory.Options
47+
// etc. We will short-circuit as soon as we find a match
48+
var index = name?.LastIndexOf ('.') ?? -1;
49+
50+
while (index > 0) {
51+
var ns = name!.Substring (0, index);
52+
var type_name = name.Substring (index + 1);
53+
54+
if (api.Packages.TryGetValue (ns, out var pkg)) {
55+
if (pkg.Types.TryGetValue (type_name, out var type))
56+
return type.First ();
57+
}
58+
59+
index = name.LastIndexOf ('.', index - 1);
60+
}
61+
62+
// See if it's purely a C# type
63+
var ret = ManagedType.DummyManagedPackages
64+
.SelectMany (p => p.AllTypes)
65+
.FirstOrDefault (t => t.FullName == name);
66+
4967
if (ret == null) {
5068
// We moved this type to "mono.android.app.IntentService" which makes this
5169
// type resolution fail if a user tries to reference it in Java.
@@ -58,43 +76,26 @@ public static JavaType FindNonGenericType (this JavaApi api, string? name)
5876
return ret;
5977
}
6078

61-
static IEnumerable<JavaPackage> FindPackages (JavaApi api, string name)
62-
{
63-
// Given a type name like "java.lang.Object", return packages that could
64-
// possibly contain the type so we don't search all packages, ie:
65-
// - java.lang
66-
// - java
67-
var package_names = new List<string> ();
68-
int index;
69-
70-
while ((index = name.LastIndexOf ('.')) >= 0) {
71-
name = name.Substring (0, index);
72-
package_names.Add (name);
73-
}
74-
75-
return api.Packages.Where (p => package_names.Contains (p.Name, StringComparer.Ordinal)).ToList ();
76-
}
77-
7879
public static void Resolve (this JavaApi api)
7980
{
8081
while (true) {
8182
bool errors = false;
82-
foreach (var t in api.Packages.SelectMany (p => p.Types).OfType<JavaClass> ().ToArray ())
83+
foreach (var t in api.AllPackages.SelectMany (p => p.AllTypes).OfType<JavaClass> ().ToArray ())
8384
try {
8485
t.Resolve ();
8586
}
8687
catch (JavaTypeResolutionException ex) {
8788
Log.LogError ("Error while processing type '{0}': {1}", t, ex.Message);
8889
errors = true;
89-
t.Parent?.Types.Remove (t);
90+
t.Parent?.RemoveType (t.Name!, t);
9091
}
91-
foreach (var t in api.Packages.SelectMany (p => p.Types).OfType<JavaInterface> ().ToArray ())
92+
foreach (var t in api.AllPackages.SelectMany (p => p.AllTypes).OfType<JavaInterface> ().ToArray ())
9293
try {
9394
t.Resolve ();
9495
} catch (JavaTypeResolutionException ex) {
9596
Log.LogError ("Error while processing type '{0}': {1}", t, ex.Message);
9697
errors = true;
97-
t.Parent?.Types.Remove (t);
98+
t.Parent?.RemoveType (t.Name!, t);
9899
}
99100
if (!errors)
100101
break;

src/Xamarin.Android.Tools.ApiXmlAdjuster/JavaApiXmlGeneratorExtensions.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@ public static void Save (this JavaApi api, XmlWriter writer)
2424
if (api.Platform != null)
2525
writer.WriteAttributeString ("platform", api.Platform);
2626

27-
foreach (var pkg in api.Packages) {
28-
if (!pkg.Types.Any (t => !t.IsReferenceOnly))
27+
foreach (var pkg in api.AllPackages) {
28+
if (!pkg.AllTypes.Any (t => !t.IsReferenceOnly))
2929
continue;
3030
writer.WriteStartElement ("package");
3131
writer.WriteAttributeString ("name", pkg.Name);
3232
if (!string.IsNullOrEmpty (pkg.JniName)) {
3333
writer.WriteAttributeString ("jni-name", pkg.JniName);
3434
}
35-
foreach (var type in pkg.Types) {
35+
foreach (var type in pkg.AllTypes) {
3636
if (type.IsReferenceOnly)
3737
continue; // skip reference only types
3838
if (type is JavaClass)

src/Xamarin.Android.Tools.ApiXmlAdjuster/JavaApiXmlLoaderExtensions.cs

+8-5
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,14 @@ public static void Load (this JavaApi api, XmlReader reader, bool isReferenceOnl
3232
break; // </api>
3333
if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "package")
3434
throw XmlUtil.UnexpectedElementOrContent ("api", reader, "package");
35-
var pkg = api.Packages.FirstOrDefault (p => p.Name == reader.GetAttribute ("name"));
36-
if (pkg == null) {
35+
36+
var name = reader.GetAttribute ("name");
37+
38+
if (!api.Packages.TryGetValue (name, out var pkg)) {
3739
pkg = new JavaPackage (api);
38-
api.Packages.Add (pkg);
40+
api.Packages.Add (name, pkg);
3941
}
42+
4043
pkg.Load (reader, isReferenceOnly);
4144
} while (true);
4245

@@ -67,11 +70,11 @@ public static void Load (this JavaPackage package, XmlReader reader, bool isRefe
6770
if (reader.LocalName == "class") {
6871
var kls = new JavaClass (package) { IsReferenceOnly = isReferenceOnly };
6972
kls.Load (reader);
70-
package.Types.Add (kls);
73+
package.AddType (kls.Name!, kls);
7174
} else if (reader.LocalName == "interface") {
7275
var iface = new JavaInterface (package) { IsReferenceOnly = isReferenceOnly };
7376
iface.Load (reader);
74-
package.Types.Add (iface);
77+
package.AddType (iface.Name!, iface);
7578
} else
7679
throw XmlUtil.UnexpectedElementOrContent ("package", reader, "class", "interface");
7780
} while (true);

tests/Java.Interop.Tools.JavaSource-Tests/JavaStubParserTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public static void m (String text) {
3535
Assert.AreEqual (null, package.Name);
3636
Assert.AreEqual (1, package.Types.Count);
3737

38-
var Example_Type = package.Types [0];
38+
var Example_Type = package.AllTypes.First ();
3939
Assert.AreEqual ("Example", Example_Type.FullName);
4040

4141
Assert.AreEqual (1, Example_Type.Members.Count);

tests/Xamarin.Android.Tools.ApiXmlAdjuster-Tests/JavaApiTest.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ public void SetupFixture ()
1919
[Test]
2020
public void TestToString ()
2121
{
22-
var pkg = api.Packages.First (p => p.Name == "android.database");
22+
var pkg = api.AllPackages.First (p => p.Name == "android.database");
2323
Assert.AreEqual ("[Package] android.database", pkg.ToString ());
24-
var kls = pkg.Types.First (t => t.FullName == "android.database.ContentObservable");
24+
var kls = pkg.AllTypes.First (t => t.FullName == "android.database.ContentObservable");
2525
Assert.AreEqual ("[Class] android.database.ContentObservable", kls.ToString ());
2626
}
2727

tests/Xamarin.Android.Tools.ApiXmlAdjuster-Tests/OverrideMarkerTest.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public void AncestralOverrides ()
5353
xapi.Resolve ();
5454
xapi.CreateGenericInheritanceMapping ();
5555
xapi.MarkOverrides ();
56-
var t = xapi.Packages.First (_ => _.Name == "XXX").Types.First (_ => _.Name == "SherlockExpandableListActivity");
56+
var t = xapi.AllPackages.First (_ => _.Name == "XXX").AllTypes.First (_ => _.Name == "SherlockExpandableListActivity");
5757
var m = t.Members.OfType<JavaMethod> ().First (_ => _.Name == "addContentView");
5858
Assert.IsNotNull (m.BaseMethod, "base method not found");
5959
}
@@ -88,7 +88,7 @@ public void GenericConstructors ()
8888
xapi = new JavaApi ();
8989
using (var xr = XmlReader.Create (new StringReader (sw.ToString ())))
9090
xapi.Load (xr, true);
91-
var t = xapi.Packages.First (_ => _.Name == "XXX").Types.First (_ => _.Name == "GenericConstructors");
91+
var t = xapi.AllPackages.First (_ => _.Name == "XXX").AllTypes.First (_ => _.Name == "GenericConstructors");
9292
var m = t.Members.OfType<JavaConstructor> ().FirstOrDefault ();
9393
Assert.IsNotNull (m.TypeParameters, "constructor not found");
9494
}

tests/Xamarin.Android.Tools.ApiXmlAdjuster-Tests/TypeResolverTest.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -76,20 +76,20 @@ public void IntentServiceHack ()
7676
var api = JavaApiTestHelper.GetLoadedApi ();
7777

7878
// Create "mono.android.app" package
79-
var mono_android_app = new JavaPackage (api) { Name = "mono.android.app", JniName = "mono/android/app", Types = new List<JavaType> () };
80-
api.Packages.Add (mono_android_app);
79+
var mono_android_app = new JavaPackage (api) { Name = "mono.android.app", JniName = "mono/android/app" };
80+
api.Packages.Add (mono_android_app.Name, mono_android_app);
8181

8282
// Remove "android.app.IntentService" type
83-
var android_app = api.Packages.Single (p => p.Name == "android.app");
84-
var intent_service = android_app.Types.Single (t => t.Name == "IntentService");
85-
android_app.Types.Remove (intent_service);
83+
var android_app = api.AllPackages.Single (p => p.Name == "android.app");
84+
var intent_service = android_app.AllTypes.Single (t => t.Name == "IntentService");
85+
android_app.RemoveType (intent_service.Name, intent_service);
8686

8787
// Create new "mono.android.app.IntentService" type
8888
var new_intent_service = new JavaClass (mono_android_app) {
8989
Name = intent_service.Name,
9090
};
9191

92-
mono_android_app.Types.Add (new_intent_service);
92+
mono_android_app.AddType (new_intent_service.Name, new_intent_service);
9393

9494
api.Resolve ();
9595

0 commit comments

Comments
 (0)