Skip to content

Commit 0b2d4d2

Browse files
committed
open-api: parse javadoc
- code cleanup -ref #3729
1 parent bfd4634 commit 0b2d4d2

7 files changed

Lines changed: 184 additions & 163 deletions

File tree

modules/jooby-openapi/src/main/java/io/jooby/internal/openapi/javadoc/ClassDoc.java

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -62,28 +62,41 @@ public String getEnumDescription(String text) {
6262
}
6363

6464
private void defaultRecordMembers() {
65-
for (var tag : tree(javadoc).filter(javadocToken(JavadocTokenTypes.JAVADOC_TAG)).toList()) {
66-
var isParam = tree(tag).anyMatch(javadocToken(JavadocTokenTypes.PARAM_LITERAL));
67-
var name =
68-
tree(tag).filter(javadocToken(JavadocTokenTypes.PARAMETER_NAME)).findFirst().orElse(null);
69-
if (isParam && name != null) {
70-
/* Virtual Field */
71-
var memberDoc =
72-
tree(tag)
73-
.filter(javadocToken(JavadocTokenTypes.DESCRIPTION))
74-
.findFirst()
75-
.orElse(EMPTY_NODE);
76-
var field =
77-
new FieldDoc(
78-
context, createVirtualMember(name.getText(), TokenTypes.VARIABLE_DEF), memberDoc);
79-
addField(field);
80-
/* Virtual method */
81-
var method =
82-
new MethodDoc(
83-
context, createVirtualMember(name.getText(), TokenTypes.METHOD_DEF), memberDoc);
84-
addMethod(method);
85-
}
86-
}
65+
JavaDocTag.javaDocTag(
66+
javadoc,
67+
tag -> {
68+
var isParam = tree(tag).anyMatch(javadocToken(JavadocTokenTypes.PARAM_LITERAL));
69+
var name =
70+
tree(tag)
71+
.filter(javadocToken(JavadocTokenTypes.PARAMETER_NAME))
72+
.findFirst()
73+
.orElse(null);
74+
return isParam && name != null;
75+
},
76+
(tag, value) -> {
77+
var name =
78+
tree(tag)
79+
.filter(javadocToken(JavadocTokenTypes.PARAMETER_NAME))
80+
.findFirst()
81+
.orElse(null);
82+
// name is never null bc previous filter
83+
Objects.requireNonNull(name, "name is null");
84+
/* Virtual Field */
85+
var memberDoc =
86+
tree(tag)
87+
.filter(javadocToken(JavadocTokenTypes.DESCRIPTION))
88+
.findFirst()
89+
.orElse(EMPTY_NODE);
90+
var field =
91+
new FieldDoc(
92+
context, createVirtualMember(name.getText(), TokenTypes.VARIABLE_DEF), memberDoc);
93+
addField(field);
94+
/* Virtual method */
95+
var method =
96+
new MethodDoc(
97+
context, createVirtualMember(name.getText(), TokenTypes.METHOD_DEF), memberDoc);
98+
addMethod(method);
99+
});
87100
}
88101

89102
private void defaultEnumMembers() {

modules/jooby-openapi/src/main/java/io/jooby/internal/openapi/javadoc/FieldDoc.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,4 @@ public FieldDoc(JavaDocParser ctx, DetailAST node, DetailAST javadoc) {
2121
public String getName() {
2222
return node.findFirstToken(TokenTypes.IDENT).getText();
2323
}
24-
25-
@Override
26-
public String getText() {
27-
return super.getText();
28-
}
2924
}

modules/jooby-openapi/src/main/java/io/jooby/internal/openapi/javadoc/JavaDocNode.java

Lines changed: 3 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -36,78 +36,8 @@ protected JavaDocNode(JavaDocParser ctx, DetailAST node, DetailNode javadoc) {
3636
this.context = ctx;
3737
this.node = node;
3838
this.javadoc = javadoc;
39-
if (this.javadoc != EMPTY_NODE) {
40-
this.extensions = parseExtensions(this.javadoc);
41-
this.tags = parseTags(this.javadoc);
42-
} else {
43-
this.extensions = Map.of();
44-
this.tags = Map.of();
45-
}
46-
}
47-
48-
private Map<String, String> parseTags(DetailNode node) {
49-
var result = new LinkedHashMap<String, String>();
50-
for (var docTag : tree(node).filter(JAVADOC_TAG).toList()) {
51-
var tag =
52-
tree(docTag)
53-
.filter(
54-
javadocToken(JavadocTokenTypes.CUSTOM_NAME)
55-
.and(it -> it.getText().equals("@tag")))
56-
.findFirst()
57-
.orElse(null);
58-
if (tag != null) {
59-
var tagText =
60-
tree(docTag)
61-
.filter(javadocToken(JavadocTokenTypes.DESCRIPTION))
62-
.findFirst()
63-
.map(it -> getText(List.of(it.getChildren()), false))
64-
.orElse(null);
65-
if (tagText != null) {
66-
var dot = tagText.indexOf(".");
67-
var tagName = tagText;
68-
String tagDescription = null;
69-
if (dot > 0) {
70-
tagName = tagText.substring(0, dot);
71-
if (dot + 1 < tagText.length()) {
72-
tagDescription = tagText.substring(dot + 1).trim();
73-
if (tagDescription.isBlank()) {
74-
tagDescription = null;
75-
}
76-
}
77-
}
78-
if (!tagName.trim().isEmpty()) {
79-
result.put(tagName, tagDescription);
80-
}
81-
}
82-
}
83-
}
84-
return result;
85-
}
86-
87-
private Map<String, Object> parseExtensions(DetailNode node) {
88-
var values = new ArrayList<String>();
89-
for (var tag : tree(node).filter(JAVADOC_TAG).toList()) {
90-
var extension =
91-
tree(tag)
92-
.filter(
93-
javadocToken(JavadocTokenTypes.CUSTOM_NAME)
94-
.and(it -> it.getText().startsWith("@x-")))
95-
.findFirst()
96-
.map(DetailNode::getText)
97-
.orElse(null);
98-
if (extension != null) {
99-
extension = extension.substring(1).trim();
100-
var extensionValue =
101-
tree(tag)
102-
.filter(javadocToken(JavadocTokenTypes.DESCRIPTION))
103-
.findFirst()
104-
.map(it -> getText(List.of(it.getChildren()), false))
105-
.orElse(null);
106-
values.add(extension);
107-
values.add(extensionValue);
108-
}
109-
}
110-
return ExtensionJavaDocParser.parse(values);
39+
this.tags = JavaDocTag.tags(javadoc);
40+
this.extensions = JavaDocTag.extensions(javadoc);
11141
}
11242

11343
static DetailNode toJavaDocNode(DetailAST node) {
@@ -163,7 +93,7 @@ public String getText() {
16393
return getText(JavaDocSupport.forward(javadoc, JAVADOC_TAG).toList(), false);
16494
}
16595

166-
protected String getText(List<DetailNode> nodes, boolean stripLeading) {
96+
protected static String getText(List<DetailNode> nodes, boolean stripLeading) {
16797
var builder = new StringBuilder();
16898
for (var node : nodes) {
16999
if (node.getType() == JavadocTokenTypes.TEXT) {
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/*
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby.internal.openapi.javadoc;
7+
8+
import static io.jooby.internal.openapi.javadoc.JavaDocNode.getText;
9+
import static io.jooby.internal.openapi.javadoc.JavaDocSupport.*;
10+
import static io.jooby.internal.openapi.javadoc.JavaDocSupport.children;
11+
12+
import java.util.*;
13+
import java.util.function.Predicate;
14+
15+
import com.puppycrawl.tools.checkstyle.api.DetailNode;
16+
import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
17+
import io.jooby.SneakyThrows.Consumer2;
18+
import io.jooby.SneakyThrows.Consumer3;
19+
import io.jooby.StatusCode;
20+
21+
public class JavaDocTag {
22+
private static final Predicate<DetailNode> CUSTOM_TAG =
23+
javadocToken(JavadocTokenTypes.CUSTOM_NAME);
24+
private static final Predicate<DetailNode> TAG =
25+
CUSTOM_TAG.and(it -> it.getText().equals("@tag"));
26+
private static final Predicate<DetailNode> EXTENSION =
27+
CUSTOM_TAG.and(it -> it.getText().startsWith("@x-"));
28+
private static final Predicate<DetailNode> THROWS =
29+
it -> tree(it).anyMatch(javadocToken(JavadocTokenTypes.THROWS_LITERAL));
30+
31+
public static Map<StatusCode, ThrowsDoc> throwList(DetailNode node) {
32+
var result = new LinkedHashMap<StatusCode, ThrowsDoc>();
33+
javaDocTag(
34+
node,
35+
THROWS,
36+
(tag, text) -> {
37+
var statusCode =
38+
tree(tag)
39+
.filter(javadocToken(JavadocTokenTypes.DESCRIPTION))
40+
.findFirst()
41+
.flatMap(
42+
it ->
43+
tree(it)
44+
.filter(javadocToken(JavadocTokenTypes.HTML_TAG_NAME))
45+
.filter(tagName -> tagName.getText().equals("code"))
46+
.flatMap(
47+
tagName ->
48+
backward(tagName)
49+
.filter(javadocToken(JavadocTokenTypes.HTML_TAG))
50+
.findFirst()
51+
.stream())
52+
.flatMap(
53+
htmlTag ->
54+
children(htmlTag)
55+
.filter(javadocToken(JavadocTokenTypes.TEXT))
56+
.findFirst()
57+
.stream())
58+
.map(DetailNode::getText)
59+
.map(
60+
value -> {
61+
try {
62+
return Integer.parseInt(value);
63+
} catch (NumberFormatException e) {
64+
return null;
65+
}
66+
})
67+
.filter(Objects::nonNull)
68+
.filter(code -> code >= 400 && code <= 600)
69+
.map(StatusCode::valueOf)
70+
.findFirst())
71+
.orElse(null);
72+
if (statusCode != null) {
73+
var throwsDoc = new ThrowsDoc(statusCode, text);
74+
result.putIfAbsent(statusCode, throwsDoc);
75+
}
76+
});
77+
return result;
78+
}
79+
80+
public static Map<String, Object> extensions(DetailNode node) {
81+
var values = new ArrayList<String>();
82+
javaDocTag(
83+
node,
84+
EXTENSION,
85+
(tag, value) -> {
86+
// Strip '@'
87+
values.add(tag.getText().substring(1));
88+
values.add(value);
89+
});
90+
return ExtensionJavaDocParser.parse(values);
91+
}
92+
93+
public static Map<String, String> tags(DetailNode node) {
94+
var result = new LinkedHashMap<String, String>();
95+
javaDocTag(
96+
node,
97+
TAG,
98+
(tag, value) -> {
99+
var dot = value.indexOf(".");
100+
var tagName = value;
101+
String tagDescription = null;
102+
if (dot > 0) {
103+
tagName = value.substring(0, dot);
104+
if (dot + 1 < value.length()) {
105+
tagDescription = value.substring(dot + 1).trim();
106+
if (tagDescription.isBlank()) {
107+
tagDescription = null;
108+
}
109+
}
110+
}
111+
if (!tagName.trim().isEmpty()) {
112+
result.put(tagName, tagDescription);
113+
}
114+
});
115+
return result;
116+
}
117+
118+
public static void javaDocTag(
119+
DetailNode tree, Predicate<DetailNode> filter, Consumer2<DetailNode, String> consumer) {
120+
javaDocTag(tree, filter, (tag, value, text) -> consumer.accept(tag, text));
121+
}
122+
123+
public static void javaDocTag(
124+
DetailNode tree,
125+
Predicate<DetailNode> filter,
126+
Consumer3<DetailNode, DetailNode, String> consumer) {
127+
if (tree != JavaDocNode.EMPTY_NODE) {
128+
for (var tag : tree(tree).filter(javadocToken(JavadocTokenTypes.JAVADOC_TAG)).toList()) {
129+
var tagName = tree(tag).filter(filter).findFirst().orElse(null);
130+
if (tagName != null) {
131+
var tagValue =
132+
tree(tag)
133+
.filter(javadocToken(JavadocTokenTypes.DESCRIPTION))
134+
.findFirst()
135+
.orElse(null);
136+
var tagText = tagValue == null ? null : getText(List.of(tagValue.getChildren()), true);
137+
consumer.accept(tagName, tagValue, tagText);
138+
}
139+
}
140+
}
141+
}
142+
}

modules/jooby-openapi/src/main/java/io/jooby/internal/openapi/javadoc/MethodDoc.java

Lines changed: 2 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -22,66 +22,7 @@ public class MethodDoc extends JavaDocNode {
2222

2323
public MethodDoc(JavaDocParser ctx, DetailAST node, DetailAST javadoc) {
2424
super(ctx, node, javadoc);
25-
throwList = throwList(this.javadoc);
26-
}
27-
28-
private Map<StatusCode, ThrowsDoc> throwList(DetailNode javadoc) {
29-
var result = new LinkedHashMap<StatusCode, ThrowsDoc>();
30-
for (var tag : tree(javadoc).filter(javadocToken(JavadocTokenTypes.JAVADOC_TAG)).toList()) {
31-
var isThrows = tree(tag).anyMatch(javadocToken(JavadocTokenTypes.THROWS_LITERAL));
32-
if (isThrows) {
33-
var text =
34-
tree(tag)
35-
.filter(javadocToken(JavadocTokenTypes.DESCRIPTION))
36-
.findFirst()
37-
.map(it -> getText(List.of(it.getChildren()), true))
38-
.orElse(null);
39-
var statusCode =
40-
tree(tag)
41-
.filter(javadocToken(JavadocTokenTypes.DESCRIPTION))
42-
.findFirst()
43-
.flatMap(
44-
it ->
45-
tree(it)
46-
.filter(javadocToken(JavadocTokenTypes.HTML_TAG_NAME))
47-
.filter(tagName -> tagName.getText().equals("code"))
48-
.flatMap(
49-
tagName ->
50-
backward(tagName)
51-
.filter(javadocToken(JavadocTokenTypes.HTML_TAG))
52-
.findFirst()
53-
.stream())
54-
.flatMap(
55-
htmlTag ->
56-
children(htmlTag)
57-
.filter(javadocToken(JavadocTokenTypes.TEXT))
58-
.findFirst()
59-
.stream())
60-
.map(DetailNode::getText)
61-
.map(
62-
value -> {
63-
try {
64-
return Integer.parseInt(value);
65-
} catch (NumberFormatException e) {
66-
return null;
67-
}
68-
})
69-
.filter(Objects::nonNull)
70-
.filter(code -> code >= 400 && code <= 600)
71-
.map(StatusCode::valueOf)
72-
.findFirst())
73-
.orElse(null);
74-
// var className = tree(tag).filter(javadocToken(JavadocTokenTypes.CLASS_NAME))
75-
// .findFirst()
76-
// .map(DetailNode::getText)
77-
// .orElse(null);
78-
if (statusCode != null) {
79-
var throwsDoc = new ThrowsDoc(statusCode, text);
80-
result.putIfAbsent(statusCode, throwsDoc);
81-
}
82-
}
83-
}
84-
return result;
25+
throwList = JavaDocTag.throwList(this.javadoc);
8526
}
8627

8728
MethodDoc(JavaDocParser ctx, DetailAST node, DetailNode javadoc) {
@@ -118,7 +59,7 @@ public String getParameterDoc(String name, String in) {
11859
}
11960
return tree(javadoc)
12061
// must be a tag
121-
.filter(it -> it.getType() == JavadocTokenTypes.JAVADOC_TAG)
62+
.filter(javadocToken(JavadocTokenTypes.JAVADOC_TAG))
12263
.filter(
12364
it -> {
12465
var children = children(it).toList();

modules/jooby-openapi/src/test/java/javadoc/JavaDocParserTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public void apiDoc() throws Exception {
4545
assertEquals("This is the Hello /endpoint", method.getSummary());
4646
assertEquals("Operation description", method.getDescription());
4747
assertEquals("Person name.", method.getParameterDoc("name"));
48-
assertEquals("Person age.", method.getParameterDoc("age"));
48+
assertEquals("Person age. Multi line doc.", method.getParameterDoc("age"));
4949
assertEquals("This line has a break.", method.getParameterDoc("list"));
5050
assertEquals("Some string.", method.getParameterDoc("str"));
5151
assertEquals("Welcome message 200.", method.getReturnDoc());

0 commit comments

Comments
 (0)