Skip to content

Commit 2b1fd8a

Browse files
committed
open-api: inheritance support
1 parent e39a80d commit 2b1fd8a

File tree

5 files changed

+155
-32
lines changed

5 files changed

+155
-32
lines changed

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

Lines changed: 58 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.puppycrawl.tools.checkstyle.JavaParser;
2525
import com.puppycrawl.tools.checkstyle.api.DetailAST;
2626
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
27+
import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
2728
import com.puppycrawl.tools.checkstyle.utils.XpathUtil;
2829
import io.jooby.Context;
2930
import io.jooby.Router;
@@ -46,26 +47,31 @@ public Optional<ClassDoc> parse(String typeName) {
4647
return ofNullable(traverse(resolveType(typeName)).get(typeName));
4748
}
4849

49-
public Map<String, ClassDoc> traverse(DetailAST tree) {
50+
private Map<String, ClassDoc> traverse(DetailAST tree) {
5051
var classes = new HashMap<String, ClassDoc>();
5152
traverse(
52-
tree,
53+
List.of(tree),
5354
TYPES,
5455
modifiers -> tree(modifiers).noneMatch(tokens(TokenTypes.LITERAL_PRIVATE)),
5556
(scope, comment) -> {
57+
var scopes = typeHierarchy(scope);
5658
var counter = new AtomicInteger(0);
5759
counter.addAndGet(comment == JavaDocNode.EMPTY_AST ? 0 : 1);
5860
var classDoc = new ClassDoc(this, scope, comment);
5961

6062
// MVC routes
6163
traverse(
62-
scope,
64+
scopes,
6365
tokens(TokenTypes.VARIABLE_DEF, TokenTypes.METHOD_DEF),
6466
modifiers -> tree(modifiers).noneMatch(tokens(TokenTypes.LITERAL_STATIC)),
6567
(member, memberComment) -> {
6668
counter.addAndGet(memberComment == JavaDocNode.EMPTY_AST ? 0 : 1);
6769
// check member belong to current scope
68-
if (scope == backward(member).filter(TYPES).findFirst().orElse(null)) {
70+
if (backward(member)
71+
.filter(TYPES)
72+
.findFirst()
73+
.filter(scopes::contains)
74+
.isPresent()) {
6975
if (member.getType() == TokenTypes.VARIABLE_DEF) {
7076
classDoc.addField(new FieldDoc(this, member, memberComment));
7177
} else {
@@ -83,6 +89,53 @@ public Map<String, ClassDoc> traverse(DetailAST tree) {
8389
return classes;
8490
}
8591

92+
private void traverse(
93+
List<DetailAST> scopes,
94+
Predicate<DetailAST> types,
95+
Predicate<DetailAST> modifiers,
96+
BiConsumer<DetailAST, DetailAST> action) {
97+
98+
for (var scope : scopes) {
99+
for (var node : tree(scope).filter(types).toList()) {
100+
var mods =
101+
tree(node)
102+
.filter(tokens(TokenTypes.MODIFIERS))
103+
.findFirst()
104+
.orElseThrow(
105+
() ->
106+
new IllegalArgumentException(
107+
"Modifiers not found on " + TokenUtil.getTokenName(node.getType())));
108+
if (modifiers.test(mods)) {
109+
var docRoot = node.getType() == TokenTypes.VARIABLE_DEF ? mods.getParent() : mods;
110+
var comment =
111+
tree(docRoot)
112+
.filter(tokens(TokenTypes.BLOCK_COMMENT_BEGIN))
113+
.findFirst()
114+
.orElse(JavaDocNode.EMPTY_AST);
115+
action.accept(node, comment);
116+
}
117+
}
118+
}
119+
}
120+
121+
private List<DetailAST> typeHierarchy(DetailAST scope) {
122+
var result = new ArrayList<DetailAST>();
123+
// call parent members first, so we can inherit and override later.
124+
children(scope)
125+
.filter(tokens(TokenTypes.EXTENDS_CLAUSE))
126+
.findFirst()
127+
.map(it -> JavaDocSupport.toQualifiedName(scope, getQualifiedName(it)))
128+
.flatMap(
129+
superClass ->
130+
tree(resolveType(superClass))
131+
.filter(TYPES)
132+
.filter(it -> superClass.endsWith("." + getSimpleName(it)))
133+
.findFirst())
134+
.ifPresent(parent -> result.addAll(typeHierarchy(parent)));
135+
result.add(scope);
136+
return result;
137+
}
138+
86139
private int scripts(
87140
DetailAST scope, ClassDoc classDoc, PathDoc pathDoc, String prefix, Set<DetailAST> visited) {
88141
var counter = new AtomicInteger(0);
@@ -96,7 +149,7 @@ private int scripts(
96149
.map(DetailAST::getText)
97150
.stream()
98151
.findFirst()
99-
.orElseThrow(() -> new IllegalStateException("No method call found: " + script));
152+
.orElseThrow(() -> new IllegalArgumentException("No method call found: " + script));
100153
var scriptComment =
101154
children(script)
102155
.filter(tokens(TokenTypes.BLOCK_COMMENT_BEGIN))
@@ -254,29 +307,6 @@ private String computePath(String prefix, String pattern) {
254307
return Router.noTrailingSlash(Router.normalizePath(prefix + pattern));
255308
}
256309

257-
private void traverse(
258-
DetailAST tree,
259-
Predicate<DetailAST> types,
260-
Predicate<DetailAST> modifiers,
261-
BiConsumer<DetailAST, DetailAST> action) {
262-
for (var node : tree(tree).filter(types).toList()) {
263-
var mods =
264-
tree(node)
265-
.filter(tokens(TokenTypes.MODIFIERS))
266-
.findFirst()
267-
.orElseThrow(() -> new IllegalStateException("Modifiers not found on " + node));
268-
if (modifiers.test(mods)) {
269-
var docRoot = node.getType() == TokenTypes.VARIABLE_DEF ? mods.getParent() : mods;
270-
var comment =
271-
tree(docRoot)
272-
.filter(tokens(TokenTypes.BLOCK_COMMENT_BEGIN))
273-
.findFirst()
274-
.orElse(JavaDocNode.EMPTY_AST);
275-
action.accept(node, comment);
276-
}
277-
}
278-
}
279-
280310
public DetailAST resolve(Path path) {
281311
return lookup(path)
282312
.map(

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

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,38 @@
2121

2222
public class JavaDocParserTest {
2323

24+
@Test
25+
public void inheritance() throws Exception {
26+
withDoc(
27+
javadoc.input.Subclass.class,
28+
doc -> {
29+
assertEquals("Subclass summary.", doc.getSummary());
30+
assertEquals("Subclass description.", doc.getDescription());
31+
32+
assertEquals("Number on subclass.", doc.getPropertyDoc("number"));
33+
assertEquals("Name on subclass.", doc.getPropertyDoc("name"));
34+
assertEquals("Desc on base class.", doc.getPropertyDoc("description"));
35+
});
36+
37+
withDoc(
38+
javadoc.input.sub.Base.class,
39+
doc -> {
40+
assertEquals("Base summary.", doc.getSummary());
41+
assertEquals("Base description.", doc.getDescription());
42+
43+
assertNull(doc.getPropertyDoc("number"));
44+
assertEquals("Name on base class.", doc.getPropertyDoc("name"));
45+
assertEquals("Desc on base class.", doc.getPropertyDoc("description"));
46+
});
47+
}
48+
2449
@Test
2550
public void multiline() throws Exception {
2651
withDoc(
2752
javadoc.input.MultilineComment.class,
2853
doc -> {
29-
// assertNull(doc.getSummary());
30-
// assertNull(doc.getDescription());
54+
assertNull(doc.getSummary());
55+
assertNull(doc.getDescription());
3156

3257
withScript(
3358
doc,

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import java.io.IOException;
1010
import java.nio.file.Path;
1111
import java.nio.file.Paths;
12-
import javadoc.input.RecordBeanDoc;
12+
import javadoc.input.Subclass;
1313

1414
import com.puppycrawl.tools.checkstyle.AstTreeStringPrinter;
1515
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
@@ -25,7 +25,7 @@ public static void main(String[] args) throws CheckstyleException, IOException {
2525
.resolve("java");
2626
var stringAst =
2727
AstTreeStringPrinter.printJavaAndJavadocTree(
28-
baseDir.resolve(toPath(RecordBeanDoc.class)).toFile());
28+
baseDir.resolve(toPath(Subclass.class)).toFile());
2929
System.out.println(stringAst);
3030
}
3131

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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 javadoc.input;
7+
8+
import javadoc.input.sub.Base;
9+
10+
/**
11+
* Subclass summary.
12+
*
13+
* <p>Subclass description.
14+
*/
15+
public class Subclass extends Base {
16+
private int number;
17+
18+
/**
19+
* Number on subclass.
20+
*
21+
* @return Number on subclass.
22+
*/
23+
public int getNumber() {
24+
return number;
25+
}
26+
27+
/**
28+
* Name on subclass.
29+
*
30+
* @return Name on subclass.
31+
*/
32+
@Override
33+
public String getName() {
34+
return super.getName();
35+
}
36+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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 javadoc.input.sub;
7+
8+
/**
9+
* Base summary.
10+
*
11+
* <p>Base description.
12+
*/
13+
public class Base {
14+
15+
private String name;
16+
17+
/** Desc on base class. */
18+
private String description;
19+
20+
/**
21+
* Name on base class.
22+
*
23+
* @return On base class.
24+
*/
25+
public String getName() {
26+
return name;
27+
}
28+
29+
public String getDescription() {
30+
return description;
31+
}
32+
}

0 commit comments

Comments
 (0)