Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 40e29fc

Browse files
committedFeb 2, 2024
Add Javadoc to Asciidoc converter
We add a converter between Javadoc and Asciidoc that converts: * from a [`DocCommentTree`](https://docs.oracle.com/en/java/javase/17/docs/api/jdk.compiler/com/sun/source/doctree/DocCommentTree.html), provided by the `javac` compiler or `javadoc` tool, * to an AsciiDoctorJ [`Document`](https://javadoc.io/static/org.asciidoctor/asciidoctorj/3.0.0-alpha.2/org/asciidoctor/ast/Document.html). We provide a primitive implementation of the AsciiDoctorJ API that converts the AST back into a text document.
1 parent 5eb5e66 commit 40e29fc

File tree

18 files changed

+1936
-0
lines changed

18 files changed

+1936
-0
lines changed
 

‎log4j-docgen/pom.xml

+28
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
<artifactId>log4j-docgen</artifactId>
2828

2929
<properties>
30+
<maven.compiler.release>17</maven.compiler.release>
3031
<bnd.baseline.fail.on.missing>false</bnd.baseline.fail.on.missing>
3132
</properties>
3233

@@ -38,11 +39,38 @@
3839
<scope>provided</scope>
3940
</dependency>
4041

42+
<dependency>
43+
<groupId>org.asciidoctor</groupId>
44+
<artifactId>asciidoctorj-api</artifactId>
45+
<version>3.0.0-alpha.2</version>
46+
</dependency>
47+
48+
<dependency>
49+
<groupId>org.apache.commons</groupId>
50+
<artifactId>commons-lang3</artifactId>
51+
</dependency>
52+
4153
<dependency>
4254
<groupId>org.freemarker</groupId>
4355
<artifactId>freemarker</artifactId>
4456
</dependency>
4557

58+
<dependency>
59+
<groupId>org.apache.logging.log4j</groupId>
60+
<artifactId>log4j-api</artifactId>
61+
</dependency>
62+
63+
<dependency>
64+
<groupId>org.apache.logging.log4j</groupId>
65+
<artifactId>log4j-plugins</artifactId>
66+
</dependency>
67+
68+
<dependency>
69+
<groupId>org.apache.logging.log4j</groupId>
70+
<artifactId>log4j-core</artifactId>
71+
<scope>test</scope>
72+
</dependency>
73+
4674
<dependency>
4775
<groupId>org.junit.jupiter</groupId>
4876
<artifactId>junit-jupiter-api</artifactId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.logging.log4j.docgen.processor;
18+
19+
import static org.apache.commons.lang3.StringUtils.substringBefore;
20+
21+
import com.sun.source.doctree.DocTree;
22+
import com.sun.source.doctree.EndElementTree;
23+
import com.sun.source.doctree.LinkTree;
24+
import com.sun.source.doctree.LiteralTree;
25+
import com.sun.source.doctree.StartElementTree;
26+
import com.sun.source.doctree.TextTree;
27+
import com.sun.source.util.SimpleDocTreeVisitor;
28+
import java.util.ArrayList;
29+
import org.apache.commons.lang3.StringUtils;
30+
import org.apache.logging.log4j.docgen.processor.internal.BlockImpl;
31+
import org.apache.logging.log4j.docgen.processor.internal.CellImpl;
32+
import org.apache.logging.log4j.docgen.processor.internal.ListImpl;
33+
import org.apache.logging.log4j.docgen.processor.internal.ListItemImpl;
34+
import org.apache.logging.log4j.docgen.processor.internal.RowImpl;
35+
import org.apache.logging.log4j.docgen.processor.internal.TableImpl;
36+
import org.asciidoctor.ast.Block;
37+
import org.asciidoctor.ast.Cell;
38+
import org.asciidoctor.ast.Document;
39+
import org.asciidoctor.ast.List;
40+
import org.asciidoctor.ast.ListItem;
41+
import org.asciidoctor.ast.Row;
42+
import org.asciidoctor.ast.Section;
43+
import org.asciidoctor.ast.StructuralNode;
44+
import org.asciidoctor.ast.Table;
45+
46+
class AbstractAsciidocTreeVisitor extends SimpleDocTreeVisitor<Void, AsciidocData> {
47+
48+
private static void appendSentences(final String text, final AsciidocData data) {
49+
final String body = StringUtils.normalizeSpace(text);
50+
final String[] sentences = body.split("(?<=\\w{2}[.!?])", -1);
51+
// Full sentences
52+
for (int i = 0; i < sentences.length - 1; i++) {
53+
data.appendWords(sentences[i].strip());
54+
data.newLine();
55+
}
56+
// Partial sentence
57+
data.appendWords(sentences[sentences.length - 1].strip());
58+
}
59+
60+
@Override
61+
public Void visitStartElement(final StartElementTree node, final AsciidocData data) {
62+
final String elementName = node.getName().toString();
63+
switch (elementName) {
64+
case "p":
65+
data.newParagraph();
66+
break;
67+
case "ol":
68+
// Nested list without a first paragraph
69+
if (data.getCurrentNode() instanceof ListItem) {
70+
data.newParagraph();
71+
}
72+
data.pushChildNode(ListImpl::new).setContext(ListImpl.ORDERED_LIST_CONTEXT);
73+
break;
74+
case "ul":
75+
// Nested list without a first paragraph
76+
if (data.getCurrentNode() instanceof ListItem) {
77+
data.newParagraph();
78+
}
79+
data.pushChildNode(ListImpl::new).setContext(ListImpl.UNORDERED_LIST_CONTEXT);
80+
break;
81+
case "li":
82+
if (!(data.getCurrentNode() instanceof List)) {
83+
throw new IllegalArgumentException("A <li> tag must be a child of a <ol> or <ul> tag.");
84+
}
85+
data.pushChildNode(ListItemImpl::new);
86+
break;
87+
case "h1":
88+
case "h2":
89+
case "h3":
90+
case "h4":
91+
case "h5":
92+
case "h6":
93+
// Flush the current paragraph
94+
data.newParagraph();
95+
StructuralNode currentNode;
96+
// Remove other types of nodes from stack
97+
while ((currentNode = data.getCurrentNode()) != null
98+
&& !(currentNode instanceof Section || currentNode instanceof Document)) {
99+
data.popNode();
100+
}
101+
break;
102+
case "table":
103+
data.pushChildNode(TableImpl::new);
104+
break;
105+
case "tr":
106+
break;
107+
case "th":
108+
data.pushChildNode(CellImpl::new).setContext(CellImpl.HEADER_CONTEXT);
109+
break;
110+
case "td":
111+
data.pushChildNode(CellImpl::new);
112+
break;
113+
case "pre":
114+
data.newParagraph();
115+
final Block currentParagraph = data.getCurrentParagraph();
116+
currentParagraph.setContext(BlockImpl.LISTING_CONTEXT);
117+
currentParagraph.setStyle(BlockImpl.SOURCE_STYLE);
118+
break;
119+
default:
120+
}
121+
return super.visitStartElement(node, data);
122+
}
123+
124+
@Override
125+
public Void visitEndElement(final EndElementTree node, final AsciidocData data) {
126+
final String elementName = node.getName().toString();
127+
switch (elementName) {
128+
case "p":
129+
// Ignore closing tags.
130+
break;
131+
case "ol":
132+
case "ul":
133+
case "li":
134+
case "table":
135+
case "th":
136+
case "td":
137+
data.popNode();
138+
break;
139+
case "h1":
140+
case "h2":
141+
case "h3":
142+
case "h4":
143+
case "h5":
144+
case "h6":
145+
// Only flush the current line
146+
if (!data.getCurrentLine().isEmpty()) {
147+
data.newLine();
148+
}
149+
// The current paragraph contains the title
150+
// We retrieve the text and empty the paragraph
151+
final Block currentParagraph = data.getCurrentParagraph();
152+
final String title = StringUtils.normalizeSpace(currentParagraph.convert());
153+
currentParagraph.setLines(new ArrayList<>());
154+
155+
// There should be no <h1> tags
156+
final int newLevel = "h1".equals(elementName) ? 2 : elementName.charAt(1) - '0';
157+
data.setCurrentSectionLevel(newLevel);
158+
data.getCurrentNode().setTitle(title);
159+
break;
160+
case "pre":
161+
data.newParagraph();
162+
break;
163+
case "tr":
164+
// We group the new cells into a row
165+
final Table table = (Table) data.getCurrentNode();
166+
final java.util.List<StructuralNode> cells = table.getBlocks();
167+
// First index of the row
168+
int idx = 0;
169+
for (final Row row : table.getHeader()) {
170+
idx += row.getCells().size();
171+
}
172+
for (final Row row : table.getBody()) {
173+
idx += row.getCells().size();
174+
}
175+
final Row row = new RowImpl();
176+
String context = CellImpl.BODY_CONTEXT;
177+
for (int i = idx; i < table.getBlocks().size(); i++) {
178+
final StructuralNode cell = cells.get(i);
179+
context = cell.getContext();
180+
if (cell instanceof Cell) {
181+
row.getCells().add((Cell) cell);
182+
}
183+
}
184+
if (CellImpl.HEADER_CONTEXT.equals(context)) {
185+
table.getHeader().add(row);
186+
} else {
187+
table.getBody().add(row);
188+
}
189+
break;
190+
default:
191+
}
192+
return super.visitEndElement(node, data);
193+
}
194+
195+
@Override
196+
public Void visitLink(final LinkTree node, final AsciidocData data) {
197+
final String className = substringBefore(node.getReference().getSignature(), '#');
198+
final String simpleName = StringUtils.substringAfterLast(className, '.');
199+
if (!data.getCurrentLine().isEmpty()) {
200+
data.append(" ");
201+
}
202+
data.append("xref:")
203+
.append(className)
204+
.append(".adoc[")
205+
.append(simpleName)
206+
.append("]");
207+
return super.visitLink(node, data);
208+
}
209+
210+
@Override
211+
public Void visitLiteral(final LiteralTree node, final AsciidocData data) {
212+
if (node.getKind() == DocTree.Kind.CODE) {
213+
if (!data.getCurrentLine().isEmpty()) {
214+
data.append(" ");
215+
}
216+
data.append("`").append(node.getBody().getBody()).append("`");
217+
} else {
218+
node.getBody().accept(this, data);
219+
}
220+
return super.visitLiteral(node, data);
221+
}
222+
223+
@Override
224+
public Void visitText(final TextTree node, final AsciidocData data) {
225+
final Block currentParagraph = data.getCurrentParagraph();
226+
if (BlockImpl.PARAGRAPH_CONTEXT.equals(currentParagraph.getContext())) {
227+
appendSentences(node.getBody(), data);
228+
} else {
229+
data.append(node.getBody());
230+
}
231+
return super.visitText(node, data);
232+
}
233+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.logging.log4j.docgen.processor;
18+
19+
import com.sun.source.doctree.DocCommentTree;
20+
import com.sun.source.doctree.DocTree;
21+
import com.sun.source.doctree.DocTreeVisitor;
22+
import com.sun.source.doctree.ParamTree;
23+
import com.sun.source.util.DocTrees;
24+
import javax.lang.model.element.Element;
25+
26+
/**
27+
* Converts a {@link DocCommentTree} into AsciiDoc text.
28+
*/
29+
class AsciidocConverter {
30+
31+
private static final DocTreeVisitor<Void, AsciidocData> DOC_COMMENT_TREE_VISITOR = new DocCommentTreeVisitor();
32+
private static final DocTreeVisitor<Void, AsciidocData> PARAM_TREE_VISITOR = new ParamTreeVisitor();
33+
34+
private final DocTrees docTrees;
35+
36+
AsciidocConverter(final DocTrees docTrees) {
37+
this.docTrees = docTrees;
38+
}
39+
40+
public String toAsciiDoc(final Element element) {
41+
final DocCommentTree tree = docTrees.getDocCommentTree(element);
42+
return tree != null ? toAsciiDoc(tree) : null;
43+
}
44+
45+
public String toAsciiDoc(final DocCommentTree tree) {
46+
final AsciidocData data = new AsciidocData();
47+
tree.accept(DOC_COMMENT_TREE_VISITOR, data);
48+
return data.getDocument().convert();
49+
}
50+
51+
public String toAsciiDoc(final ParamTree tree) {
52+
final AsciidocData data = new AsciidocData();
53+
tree.accept(PARAM_TREE_VISITOR, data);
54+
return data.getDocument().convert();
55+
}
56+
57+
private static class DocCommentTreeVisitor extends AbstractAsciidocTreeVisitor {
58+
@Override
59+
public Void visitDocComment(final DocCommentTree node, final AsciidocData data) {
60+
// Summary block wrapped in a new paragraph.
61+
for (final DocTree docTree : node.getFirstSentence()) {
62+
docTree.accept(this, data);
63+
}
64+
data.newParagraph();
65+
// Body
66+
for (final DocTree docTree : node.getBody()) {
67+
docTree.accept(this, data);
68+
}
69+
// Flushes the last paragraph
70+
data.newParagraph();
71+
return super.visitDocComment(node, data);
72+
}
73+
}
74+
75+
private static class ParamTreeVisitor extends AbstractAsciidocTreeVisitor {
76+
@Override
77+
public Void visitParam(final ParamTree node, final AsciidocData data) {
78+
for (final DocTree docTree : node.getDescription()) {
79+
docTree.accept(this, data);
80+
}
81+
// Flushes the last paragraph
82+
data.newParagraph();
83+
return super.visitParam(node, data);
84+
}
85+
}
86+
}

0 commit comments

Comments
 (0)
Please sign in to comment.