diff --git a/src/Java.Interop.Tools.JavaSource/IronyExtensions.cs b/src/Java.Interop.Tools.JavaSource/IronyExtensions.cs new file mode 100644 index 000000000..fd4ebdd67 --- /dev/null +++ b/src/Java.Interop.Tools.JavaSource/IronyExtensions.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Irony.Ast; +using Irony.Parsing; + +namespace Java.Interop.Tools.JavaSource { + + static class IronyExtensions { + + public static void MakeStarRule (this NonTerminal star, Grammar grammar, BnfTerm delimiter, BnfTerm of) + { + star.Rule = grammar.MakeStarRule (star, delimiter, of); + } + + public static void MakeStarRule (this NonTerminal star, Grammar grammar, BnfTerm of) + { + star.Rule = grammar.MakeStarRule (star, of); + } + } +} diff --git a/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/JavadocInfo.cs b/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/JavadocInfo.cs new file mode 100644 index 000000000..6d519097f --- /dev/null +++ b/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/JavadocInfo.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Xml.Linq; + +using Irony.Ast; +using Irony.Parsing; + +namespace Java.Interop.Tools.JavaSource { + + sealed class JavadocInfo { + public readonly ICollection Parameters = new Collection (); + public readonly ICollection Paragraphs = new Collection (); + public readonly ICollection Exceptions = new Collection (); + } +} diff --git a/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/SourceJavadocToXmldocGrammar.BlockTagsBnfTerms.cs b/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/SourceJavadocToXmldocGrammar.BlockTagsBnfTerms.cs new file mode 100644 index 000000000..0c0d0bc59 --- /dev/null +++ b/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/SourceJavadocToXmldocGrammar.BlockTagsBnfTerms.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; + +using Irony.Ast; +using Irony.Parsing; + +namespace Java.Interop.Tools.JavaSource { + + public partial class SourceJavadocToXmldocGrammar { + + // https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#javadoctags + public class BlockTagsBnfTerms { + + internal BlockTagsBnfTerms () + { + } + + internal void CreateRules (SourceJavadocToXmldocGrammar grammar) + { + AllBlockTerms.Rule = AuthorDeclaration + | ApiSinceDeclaration + | DeprecatedDeclaration + | DeprecatedSinceDeclaration + | ExceptionDeclaration + | ParamDeclaration + | ReturnDeclaration + | SeeDeclaration + | SerialDataDeclaration + | SerialFieldDeclaration + | SinceDeclaration + | ThrowsDeclaration + | VersionDeclaration + ; + BlockValue.Rule = Cdata | grammar.InlineTagsTerms.AllInlineTerms; + + AuthorDeclaration.Rule = "@author" + BlockValue; + AuthorDeclaration.AstConfig.NodeCreator = (context, parseNode) => { + // Ignore; not sure how best to convert to Xmldoc + parseNode.AstNode = ""; + }; + + ApiSinceDeclaration.Rule = "@apiSince" + BlockValue; + ApiSinceDeclaration.AstConfig.NodeCreator = (context, parseNode) => { + parseNode.AstNode = new XElement ("para", "Added in API level " + parseNode.ChildNodes [1].AstNode?.ToString () + "."); + }; + + DeprecatedDeclaration.Rule = "@deprecated" + BlockValue; + DeprecatedDeclaration.AstConfig.NodeCreator = (context, parseNode) => { + parseNode.AstNode = new XElement ("para", "This member is deprecated. " + parseNode.ChildNodes [1].AstNode?.ToString ()); + }; + + DeprecatedSinceDeclaration.Rule = "@deprecatedSince" + BlockValue; + DeprecatedSinceDeclaration.AstConfig.NodeCreator = (context, parseNode) => { + parseNode.AstNode = new XElement ("para", "This member was deprecated in API level " + parseNode.ChildNodes [1].AstNode?.ToString () + "."); + }; + + var nonSpaceTerm = new RegexBasedTerminal ("[^ ]", "[^ ]+") { + AstConfig = new AstNodeConfig { + NodeCreator = (context, parseNode) => parseNode.AstNode = parseNode.Token.Value, + }, + }; + + ExceptionDeclaration.Rule = "@exception" + nonSpaceTerm + BlockValue; + ExceptionDeclaration.AstConfig.NodeCreator = (context, parseNode) => { + // TODO: convert `nonSpaceTerm` into a proper CREF + parseNode.AstNode = new XElement ("exception", + new XAttribute ("cref", parseNode.ChildNodes [1].AstNode?.ToString ()), + parseNode.ChildNodes [2].AstNode); + }; + + ParamDeclaration.Rule = "@param" + nonSpaceTerm + BlockValue; + ParamDeclaration.AstConfig.NodeCreator = (context, parseNode) => { + parseNode.AstNode = new XElement ("param", + new XAttribute ("name", parseNode.ChildNodes [1].AstNode?.ToString ()), + parseNode.ChildNodes [2].AstNode); + }; + + ReturnDeclaration.Rule = "@return" + BlockValue; + ReturnDeclaration.Flags = TermFlags.IsMultiline; + ReturnDeclaration.AstConfig.NodeCreator = (context, parseNode) => { + var returns = parseNode.ChildNodes [1].AstNode.ToString (); + parseNode.AstNode = new XElement ("returns", returns); + }; + + SeeDeclaration.Rule = "@see" + BlockValue; + SeeDeclaration.AstConfig.NodeCreator = (context, parseNode) => { + // TODO: @see supports multiple forms; see: https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#see + parseNode.AstNode = new XElement ("altmember", + new XAttribute ("cref", parseNode.ChildNodes [1].AstNode?.ToString ())); + }; + + SinceDeclaration.Rule = "@since" + BlockValue; + SinceDeclaration.AstConfig.NodeCreator = (context, parseNode) => { + parseNode.AstNode = new XElement ("para", "Added in " + parseNode.ChildNodes [1].AstNode?.ToString () + "."); + }; + + ThrowsDeclaration.Rule = "@throws" + nonSpaceTerm + BlockValue; + ThrowsDeclaration.AstConfig.NodeCreator = (context, parseNode) => { + // TODO: convert `nonSpaceTerm` into a proper CREF + parseNode.AstNode = new XElement ("exception", + new XAttribute ("cref", parseNode.ChildNodes [1].AstNode?.ToString ()), + parseNode.ChildNodes [2].AstNode); + }; + + // Ignore serialization informatino + SerialDeclaration.Rule = "@serial" + BlockValue; + SerialDeclaration.AstConfig.NodeCreator = (context, parseNode) => { + parseNode.AstNode = ""; + }; + + SerialDataDeclaration.Rule = "@serialData" + BlockValue; + SerialDataDeclaration.AstConfig.NodeCreator = (context, parseNode) => { + parseNode.AstNode = ""; + }; + + SerialFieldDeclaration.Rule = "@serialField" + BlockValue; + SerialFieldDeclaration.AstConfig.NodeCreator = (context, parseNode) => { + parseNode.AstNode = ""; + }; + + // Ignore Version + VersionDeclaration.Rule = "@version" + BlockValue; + VersionDeclaration.AstConfig.NodeCreator = (context, parseNode) => { + parseNode.AstNode = ""; + }; + } + + public readonly NonTerminal AllBlockTerms = new NonTerminal (nameof (AllBlockTerms), ConcatChildNodes); + + public readonly Terminal Cdata = new RegexBasedTerminal (nameof (BlockValue), "[^<]*") { + AstConfig = new AstNodeConfig { + NodeCreator = (context, parseNode) => parseNode.AstNode = parseNode.Token.Value.ToString (), + }, + }; + + public readonly NonTerminal BlockValue = new NonTerminal (nameof (BlockValue), ConcatChildNodes); + public readonly NonTerminal AuthorDeclaration = new NonTerminal (nameof (AuthorDeclaration)); + public readonly NonTerminal ApiSinceDeclaration = new NonTerminal (nameof (ApiSinceDeclaration)); + public readonly NonTerminal DeprecatedDeclaration = new NonTerminal (nameof (DeprecatedDeclaration)); + public readonly NonTerminal DeprecatedSinceDeclaration = new NonTerminal (nameof (DeprecatedSinceDeclaration)); + public readonly NonTerminal ExceptionDeclaration = new NonTerminal (nameof (ExceptionDeclaration)); + public readonly NonTerminal ParamDeclaration = new NonTerminal (nameof (ParamDeclaration)); + public readonly NonTerminal ReturnDeclaration = new NonTerminal (nameof (ReturnDeclaration)); + public readonly NonTerminal SeeDeclaration = new NonTerminal (nameof (SeeDeclaration)); + public readonly NonTerminal SerialDeclaration = new NonTerminal (nameof (SerialDeclaration)); + public readonly NonTerminal SerialDataDeclaration = new NonTerminal (nameof (SerialDataDeclaration)); + public readonly NonTerminal SerialFieldDeclaration = new NonTerminal (nameof (SerialFieldDeclaration)); + public readonly NonTerminal SinceDeclaration = new NonTerminal (nameof (SinceDeclaration)); + public readonly NonTerminal ThrowsDeclaration = new NonTerminal (nameof (ThrowsDeclaration)); + public readonly NonTerminal VersionDeclaration = new NonTerminal (nameof (VersionDeclaration)); + } + } +} diff --git a/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/SourceJavadocToXmldocGrammar.HtmlBnfTerms.cs b/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/SourceJavadocToXmldocGrammar.HtmlBnfTerms.cs new file mode 100644 index 000000000..c6fb1fe9d --- /dev/null +++ b/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/SourceJavadocToXmldocGrammar.HtmlBnfTerms.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; + +using Irony.Ast; +using Irony.Parsing; + +namespace Java.Interop.Tools.JavaSource { + + using static IronyExtensions; + + public partial class SourceJavadocToXmldocGrammar { + + public class HtmlBnfTerms { + internal HtmlBnfTerms () + { + } + + internal void CreateRules (SourceJavadocToXmldocGrammar grammar) + { + AllHtmlTerms.Rule = InlineDeclaration + | PBlockDeclaration + ; + + InlineDeclaration.Rule = ParsedCharacterData + | FontStyleDeclaration + /* + | PhraseDeclaration + | SpecialDeclaration + | FormCtrlDeclaration + */ + | grammar.InlineTagsTerms.AllInlineTerms + | UnknownHtmlElementStart + ; + InlineDeclarations.MakeStarRule (grammar, InlineDeclaration); + + var fontstyle_tt = new NonTerminal ("") { + Rule = CreateStartElement ("tt", grammar) + InlineDeclarations + CreateEndElement ("tt", grammar), + AstConfig = new AstNodeConfig { + NodeCreator = (context, parseNode) => { + parseNode.AstNode = new XElement ("c", + parseNode.ChildNodes + .Select (c => c.AstNode ?? "")); + }, + }, + }; + + var fontstyle_i = new NonTerminal ("", ConcatChildNodes) { + Rule = CreateStartElement ("i", grammar) + InlineDeclarations + CreateEndElement ("i", grammar), + }; + + FontStyleDeclaration.Rule = fontstyle_tt | fontstyle_i; + + PBlockDeclaration.Rule = + CreateStartElement ("p", grammar) + InlineDeclarations + CreateEndElement ("p", grammar, optional:true) + ; + PBlockDeclaration.AstConfig.NodeCreator = (context, parseNode) => { + parseNode.AstNode = new XElement ("para", + parseNode.ChildNodes + .Select (c => c.AstNode ?? "")); + }; + } + + public readonly NonTerminal AllHtmlTerms = new NonTerminal (nameof (AllHtmlTerms), ConcatChildNodes); + + + // https://www.w3.org/TR/html401/struct/global.html#h-7.5.3 +// public readonly Terminal ParsedCharacterData = new RegexBasedTerminal (nameof (ParsedCharacterData), "[^<{@}]*") { +// public readonly Terminal ParsedCharacterData = new WikiTextTerminal (nameof (ParsedCharacterData)) {* + public readonly Terminal ParsedCharacterData = new CharacterDataTerminal ("#PCDATA") { +// Priority = TerminalPriority.Low, + AstConfig = new AstNodeConfig { + NodeCreator = (context, parseNode) => parseNode.AstNode = parseNode.Token.Value.ToString (), + }, + }; + + // https://www.w3.org/TR/html4/sgml/dtd.html#inline + public readonly NonTerminal InlineDeclaration = new NonTerminal (nameof (InlineDeclaration), ConcatChildNodes); + public readonly NonTerminal InlineDeclarations = new NonTerminal (nameof (InlineDeclarations), ConcatChildNodes); + // https://www.w3.org/TR/html4/sgml/dtd.html#fontstyle + public readonly NonTerminal FontStyleDeclaration = new NonTerminal (nameof (FontStyleDeclaration), ConcatChildNodes); + // https://www.w3.org/TR/html4/sgml/dtd.html#phrase + public readonly NonTerminal PhraseDeclaration = new NonTerminal (nameof (PhraseDeclaration), ConcatChildNodes); + // https://www.w3.org/TR/html4/sgml/dtd.html#special + public readonly NonTerminal SpecialDeclaration = new NonTerminal (nameof (SpecialDeclaration), ConcatChildNodes); + // https://www.w3.org/TR/html4/sgml/dtd.html#formctrl + public readonly NonTerminal FormCtrlDeclaration = new NonTerminal (nameof (FormCtrlDeclaration), ConcatChildNodes); + // https://www.w3.org/TR/html4/sgml/dtd.html#block + public readonly NonTerminal BlockDeclaration = new NonTerminal (nameof (BlockDeclaration), ConcatChildNodes); + public readonly NonTerminal PBlockDeclaration = new NonTerminal (nameof (PBlockDeclaration), ConcatChildNodes); + + public readonly Terminal UnknownHtmlElementStart = new UnknownHtmlElementStartTerminal (nameof (UnknownHtmlElementStart)) { + AstConfig = new AstNodeConfig { + NodeCreator = (context, parseNode) => parseNode.AstNode = parseNode.Token.Value.ToString (), + }, + }; + + static NonTerminal CreateStartElement (string startElement, Grammar grammar) + { + var start = new NonTerminal ("<" + startElement + ">", nodeCreator: (context, parseNode) => parseNode.AstNode = "") { + Rule = grammar.ToTerm ("<" + startElement + ">") | "<" + startElement.ToUpperInvariant () + ">", + }; + return start; + } + + static NonTerminal CreateEndElement (string endElement, Grammar grammar, bool optional = false) + { + var end = new NonTerminal (endElement, nodeCreator: (context, parseNode) => parseNode.AstNode = "") { + Rule = grammar.ToTerm ("") | "", + }; + if (optional) { + end.Rule |= grammar.Empty; + } + return end; + } + } + } + + // Based in part on WikiTextTerminal + class CharacterDataTerminal : Terminal { + + private char[] _stopChars; + + public CharacterDataTerminal (string name) + : base (name) + { + base.Priority = TerminalPriority.Low; + } + + public override void Init (GrammarData grammarData) + { + base.Init (grammarData); + var stopCharSet = new Irony.CharHashSet (); + foreach(var term in grammarData.Terminals) { + var firsts = term.GetFirsts (); + if (firsts == null) + continue; + foreach (var first in firsts) { + if (string.IsNullOrEmpty (first)) + continue; + stopCharSet.Add (first [0]); + } + } + _stopChars = stopCharSet.ToArray(); + } + + public override Token TryMatch (ParsingContext context, ISourceStream source) + { + var stopIndex = source.Text.IndexOfAny (_stopChars, source.Location.Position); + if (stopIndex == source.Location.Position) + return null; + if (stopIndex < 0) + stopIndex = source.Text.Length; + source.PreviewPosition = stopIndex; + + // preserve leading whitespace, if present. + int start = source.Location.Position; + while (start > 0 && char.IsWhiteSpace (source.Text, start-1)) { + start--; + } + var content = source.Text.Substring (start, stopIndex - start); + + return source.CreateToken (this.OutputTerminal, content); + } + } + + class UnknownHtmlElementStartTerminal : Terminal { + + public UnknownHtmlElementStartTerminal (string name) + : base (name) + { + base.Priority = TerminalPriority.Low-1; + } + + public override void Init (GrammarData grammarData) + { + base.Init (grammarData); + } + + public override Token TryMatch (ParsingContext context, ISourceStream source) + { + if (source.Text [source.Location.Position] != '<') + return null; + source.PreviewPosition += 1; + return source.CreateToken (this.OutputTerminal, "<"); + } + } +} diff --git a/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/SourceJavadocToXmldocGrammar.InlineTagsBnfTerms.cs b/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/SourceJavadocToXmldocGrammar.InlineTagsBnfTerms.cs new file mode 100644 index 000000000..0492cb39f --- /dev/null +++ b/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/SourceJavadocToXmldocGrammar.InlineTagsBnfTerms.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; + +using Irony.Ast; +using Irony.Parsing; + +namespace Java.Interop.Tools.JavaSource { + + public partial class SourceJavadocToXmldocGrammar { + + // https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#javadoctags + public class InlineTagsBnfTerms { + + public InlineTagsBnfTerms () + { + } + + internal void CreateRules (SourceJavadocToXmldocGrammar grammar) + { + AllInlineTerms.Rule = CodeDeclaration + | DocRootDeclaration + | InheritDocDeclaration + | LinkDeclaration + | LinkplainDeclaration + | LiteralDeclaration + | ValueDeclaration + ; + +// InlineValue.Priority = TerminalPriority.Low; + + CodeDeclaration.Rule = grammar.ToTerm ("{@code") + InlineValue + "}"; + CodeDeclaration.AstConfig.NodeCreator = (context, parseNode) => { + parseNode.AstNode = new XElement ("c", parseNode.ChildNodes [1].AstNode.ToString ().Trim ()); + }; + + DocRootDeclaration.Rule = grammar.ToTerm ("{@docRoot}"); + DocRootDeclaration.AstConfig.NodeCreator = (context, parseNode) => { + parseNode.AstNode = new XText ("[TODO: @docRoot]"); + }; + + InheritDocDeclaration.Rule = grammar.ToTerm ("{@inheritDoc}"); + InheritDocDeclaration.AstConfig.NodeCreator = (context, parseNode) => { + parseNode.AstNode = new XText ("[TODO: @inheritDoc]"); + }; + + LinkDeclaration.Rule = grammar.ToTerm ("{@link") + InlineValue + "}"; + LinkDeclaration.AstConfig.NodeCreator = (context, parseNode) => { + // TODO: *everything*; {@link target label}, but target can contain spaces! + // Also need to convert to appropriate CREF value + var target = parseNode.ChildNodes [1].AstNode; + var x = new XElement ("c"); + parseNode.AstNode = new XElement ("c", new XElement ("see", new XAttribute ("cref", target))); + }; + + LinkplainDeclaration.Rule = grammar.ToTerm ("{@linkplain") + InlineValue + "}"; + LinkplainDeclaration.AstConfig.NodeCreator = (context, parseNode) => { + // TODO: *everything*; {@link target label}, but target can contain spaces! + // Also need to convert to appropriate CREF value + var target = parseNode.ChildNodes [1].AstNode; + parseNode.AstNode = new XElement ("see", new XAttribute ("cref", target)); + }; + + LiteralDeclaration.Rule = grammar.ToTerm ("{@literal") + InlineValue + "}"; + LiteralDeclaration.AstConfig.NodeCreator = (context, parseNode) => { + var content = parseNode.ChildNodes [1].AstNode.ToString (); + parseNode.AstNode = new XText (content); + }; + + ValueDeclaration.Rule = grammar.ToTerm ("{@value}") + | grammar.ToTerm ("{@value") + InlineValue + "}"; + ValueDeclaration.AstConfig.NodeCreator = (context, parseNode) => { + if (parseNode.ChildNodes.Count > 1) { + var field = parseNode.ChildNodes [1].AstNode.ToString (); + parseNode.AstNode = new XText ($"[TODO: @value for `{field}`]"); + } + else { + parseNode.AstNode = new XText ("[TODO: @value]"); + } + }; + } + + public readonly NonTerminal AllInlineTerms = new NonTerminal (nameof (AllInlineTerms), ConcatChildNodes); + + public readonly Terminal InlineValue = new RegexBasedTerminal (nameof (InlineValue), "[^}]*") { + AstConfig = new AstNodeConfig { + NodeCreator = (context, parseNode) => parseNode.AstNode = parseNode.Token.Value, + }, + }; + + // https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#code + public readonly NonTerminal CodeDeclaration = new NonTerminal (nameof (CodeDeclaration)); + + // https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#docRoot + public readonly NonTerminal DocRootDeclaration = new NonTerminal (nameof (DocRootDeclaration)); + + // https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#inheritDoc + public readonly NonTerminal InheritDocDeclaration = new NonTerminal (nameof (InheritDocDeclaration)); + + // https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#link + public readonly NonTerminal LinkDeclaration = new NonTerminal (nameof (LinkDeclaration)); + + // https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#linkplain + public readonly NonTerminal LinkplainDeclaration = new NonTerminal (nameof (LinkplainDeclaration)); + + // https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#literal + public readonly NonTerminal LiteralDeclaration = new NonTerminal (nameof (LinkplainDeclaration)); + + // https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#value + public readonly NonTerminal ValueDeclaration = new NonTerminal (nameof (ValueDeclaration)); + } + } +} diff --git a/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/SourceJavadocToXmldocGrammar.cs b/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/SourceJavadocToXmldocGrammar.cs new file mode 100644 index 000000000..de361ee9f --- /dev/null +++ b/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/SourceJavadocToXmldocGrammar.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; + +using Irony.Ast; +using Irony.Parsing; + +namespace Java.Interop.Tools.JavaSource { + + [Language ("SourceJavadocToXmldoc", "0.1", "Convert Javadoc within Java source code, sans comment delimiter, to CSC /doc XML.")] + public partial class SourceJavadocToXmldocGrammar : Grammar { + + public readonly BlockTagsBnfTerms BlockTagsTerms; + public readonly InlineTagsBnfTerms InlineTagsTerms; + public readonly HtmlBnfTerms HtmlTerms; + + public SourceJavadocToXmldocGrammar () + { + BlockTagsTerms = new BlockTagsBnfTerms (); + InlineTagsTerms = new InlineTagsBnfTerms (); + HtmlTerms = new HtmlBnfTerms (); + + BlockTagsTerms.CreateRules (this); + InlineTagsTerms.CreateRules (this); + HtmlTerms.CreateRules (this); + + var root = new NonTerminal ("", ConcatChildNodes) { + Rule = BlockTagsTerms.AllBlockTerms | HtmlTerms.AllHtmlTerms, + }; + + this.Root = root; + } + + internal static void ConcatChildNodes (AstContext context, ParseTreeNode parseNode) + { + switch (parseNode.ChildNodes.Count) { + case 0: + parseNode.AstNode = ""; + break; + case 1: + parseNode.AstNode = parseNode.ChildNodes [0].AstNode ?? ""; + break; + default: { + parseNode.AstNode = parseNode.ChildNodes + .Select (c => c.AstNode ?? "") + .ToArray (); + break; + } + } + } + + internal static JavadocInfo GetRoot (AstContext context) + { + const string key = ".__JavadocInfo"; + if (!context.Values.TryGetValue (key, out var r)) { + context.Values.Add (key, r = new JavadocInfo ()); + } + return (JavadocInfo) r; + } + } +} diff --git a/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/SourceJavadocToXmldocParser.cs b/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/SourceJavadocToXmldocParser.cs new file mode 100644 index 000000000..5018b1bab --- /dev/null +++ b/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/SourceJavadocToXmldocParser.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using System.Text; + +using Irony; +using Irony.Ast; +using Irony.Parsing; + +namespace Java.Interop.Tools.JavaSource { + + public class SourceJavadocToXmldocParser : Irony.Parsing.Parser { + + public SourceJavadocToXmldocParser () + : base (CreateGrammar ()) + { + } + + static Grammar CreateGrammar () + { + return new SourceJavadocToXmldocGrammar () { + LanguageFlags = LanguageFlags.Default | LanguageFlags.CreateAst, + }; + } + + public IEnumerable TryParse (string javadoc, string fileName = null, Action onError = null) + { + onError = onError ?? DumpMessages; + + ParseTree parseTree; + var r = TryParse (javadoc, fileName, out parseTree); + if (parseTree.HasErrors ()) { + onError (parseTree); + } + return r; + } + + public IEnumerable TryParse (string javadoc, string fileName, out ParseTree parseTree) + { + parseTree = base.Parse (javadoc, fileName); + if (parseTree.HasErrors ()) { + return Array.Empty(); + } + return CreateParseIterator (parseTree); + } + + static IEnumerable CreateParseIterator (ParseTree parseTree) + { + var ast = parseTree.Root.AstNode; + if (ast is XNode node) { + yield return node; + } + else if (ast is JavadocInfo info) { + foreach (var n in info.Parameters) + yield return n; + var summary = CreateSummaryNode (info); + if (summary != null) + yield return summary; + yield return new XElement ("remarks", info.Paragraphs); + } + else { + yield return new XCData (ast?.ToString ()); + } + } + + static XNode CreateSummaryNode (JavadocInfo info) + { + var summaryNode = info.Paragraphs.FirstOrDefault (); + if (summaryNode == null) + return null; + + string content = null; + if (summaryNode is XElement p) + content = p.FirstNode?.ToString (); + else + content = summaryNode.ToString (); + + if (string.IsNullOrWhiteSpace (content)) + return null; + + var dot = content.IndexOf ('.'); + if (dot <= 0) + return new XElement ("summary", content); + return new XElement ("summary", content.Substring (0, dot)); + } + + static void DumpMessages (ParseTree parseTree) + { + foreach (var m in parseTree.ParserMessages) { + Console.Error.WriteLine ($"{m.Level} {m.Location}: {m.Message}"); + } + } + } +} diff --git a/tests/Java.Interop-Tests/Java.Interop/TestType.cs b/tests/Java.Interop-Tests/Java.Interop/TestType.cs index 4640d5f14..2cd108b1f 100644 --- a/tests/Java.Interop-Tests/Java.Interop/TestType.cs +++ b/tests/Java.Interop-Tests/Java.Interop/TestType.cs @@ -172,7 +172,8 @@ static IntPtr GetStringValueHandler (IntPtr jnienv, IntPtr n_self, int value) static Delegate GetMethodThrowsHandler () { Action h = MethodThrowsHandler; - return JniEnvironment.Runtime.MarshalMemberBuilder.CreateMarshalToManagedDelegate (h); +// return JniEnvironment.Runtime.MarshalMemberBuilder.CreateMarshalToManagedDelegate (h); + return h; } static void MethodThrowsHandler (IntPtr jnienv, IntPtr n_self) diff --git a/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocGrammar.BlockTagsBnfTermsTests.cs b/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocGrammar.BlockTagsBnfTermsTests.cs new file mode 100644 index 000000000..70b34935a --- /dev/null +++ b/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocGrammar.BlockTagsBnfTermsTests.cs @@ -0,0 +1,115 @@ +using System; +using System.Linq; +using System.Xml.Linq; + +using NUnit.Framework; + +using Java.Interop.Tools.JavaSource; + +using Irony; +using Irony.Parsing; + +namespace Java.Interop.Tools.JavaSource.Tests +{ + [TestFixture] + public class SourceJavadocToXmldocGrammarBlockTagsBnfTermsTests { + + static Parser CreateParser (Func root) + { + var g = new SourceJavadocToXmldocGrammar () { + LanguageFlags = LanguageFlags.Default | LanguageFlags.CreateAst, + }; + g.Root = root (g); + return new Parser (g); + } + + [Test] + public void ApiSinceDeclaration () + { + var p = CreateParser (g => g.BlockTagsTerms.ApiSinceDeclaration); + + var r = p.Parse ("@apiSince 3"); + Assert.IsFalse (r.HasErrors (), "@apiSince: " + DumpErrors (r.ParserMessages)); + Assert.AreEqual ("Added in API level 3.", r.Root.AstNode.ToString ()); + } + + [Test] + public void DeprecatedDeclaration () + { + var p = CreateParser (g => g.BlockTagsTerms.DeprecatedDeclaration); + + var r = p.Parse ("@deprecated Insert reason here."); + Assert.IsFalse (r.HasErrors (), "@deprecated: " + DumpErrors (r.ParserMessages)); + Assert.AreEqual ("This member is deprecated. Insert reason here.", r.Root.AstNode.ToString ()); + } + + [Test] + public void DeprecatedSinceDeclaration () + { + var p = CreateParser (g => g.BlockTagsTerms.DeprecatedSinceDeclaration); + + var r = p.Parse ("@deprecatedSince 3"); + Assert.IsFalse (r.HasErrors (), "@deprecatedSince: " + DumpErrors (r.ParserMessages)); + Assert.AreEqual ("This member was deprecated in API level 3.", r.Root.AstNode.ToString ()); + } + + [Test] + public void ExceptionDeclaration () + { + var p = CreateParser (g => g.BlockTagsTerms.ExceptionDeclaration); + + var r = p.Parse ("@exception Throwable Just Because."); + Assert.IsFalse (r.HasErrors (), "@exception: " + DumpErrors (r.ParserMessages)); + Assert.AreEqual ("Just Because.", r.Root.AstNode.ToString ()); + } + + [Test] + public void ParamDeclaration () + { + var p = CreateParser (g => g.BlockTagsTerms.ParamDeclaration); + + var r = p.Parse ("@param a Insert description here"); + Assert.IsFalse (r.HasErrors (), "@param: " + DumpErrors (r.ParserMessages)); + Assert.AreEqual ("Insert description here", r.Root.AstNode.ToString ()); + } + + [Test] + public void ReturnDeclaration () + { + var p = CreateParser (g => g.BlockTagsTerms.ReturnDeclaration); + + var r = p.Parse ("@return insert description here"); + Assert.IsFalse (r.HasErrors (), "single-line @return: " + DumpErrors (r.ParserMessages)); + Assert.AreEqual ("insert description here", r.Root.AstNode.ToString ()); + + r = p.Parse ("@return line 1\n\tline two"); + Assert.IsFalse (r.HasErrors (), "multi-line @return: " + DumpErrors (r.ParserMessages)); + Assert.AreEqual ("line 1\n\tline two", r.Root.AstNode.ToString ()); + } + + [Test] + public void SeeDeclaration () + { + var p = CreateParser (g => g.BlockTagsTerms.SeeDeclaration); + + var r = p.Parse ("@see \"Insert Book Name Here\""); + Assert.IsFalse (r.HasErrors (), "@see: " + DumpErrors (r.ParserMessages)); + Assert.AreEqual ("", r.Root.AstNode.ToString ()); + } + + [Test] + public void SinceDeclaration () + { + var p = CreateParser (g => g.BlockTagsTerms.SinceDeclaration); + + var r = p.Parse ("@since Insert Version Here"); + Assert.IsFalse (r.HasErrors (), "@since: " + DumpErrors (r.ParserMessages)); + Assert.AreEqual ("Added in Insert Version Here.", r.Root.AstNode.ToString ()); + } + + static string DumpErrors (LogMessageList messages) + { + return string.Join ("; ", messages.Select (m => $"{m.Level} {m.Location}: {m.Message}")); + } + } +} diff --git a/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocGrammar.HtmlBnfTermsTests.cs b/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocGrammar.HtmlBnfTermsTests.cs new file mode 100644 index 000000000..0058a3d9a --- /dev/null +++ b/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocGrammar.HtmlBnfTermsTests.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using System.Text; + +using NUnit.Framework; + +using Java.Interop.Tools.JavaSource; + +using Irony; +using Irony.Parsing; + +namespace Java.Interop.Tools.JavaSource.Tests +{ + [TestFixture] + public class SourceJavadocToXmldocGrammarHtmlBnfTermsTests : SourceJavadocToXmldocGrammarFixture { + + [Test] + public void PBlockDeclaration () + { + var p = CreateParser (g => g.HtmlTerms.PBlockDeclaration); + + var r = p.Parse ("

paragraph text\nand more!"); + Assert.IsFalse (r.HasErrors (), DumpMessages (r)); + Assert.AreEqual ("paragraph text\nand more!", r.Root.AstNode.ToString ()); + + r = p.Parse ("

r= {@code Object} and following {@literal AC} text

"); + Assert.IsFalse (r.HasErrors (), DumpMessages (r)); + Assert.AreEqual ("r= Object and following A<B>C text", r.Root.AstNode.ToString ()); + + r = p.Parse("

r= unknown text"); + Assert.IsFalse (r.HasErrors (), DumpMessages (r)); + Assert.AreEqual ("r= <em>unknown</em> text", r.Root.AstNode.ToString ()); + } + } +} diff --git a/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocGrammar.InlineTagsBnfTermsTests.cs b/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocGrammar.InlineTagsBnfTermsTests.cs new file mode 100644 index 000000000..c1a00e289 --- /dev/null +++ b/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocGrammar.InlineTagsBnfTermsTests.cs @@ -0,0 +1,92 @@ +using System; +using System.Linq; +using System.Xml.Linq; + +using NUnit.Framework; + +using Java.Interop.Tools.JavaSource; + +using Irony; +using Irony.Parsing; + +namespace Java.Interop.Tools.JavaSource.Tests +{ + [TestFixture] + public class SourceJavadocToXmldocGrammarInlineTagsBnfTermsTests : SourceJavadocToXmldocGrammarFixture { + + [Test] + public void CodeDeclaration () + { + var p = CreateParser (g => g.InlineTagsTerms.CodeDeclaration); + + var r = p.Parse ("{@code Object}"); + Assert.IsFalse (r.HasErrors (), DumpMessages (r)); + Assert.AreEqual ("Object", r.Root.AstNode.ToString ()); + } + + [Test] + public void DocRootDeclaration () + { + var p = CreateParser (g => g.InlineTagsTerms.DocRootDeclaration); + + var r = p.Parse ("{@docRoot}"); + Assert.IsFalse (r.HasErrors (), DumpMessages (r)); + Assert.AreEqual ("[TODO: @docRoot]", r.Root.AstNode.ToString ()); + } + + [Test] + public void InheritDocDeclaration () + { + var p = CreateParser (g => g.InlineTagsTerms.InheritDocDeclaration); + + var r = p.Parse ("{@inheritDoc}"); + Assert.IsFalse (r.HasErrors (), DumpMessages (r)); + Assert.AreEqual ("[TODO: @inheritDoc]", r.Root.AstNode.ToString ()); + } + + [Test] + public void LinkDeclaration () + { + var p = CreateParser (g => g.InlineTagsTerms.LinkDeclaration); + + var r = p.Parse ("{@link #ctor}"); + Assert.IsFalse (r.HasErrors (), DumpMessages (r)); + var c = (XElement) r.Root.AstNode; + Assert.AreEqual ("", c.ToString (SaveOptions.DisableFormatting)); + } + + [Test] + public void LinkplainDeclaration () + { + var p = CreateParser (g => g.InlineTagsTerms.LinkplainDeclaration); + + var r = p.Parse ("{@linkplain #ctor}"); + Assert.IsFalse (r.HasErrors (), DumpMessages (r)); + Assert.AreEqual ("", r.Root.AstNode.ToString ()); + } + + [Test] + public void LiteralDeclaration () + { + var p = CreateParser (g => g.InlineTagsTerms.LiteralDeclaration); + + var r = p.Parse ("{@literal AC}"); + Assert.IsFalse (r.HasErrors (), DumpMessages (r)); + Assert.AreEqual ("A<B>C", r.Root.AstNode.ToString ()); + } + + [Test] + public void ValueDeclaration () + { + var p = CreateParser (g => g.InlineTagsTerms.ValueDeclaration); + + var r = p.Parse ("{@value}"); + Assert.IsFalse (r.HasErrors (), DumpMessages (r)); + Assert.AreEqual ("[TODO: @value]", r.Root.AstNode.ToString ()); + + r = p.Parse ("{@value #field}"); + Assert.IsFalse (r.HasErrors (), DumpMessages (r)); + Assert.AreEqual ("[TODO: @value for `#field`]", r.Root.AstNode.ToString ()); + } + } +} diff --git a/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocGrammarFixture.cs b/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocGrammarFixture.cs new file mode 100644 index 000000000..d6fbc7f33 --- /dev/null +++ b/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocGrammarFixture.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using System.Text; + +using NUnit.Framework; + +using Java.Interop.Tools.JavaSource; + +using Irony; +using Irony.Parsing; + +namespace Java.Interop.Tools.JavaSource.Tests +{ + [TestFixture] + public class SourceJavadocToXmldocGrammarFixture { + + public static Parser CreateParser (Func root) + { + var g = new SourceJavadocToXmldocGrammar () { + LanguageFlags = LanguageFlags.Default | LanguageFlags.CreateAst, + }; + g.Root = root (g); + return new Parser (g); + } + + public static string DumpMessages (ParseTree tree) + { + var lines = GetLines (tree.SourceText); + var message = new StringBuilder (); + foreach (var m in tree.ParserMessages) { + message.AppendLine ($"{m.Level} {m.Location}: {m.Message}"); + message.AppendLine (lines [m.Location.Line]); + message.Append (new string (' ', m.Location.Column)); + message.Append ("^"); + message.AppendLine (); + } + return message.ToString (); + } + + static List GetLines (string text) + { + var lines = new List(); + var reader = new StringReader (text); + string line; + while ((line = reader.ReadLine()) != null) { + lines.Add (line); + } + return lines; + } + } +} diff --git a/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocParserTests.cs b/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocParserTests.cs new file mode 100644 index 000000000..8f3e6673e --- /dev/null +++ b/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocParserTests.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using System.Text; + +using NUnit.Framework; + +using Java.Interop.Tools.JavaSource; + +using Irony; +using Irony.Parsing; + +namespace Java.Interop.Tools.JavaSource.Tests +{ + [TestFixture] + public class SourceJavadocToXmldocParserTests : SourceJavadocToXmldocGrammarFixture { + + // [Test] + public void TryParse () + { + foreach (var values in TryParse_Success) { + ParseTree parseTree; + var p = new SourceJavadocToXmldocParser (); + var n = p.TryParse (values.Javadoc, null, out parseTree); + Assert.IsFalse (parseTree.HasErrors (), DumpMessages (parseTree)); + Assert.AreEqual (values.Xmldoc, parseTree.Root.AstNode.ToString ()); + } + + } + + static readonly ParseResult[] TryParse_Success = new ParseResult[]{ + new ParseResult { + Javadoc = @"This is the summary sentence. Insert +more description here. + +What about soft paragraphs? + +

What about hard paragraphs? + +@param a something +@see #method() +@apiSince 1 +", + Xmldoc = @"something +

This is the summary sentence. + + This is the summary sentence. Insert + more description here. + + What about soft paragraphs? + + What about hard paragraphs? + Added in API level 1. + + +", + }, + }; + + class ParseResult { + public string Javadoc; + public string Xmldoc; + } + } +} diff --git a/tools/generator/CodeGenerator.cs b/tools/generator/CodeGenerator.cs index d88daaafd..dbef9a5d6 100644 --- a/tools/generator/CodeGenerator.cs +++ b/tools/generator/CodeGenerator.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Xml; +using System.Xml.Linq; using Mono.Cecil; using MonoDroid.Generation; using Xamarin.AndroidTools.AnnotationSupport; @@ -179,6 +180,7 @@ static void Run (CodeGeneratorOptions options, DirectoryAssemblyResolver resolve SealedProtectedFixups.Fixup (gens); GenerateAnnotationAttributes (gens, annotations_zips); + JavadocFixups.Fixup (gens, options.JavadocXmlFiles); //SymbolTable.Dump (); diff --git a/tools/generator/CodeGeneratorOptions.cs b/tools/generator/CodeGeneratorOptions.cs index b1bb8d283..532e8d9a4 100644 --- a/tools/generator/CodeGeneratorOptions.cs +++ b/tools/generator/CodeGeneratorOptions.cs @@ -15,6 +15,7 @@ public CodeGeneratorOptions () FixupFiles = new Collection (); LibraryPaths = new Collection (); AnnotationsZipFiles = new Collection (); + JavadocXmlFiles = new Collection (); } public string ApiLevel {get; set;} @@ -24,6 +25,7 @@ public CodeGeneratorOptions () public Collection AssemblyReferences {get; private set;} public Collection FixupFiles {get; private set;} public Collection LibraryPaths {get; private set;} + public Collection JavadocXmlFiles {get; private set;} public bool GlobalTypeNames {get; set;} public bool OnlyBindPublicTypes {get; set;} public string ApiDescriptionFile {get; set;} @@ -118,6 +120,9 @@ public static CodeGeneratorOptions Parse (string[] args) { "only-xml-adjuster", "Run only API XML adjuster for class-parse input.", v => opts.OnlyRunApiXmlAdjuster = v != null }, + { "with-javadoc-xml=", + "{PATH} to `api.xml` containing Javadoc docs in\n`` elements", + v => opts.JavadocXmlFiles.Add (v) }, { "xml-adjuster-output=", "specify API XML adjuster output XML for class-parse input.", v => opts.ApiXmlAdjusterOutput = v }, diff --git a/tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs b/tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs index ebb58aa6d..dac3593c2 100644 --- a/tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs +++ b/tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs @@ -113,6 +113,7 @@ public static Field CreateField (GenBase declaringType, XElement elem, CodeGener IsFinal = elem.XGetAttribute ("final") == "true", IsStatic = elem.XGetAttribute ("static") == "true", JavaName = elem.XGetAttribute ("name"), + JniSignature = elem.XGetAttribute ("jni-signature"), NotNull = elem.XGetAttribute ("not-null") == "true", SetterParameter = CreateParameter (elem, options), TypeName = elem.XGetAttribute ("type"), diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Field.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Field.cs index 7df4d8348..a986a2253 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Field.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Field.cs @@ -24,6 +24,9 @@ public class Field : ApiVersionsSupport.IApiAvailability, ISourceLineInfo public string TypeName { get; set; } public string Value { get; set; } public string Visibility { get; set; } + public string JniSignature { get; set; } + + public string Javadoc { get; set; } public int LineNumber { get; set; } = -1; public int LinePosition { get; set; } = -1; diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs index 2fa22ee43..0800b257e 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs @@ -39,6 +39,8 @@ protected GenBase (GenBaseSupport support) public string ReturnCast => string.Empty; + public string Javadoc { get; set; } + // This means Ctors/Methods/Properties/Fields has not been populated yet. // If this type is retrieved from the SymbolTable, it will call PopulateAction // to fill in members before returning it to the user. diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Javadoc.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Javadoc.cs new file mode 100644 index 000000000..ec84ffa5d --- /dev/null +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Javadoc.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml.Linq; + +using Java.Interop.Tools.JavaSource; + +namespace MonoDroid.Generation +{ + public static class Javadoc { + + public static void AddJavadocs (ICollection comments, string javadoc) + { + if (javadoc == null || string.IsNullOrWhiteSpace (javadoc)) + return; + + javadoc = javadoc.Trim (); + + try { + var parser = new SourceJavadocToXmldocParser (); + var nodes = parser.TryParse (javadoc); + foreach (var node in (nodes ?? new XNode [0])) { + AddNode (comments, node); + } + } + catch (Exception e) { + Console.Error.WriteLine ("## Unable to translate remarks:"); + Console.Error.WriteLine (e.ToString ()); + Console.Error.WriteLine ("```"); + Console.Error.WriteLine (javadoc); + Console.Error.WriteLine ("```"); + Console.Error.WriteLine (); + } + } + + static void AddNode (ICollection comments, XNode node) + { + if (node == null) + return; + var contents = node.ToString (); + + var lines = new StringReader (contents); + string line; + while ((line = lines.ReadLine ()) != null) { + comments.Add ($"/// {line}"); + } + } + } +} diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/MethodBase.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/MethodBase.cs index e79b7c78d..28bc07145 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/MethodBase.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/MethodBase.cs @@ -28,6 +28,8 @@ protected MethodBase (GenBase declaringType) public int LinePosition { get; set; } = -1; public string SourceFile { get; set; } + public string Javadoc { get; set; } + public string [] AutoDetectEnumifiedOverrideParameters (AncestorDescendantCache cache) { if (Parameters.All (p => p.Type != "int")) diff --git a/tools/generator/Java.Interop.Tools.Generator.Transformation/JavadocFixups.cs b/tools/generator/Java.Interop.Tools.Generator.Transformation/JavadocFixups.cs new file mode 100644 index 000000000..01cc08b8f --- /dev/null +++ b/tools/generator/Java.Interop.Tools.Generator.Transformation/JavadocFixups.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; + +using MonoDroid.Generation; +using MonoDroid.Utils; + +namespace Java.Interop.Tools.Generator.Transformation +{ + public static class JavadocFixups + { + public static void Fixup (List gens, IList javadocXmlFiles) + { + Console.WriteLine ($"# jonp: fixup Javadocs? {javadocXmlFiles != null}"); + if (javadocXmlFiles == null) + return; + foreach (var path in javadocXmlFiles) { + if (!File.Exists (path)) + continue; + + Console.WriteLine ($"# jonp: reading Javadocs from: {path}"); + XDocument doc = XDocument.Load (path); + + foreach (var type in gens) { + AddJavadoc (type, doc); + } + } + } + + static void AddJavadoc (GenBase type, XDocument javadoc) + { + var typeJavadoc = javadoc.Elements ("api") + .Elements ("package") + .Where (p => type.PackageName == (string) p.Attribute ("name")) + .Elements () + .Where (e => type.JniName == (string) e.Attribute ("jni-signature")) + .FirstOrDefault (); + if (typeJavadoc == null) + return; + + if (string.IsNullOrEmpty (type.Javadoc)) { + type.Javadoc = typeJavadoc.Element ("javadoc")?.Value; + Console.WriteLine ($"# jonp: setting javadoc for {type.FullName}? {type.Javadoc != null}"); + } + + foreach (var method in type.Methods) { + if (!string.IsNullOrEmpty (method.Javadoc)) + continue; + var methodJavadoc = typeJavadoc + .Elements ("method") + .Where (m => method.JavaName == (string) m.Attribute ("name") && method.JniSignature == (string) m.Attribute ("jni-signature")) + .Elements ("javadoc") + .FirstOrDefault (); + if (methodJavadoc == null) + continue; + method.Javadoc = methodJavadoc.Value; + } + + foreach (var field in type.Fields) { + if (!string.IsNullOrEmpty (field.Javadoc)) + continue; + var fieldJavadoc = typeJavadoc + .Elements ("field") + .Where (m => field.JavaName == (string) m.Attribute ("name") && field.JniSignature == (string) m.Attribute ("jni-signature")) + .Elements ("javadoc") + .FirstOrDefault (); + if (fieldJavadoc == null) + continue; + field.Javadoc = fieldJavadoc.Value; + } + + if (type is ClassGen @class) { + foreach (var ctor in @class.Ctors) { + if (!string.IsNullOrEmpty (ctor.Javadoc)) + continue; + var ctorJavadoc = typeJavadoc + .Elements ("constructor") + .Where (c => ctor.JniSignature == (string) c.Attribute ("jni-signature")) + .Elements ("javadoc") + .FirstOrDefault (); + if (ctorJavadoc == null) + continue; + ctor.Javadoc = ctorJavadoc.Value; + } + } + } + } +} diff --git a/tools/generator/SourceWriters/BoundClass.cs b/tools/generator/SourceWriters/BoundClass.cs index afce639e8..943af47e1 100644 --- a/tools/generator/SourceWriters/BoundClass.cs +++ b/tools/generator/SourceWriters/BoundClass.cs @@ -39,6 +39,7 @@ public BoundClass (ClassGen klass, CodeGenerationOptions opt, CodeGeneratorConte AddImplementedInterfaces (klass); + Javadoc.AddJavadocs (Comments, klass.Javadoc); Comments.Add ($"// Metadata.xml XPath class reference: path=\"{klass.MetadataXPathReference}\""); if (klass.IsDeprecated) diff --git a/tools/generator/SourceWriters/BoundConstructor.cs b/tools/generator/SourceWriters/BoundConstructor.cs index 5ec69de81..bc4b1b9a6 100644 --- a/tools/generator/SourceWriters/BoundConstructor.cs +++ b/tools/generator/SourceWriters/BoundConstructor.cs @@ -24,6 +24,7 @@ public BoundConstructor (ClassGen klass, Ctor constructor, bool useBase, CodeGen Name = klass.Name; + Javadoc.AddJavadocs (Comments, constructor.Javadoc); Comments.Add (string.Format ("// Metadata.xml XPath constructor reference: path=\"{0}/constructor[@name='{1}'{2}]\"", klass.MetadataXPathReference, klass.JavaSimpleName, constructor.Parameters.GetMethodXPathPredicate ())); Attributes.Add (new RegisterAttr (".ctor", constructor.JniSignature, string.Empty, additionalProperties: constructor.AdditionalAttributeString ())); diff --git a/tools/generator/SourceWriters/BoundField.cs b/tools/generator/SourceWriters/BoundField.cs index fb89716b1..1aa37896d 100644 --- a/tools/generator/SourceWriters/BoundField.cs +++ b/tools/generator/SourceWriters/BoundField.cs @@ -19,6 +19,7 @@ public BoundField (GenBase type, Field field, CodeGenerationOptions opt) Name = field.Name; Type = new TypeReferenceWriter (opt.GetOutputName (field.Symbol.FullName)); + Javadoc.AddJavadocs (Comments, field.Javadoc); Comments.Add ($"// Metadata.xml XPath field reference: path=\"{type.MetadataXPathReference}/field[@name='{field.JavaName}']\""); Attributes.Add (new RegisterAttr (field.JavaName, additionalProperties: field.AdditionalAttributeString ())); diff --git a/tools/generator/SourceWriters/BoundFieldAsProperty.cs b/tools/generator/SourceWriters/BoundFieldAsProperty.cs index dcb332fae..8f6c1f5cd 100644 --- a/tools/generator/SourceWriters/BoundFieldAsProperty.cs +++ b/tools/generator/SourceWriters/BoundFieldAsProperty.cs @@ -25,6 +25,7 @@ public BoundFieldAsProperty (GenBase type, Field field, CodeGenerationOptions op var fieldType = field.Symbol.IsArray ? "IList<" + field.Symbol.ElementType + ">" + opt.NullableOperator : opt.GetTypeReferenceName (field); PropertyType = new TypeReferenceWriter (fieldType); + Javadoc.AddJavadocs (Comments, field.Javadoc); Comments.Add ($"// Metadata.xml XPath field reference: path=\"{type.MetadataXPathReference}/field[@name='{field.JavaName}']\""); if (field.IsEnumified) diff --git a/tools/generator/SourceWriters/BoundInterface.cs b/tools/generator/SourceWriters/BoundInterface.cs index c0439ca0e..7091bdb63 100644 --- a/tools/generator/SourceWriters/BoundInterface.cs +++ b/tools/generator/SourceWriters/BoundInterface.cs @@ -37,6 +37,7 @@ public BoundInterface (InterfaceGen iface, CodeGenerationOptions opt, CodeGenera SetVisibility (iface.Visibility); + Javadoc.AddJavadocs (Comments, iface.Javadoc); Comments.Add ($"// Metadata.xml XPath interface reference: path=\"{iface.MetadataXPathReference}\""); if (iface.IsDeprecated) diff --git a/tools/generator/SourceWriters/BoundInterfaceMethodDeclaration.cs b/tools/generator/SourceWriters/BoundInterfaceMethodDeclaration.cs index 98817006f..c577dae65 100644 --- a/tools/generator/SourceWriters/BoundInterfaceMethodDeclaration.cs +++ b/tools/generator/SourceWriters/BoundInterfaceMethodDeclaration.cs @@ -33,6 +33,8 @@ public BoundInterfaceMethodDeclaration (Method method, string adapter, CodeGener Attributes.Add (new RegisterAttr (method.JavaName, method.JniSignature, method.ConnectorName + ":" + method.GetAdapterName (opt, adapter), additionalProperties: method.AdditionalAttributeString ())); + Javadoc.AddJavadocs (Comments, method.Javadoc); + SourceWriterExtensions.AddMethodCustomAttributes (Attributes, method); this.AddMethodParameters (method.Parameters, opt); } diff --git a/tools/generator/SourceWriters/BoundMethod.cs b/tools/generator/SourceWriters/BoundMethod.cs index 0525b31e4..89170018f 100644 --- a/tools/generator/SourceWriters/BoundMethod.cs +++ b/tools/generator/SourceWriters/BoundMethod.cs @@ -59,6 +59,8 @@ public BoundMethod (GenBase type, Method method, CodeGenerationOptions opt, bool ReturnType = new TypeReferenceWriter (opt.GetTypeReferenceName (method.RetVal)); + Javadoc.AddJavadocs (Comments, method.Javadoc); + if (method.DeclaringType.IsGeneratable) Comments.Add ($"// Metadata.xml XPath method reference: path=\"{method.GetMetadataXPathReference (method.DeclaringType)}\""); diff --git a/tools/generator/SourceWriters/BoundMethodAbstractDeclaration.cs b/tools/generator/SourceWriters/BoundMethodAbstractDeclaration.cs index 03a060666..a9ccc82bd 100644 --- a/tools/generator/SourceWriters/BoundMethodAbstractDeclaration.cs +++ b/tools/generator/SourceWriters/BoundMethodAbstractDeclaration.cs @@ -41,6 +41,8 @@ public BoundMethodAbstractDeclaration (GenBase gen, Method method, CodeGeneratio method_callback = new MethodCallback (impl, method, opt, null, method.IsReturnCharSequence); + Javadoc.AddJavadocs (Comments, method.Javadoc); + if (method.DeclaringType.IsGeneratable) Comments.Add ($"// Metadata.xml XPath method reference: path=\"{method.GetMetadataXPathReference (method.DeclaringType)}\""); diff --git a/tools/generator/SourceWriters/BoundMethodStringOverload.cs b/tools/generator/SourceWriters/BoundMethodStringOverload.cs index 47dea8e1d..2fa9526ac 100644 --- a/tools/generator/SourceWriters/BoundMethodStringOverload.cs +++ b/tools/generator/SourceWriters/BoundMethodStringOverload.cs @@ -27,6 +27,8 @@ public BoundMethodStringOverload (Method method, CodeGenerationOptions opt) if (method.Deprecated != null) Attributes.Add (new ObsoleteAttr (method.Deprecated.Replace ("\"", "\"\"").Trim ())); + Javadoc.AddJavadocs (Comments, method.Javadoc); + this.AddMethodParametersStringOverloads (method.Parameters, opt); } diff --git a/tools/generator/generator.csproj b/tools/generator/generator.csproj index 88489255d..80bcf2b0f 100644 --- a/tools/generator/generator.csproj +++ b/tools/generator/generator.csproj @@ -30,6 +30,7 @@ + @@ -48,6 +49,7 @@ +