99import static io .jooby .SneakyThrows .throwingFunction ;
1010import static io .jooby .internal .openapi .javadoc .JavaDocSupport .*;
1111import static io .jooby .internal .openapi .javadoc .JavaDocSupport .tokens ;
12+ import static java .util .Optional .ofNullable ;
1213
1314import java .io .File ;
1415import java .nio .file .Files ;
1819import java .util .concurrent .atomic .AtomicInteger ;
1920import java .util .function .BiConsumer ;
2021import java .util .function .Predicate ;
22+ import java .util .stream .Collectors ;
2123
2224import com .puppycrawl .tools .checkstyle .JavaParser ;
2325import com .puppycrawl .tools .checkstyle .api .DetailAST ;
2628import io .jooby .Router ;
2729
2830public class JavaDocParser {
31+ private record ScriptRef (String operationId , DetailAST comment ) {}
2932
3033 private final List <Path > baseDir ;
3134 private final Map <Path , DetailAST > cache = new HashMap <>();
@@ -39,7 +42,7 @@ public JavaDocParser(List<Path> baseDir) {
3942 }
4043
4144 public Optional <ClassDoc > parse (String typeName ) {
42- return Optional . ofNullable (traverse (resolveType (typeName )).get (typeName ));
45+ return ofNullable (traverse (resolveType (typeName )).get (typeName ));
4346 }
4447
4548 public Map <String , ClassDoc > traverse (DetailAST tree ) {
@@ -76,7 +79,7 @@ public Map<String, ClassDoc> traverse(DetailAST tree) {
7679 }
7780 });
7881 // Script routes
79- scripts (scope , null , null , new HashSet <>(), classDoc );
82+ scripts (scope , classDoc , null , null , new HashSet <>());
8083
8184 if (counter .get () > 0 ) {
8285 classes .put (classDoc .getName (), classDoc );
@@ -86,7 +89,7 @@ public Map<String, ClassDoc> traverse(DetailAST tree) {
8689 }
8790
8891 private void scripts (
89- DetailAST scope , PathDoc pathDoc , String prefix , Set <DetailAST > visited , ClassDoc classDoc ) {
92+ DetailAST scope , ClassDoc classDoc , PathDoc pathDoc , String prefix , Set <DetailAST > visited ) {
9093 for (var script : tree (scope ).filter (tokens (TokenTypes .METHOD_CALL )).toList ()) {
9194 if (visited .add (script )) {
9295 // Test for HTTP method name
@@ -107,13 +110,17 @@ private void scripts(
107110 pathLiteral (script )
108111 .ifPresent (
109112 pattern -> {
113+ var resolvedComment = resolveScriptComment (classDoc , script , scriptComment );
110114 var scriptDoc =
111115 new ScriptDoc (
112116 this ,
113117 callName .toUpperCase (),
114118 computePath (prefix , pattern ),
115119 script ,
116- scriptComment );
120+ resolvedComment .comment );
121+ if (resolvedComment .operationId () != null ) {
122+ scriptDoc .setOperationId (resolvedComment .operationId ());
123+ }
117124 scriptDoc .setPath (pathDoc );
118125 classDoc .addScript (scriptDoc );
119126 });
@@ -123,16 +130,141 @@ private void scripts(
123130 path -> {
124131 scripts (
125132 script ,
133+ classDoc ,
126134 new PathDoc (this , script , scriptComment ),
127135 computePath (prefix , path ),
128- visited ,
129- classDoc );
136+ visited );
130137 });
131138 }
132139 }
133140 }
134141 }
135142
143+ /**
144+ * get("/reference", this::findPetById); post("/static-reference",
145+ * javadoc.input.LambdaRefApp::staticFindPetById); put("/external-reference",
146+ * RequestHandler::external); get("/external-subPackage-reference",
147+ * SubPackageHandler::subPackage);
148+ *
149+ * @param classDoc
150+ * @param script
151+ * @param defaultComment
152+ * @return
153+ */
154+ private ScriptRef resolveScriptComment (
155+ ClassDoc classDoc , DetailAST script , DetailAST defaultComment ) {
156+ // ELIST -> LAMBDA (children)
157+ // ELIST -> EXPR -> METHOD_REF (tree)
158+ return children (script )
159+ .filter (tokens (TokenTypes .ELIST ))
160+ .findFirst ()
161+ .map (
162+ statementList ->
163+ children (statementList )
164+ .filter (tokens (TokenTypes .LAMBDA ))
165+ .findFirst ()
166+ .map (lambda -> new ScriptRef (null , defaultComment ))
167+ .orElseGet (
168+ () ->
169+ tree (statementList )
170+ .filter (tokens (TokenTypes .METHOD_REF ))
171+ .findFirst ()
172+ .flatMap (
173+ ref -> ofNullable (resolveFromMethodRef (classDoc , script , ref )))
174+ .orElseGet (() -> new ScriptRef (null , defaultComment ))))
175+ .orElseGet (() -> new ScriptRef (null , defaultComment ));
176+ }
177+
178+ private ScriptRef resolveFromMethodRef (ClassDoc classDoc , DetailAST script , DetailAST methodRef ) {
179+ var referenceOwner = getTypeName (methodRef );
180+ DetailAST scope = null ;
181+ String className ;
182+ if (referenceOwner .equals ("this" )) {
183+ scope = classDoc .getNode ();
184+ className = classDoc .getName ();
185+ } else {
186+ // resolve className
187+ className = toQualifiedName (classDoc , referenceOwner );
188+ scope = resolveType (className );
189+ if (scope == JavaDocNode .EMPTY_AST ) {
190+ // not found
191+ return null ;
192+ }
193+ }
194+ var methodName =
195+ children (methodRef ).filter (tokens (TokenTypes .IDENT )).toList ().getLast ().getText ();
196+ var method =
197+ tree (scope )
198+ .filter (tokens (TokenTypes .METHOD_DEF ))
199+ .filter (
200+ it ->
201+ children (it )
202+ .filter (tokens (TokenTypes .IDENT ))
203+ .findFirst ()
204+ .filter (e -> e .getText ().equals (methodName ))
205+ .isPresent ())
206+ // One Argument
207+ .filter (it -> tree (it ).filter (tokens (TokenTypes .PARAMETER_DEF )).count () == 1 )
208+ // Context Type
209+ .filter (
210+ it ->
211+ tree (it )
212+ .filter (tokens (TokenTypes .PARAMETER_DEF ))
213+ .findFirst ()
214+ .flatMap (p -> children (p ).filter (tokens (TokenTypes .TYPE )).findFirst ())
215+ .filter (type -> getTypeName (type ).equals ("Context" ))
216+ .isPresent ())
217+ .findFirst ()
218+ .orElseThrow (
219+ () ->
220+ new IllegalArgumentException (
221+ "No method found: " + className + "." + methodName ));
222+ return children (method )
223+ .filter (tokens (TokenTypes .MODIFIERS ))
224+ .findFirst ()
225+ .flatMap (it -> children (it ).filter (tokens (TokenTypes .BLOCK_COMMENT_BEGIN )).findFirst ())
226+ .map (comment -> new ScriptRef (methodName , comment ))
227+ .orElseGet (() -> new ScriptRef (null , JavaDocNode .EMPTY_AST ));
228+ }
229+
230+ private static String getTypeName (DetailAST methodRef ) {
231+ var referenceOwner =
232+ tree (methodRef .getFirstChild ())
233+ .filter (tokens (TokenTypes .DOT ).negate ())
234+ .map (DetailAST ::getText )
235+ .collect (Collectors .joining ("." ));
236+ return referenceOwner ;
237+ }
238+
239+ private static String toQualifiedName (ClassDoc classDoc , String referenceOwner ) {
240+ var className = referenceOwner ;
241+ if (!className .contains ("." )) {
242+ if (!classDoc .getSimpleName ().equals (className )) {
243+ var cu =
244+ backward (classDoc .getNode ())
245+ .filter (tokens (TokenTypes .COMPILATION_UNIT ))
246+ .findFirst ()
247+ .orElseThrow (
248+ () ->
249+ new IllegalArgumentException (
250+ "No compilation unit found: " + referenceOwner ));
251+ className =
252+ children (cu )
253+ .filter (tokens (TokenTypes .IMPORT ))
254+ .map (
255+ it ->
256+ tree (it .getFirstChild ())
257+ .filter (tokens (TokenTypes .DOT ).negate ())
258+ .map (DetailAST ::getText )
259+ .collect (Collectors .joining ("." )))
260+ .filter (qualifiedName -> qualifiedName .endsWith ("." + referenceOwner ))
261+ .findFirst ()
262+ .orElseGet (() -> String .join ("." , classDoc .getPackage (), referenceOwner ));
263+ }
264+ }
265+ return className ;
266+ }
267+
136268 /**
137269 * ELIST -> EXPR -> STRING_LITERAL
138270 *
0 commit comments