Skip to content

Commit 3a103ca

Browse files
authored
Merge pull request #1057 from dddjava/agent/javaparser-nested
ネスト型の用語を扱えるようにする
2 parents abaeb9b + 34c382b commit 3a103ca

File tree

5 files changed

+165
-38
lines changed

5 files changed

+165
-38
lines changed
Lines changed: 27 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,20 @@
11
package org.dddjava.jig.infrastructure.javaparser;
22

33
import com.github.javaparser.ast.ImportDeclaration;
4-
import com.github.javaparser.ast.Node;
54
import com.github.javaparser.ast.PackageDeclaration;
6-
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
7-
import com.github.javaparser.ast.body.EnumDeclaration;
8-
import com.github.javaparser.ast.body.RecordDeclaration;
9-
import com.github.javaparser.ast.nodeTypes.NodeWithJavadoc;
10-
import com.github.javaparser.ast.nodeTypes.NodeWithMembers;
11-
import com.github.javaparser.ast.nodeTypes.NodeWithSimpleName;
5+
import com.github.javaparser.ast.body.*;
126
import com.github.javaparser.ast.stmt.LocalClassDeclarationStmt;
137
import com.github.javaparser.ast.stmt.LocalRecordDeclarationStmt;
148
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
159
import org.dddjava.jig.application.GlossaryRepository;
1610
import org.dddjava.jig.domain.model.data.enums.EnumModel;
1711
import org.dddjava.jig.domain.model.data.types.TypeId;
1812
import org.dddjava.jig.domain.model.sources.javasources.JavaSourceModel;
19-
import org.jspecify.annotations.Nullable;
2013
import org.slf4j.Logger;
2114
import org.slf4j.LoggerFactory;
2215

16+
import java.util.ArrayList;
2317
import java.util.List;
24-
import java.util.Optional;
2518

2619
/**
2720
* クラスからの情報の読み取り
@@ -32,10 +25,7 @@ class JavaparserClassVisitor extends VoidVisitorAdapter<GlossaryRepository> {
3225
private static final Logger logger = LoggerFactory.getLogger(JavaparserClassVisitor.class);
3326

3427
private final String packageName;
35-
@Nullable
36-
private TypeId typeId;
37-
38-
private Optional<EnumModel> enumModel = Optional.empty();
28+
private final List<EnumModel> enumModels = new ArrayList<>();
3929

4030
public JavaparserClassVisitor(String packageName) {
4131
this.packageName = packageName;
@@ -55,24 +45,27 @@ public void visit(ImportDeclaration importDeclaration, GlossaryRepository arg) {
5545

5646
@Override
5747
public void visit(ClassOrInterfaceDeclaration node, GlossaryRepository arg) {
58-
visitClassOrInterfaceOrEnumOrRecord(node, arg);
48+
visitTypeDeclaration(node, arg);
5949
}
6050

6151
@Override
6252
public void visit(EnumDeclaration enumDeclaration, GlossaryRepository arg) {
63-
TypeId typeId = visitClassOrInterfaceOrEnumOrRecord(enumDeclaration, arg);
53+
TypeId typeId = visitTypeDeclaration(enumDeclaration, arg);
6454

6555
// enum 固有の読み取りを行う
6656
var visitor = new JavaparserEnumVisitor(typeId);
6757
enumDeclaration.accept(visitor, arg);
68-
enumModel = Optional.of(visitor.createEnumModel());
69-
70-
super.visit(enumDeclaration, arg);
58+
enumModels.add(visitor.createEnumModel());
7159
}
7260

7361
@Override
7462
public void visit(RecordDeclaration recordDeclaration, GlossaryRepository arg) {
75-
visitClassOrInterfaceOrEnumOrRecord(recordDeclaration, arg);
63+
visitTypeDeclaration(recordDeclaration, arg);
64+
}
65+
66+
@Override
67+
public void visit(AnnotationDeclaration annotationDeclaration, GlossaryRepository arg) {
68+
visitTypeDeclaration(annotationDeclaration, arg);
7669
}
7770

7871
@Override
@@ -88,37 +81,34 @@ public void visit(LocalClassDeclarationStmt localClassDeclarationStmt, GlossaryR
8881
}
8982

9083
/**
91-
* class/interface/enum/record の共通処理
84+
* 型定義の共通処理
9285
*/
93-
private <T extends Node & NodeWithSimpleName<?> & NodeWithJavadoc<?> & NodeWithMembers<?>> TypeId visitClassOrInterfaceOrEnumOrRecord(T node, GlossaryRepository glossaryRepository) {
94-
var fqn = packageName + node.getNameAsString();
95-
96-
if (typeId != null) {
97-
logger.warn("1つの *.java ファイルの2つ目以降の class/interface/enum/record には対応していません。{} のロードはスキップされます。対応が必要な場合は読ませたい構造のサンプルを添えてIssueを作成してください。",
98-
fqn
99-
);
100-
return typeId;
101-
}
102-
103-
typeId = TypeId.valueOf(fqn);
86+
private TypeId visitTypeDeclaration(TypeDeclaration<?> node, GlossaryRepository glossaryRepository) {
87+
var typeId = TypeId.valueOf(resolveFqn(node));
10488
// クラスのJavadocが記述されていれば採用
10589
node.getJavadoc().ifPresent(javadoc -> {
10690
String javadocText = javadoc.getDescription().toText();
10791
glossaryRepository.register(TermFactory.fromClass(glossaryRepository.fromTypeId(typeId), javadocText));
10892
});
109-
// メンバの情報を別のVisitorで読む
110-
node.accept(new JavaparserMemberVisitor(typeId), glossaryRepository);
111-
93+
// メンバの情報を別のVisitorで読む(型は再帰先で処理する)
94+
var memberVisitor = new JavaparserMemberVisitor(typeId);
11295
node.getMembers().forEach(member -> {
113-
if (member instanceof ClassOrInterfaceDeclaration classOrInterfaceDeclaration) {
114-
logger.debug("nested class or interface: {}", classOrInterfaceDeclaration.getFullyQualifiedName());
96+
if (member instanceof TypeDeclaration<?> typeDeclaration) {
97+
typeDeclaration.accept(this, glossaryRepository);
98+
} else {
99+
member.accept(memberVisitor, glossaryRepository);
115100
}
116101
});
117102

118103
return typeId;
119104
}
120105

106+
private String resolveFqn(TypeDeclaration<?> node) {
107+
return node.getFullyQualifiedName()
108+
.orElse(packageName + node.getNameAsString());
109+
}
110+
121111
public JavaSourceModel javaSourceModel() {
122-
return JavaSourceModel.from(enumModel.map(List::of).orElseGet(List::of));
112+
return JavaSourceModel.from(enumModels);
123113
}
124114
}

jig-core/src/test/java/org/dddjava/jig/infrastructure/javaparser/JavaparserReaderTest.java

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
import org.dddjava.jig.domain.model.data.packages.PackageId;
77
import org.dddjava.jig.domain.model.data.terms.Term;
88
import org.dddjava.jig.domain.model.data.terms.TermKind;
9+
import org.dddjava.jig.domain.model.data.types.TypeId;
910
import org.dddjava.jig.infrastructure.javaparser.ut.ParseTargetCanonicalClass;
11+
import org.dddjava.jig.infrastructure.javaparser.ut.ParseTargetMultipleTopLevelClass;
12+
import org.dddjava.jig.infrastructure.javaparser.ut.ParseTargetNestedClass;
1013
import org.dddjava.jig.infrastructure.onmemoryrepository.OnMemoryGlossaryRepository;
1114
import org.junit.jupiter.api.BeforeEach;
1215
import org.junit.jupiter.api.Test;
@@ -112,6 +115,76 @@ void setUp() {
112115
assertEquals("フィールドコメント", term.title());
113116
}
114117

118+
@Test
119+
void ネストしたクラスとメソッドを読み取れる() {
120+
Path path = Path.of("ut", "ParseTargetNestedClass.java");
121+
GlossaryRepository glossaryRepository = new OnMemoryGlossaryRepository();
122+
123+
sut.parseJavaFile(getJavaFilePath(path), glossaryRepository);
124+
125+
var glossary = glossaryRepository.all();
126+
var outerTerm = glossary.termOf(
127+
TestSupport.getTypeIdFromClass(ParseTargetNestedClass.class).value(),
128+
TermKind.クラス
129+
);
130+
var innerTypeId = TypeId.valueOf("org.dddjava.jig.infrastructure.javaparser.ut.ParseTargetNestedClass.Inner");
131+
var innerTerm = glossary.termOf(innerTypeId.value(), TermKind.クラス);
132+
var innerMethodTerm = glossary.termOf(
133+
JigMethodId.from(innerTypeId, "innerMethod", List.of()).value(),
134+
TermKind.メソッド
135+
);
136+
var innerEnumTypeId = TypeId.valueOf("org.dddjava.jig.infrastructure.javaparser.ut.ParseTargetNestedClass.InnerEnum");
137+
var innerEnumTerm = glossary.termOf(innerEnumTypeId.value(), TermKind.クラス);
138+
var innerRecordTypeId = TypeId.valueOf("org.dddjava.jig.infrastructure.javaparser.ut.ParseTargetNestedClass.InnerRecord");
139+
var innerRecordTerm = glossary.termOf(innerRecordTypeId.value(), TermKind.クラス);
140+
var innerRecordMethodTerm = glossary.termOf(
141+
JigMethodId.from(innerRecordTypeId, "label", List.of()).value(),
142+
TermKind.メソッド
143+
);
144+
var innerAnnotationTypeId = TypeId.valueOf("org.dddjava.jig.infrastructure.javaparser.ut.ParseTargetNestedClass.InnerAnnotation");
145+
var innerAnnotationTerm = glossary.termOf(innerAnnotationTypeId.value(), TermKind.クラス);
146+
147+
assertEquals("外側クラスコメント", outerTerm.title());
148+
assertEquals("内側クラスコメント", innerTerm.title());
149+
assertEquals("内側メソッドコメント", innerMethodTerm.title());
150+
assertEquals("内側enumコメント", innerEnumTerm.title());
151+
assertEquals("内側recordコメント", innerRecordTerm.title());
152+
assertEquals("内側recordメソッドコメント", innerRecordMethodTerm.title());
153+
assertEquals("内側annotationコメント", innerAnnotationTerm.title());
154+
}
155+
156+
@Test
157+
void トップレベルに複数クラスを定義した場合も読み取れる() {
158+
Path path = Path.of("ut", "ParseTargetMultipleTopLevelClass.java");
159+
GlossaryRepository glossaryRepository = new OnMemoryGlossaryRepository();
160+
161+
sut.parseJavaFile(getJavaFilePath(path), glossaryRepository);
162+
163+
var glossary = glossaryRepository.all();
164+
var firstTypeId = TestSupport.getTypeIdFromClass(ParseTargetMultipleTopLevelClass.class);
165+
var secondTypeId = TypeId.valueOf("org.dddjava.jig.infrastructure.javaparser.ut.SecondTopLevelClass");
166+
167+
var firstTerm = glossary.termOf(firstTypeId.value(), TermKind.クラス);
168+
var secondTerm = glossary.termOf(secondTypeId.value(), TermKind.クラス);
169+
170+
assertEquals("最初のクラスコメント", firstTerm.title());
171+
assertEquals("2つ目のクラスコメント", secondTerm.title());
172+
}
173+
174+
@Test
175+
void トップレベルのアノテーションを読み取れる() {
176+
Path path = Path.of("ut", "ParseTargetTopLevelAnnotation.java");
177+
GlossaryRepository glossaryRepository = new OnMemoryGlossaryRepository();
178+
179+
sut.parseJavaFile(getJavaFilePath(path), glossaryRepository);
180+
181+
var glossary = glossaryRepository.all();
182+
var annotationTypeId = TypeId.valueOf("org.dddjava.jig.infrastructure.javaparser.ut.ParseTargetTopLevelAnnotation");
183+
var annotationTerm = glossary.termOf(annotationTypeId.value(), TermKind.クラス);
184+
185+
assertEquals("トップレベルannotationコメント", annotationTerm.title());
186+
}
187+
115188
private Path getJavaFilePath(Path requireJavaFilePath) {
116189
StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
117190

@@ -129,4 +202,4 @@ private Path getJavaFilePath(Path requireJavaFilePath) {
129202
throw new AssertionError(e);
130203
}
131204
}
132-
}
205+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package org.dddjava.jig.infrastructure.javaparser.ut;
2+
3+
/**
4+
* 最初のクラスコメント
5+
*/
6+
public class ParseTargetMultipleTopLevelClass {
7+
}
8+
9+
/**
10+
* 2つ目のクラスコメント
11+
*/
12+
class SecondTopLevelClass {
13+
}
14+
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package org.dddjava.jig.infrastructure.javaparser.ut;
2+
3+
/**
4+
* 外側クラスコメント
5+
*/
6+
public class ParseTargetNestedClass {
7+
8+
/**
9+
* 内側クラスコメント
10+
*/
11+
static class Inner {
12+
/**
13+
* 内側メソッドコメント
14+
*/
15+
void innerMethod() {
16+
}
17+
}
18+
19+
/**
20+
* 内側enumコメント
21+
*/
22+
enum InnerEnum {
23+
VALUE
24+
}
25+
26+
/**
27+
* 内側recordコメント
28+
*/
29+
record InnerRecord(String name) {
30+
/**
31+
* 内側recordメソッドコメント
32+
*/
33+
String label() {
34+
return name;
35+
}
36+
}
37+
38+
/**
39+
* 内側annotationコメント
40+
*/
41+
@interface InnerAnnotation {
42+
}
43+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.dddjava.jig.infrastructure.javaparser.ut;
2+
3+
/**
4+
* トップレベルannotationコメント
5+
*/
6+
public @interface ParseTargetTopLevelAnnotation {
7+
}

0 commit comments

Comments
 (0)