Skip to content

Commit b4e151e

Browse files
atsushienojonpryor
authored andcommitted
[class-parse] support updated DroidDoc. (#96)
With the latest Android N, Google has (again) changed API documentation format (i.e. doclet) and that broke API XML documentation parser. Unlike previous ones or Oracle Javadoc doclets, it is not easily matcheable with existing regex structure. So I made not a small set of changes to make it working. Unlike previous ones it takes a lot more time to process (e.g. more than minutes to process all API Level 24 docs), but better than broken. This is required to suppport any further API Levels (like 25). (ClassParse task has an issue that it never supported correct Javadoc processing as it ignores doclet differences. This only changes the default to droiddoc2 just to be able to process the latest doc. It needs different fix.)
1 parent 8dac926 commit b4e151e

File tree

3 files changed

+92
-48
lines changed

3 files changed

+92
-48
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ namespace Xamarin.Android.Tools.Bytecode {
1313

1414
public enum JavaDocletType {
1515
DroidDoc,
16+
DroidDoc2,
1617
Java6,
1718
Java7,
1819
Java8,
@@ -225,7 +226,8 @@ void FixupParametersFromDocs (XElement api)
225226
AndroidDocScraper CreateDocScraper (string dir)
226227
{
227228
switch (DocletType) {
228-
default: return new DroidDocScraper (dir);
229+
default: return new DroidDoc2Scraper (dir);
230+
case JavaDocletType.DroidDoc: return new DroidDocScraper (dir);
229231
case JavaDocletType.Java6: return new JavaDocScraper (dir);
230232
case JavaDocletType.Java7: return new Java7DocScraper (dir);
231233
case JavaDocletType.Java8: return new Java8DocScraper (dir);

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

Lines changed: 88 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -35,20 +35,60 @@ namespace Xamarin.Android.Tools.Bytecode
3535
{
3636
enum JavaDocKind {
3737
DroidDoc,
38+
DroidDoc2,
3839
Java6,
39-
Java7
40+
Java7,
41+
Java8
4042
}
4143

4244
class DroidDocScraper : AndroidDocScraper
4345
{
4446
const String pattern_head_droiddoc = "<span class=\"sympad\"><a href=\".*";
45-
47+
4648
public DroidDocScraper (string dir)
4749
: base (dir, pattern_head_droiddoc, null, " ", false)
4850
{
4951
}
5052
}
5153

54+
class DroidDoc2Scraper : AndroidDocScraper
55+
{
56+
const String pattern_head_droiddoc = "<tr class=\"api .+\".*>.*<code>.*<a href=\".*";
57+
const String reset_pattern_head = "<p>";
58+
59+
public DroidDoc2Scraper (string dir)
60+
: base (dir, pattern_head_droiddoc, reset_pattern_head, " ", true, "\\(", ", ", "\\)", "\\s*</code>")
61+
{
62+
}
63+
64+
string prev_path;
65+
string [] prev_contents;
66+
67+
protected override IEnumerable<string> GetContentLines (string path)
68+
{
69+
if (prev_path == path)
70+
return prev_contents;
71+
else {
72+
prev_path = path;
73+
string all = File.ReadAllText (path).Replace ('\r', ' ').Replace ('\n', ' ');
74+
int start = all.IndexOf ("<!-- ======== START OF CLASS DATA ======== -->", StringComparison.Ordinal);
75+
all = start < 0 ? all : all.Substring (start);
76+
int end = all.IndexOf ("<!-- ========= END OF CLASS DATA ========= -->", StringComparison.Ordinal);
77+
all = end < 0 ? all : all.Substring (0, end);
78+
// <tr>...</tr> is the basic structure so </tr> is used as the end of member, but we also use <p> here.
79+
// Sometimes another </code> can appear after "</code>" (for the end of context member) and that messes regex match.
80+
// So, with any <p>, we interrupt consecutive matches.
81+
prev_contents = all.Split (new string [] { "<p>", "</tr>" }, StringSplitOptions.RemoveEmptyEntries);
82+
return prev_contents;
83+
}
84+
}
85+
86+
protected override bool ShouldResetMatchBuffer (string text)
87+
{
88+
return true;
89+
}
90+
}
91+
5292
class JavaDocScraper : AndroidDocScraper
5393
{
5494
const String pattern_head_javadoc = "<TD><CODE><B><A HREF=\"[./]*"; // I'm not sure how path could be specified... (./ , ../ , or even /)
@@ -80,7 +120,7 @@ class Java8DocScraper : AndroidDocScraper
80120
const String parameter_pair_splitter_javadoc = "&nbsp;";
81121

82122
public Java8DocScraper (string dir)
83-
: base (dir, pattern_head_javadoc, reset_pattern_head_javadoc, parameter_pair_splitter_javadoc, true, "-", "-", "-")
123+
: base (dir, pattern_head_javadoc, reset_pattern_head_javadoc, parameter_pair_splitter_javadoc, true, "-", "-", "-", null)
84124
{
85125
}
86126
}
@@ -94,14 +134,15 @@ public abstract class AndroidDocScraper
94134
readonly String open_method;
95135
readonly String param_sep;
96136
readonly String close_method;
137+
readonly String post_close_method_parens;
97138
string root;
98-
139+
99140
protected AndroidDocScraper (string dir, String patternHead, String resetPatternHead, String parameterPairSplitter, bool continuousParamLines)
100-
: this (dir, patternHead, resetPatternHead, parameterPairSplitter, continuousParamLines, "\\(", ", ", "\\)")
141+
: this (dir, patternHead, resetPatternHead, parameterPairSplitter, continuousParamLines, "\\(", ", ", "\\)", null)
101142
{
102143
}
103144

104-
protected AndroidDocScraper (string dir, String patternHead, String resetPatternHead, String parameterPairSplitter, bool continuousParamLines, string openMethod, string paramSep, string closeMethod)
145+
protected AndroidDocScraper (string dir, String patternHead, String resetPatternHead, String parameterPairSplitter, bool continuousParamLines, string openMethod, string paramSep, string closeMethod, string postCloseMethodParens)
105146
{
106147
if (dir == null)
107148
throw new ArgumentNullException ("dir");
@@ -112,7 +153,8 @@ protected AndroidDocScraper (string dir, String patternHead, String resetPattern
112153
continuous_param_lines = continuousParamLines;
113154
open_method = openMethod;
114155
param_sep = paramSep;
115-
close_method = closeMethod;
156+
close_method = closeMethod;
157+
post_close_method_parens = postCloseMethodParens ?? string.Empty;
116158
if (!Directory.Exists (dir))
117159
throw new Exception ("Directory '" + dir + "' does not exist");
118160

@@ -124,18 +166,22 @@ protected AndroidDocScraper (string dir, String patternHead, String resetPattern
124166
//foreach (var f in Directory.GetFiles (dir, "*.html", SearchOption.AllDirectories))
125167
// LoadDocument (f.Substring (dir.Length + 1), f);
126168
}
127-
128-
void LoadDocument (string packageDir, string file)
169+
170+
protected virtual IEnumerable<string> GetContentLines (string path)
129171
{
130-
string pkgName = packageDir.Replace ('/', '.');
131-
string className = Path.GetFileNameWithoutExtension (file).Replace ('$', '.');
132-
133-
string html = File.ReadAllText (file);
172+
return File.ReadAllText (path).Split ('\n');
173+
}
174+
175+
protected virtual bool ShouldResetMatchBuffer (string text)
176+
{
177+
// sometimes we get incomplete tag, so cache it until it gets complete or matched.
178+
// I *know* this is a hack.
179+
return reset_pattern_head == null || text.EndsWith (">", StringComparison.Ordinal) || !continuous_param_lines && !text.StartsWith (reset_pattern_head, StringComparison.Ordinal);
134180
}
135181

136-
public String[] GetParameterNames (string package, string type, string method, string[] ptypes, bool isVarArgs)
182+
public virtual String[] GetParameterNames (string package, string type, string method, string[] ptypes, bool isVarArgs)
137183
{
138-
String path = package.Replace ('.', '/') + '/' + type.Replace ('$', '.') + ".html";
184+
string path = package.Replace ('.', '/') + '/' + type.Replace ('$', '.') + ".html";
139185
string file = Path.Combine (root, path);
140186
if (!File.Exists (file)) {
141187
Log.Warning (1,"Warning: no document found : " + file);
@@ -153,44 +199,39 @@ public String[] GetParameterNames (string package, string type, string method, s
153199
buffer.Append (param_sep);
154200
buffer.Append (ptypes [i].Replace ('$', '.'));
155201
}
202+
buffer.Replace ("[", "\\[").Replace ("]", "\\]");
156203
buffer.Append (close_method);
157-
buffer.Append ("\".*\\((.*)\\)");
158-
buffer.Replace ("[", "\\[").Replace ("]", "\\]").Replace ("?", "\\?");
204+
buffer.Append ("\".*\\(([^(]*)\\)");
205+
buffer.Append (post_close_method_parens);
206+
buffer.Replace ("?", "\\?");
159207
Regex pattern = new Regex (buffer.ToString (), RegexOptions.Multiline);
160208

161209
try {
162-
var reader = File.OpenText (file);
163-
try {
164-
String text = "";
165-
String prev = null;
166-
while ((text = reader.ReadLine ()) != null) {
167-
if (prev != null)
168-
prev = text = prev + text;
169-
var matcher = pattern.Match (text);
170-
if (matcher.Success) {
171-
var plist = matcher.Groups [1];
172-
String[] parms = plist.Value.Split (new string [] {", "}, StringSplitOptions.RemoveEmptyEntries);
173-
if (parms.Length != ptypes.Length) {
174-
Log.Warning (1, "failed matching {0} (expected {1} params, got {2} params)", buffer, ptypes.Length, parms.Length);
175-
return null;
176-
}
177-
String[] result = new String [ptypes.Length];
178-
for (int i = 0; i < ptypes.Length; i++) {
179-
String[] toks = parms [i].Split (parameter_pair_splitter);
180-
result [i] = toks [toks.Length - 1];
181-
}
182-
reader.Close ();
183-
return result;
210+
String text = "";
211+
String prev = null;
212+
foreach (var _text in GetContentLines (file)) {
213+
text = _text.TrimEnd ('\r');
214+
if (prev != null)
215+
prev = text = prev + text;
216+
var matcher = pattern.Match (text);
217+
if (matcher.Success) {
218+
var plist = matcher.Groups [1];
219+
String[] parms = plist.Value.Split (new string [] {", "}, StringSplitOptions.RemoveEmptyEntries);
220+
if (parms.Length != ptypes.Length) {
221+
Log.Warning (1, "failed matching {0} (expected {1} params, got {2} params)", buffer, ptypes.Length, parms.Length);
222+
return null;
223+
}
224+
String[] result = new String [ptypes.Length];
225+
for (int i = 0; i < ptypes.Length; i++) {
226+
String[] toks = parms [i].Split (parameter_pair_splitter);
227+
result [i] = toks [toks.Length - 1];
184228
}
185-
// sometimes we get incomplete tag, so cache it until it gets complete or matched.
186-
// I *know* this is a hack.
187-
if (reset_pattern_head == null || text.EndsWith (">", StringComparison.Ordinal) || !continuous_param_lines && !text.StartsWith (reset_pattern_head, StringComparison.Ordinal))
188-
prev = null;
189-
else
190-
prev = text;
229+
return result;
191230
}
192-
} finally {
193-
reader.Close ();
231+
if (ShouldResetMatchBuffer (text))
232+
prev = null;
233+
else
234+
prev = text;
194235
}
195236
} catch (Exception e) {
196237
Log.Error ("ERROR in {0}.{1}: {2}", type, method, e);

tools/class-parse/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ public static void Main (string[] args)
9090

9191
static Dictionary<string, JavaDocletType> JavaDocletTypeMapping = new Dictionary<string, JavaDocletType> {
9292
{ "droiddoc", JavaDocletType.DroidDoc },
93+
{ "droiddoc2", JavaDocletType.DroidDoc2 },
9394
{ "java6", JavaDocletType.Java6 },
9495
{ "java7", JavaDocletType.Java7 },
9596
{ "java8", JavaDocletType.Java8 },

0 commit comments

Comments
 (0)