diff --git a/jig-core/src/main/java/org/dddjava/jig/infrastructure/javaparser/JavaparserClassVisitor.java b/jig-core/src/main/java/org/dddjava/jig/infrastructure/javaparser/JavaparserClassVisitor.java index a696e2a6c..f40df199c 100644 --- a/jig-core/src/main/java/org/dddjava/jig/infrastructure/javaparser/JavaparserClassVisitor.java +++ b/jig-core/src/main/java/org/dddjava/jig/infrastructure/javaparser/JavaparserClassVisitor.java @@ -1,14 +1,8 @@ package org.dddjava.jig.infrastructure.javaparser; import com.github.javaparser.ast.ImportDeclaration; -import com.github.javaparser.ast.Node; import com.github.javaparser.ast.PackageDeclaration; -import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; -import com.github.javaparser.ast.body.EnumDeclaration; -import com.github.javaparser.ast.body.RecordDeclaration; -import com.github.javaparser.ast.nodeTypes.NodeWithJavadoc; -import com.github.javaparser.ast.nodeTypes.NodeWithMembers; -import com.github.javaparser.ast.nodeTypes.NodeWithSimpleName; +import com.github.javaparser.ast.body.*; import com.github.javaparser.ast.stmt.LocalClassDeclarationStmt; import com.github.javaparser.ast.stmt.LocalRecordDeclarationStmt; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; @@ -16,12 +10,11 @@ import org.dddjava.jig.domain.model.data.enums.EnumModel; import org.dddjava.jig.domain.model.data.types.TypeId; import org.dddjava.jig.domain.model.sources.javasources.JavaSourceModel; -import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; import java.util.List; -import java.util.Optional; /** * クラスからの情報の読み取り @@ -32,10 +25,7 @@ class JavaparserClassVisitor extends VoidVisitorAdapter { private static final Logger logger = LoggerFactory.getLogger(JavaparserClassVisitor.class); private final String packageName; - @Nullable - private TypeId typeId; - - private Optional enumModel = Optional.empty(); + private final List enumModels = new ArrayList<>(); public JavaparserClassVisitor(String packageName) { this.packageName = packageName; @@ -55,24 +45,27 @@ public void visit(ImportDeclaration importDeclaration, GlossaryRepository arg) { @Override public void visit(ClassOrInterfaceDeclaration node, GlossaryRepository arg) { - visitClassOrInterfaceOrEnumOrRecord(node, arg); + visitTypeDeclaration(node, arg); } @Override public void visit(EnumDeclaration enumDeclaration, GlossaryRepository arg) { - TypeId typeId = visitClassOrInterfaceOrEnumOrRecord(enumDeclaration, arg); + TypeId typeId = visitTypeDeclaration(enumDeclaration, arg); // enum 固有の読み取りを行う var visitor = new JavaparserEnumVisitor(typeId); enumDeclaration.accept(visitor, arg); - enumModel = Optional.of(visitor.createEnumModel()); - - super.visit(enumDeclaration, arg); + enumModels.add(visitor.createEnumModel()); } @Override public void visit(RecordDeclaration recordDeclaration, GlossaryRepository arg) { - visitClassOrInterfaceOrEnumOrRecord(recordDeclaration, arg); + visitTypeDeclaration(recordDeclaration, arg); + } + + @Override + public void visit(AnnotationDeclaration annotationDeclaration, GlossaryRepository arg) { + visitTypeDeclaration(annotationDeclaration, arg); } @Override @@ -88,37 +81,34 @@ public void visit(LocalClassDeclarationStmt localClassDeclarationStmt, GlossaryR } /** - * class/interface/enum/record の共通処理 + * 型定義の共通処理 */ - private & NodeWithJavadoc & NodeWithMembers> TypeId visitClassOrInterfaceOrEnumOrRecord(T node, GlossaryRepository glossaryRepository) { - var fqn = packageName + node.getNameAsString(); - - if (typeId != null) { - logger.warn("1つの *.java ファイルの2つ目以降の class/interface/enum/record には対応していません。{} のロードはスキップされます。対応が必要な場合は読ませたい構造のサンプルを添えてIssueを作成してください。", - fqn - ); - return typeId; - } - - typeId = TypeId.valueOf(fqn); + private TypeId visitTypeDeclaration(TypeDeclaration node, GlossaryRepository glossaryRepository) { + var typeId = TypeId.valueOf(resolveFqn(node)); // クラスのJavadocが記述されていれば採用 node.getJavadoc().ifPresent(javadoc -> { String javadocText = javadoc.getDescription().toText(); glossaryRepository.register(TermFactory.fromClass(glossaryRepository.fromTypeId(typeId), javadocText)); }); - // メンバの情報を別のVisitorで読む - node.accept(new JavaparserMemberVisitor(typeId), glossaryRepository); - + // メンバの情報を別のVisitorで読む(型は再帰先で処理する) + var memberVisitor = new JavaparserMemberVisitor(typeId); node.getMembers().forEach(member -> { - if (member instanceof ClassOrInterfaceDeclaration classOrInterfaceDeclaration) { - logger.debug("nested class or interface: {}", classOrInterfaceDeclaration.getFullyQualifiedName()); + if (member instanceof TypeDeclaration typeDeclaration) { + typeDeclaration.accept(this, glossaryRepository); + } else { + member.accept(memberVisitor, glossaryRepository); } }); return typeId; } + private String resolveFqn(TypeDeclaration node) { + return node.getFullyQualifiedName() + .orElse(packageName + node.getNameAsString()); + } + public JavaSourceModel javaSourceModel() { - return JavaSourceModel.from(enumModel.map(List::of).orElseGet(List::of)); + return JavaSourceModel.from(enumModels); } } diff --git a/jig-core/src/test/java/org/dddjava/jig/infrastructure/javaparser/JavaparserReaderTest.java b/jig-core/src/test/java/org/dddjava/jig/infrastructure/javaparser/JavaparserReaderTest.java index feb6c6007..d101d5af7 100644 --- a/jig-core/src/test/java/org/dddjava/jig/infrastructure/javaparser/JavaparserReaderTest.java +++ b/jig-core/src/test/java/org/dddjava/jig/infrastructure/javaparser/JavaparserReaderTest.java @@ -6,7 +6,10 @@ import org.dddjava.jig.domain.model.data.packages.PackageId; import org.dddjava.jig.domain.model.data.terms.Term; import org.dddjava.jig.domain.model.data.terms.TermKind; +import org.dddjava.jig.domain.model.data.types.TypeId; import org.dddjava.jig.infrastructure.javaparser.ut.ParseTargetCanonicalClass; +import org.dddjava.jig.infrastructure.javaparser.ut.ParseTargetMultipleTopLevelClass; +import org.dddjava.jig.infrastructure.javaparser.ut.ParseTargetNestedClass; import org.dddjava.jig.infrastructure.onmemoryrepository.OnMemoryGlossaryRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -112,6 +115,76 @@ void setUp() { assertEquals("フィールドコメント", term.title()); } + @Test + void ネストしたクラスとメソッドを読み取れる() { + Path path = Path.of("ut", "ParseTargetNestedClass.java"); + GlossaryRepository glossaryRepository = new OnMemoryGlossaryRepository(); + + sut.parseJavaFile(getJavaFilePath(path), glossaryRepository); + + var glossary = glossaryRepository.all(); + var outerTerm = glossary.termOf( + TestSupport.getTypeIdFromClass(ParseTargetNestedClass.class).value(), + TermKind.クラス + ); + var innerTypeId = TypeId.valueOf("org.dddjava.jig.infrastructure.javaparser.ut.ParseTargetNestedClass.Inner"); + var innerTerm = glossary.termOf(innerTypeId.value(), TermKind.クラス); + var innerMethodTerm = glossary.termOf( + JigMethodId.from(innerTypeId, "innerMethod", List.of()).value(), + TermKind.メソッド + ); + var innerEnumTypeId = TypeId.valueOf("org.dddjava.jig.infrastructure.javaparser.ut.ParseTargetNestedClass.InnerEnum"); + var innerEnumTerm = glossary.termOf(innerEnumTypeId.value(), TermKind.クラス); + var innerRecordTypeId = TypeId.valueOf("org.dddjava.jig.infrastructure.javaparser.ut.ParseTargetNestedClass.InnerRecord"); + var innerRecordTerm = glossary.termOf(innerRecordTypeId.value(), TermKind.クラス); + var innerRecordMethodTerm = glossary.termOf( + JigMethodId.from(innerRecordTypeId, "label", List.of()).value(), + TermKind.メソッド + ); + var innerAnnotationTypeId = TypeId.valueOf("org.dddjava.jig.infrastructure.javaparser.ut.ParseTargetNestedClass.InnerAnnotation"); + var innerAnnotationTerm = glossary.termOf(innerAnnotationTypeId.value(), TermKind.クラス); + + assertEquals("外側クラスコメント", outerTerm.title()); + assertEquals("内側クラスコメント", innerTerm.title()); + assertEquals("内側メソッドコメント", innerMethodTerm.title()); + assertEquals("内側enumコメント", innerEnumTerm.title()); + assertEquals("内側recordコメント", innerRecordTerm.title()); + assertEquals("内側recordメソッドコメント", innerRecordMethodTerm.title()); + assertEquals("内側annotationコメント", innerAnnotationTerm.title()); + } + + @Test + void トップレベルに複数クラスを定義した場合も読み取れる() { + Path path = Path.of("ut", "ParseTargetMultipleTopLevelClass.java"); + GlossaryRepository glossaryRepository = new OnMemoryGlossaryRepository(); + + sut.parseJavaFile(getJavaFilePath(path), glossaryRepository); + + var glossary = glossaryRepository.all(); + var firstTypeId = TestSupport.getTypeIdFromClass(ParseTargetMultipleTopLevelClass.class); + var secondTypeId = TypeId.valueOf("org.dddjava.jig.infrastructure.javaparser.ut.SecondTopLevelClass"); + + var firstTerm = glossary.termOf(firstTypeId.value(), TermKind.クラス); + var secondTerm = glossary.termOf(secondTypeId.value(), TermKind.クラス); + + assertEquals("最初のクラスコメント", firstTerm.title()); + assertEquals("2つ目のクラスコメント", secondTerm.title()); + } + + @Test + void トップレベルのアノテーションを読み取れる() { + Path path = Path.of("ut", "ParseTargetTopLevelAnnotation.java"); + GlossaryRepository glossaryRepository = new OnMemoryGlossaryRepository(); + + sut.parseJavaFile(getJavaFilePath(path), glossaryRepository); + + var glossary = glossaryRepository.all(); + var annotationTypeId = TypeId.valueOf("org.dddjava.jig.infrastructure.javaparser.ut.ParseTargetTopLevelAnnotation"); + var annotationTerm = glossary.termOf(annotationTypeId.value(), TermKind.クラス); + + assertEquals("トップレベルannotationコメント", annotationTerm.title()); + } + private Path getJavaFilePath(Path requireJavaFilePath) { StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); @@ -129,4 +202,4 @@ private Path getJavaFilePath(Path requireJavaFilePath) { throw new AssertionError(e); } } -} \ No newline at end of file +} diff --git a/jig-core/src/test/java/org/dddjava/jig/infrastructure/javaparser/ut/ParseTargetMultipleTopLevelClass.java b/jig-core/src/test/java/org/dddjava/jig/infrastructure/javaparser/ut/ParseTargetMultipleTopLevelClass.java new file mode 100644 index 000000000..a4acb1ae1 --- /dev/null +++ b/jig-core/src/test/java/org/dddjava/jig/infrastructure/javaparser/ut/ParseTargetMultipleTopLevelClass.java @@ -0,0 +1,14 @@ +package org.dddjava.jig.infrastructure.javaparser.ut; + +/** + * 最初のクラスコメント + */ +public class ParseTargetMultipleTopLevelClass { +} + +/** + * 2つ目のクラスコメント + */ +class SecondTopLevelClass { +} + diff --git a/jig-core/src/test/java/org/dddjava/jig/infrastructure/javaparser/ut/ParseTargetNestedClass.java b/jig-core/src/test/java/org/dddjava/jig/infrastructure/javaparser/ut/ParseTargetNestedClass.java new file mode 100644 index 000000000..b51d57ad3 --- /dev/null +++ b/jig-core/src/test/java/org/dddjava/jig/infrastructure/javaparser/ut/ParseTargetNestedClass.java @@ -0,0 +1,43 @@ +package org.dddjava.jig.infrastructure.javaparser.ut; + +/** + * 外側クラスコメント + */ +public class ParseTargetNestedClass { + + /** + * 内側クラスコメント + */ + static class Inner { + /** + * 内側メソッドコメント + */ + void innerMethod() { + } + } + + /** + * 内側enumコメント + */ + enum InnerEnum { + VALUE + } + + /** + * 内側recordコメント + */ + record InnerRecord(String name) { + /** + * 内側recordメソッドコメント + */ + String label() { + return name; + } + } + + /** + * 内側annotationコメント + */ + @interface InnerAnnotation { + } +} diff --git a/jig-core/src/test/java/org/dddjava/jig/infrastructure/javaparser/ut/ParseTargetTopLevelAnnotation.java b/jig-core/src/test/java/org/dddjava/jig/infrastructure/javaparser/ut/ParseTargetTopLevelAnnotation.java new file mode 100644 index 000000000..b43517562 --- /dev/null +++ b/jig-core/src/test/java/org/dddjava/jig/infrastructure/javaparser/ut/ParseTargetTopLevelAnnotation.java @@ -0,0 +1,7 @@ +package org.dddjava.jig.infrastructure.javaparser.ut; + +/** + * トップレベルannotationコメント + */ +public @interface ParseTargetTopLevelAnnotation { +}