Skip to content

Commit 2d84c42

Browse files
committed
1 parent bfb8492 commit 2d84c42

File tree

4 files changed

+212
-0
lines changed

4 files changed

+212
-0
lines changed

src/Xamarin.Android.Tools.Bytecode/ClassPath.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ public class ClassPath {
3232

3333
public string AndroidFrameworkPlatform { get; set; }
3434

35+
public IEnumerable<string> ParametersDescriptionFiles { get; set; }
36+
3537
public bool AutoRename { get; set; }
3638

3739
public ClassPath (string path = null)
@@ -233,6 +235,17 @@ void FixupParametersFromDocs (XElement api)
233235
}
234236
}
235237

238+
void FixupParametersFromParametersDescription (XElement api)
239+
{
240+
if (ParametersDescriptionFiles == null)
241+
return;
242+
foreach (var path in ParametersDescriptionFiles) {
243+
if (!File.Exists (path))
244+
continue;
245+
new JavaParameterNamesLoader ().ApplyParameterNameChanges (api, path);
246+
}
247+
}
248+
236249
JavaDocletType GetDocletType (string path)
237250
{
238251
var kind = JavaDocletType.DroidDoc;
@@ -328,6 +341,7 @@ public XElement ToXElement ()
328341
packagesDictionary [p].OrderBy (c => c.ThisClass.Name.Value, StringComparer.OrdinalIgnoreCase)
329342
.Select (c => new XmlClassDeclarationBuilder (c).ToXElement ()))));
330343
FixupParametersFromDocs (api);
344+
FixupParametersFromParametersDescription (api);
331345
return api;
332346
}
333347

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Xml.Linq;
6+
using System.Xml.XPath;
7+
8+
namespace Xamarin.Android.Tools.Bytecode
9+
{
10+
public class JavaParameterNamesLoader
11+
{
12+
void FillSyntheticMethodsFixup (List<Package> fixup, XElement type, HashSet<XElement> alreadyChecked, Dictionary<string,XElement> fullNameMap)
13+
{
14+
if (alreadyChecked.Contains (type))
15+
return;
16+
alreadyChecked.Add (type);
17+
18+
var fpkg = fixup.FirstOrDefault (p => p.Name == type.Parent.Attribute ("name").Value);
19+
if (fpkg == null)
20+
return;
21+
var ftype = fpkg.Types.FirstOrDefault (t => t.Name == type.Attribute ("name").Value);
22+
if (ftype == null)
23+
return;
24+
25+
var extends = type.Attribute ("extends")?.Value;
26+
var bt = extends != null ? fullNameMap [extends] : null;
27+
if (bt == null || bt.Attribute ("visibility").Value != "")
28+
return;
29+
var fbpkg = fixup.FirstOrDefault (p => p.Name == bt.Parent.Attribute ("name").Value);
30+
if (fbpkg == null)
31+
return;
32+
var fbtype = fbpkg.Types.FirstOrDefault (t => t.Name == bt.Attribute ("name").Value);
33+
if (fbtype == null)
34+
return;
35+
36+
// FIXME: it is hacky; it should remove the conflicting synthetic methods.
37+
ftype.Methods = fbtype.Methods.Concat (ftype.Methods).ToList ();
38+
}
39+
40+
public void ApplyParameterNameChanges (XElement api, string path)
41+
{
42+
var fixup = LoadParameterFixupDescription (path);
43+
44+
// We have to supply "dummy" fixups for "synthetic" methods that might come
45+
// from non-public ancestor classes.
46+
// Unfortunately ancestor types are unknown in the fixup description, so
47+
// they have to be extracted from XML API metadata. So, do it here.
48+
var hashset = new HashSet<XElement> ();
49+
var fullNameMap = api.Elements ("package").SelectMany (p => p.Elements ()).ToDictionary (e => e.Parent.Attribute ("name").Value + "." + e.Attribute ("name").Value);
50+
foreach (var t in api.Elements ("package").SelectMany (p => p.Elements ()))
51+
FillSyntheticMethodsFixup (fixup, t, hashset, fullNameMap);
52+
53+
var methods = api.XPathSelectElements ("package/*/*");
54+
foreach (var method in methods) {
55+
switch (method.Name.LocalName) {
56+
case "method":
57+
case "constructor":
58+
59+
string package = method.Parent.Parent.Attribute ("name").Value;
60+
string type = method.Parent.Attribute ("name").Value;
61+
string mname = method.Attribute ("name").Value;
62+
var parameters = method.Elements ("parameter").ToArray ();
63+
if (!parameters.Any ()) // we don't care about parameterless methods.
64+
continue;
65+
var matchedPackage = fixup.FirstOrDefault (p => p.Name == package);
66+
if (matchedPackage == null) {
67+
Log.Warning (0, "Package {0} not found.", package);
68+
continue;
69+
}
70+
var matchedType = matchedPackage.Types.FirstOrDefault (t => t.Name == type);
71+
if (matchedType == null) {
72+
Log.Warning (0, "Type {0} not found.", package + '.' + type);
73+
continue;
74+
}
75+
var matchedMethods = matchedType.Methods.Where (m => (m.Name == "#ctor" ? method.Name.LocalName == "constructor" : m.Name == mname) && m.Parameters.Count == parameters.Length);
76+
if (!matchedMethods.Any ()) {
77+
Log.Warning (0, "Method {0} with {1} parameters not found.", package + '.' + type + '.' + mname, parameters.Length);
78+
continue;
79+
}
80+
var matched = matchedMethods.FirstOrDefault (m => m.Parameters.Zip (parameters, (f, x) => f.Type == x.Attribute ("type").Value.Replace (", ", ",")).All (b => b));
81+
82+
if (matched == null) {
83+
Log.Warning (0, "Method {0}({1}) not found.",
84+
package + '.' + type + '.' + mname,
85+
string.Join (",", parameters.Select (para => para.Attribute ("type").Value)));
86+
continue;
87+
}
88+
89+
for (int i = 0; i < parameters.Length; i++)
90+
parameters [i].SetAttributeValue ("name", matched.Parameters [i].Name);
91+
92+
matched.Applied = true;
93+
break;
94+
}
95+
}
96+
foreach (var p in fixup)
97+
foreach (var t in p.Types)
98+
foreach (var m in t.Methods.Where (m => !m.Applied))
99+
Log.Warning (0, "Method parameter description for {0}.{1}.{2}({3}) is never applied",
100+
p.Name, t.Name, m.Name, string.Join (",", m.Parameters.Select (para => para.Type)));
101+
}
102+
103+
class Parameter
104+
{
105+
public string Type { get; set; }
106+
public string Name { get; set; }
107+
}
108+
109+
class Method
110+
{
111+
public string Name { get; set; }
112+
public List<Parameter> Parameters { get; set; }
113+
public bool Applied { get; set; }
114+
}
115+
116+
class Type
117+
{
118+
public string Name { get; set; }
119+
public List<Method> Methods { get; set; }
120+
}
121+
122+
class Package
123+
{
124+
public string Name { get; set; }
125+
public List<Type> Types { get; set; }
126+
}
127+
128+
// from https://github.com/atsushieno/xamarin-android-docimporter-ng/blob/83ed572ebfdcec8a8becd3337943a488bf56d57d/Xamarin.Android.Tools.JavaStubImporter/JavaApiParameterNamesXmlExporter.cs#L78
129+
/*
130+
* The Text Format is:
131+
*
132+
* package {packagename}
133+
* ---------------------------------------
134+
* interface {interfacename}{optional_type_parameters} -or-
135+
* class {classname}{optional_type_parameters}
136+
* {optional_type_parameters}{methodname}({parameters})
137+
*
138+
* optional_type_parameters: "" -or- "<A,B,C>" (no constraints allowed)
139+
* parameters: type1 p0, type2 p1 (pairs of {type} {name}, joined by ", ")
140+
*
141+
* It is with strict indentations. two spaces for types, four spaces for methods.
142+
*
143+
* Constructors are named as "#ctor".
144+
*
145+
* Commas are used by both parameter types and parameter separators,
146+
* but only parameter separators can be followed by a whitespace.
147+
* It is useful when writing text parsers for this format.
148+
*
149+
* Type names may contain whitespaces in case it is with generic constraints (e.g. "? extends FooBar"),
150+
* so when parsing a parameter type-name pair, the only trustworthy whitespace for tokenizing name is the *last* one.
151+
*
152+
*/
153+
List<Package> LoadParameterFixupDescription (string path)
154+
{
155+
var fixup = new List<Package> ();
156+
string package = null;
157+
var types = new List<Type> ();
158+
string type = null;
159+
var methods = new List<Method> ();
160+
foreach (var l in File.ReadAllLines (path)) {
161+
if (l.Trim ().Length == 0 || l.StartsWith ("--------", StringComparison.Ordinal))
162+
continue;
163+
if (l.StartsWith ("package ", StringComparison.Ordinal)) {
164+
package = l.Substring ("package ".Length);
165+
types = new List<Type> ();
166+
fixup.Add (new Package { Name = package, Types = types });
167+
continue;
168+
} else if (l.StartsWith (" ", StringComparison.Ordinal)) {
169+
int open = l.IndexOf ('(');
170+
string parameters = l.Substring (open + 1).TrimEnd (')');
171+
string name = l.Substring (4, open - 4);
172+
if (name.FirstOrDefault () == '<') // generic method can begin with type parameters.
173+
name = name.Substring (name.IndexOf (' ') + 1);
174+
methods.Add (new Method {
175+
Name = name,
176+
Parameters = parameters.Replace (", ", "\0").Split ('\0')
177+
.Select (s => s.Split (' '))
178+
.Select (a => new Parameter { Type = string.Join (" ", a.Take (a.Length - 1)), Name = a.Last () }).ToList ()
179+
});
180+
} else {
181+
type = l.Substring (l.IndexOf (' ', 2) + 1);
182+
// To match type name from class-parse, we need to strip off generic arguments here (generics are erased).
183+
if (type.IndexOf ('<') > 0)
184+
type = type.Substring (0, type.IndexOf ('<'));
185+
methods = new List<Method> ();
186+
types.Add (new Type { Name = type, Methods = methods });
187+
}
188+
}
189+
return fixup;
190+
}
191+
}
192+
}

src/Xamarin.Android.Tools.Bytecode/Xamarin.Android.Tools.Bytecode.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
<Compile Include="JavaDocumentScraper.cs" />
5050
<Compile Include="ClassPath.cs" />
5151
<Compile Include="Log.cs" />
52+
<Compile Include="JavaParameterNamesLoader.cs" />
5253
</ItemGroup>
5354
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
5455
</Project>

tools/class-parse/Program.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public static void Main (string[] args)
2424
var outputFile = (string) null;
2525
string platform = null;
2626
var docsPaths = new List<string> ();
27+
var paramDescs = new List<string> ();
2728
var p = new OptionSet () {
2829
"usage: class-dump [-dump] FILES",
2930
"",
@@ -39,6 +40,9 @@ public static void Main (string[] args)
3940
{ "docspath=",
4041
"Documentation {PATH} for parameter fixup",
4142
doc => docsPaths.Add (doc) },
43+
{ "parameters-description=",
44+
"Parameter description file {PATH}",
45+
desc => paramDescs.Add (desc) },
4246
{ "docstype=",
4347
"{TYPE} of the docs within --docspath. Values:\n " +
4448
string.Join ("\n ", JavaDocletTypeMapping.Keys.OrderBy (s => s)),
@@ -71,6 +75,7 @@ public static void Main (string[] args)
7175
ApiSource = "class-parse",
7276
AndroidFrameworkPlatform = platform,
7377
DocumentationPaths = docsPaths.Count == 0 ? null : docsPaths,
78+
ParametersDescriptionFiles = paramDescs.Count == 0 ? null : paramDescs,
7479
DocletType = docsType,
7580
AutoRename = autorename
7681
};

0 commit comments

Comments
 (0)