Skip to content

Commit 7009f99

Browse files
committed
openapi: parse javadoc
- Parse query string from JavaBean - ref #3729
1 parent 134e853 commit 7009f99

File tree

10 files changed

+312
-33
lines changed

10 files changed

+312
-33
lines changed

modules/jooby-openapi/src/main/java/io/jooby/internal/openapi/ParameterExt.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
import com.fasterxml.jackson.annotation.JsonIgnore;
1111

1212
public class ParameterExt extends io.swagger.v3.oas.models.parameters.Parameter {
13+
/* keep track of expanded query bean parameters. */
14+
@JsonIgnore private String containerType;
15+
1316
@JsonIgnore private String javaType;
1417

1518
@JsonIgnore private Object defaultValue;
@@ -24,6 +27,14 @@ public String getJavaType() {
2427
return javaType;
2528
}
2629

30+
public void setContainerType(String containerType) {
31+
this.containerType = containerType;
32+
}
33+
34+
public String getContainerType() {
35+
return containerType;
36+
}
37+
2738
public Object getDefaultValue() {
2839
if (defaultValue != null) {
2940
if (javaType.equals(boolean.class.getName())) {

modules/jooby-openapi/src/main/java/io/jooby/internal/openapi/RouteParser.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ private List<Parameter> checkParameters(ParserContext ctx, List<Parameter> param
189189
String name = (String) ((Map.Entry) e).getKey();
190190
Schema s = (Schema) ((Map.Entry) e).getValue();
191191
ParameterExt p = new ParameterExt();
192+
p.setContainerType(javaType);
192193
p.setName(name);
193194
p.setIn(parameter.getIn());
194195
p.setSchema(s);

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ public class ClassDoc extends JavaDocNode {
2222
private final DetailAST node;
2323
private List<MethodDoc> methods = new ArrayList<>();
2424

25-
public ClassDoc(DetailAST node, DetailAST javaDoc) {
26-
super(javaDoc);
25+
public ClassDoc(JavaDocContext ctx, DetailAST node, DetailAST javaDoc) {
26+
super(ctx, javaDoc);
2727
this.node = node;
2828
}
2929

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
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 com.puppycrawl.tools.checkstyle.JavaParser.parseFile;
9+
import static io.jooby.SneakyThrows.throwingFunction;
10+
11+
import java.nio.file.Files;
12+
import java.nio.file.Path;
13+
import java.util.HashMap;
14+
import java.util.Map;
15+
16+
import com.puppycrawl.tools.checkstyle.JavaParser;
17+
import com.puppycrawl.tools.checkstyle.api.DetailAST;
18+
19+
public class JavaDocContext {
20+
private final Path baseDir;
21+
private final Map<Path, DetailAST> cache = new HashMap<>();
22+
23+
public JavaDocContext(Path baseDir) {
24+
this.baseDir = baseDir;
25+
}
26+
27+
public DetailAST resolve(Path path) {
28+
return cache.computeIfAbsent(
29+
baseDir.resolve(path),
30+
throwingFunction(
31+
filePath -> {
32+
if (Files.exists(filePath)) {
33+
return parseFile(filePath.toFile(), JavaParser.Options.WITH_COMMENTS);
34+
} else {
35+
return NULL;
36+
}
37+
}));
38+
}
39+
40+
public static final DetailAST NULL =
41+
new DetailAST() {
42+
@Override
43+
public int getChildCount() {
44+
return 0;
45+
}
46+
47+
@Override
48+
public int getChildCount(int type) {
49+
return 0;
50+
}
51+
52+
@Override
53+
public DetailAST getParent() {
54+
return null;
55+
}
56+
57+
@Override
58+
public String getText() {
59+
return "";
60+
}
61+
62+
@Override
63+
public int getType() {
64+
return 0;
65+
}
66+
67+
@Override
68+
public int getLineNo() {
69+
return 0;
70+
}
71+
72+
@Override
73+
public int getColumnNo() {
74+
return 0;
75+
}
76+
77+
@Override
78+
public DetailAST getLastChild() {
79+
return null;
80+
}
81+
82+
@Override
83+
public boolean branchContains(int type) {
84+
return false;
85+
}
86+
87+
@Override
88+
public DetailAST getPreviousSibling() {
89+
return null;
90+
}
91+
92+
@Override
93+
public DetailAST findFirstToken(int type) {
94+
return null;
95+
}
96+
97+
@Override
98+
public DetailAST getNextSibling() {
99+
return null;
100+
}
101+
102+
@Override
103+
public DetailAST getFirstChild() {
104+
return null;
105+
}
106+
107+
@Override
108+
public int getNumberOfChildren() {
109+
return 0;
110+
}
111+
112+
@Override
113+
public boolean hasChildren() {
114+
return false;
115+
}
116+
};
117+
}

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,17 @@
1616
import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
1717

1818
public class JavaDocNode {
19+
protected final JavaDocContext context;
1920
protected final DetailNode javadoc;
2021
protected static final Set<Integer> STOP_TOKENS = Set.of(JavadocTokenTypes.JAVADOC_TAG);
2122

22-
public JavaDocNode(DetailAST node) {
23-
this.javadoc = new JavadocDetailNodeParser().parseJavadocAsDetailNode(node).getTree();
23+
public JavaDocNode(JavaDocContext ctx, DetailAST node) {
24+
this.context = ctx;
25+
this.javadoc = toJavaDocNode(node);
26+
}
27+
28+
private static DetailNode toJavaDocNode(DetailAST node) {
29+
return new JavadocDetailNodeParser().parseJavadocAsDetailNode(node).getTree();
2430
}
2531

2632
public String getText() {
@@ -49,6 +55,10 @@ protected String getText(List<DetailNode> nodes, boolean stripLeading) {
4955

5056
@Override
5157
public String toString() {
52-
return DetailNodeTreeStringPrinter.printTree(javadoc, "", "");
58+
return toString(javadoc);
59+
}
60+
61+
protected String toString(DetailNode node) {
62+
return DetailNodeTreeStringPrinter.printTree(node, "", "");
5363
}
5464
}

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

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,39 @@
55
*/
66
package io.jooby.internal.openapi.javadoc;
77

8-
import static io.jooby.internal.openapi.javadoc.JavaDocSupport.backward;
9-
import static io.jooby.internal.openapi.javadoc.JavaDocSupport.tokens;
8+
import static io.jooby.internal.openapi.javadoc.JavaDocSupport.*;
109

11-
import java.io.IOException;
1210
import java.nio.file.Path;
1311
import java.util.Optional;
12+
import java.util.function.Predicate;
1413

15-
import com.puppycrawl.tools.checkstyle.JavaParser;
16-
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
1714
import com.puppycrawl.tools.checkstyle.api.DetailAST;
1815
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
1916

2017
public class JavaDocParser {
2118

22-
public static Optional<ClassDoc> parse(Path filePath) throws CheckstyleException, IOException {
19+
private static final Predicate<DetailAST> HAS_CLASS =
20+
it -> backward(it).anyMatch(tokens(TokenTypes.CLASS_DEF));
21+
22+
private final JavaDocContext context;
23+
24+
public JavaDocParser(JavaDocContext context) {
25+
this.context = context;
26+
}
27+
28+
public Optional<ClassDoc> parse(Path filePath) throws Exception {
2329
ClassDoc result = null;
24-
var tree = JavaParser.parseFile(filePath.toFile(), JavaParser.Options.WITH_COMMENTS);
30+
var tree = context.resolve(filePath);
2531
for (var comment :
26-
JavaDocSupport.forward(tree).filter(tokens(TokenTypes.COMMENT_CONTENT)).toList()) {
32+
forward(tree).filter(tokens(TokenTypes.COMMENT_CONTENT)).filter(HAS_CLASS).toList()) {
2733
var nodePath = path(comment);
2834
// ensure class
2935
if (result == null) {
30-
result = new ClassDoc(nodePath[1], comment.getParent());
36+
result = new ClassDoc(context, nodePath[1], comment.getParent());
3137
}
3238
if (nodePath[nodePath.length - 1] != null) {
3339
// there is a method here
34-
var method = new MethodDoc(nodePath[nodePath.length - 1], comment.getParent());
40+
var method = new MethodDoc(context, nodePath[nodePath.length - 1], comment.getParent());
3541
result.addMethod(method);
3642
}
3743
}
@@ -50,7 +56,7 @@ private static DetailAST[] path(DetailAST comment) {
5056
.findFirst()
5157
.orElseThrow(() -> new IllegalStateException("no type found"));
5258
var packageDef =
53-
JavaDocSupport.forward(classDef.getParent())
59+
forward(classDef.getParent())
5460
.filter(tokens(TokenTypes.PACKAGE_DEF))
5561
.findFirst()
5662
.orElse(null);

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

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,23 @@
77

88
import static io.jooby.internal.openapi.javadoc.JavaDocSupport.*;
99

10+
import java.io.File;
11+
import java.nio.file.Path;
12+
import java.nio.file.Paths;
1013
import java.util.ArrayList;
1114
import java.util.List;
1215
import java.util.stream.Stream;
1316

1417
import com.puppycrawl.tools.checkstyle.api.DetailAST;
18+
import com.puppycrawl.tools.checkstyle.api.DetailNode;
1519
import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
1620
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
1721

1822
public class MethodDoc extends JavaDocNode {
1923
private final DetailAST node;
2024

21-
public MethodDoc(DetailAST node, DetailAST javadoc) {
22-
super(javadoc);
25+
public MethodDoc(JavaDocContext ctx, DetailAST node, DetailAST javadoc) {
26+
super(ctx, javadoc);
2327
this.node = node;
2428
}
2529

@@ -62,6 +66,18 @@ public List<String> getParameterNames() {
6266
}
6367

6468
public String getParameterDoc(String name) {
69+
return getParameterDoc(name, null);
70+
}
71+
72+
public String getParameterDoc(String name, String in) {
73+
DetailNode javadoc = this.javadoc;
74+
if (in != null) {
75+
var tree = context.resolve(toJavaPath(in));
76+
if (tree == JavaDocContext.NULL) {
77+
return null;
78+
}
79+
return getPropertyDoc(tree, name);
80+
}
6581
return tree(javadoc)
6682
// must be a tag
6783
.filter(it -> it.getType() == JavadocTokenTypes.JAVADOC_TAG)
@@ -87,4 +103,72 @@ public String getParameterDoc(String name) {
87103
.filter(it -> !it.isEmpty())
88104
.orElse(null);
89105
}
106+
107+
private Path toJavaPath(String in) {
108+
var segments = in.split("\\.");
109+
segments[segments.length - 1] = segments[segments.length - 1] + ".java";
110+
return Paths.get(String.join(File.separator, segments));
111+
}
112+
113+
private String getPropertyDoc(DetailAST bean, String name) {
114+
var comment = commentFromGetter(bean, name);
115+
if (comment == null) {
116+
comment = commentFromField(bean, name);
117+
}
118+
return comment == null ? null : new JavaDocNode(context, comment).getText();
119+
}
120+
121+
private DetailAST commentFromGetter(DetailAST bean, String name) {
122+
var methods = JavaDocSupport.forward(bean).filter(tokens(TokenTypes.METHOD_DEF)).toList();
123+
var getterName = "get" + Character.toUpperCase(name.charAt(0)) + name.substring(1);
124+
for (var method : methods) {
125+
var noArgs = tree(method).noneMatch(tokens(TokenTypes.PARAMETER_DEF));
126+
var isPublic = tree(method).anyMatch(tokens(TokenTypes.LITERAL_PUBLIC));
127+
var methodName =
128+
children(method)
129+
.filter(tokens(TokenTypes.IDENT))
130+
.map(DetailAST::getText)
131+
.findFirst()
132+
.orElseThrow(() -> new IllegalStateException("Method name not found"));
133+
if (noArgs && isPublic && (methodName.equals(getterName) || methodName.equals(name))) {
134+
var comment = commentFromMember(method);
135+
if (comment != null) {
136+
return comment;
137+
}
138+
}
139+
}
140+
return null;
141+
}
142+
143+
private DetailAST commentFromField(DetailAST bean, String name) {
144+
for (var field :
145+
JavaDocSupport.forward(bean).filter(tokens(TokenTypes.VARIABLE_DEF)).toList()) {
146+
var isInstance = tree(field).noneMatch(tokens(TokenTypes.LITERAL_STATIC));
147+
var fieldName =
148+
children(field)
149+
.filter(tokens(TokenTypes.IDENT))
150+
.map(DetailAST::getText)
151+
.findFirst()
152+
.orElseThrow(() -> new IllegalStateException("Field name not found"));
153+
if (isInstance && fieldName.equals(name)) {
154+
var comment = commentFromMember(field);
155+
if (comment != null) {
156+
return comment;
157+
}
158+
}
159+
}
160+
return null;
161+
}
162+
163+
private static DetailAST commentFromMember(DetailAST member) {
164+
var modifiers = tree(member).filter(tokens(TokenTypes.MODIFIERS)).findFirst().orElse(null);
165+
if (modifiers != null) {
166+
var comment =
167+
tree(modifiers).filter(tokens(TokenTypes.BLOCK_COMMENT_BEGIN)).findFirst().orElse(null);
168+
if (comment != null) {
169+
return comment;
170+
}
171+
}
172+
return null;
173+
}
90174
}

0 commit comments

Comments
 (0)