Skip to content

Commit 28b9247

Browse files
committed
[generator] Add --with-javadoc-xml=FILE support.
Commit 69e1b80 added `tools/java-source-utils`, which can parse Java source code and extract Javadoc comments into an XML file: $ java -jar "java-source-utils.jar" -v \ $HOME/android-toolchain/sdk/platforms/android-29/android.jar \ --output-javadoc=android-javadoc.xml What can we *do* with the generated `android-javadoc.xml`? `android-javadoc.xml` contains parameter names, and thus can be used with `class-parse --docspath`; see commit 806082f. What we *really* want to do is make the Javadoc information *useful* to consumers of the binding assembly. This means that we want a C# XML Documentation file for the binding assembly. The most straightforward way to get a C# XML Documentation File is to emit [C# XML Documentation Comments][0] into the binding source code! Add a new `generator --with-javadoc-xml=FILE` option. When specified, `FILE` will be treated as an XML file containing the output from `java-source-utils.jar --output-javadoc` (see 69e1b80)`, and all `<javadoc/>` elements within the XML file will be associated with C# types and members to emit, based on the `//@jni-signature` and `//@name` attributes, as appropriate. When the bindings are written to disk, the Javadoc comments will be translated to C# XML Documentation comments, in a "best effort" basis. (THIS WILL BE INCOMPLETE.) TODO: For "demonstration" purposes, the C# XML Doc comments are simply a `<format type="text/html"/>` block containing the XML-ized Javadoc HTML. This should be improved to mirror javadoc-to-mdoc before merge. [0]: https://docs.microsoft.com/en-us/dotnet/csharp/codedoc
1 parent 69e1b80 commit 28b9247

File tree

8 files changed

+173
-0
lines changed

8 files changed

+173
-0
lines changed

tools/generator/CodeGenerator.cs

+74
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.IO;
44
using System.Linq;
55
using System.Xml;
6+
using System.Xml.Linq;
67
using Mono.Cecil;
78
using MonoDroid.Generation;
89
using Xamarin.AndroidTools.AnnotationSupport;
@@ -178,6 +179,7 @@ static void Run (CodeGeneratorOptions options, DirectoryAssemblyResolver resolve
178179
SealedProtectedFixups.Fixup (gens);
179180

180181
GenerateAnnotationAttributes (gens, annotations_zips);
182+
AddJavadoc (gens, options.JavadocXmlFiles);
181183

182184
//SymbolTable.Dump ();
183185

@@ -356,6 +358,78 @@ static void GenerateAnnotationAttributes (List<GenBase> gens, IEnumerable<string
356358
}
357359
}
358360

361+
static void AddJavadoc (List<GenBase> gens, IList<string> javadocXmlFiles)
362+
{
363+
if (javadocXmlFiles == null)
364+
return;
365+
foreach (var path in javadocXmlFiles) {
366+
if (!File.Exists (path))
367+
continue;
368+
369+
XDocument doc = XDocument.Load (path);
370+
371+
foreach (var type in gens) {
372+
AddJavadoc (type, doc);
373+
}
374+
}
375+
}
376+
377+
static void AddJavadoc (GenBase type, XDocument javadoc)
378+
{
379+
var typeJavadoc = javadoc.Elements ("api")
380+
.Elements ("package")
381+
.Where (p => type.PackageName == (string) p.Attribute ("name"))
382+
.Elements ()
383+
.Where (e => type.JniName == (string) e.Attribute ("jni-signature"))
384+
.FirstOrDefault ();
385+
if (typeJavadoc == null)
386+
return;
387+
388+
if (string.IsNullOrEmpty (type.Javadoc))
389+
type.Javadoc = typeJavadoc.Element ("javadoc")?.Value;
390+
391+
foreach (var method in type.Methods) {
392+
if (!string.IsNullOrEmpty (method.Javadoc))
393+
continue;
394+
var methodJavadoc = typeJavadoc
395+
.Elements ("method")
396+
.Where (m => method.JavaName == (string) m.Attribute ("name") && method.JniSignature == (string) m.Attribute ("jni-signature"))
397+
.Elements ("javadoc")
398+
.FirstOrDefault ();
399+
if (methodJavadoc == null)
400+
continue;
401+
method.Javadoc = methodJavadoc.Value;
402+
}
403+
404+
foreach (var field in type.Fields) {
405+
if (!string.IsNullOrEmpty (field.Javadoc))
406+
continue;
407+
var fieldJavadoc = typeJavadoc
408+
.Elements ("field")
409+
.Where (m => field.JavaName == (string) m.Attribute ("name") && field.JniSignature == (string) m.Attribute ("jni-signature"))
410+
.Elements ("javadoc")
411+
.FirstOrDefault ();
412+
if (fieldJavadoc == null)
413+
continue;
414+
field.Javadoc = fieldJavadoc.Value;
415+
}
416+
417+
if (type is ClassGen @class) {
418+
foreach (var ctor in @class.Ctors) {
419+
if (!string.IsNullOrEmpty (ctor.Javadoc))
420+
continue;
421+
var ctorJavadoc = typeJavadoc
422+
.Elements ("constructor")
423+
.Where (c => ctor.JniSignature == (string) c.Attribute ("jni-signature"))
424+
.Elements ("javadoc")
425+
.FirstOrDefault ();
426+
if (ctorJavadoc == null)
427+
continue;
428+
ctor.Javadoc = ctorJavadoc.Value;
429+
}
430+
}
431+
}
432+
359433
static void AddAnnotationTo (AnnotatedItem item, string annotation)
360434
{
361435
if (item.ManagedInfo.PropertyObject != null)

tools/generator/CodeGeneratorOptions.cs

+5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public CodeGeneratorOptions ()
1515
FixupFiles = new Collection<string> ();
1616
LibraryPaths = new Collection<string> ();
1717
AnnotationsZipFiles = new Collection<string> ();
18+
JavadocXmlFiles = new Collection<string> ();
1819
}
1920

2021
public string ApiLevel {get; set;}
@@ -24,6 +25,7 @@ public CodeGeneratorOptions ()
2425
public Collection<string> AssemblyReferences {get; private set;}
2526
public Collection<string> FixupFiles {get; private set;}
2627
public Collection<string> LibraryPaths {get; private set;}
28+
public Collection<string> JavadocXmlFiles {get; private set;}
2729
public bool GlobalTypeNames {get; set;}
2830
public bool OnlyBindPublicTypes {get; set;}
2931
public string ApiDescriptionFile {get; set;}
@@ -118,6 +120,9 @@ public static CodeGeneratorOptions Parse (string[] args)
118120
{ "only-xml-adjuster",
119121
"Run only API XML adjuster for class-parse input.",
120122
v => opts.OnlyRunApiXmlAdjuster = v != null },
123+
{ "with-javadoc-xml=",
124+
"{PATH} to `api.xml` containing Javadoc docs in `<javadoc/>` elements",
125+
v => opts.JavadocXmlFiles.Add (v) },
121126
{ "xml-adjuster-output=",
122127
"specify API XML adjuster output XML for class-parse input.",
123128
v => opts.ApiXmlAdjusterOutput = v },

tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs

+7
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ public void WriteClass (ClassGen @class, string indent, GenerationInfo gen_info)
6767
obj_type = gs != null && gs.IsConcrete ? gs.GetGenericType (null) : opt.GetOutputName (@class.base_symbol.FullName);
6868
}
6969

70+
Javadoc.WriteJavadocs (writer, indent, @class.Javadoc);
71+
7072
writer.WriteLine ("{0}// Metadata.xml XPath class reference: path=\"{1}\"", indent, @class.MetadataXPathReference);
7173

7274
if (@class.IsDeprecated)
@@ -412,6 +414,8 @@ public bool WriteFields (List<Field> fields, string indent, GenBase gen, HashSet
412414

413415
internal virtual void WriteField (Field field, string indent, GenBase type)
414416
{
417+
Javadoc.WriteJavadocs (writer, indent, field.Javadoc);
418+
415419
if (field.IsEnumified)
416420
writer.WriteLine ("[global::Android.Runtime.GeneratedEnum]");
417421
if (field.NeedsProperty) {
@@ -520,6 +524,8 @@ public void WriteInterfaceDeclaration (InterfaceGen @interface, string indent, G
520524
sb.Append (opt.GetOutputName (isym.FullName));
521525
}
522526

527+
Javadoc.WriteJavadocs (writer, indent, @interface.Javadoc);
528+
523529
writer.WriteLine ("{0}// Metadata.xml XPath interface reference: path=\"{1}\"", indent, @interface.MetadataXPathReference);
524530

525531
if (@interface.IsDeprecated)
@@ -1436,6 +1442,7 @@ public void WriteMethod (Method method, string indent, GenBase type, bool genera
14361442
}
14371443
string ret = opt.GetTypeReferenceName (method.RetVal);
14381444
WriteMethodIdField (method, indent);
1445+
Javadoc.WriteJavadocs (writer, indent, method.Javadoc);
14391446
if (method.DeclaringType.IsGeneratable)
14401447
writer.WriteLine ("{0}// Metadata.xml XPath method reference: path=\"{1}\"", indent, method.GetMetadataXPathReference (method.DeclaringType));
14411448
if (method.Deprecated != null)

tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs

+1
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ public static Field CreateField (GenBase declaringType, XElement elem)
109109
IsFinal = elem.XGetAttribute ("final") == "true",
110110
IsStatic = elem.XGetAttribute ("static") == "true",
111111
JavaName = elem.XGetAttribute ("name"),
112+
JniSignature = elem.XGetAttribute ("jni-signature"),
112113
NotNull = elem.XGetAttribute ("not-null") == "true",
113114
SetterParameter = CreateParameter (elem),
114115
TypeName = elem.XGetAttribute ("type"),

tools/generator/Java.Interop.Tools.Generator.ObjectModel/Field.cs

+3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ public class Field : ApiVersionsSupport.IApiAvailability
2424
public string TypeName { get; set; }
2525
public string Value { get; set; }
2626
public string Visibility { get; set; }
27+
public string JniSignature { get; set; }
28+
29+
public string Javadoc { get; set; }
2730

2831
internal string GetMethodPrefix => TypeNameUtilities.GetCallPrefix (Symbol);
2932

tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs

+2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ protected GenBase (GenBaseSupport support)
3535

3636
public string ReturnCast => string.Empty;
3737

38+
public string Javadoc { get; set; }
39+
3840
// This means Ctors/Methods/Properties/Fields has not been populated yet.
3941
// If this type is retrieved from the SymbolTable, it will call PopulateAction
4042
// to fill in members before returning it to the user.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
5+
using HtmlAgilityPack;
6+
7+
namespace MonoDroid.Generation
8+
{
9+
public static class Javadoc {
10+
11+
public static void WriteJavadocs (TextWriter writer, string indent, string javadoc)
12+
{
13+
if (javadoc == null || string.IsNullOrWhiteSpace (javadoc))
14+
return;
15+
16+
javadoc = javadoc.Trim ();
17+
18+
WriteSummary (writer, indent, javadoc);
19+
WriteRemarks (writer, indent, javadoc);
20+
}
21+
22+
static void WriteSummary (TextWriter writer, string indent, string javadoc)
23+
{
24+
var summary = javadoc;
25+
int dotIndex = javadoc.IndexOf ('.');
26+
if (dotIndex > 0) {
27+
summary = javadoc.Substring (0, dotIndex+1);
28+
}
29+
30+
writer.WriteLine ($"{indent}/// <summary>");
31+
using var r = new StringReader (summary);
32+
string line;
33+
while ((line = r.ReadLine ()) != null) {
34+
writer.WriteLine ($"{indent}/// {line}");
35+
}
36+
writer.WriteLine ($"{indent}/// </summary>");
37+
}
38+
39+
static void WriteRemarks (TextWriter writer, string indent, string javadoc)
40+
{
41+
// TODO: better translate HTML to C# XML Doc Comments
42+
string javadocXml = null;
43+
44+
try {
45+
var doc = new HtmlDocument() {
46+
OptionAutoCloseOnEnd = true,
47+
OptionFixNestedTags = true,
48+
OptionOutputAsXml = true,
49+
};
50+
doc.LoadHtml (javadoc);
51+
52+
var javadocXmlOutput = new StringWriter ();
53+
doc.Save (javadocXmlOutput);
54+
55+
javadocXml = javadocXmlOutput.ToString ();
56+
}
57+
catch (Exception e) {
58+
Console.Error.WriteLine ("## Unable to translate remarks:");
59+
Console.Error.WriteLine (e.ToString ());
60+
Console.Error.WriteLine (javadoc);
61+
Console.Error.WriteLine ();
62+
return;
63+
}
64+
65+
var lines = new StringReader (javadocXml);
66+
67+
writer.WriteLine ($"{indent}/// <remarks>");
68+
writer.WriteLine ($"{indent}/// <format type=\"text/html\">");
69+
70+
string line;
71+
while ((line = lines.ReadLine ()) != null) {
72+
writer.WriteLine ($"{indent}/// {line}");
73+
}
74+
75+
writer.WriteLine ($"{indent}/// </format>");
76+
writer.WriteLine ($"{indent}/// </remarks>");
77+
}
78+
}
79+
}

tools/generator/Java.Interop.Tools.Generator.ObjectModel/MethodBase.cs

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ protected MethodBase (GenBase declaringType)
2424
public ParameterList Parameters { get; } = new ParameterList ();
2525
public string Visibility { get; set; }
2626

27+
public string Javadoc { get; set; }
28+
2729
public string [] AutoDetectEnumifiedOverrideParameters (AncestorDescendantCache cache)
2830
{
2931
if (Parameters.All (p => p.Type != "int"))

0 commit comments

Comments
 (0)