Skip to content

Commit c325f36

Browse files
committed
1 parent 61c70ec commit c325f36

File tree

8 files changed

+247
-11
lines changed

8 files changed

+247
-11
lines changed

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

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

3131
public string AndroidFrameworkPlatform { get; set; }
3232

33+
public IEnumerable<string> ParametersDescriptionFiles { get; set; }
34+
3335
public bool AutoRename { get; set; }
3436

3537
public ClassPath (string path = null)
@@ -231,6 +233,17 @@ void FixupParametersFromDocs (XElement api)
231233
}
232234
}
233235

236+
void FixupParametersFromParametersDescription (XElement api)
237+
{
238+
if (ParametersDescriptionFiles == null)
239+
return;
240+
foreach (var path in ParametersDescriptionFiles) {
241+
if (!File.Exists (path))
242+
continue;
243+
new JavaParameterNamesLoader ().ApplyParameterNameChanges (api, path);
244+
}
245+
}
246+
234247
IAndroidDocScraper CreateDocScraper (string src)
235248
{
236249
switch (AndroidDocScraper.GetDocletType (src)) {
@@ -297,6 +310,7 @@ public XElement ToXElement ()
297310
packagesDictionary [p].OrderBy (c => c.ThisClass.Name.Value, StringComparer.OrdinalIgnoreCase)
298311
.Select (c => new XmlClassDeclarationBuilder (c).ToXElement ()))));
299312
FixupParametersFromDocs (api);
313+
FixupParametersFromParametersDescription (api);
300314
return api;
301315
}
302316

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
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/master/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+
* Anything after # is treated as comment.
139+
*
140+
* optional_type_parameters: "" -or- "<A,B,C>" (no constraints allowed)
141+
* parameters: type1 p0, type2 p1 (pairs of {type} {name}, joined by ", ")
142+
*
143+
* It is with strict indentations. two spaces for types, four spaces for methods.
144+
*
145+
* Constructors are named as "#ctor".
146+
*
147+
* Commas are used by both parameter types and parameter separators,
148+
* but only parameter separators can be followed by a whitespace.
149+
* It is useful when writing text parsers for this format.
150+
*
151+
* Type names may contain whitespaces in case it is with generic constraints (e.g. "? extends FooBar"),
152+
* so when parsing a parameter type-name pair, the only trustworthy whitespace for tokenizing name is the *last* one.
153+
*
154+
*/
155+
List<Package> LoadParameterFixupDescription (string path)
156+
{
157+
var fixup = new List<Package> ();
158+
string package = null;
159+
var types = new List<Type> ();
160+
string type = null;
161+
var methods = new List<Method> ();
162+
foreach (var l in File.ReadAllLines (path)) {
163+
var line = l.IndexOf ('#') >= 0 ? l.Substring (0, l.IndexOf ('#')) : l;
164+
if (line.Trim ().Length == 0)
165+
continue;
166+
if (line.StartsWith ("package ", StringComparison.Ordinal)) {
167+
package = line.Substring ("package ".Length);
168+
types = new List<Type> ();
169+
fixup.Add (new Package { Name = package, Types = types });
170+
continue;
171+
} else if (line.StartsWith (" ", StringComparison.Ordinal)) {
172+
int open = line.IndexOf ('(');
173+
string parameters = line.Substring (open + 1).TrimEnd (')');
174+
string name = line.Substring (4, open - 4);
175+
if (name.FirstOrDefault () == '<') // generic method can begin with type parameters.
176+
name = name.Substring (name.IndexOf (' ') + 1);
177+
methods.Add (new Method {
178+
Name = name,
179+
Parameters = parameters.Replace (", ", "\0").Split ('\0')
180+
.Select (s => s.Split (' '))
181+
.Select (a => new Parameter { Type = string.Join (" ", a.Take (a.Length - 1)), Name = a.Last () }).ToList ()
182+
});
183+
} else {
184+
type = line.Substring (line.IndexOf (' ', 2) + 1);
185+
// To match type name from class-parse, we need to strip off generic arguments here (generics are erased).
186+
if (type.IndexOf ('<') > 0)
187+
type = type.Substring (0, type.IndexOf ('<'));
188+
methods = new List<Method> ();
189+
types.Add (new Type { Name = type, Methods = methods });
190+
}
191+
}
192+
return fixup;
193+
}
194+
}
195+
}

src/Xamarin.Android.Tools.Bytecode/Tests/ClassFileFixture.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,16 @@ protected static void AssertXmlDeclaration (string classResource, string xmlReso
5555
Assert.AreEqual (expected, actual.ToString ());
5656
}
5757

58-
protected static void AssertXmlDeclaration (string[] classResources, string xmlResource, string documentationPath = null)
58+
protected static void AssertXmlDeclaration (string[] classResources, string xmlResource, string documentationPath = null, string parameterDescriptionFile = null)
5959
{
6060
var classPathBuilder = new ClassPath () {
6161
ApiSource = "class-parse",
62-
DocumentationPaths = new string[] {
62+
DocumentationPaths = documentationPath == null ? null : new string[] {
6363
documentationPath,
6464
},
65+
ParametersDescriptionFiles = parameterDescriptionFile == null ? null : new string [] {
66+
parameterDescriptionFile,
67+
},
6568
AutoRename = true
6669
};
6770
foreach(var classFile in classResources.Select(s => LoadClassFile (s)))
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package java.util
2+
class Collection<E>
3+
add(E e)

src/Xamarin.Android.Tools.Bytecode/Tests/ParameterFixupTests.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,22 @@ public void DocletType_ShouldDetectDroidDocs ()
8787

8888
Assert.AreEqual(JavaDocletType.DroidDoc2, AndroidDocScraper.GetDocletType(droidDocsPath));
8989
}
90+
91+
[Test]
92+
public void XmlDeclaration_FixedUpFromParameterDescription ()
93+
{
94+
var androidSdkPath = Environment.GetEnvironmentVariable ("ANDROID_SDK_PATH");
95+
if (string.IsNullOrEmpty (androidSdkPath)) {
96+
Assert.Ignore ("The `ANDROID_SDK_PATH` environment variable isn't set; " +
97+
"cannot test importing parameter names from HTML. Skipping...");
98+
return;
99+
}
100+
try {
101+
AssertXmlDeclaration (new string [] {"Collection.class"}, "ParameterFixupFromDocs.xml", null, "ParameterDescription.txt");
102+
} catch (Exception ex) {
103+
Assert.Fail ("An unexpected exception was thrown : {0}", ex);
104+
}
105+
}
90106
}
91107
}
92108

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

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,8 @@
5757
<Compile Include="ParameterFixupTests.cs" />
5858
</ItemGroup>
5959
<ItemGroup>
60-
<TestJar
61-
Include="java\**\*.java"
62-
Exclude="java\java\util\Collection.java"
63-
/>
64-
<TestJarNoParameters
65-
Include="java\java\util\Collection.java"
66-
/>
60+
<TestJar Include="java\**\*.java" Exclude="java\java\util\Collection.java" />
61+
<TestJarNoParameters Include="java\java\util\Collection.java" />
6762
</ItemGroup>
6863
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
6964
<PropertyGroup>
@@ -74,8 +69,8 @@
7469
</PropertyGroup>
7570
<Target Name="BuildClasses" Inputs="@(TestJar)" Outputs="@(TestJar-&gt;'$(IntermediateOutputPath)classes\%(RecursiveDir)%(Filename).class')">
7671
<MakeDir Directories="$(IntermediateOutputPath)classes" />
77-
<Exec Command="&quot;$(JavaCPath)&quot; -parameters -source 1.5 -target 1.6 -g -d &quot;$(IntermediateOutputPath)classes&quot; @(TestJar->'%(Identity)', ' ')" />
78-
<Exec Command="&quot;$(JavaCPath)&quot; -source 1.5 -target 1.6 -g -d &quot;$(IntermediateOutputPath)classes&quot; @(TestJarNoParameters->'%(Identity)', ' ')" />
72+
<Exec Command="&quot;$(JavaCPath)&quot; -parameters -source 1.5 -target 1.6 -g -d &quot;$(IntermediateOutputPath)classes&quot; @(TestJar-&gt;'%(Identity)', ' ')" />
73+
<Exec Command="&quot;$(JavaCPath)&quot; -source 1.5 -target 1.6 -g -d &quot;$(IntermediateOutputPath)classes&quot; @(TestJarNoParameters-&gt;'%(Identity)', ' ')" />
7974
</Target>
8075
<ItemGroup>
8176
<ProjectReference Include="..\Xamarin.Android.Tools.Bytecode.csproj">
@@ -165,5 +160,9 @@
165160
</ItemGroup>
166161
<ItemGroup>
167162
<None Include="packages.config" />
163+
<None Include="ParameterDescription.txt">
164+
<LogicalName>ParameterDescription.txt</LogicalName>
165+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
166+
</None>
168167
</ItemGroup>
169168
</Project>

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
@@ -23,6 +23,7 @@ public static void Main (string[] args)
2323
var outputFile = (string) null;
2424
string platform = null;
2525
var docsPaths = new List<string> ();
26+
var paramDescs = new List<string> ();
2627
var p = new OptionSet () {
2728
"usage: class-dump [-dump] FILES",
2829
"",
@@ -38,6 +39,9 @@ public static void Main (string[] args)
3839
{ "docspath=",
3940
"Documentation {PATH} for parameter fixup",
4041
doc => docsPaths.Add (doc) },
42+
{ "parameters-description=",
43+
"Parameter description file {PATH}",
44+
desc => paramDescs.Add (desc) },
4145
{ "docstype=",
4246
"OBSOLETE: Previously used to specify a doc type (now auto detected).",
4347
t => docsType = t != null },
@@ -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
AutoRename = autorename
7580
};
7681
foreach (var file in files) {

0 commit comments

Comments
 (0)