Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,27 +1,20 @@
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;
import org.dddjava.jig.application.GlossaryRepository;
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;

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

private final String packageName;
@Nullable
private TypeId typeId;

private Optional<EnumModel> enumModel = Optional.empty();
private final List<EnumModel> enumModels = new ArrayList<>();

public JavaparserClassVisitor(String packageName) {
this.packageName = packageName;
Expand All @@ -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
Expand All @@ -88,37 +81,34 @@ public void visit(LocalClassDeclarationStmt localClassDeclarationStmt, GlossaryR
}

/**
* class/interface/enum/record の共通処理
* 型定義の共通処理
*/
private <T extends Node & NodeWithSimpleName<?> & 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand All @@ -129,4 +202,4 @@ private Path getJavaFilePath(Path requireJavaFilePath) {
throw new AssertionError(e);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.dddjava.jig.infrastructure.javaparser.ut;

/**
* 最初のクラスコメント
*/
public class ParseTargetMultipleTopLevelClass {
}

/**
* 2つ目のクラスコメント
*/
class SecondTopLevelClass {
}

Original file line number Diff line number Diff line change
@@ -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 {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.dddjava.jig.infrastructure.javaparser.ut;

/**
* トップレベルannotationコメント
*/
public @interface ParseTargetTopLevelAnnotation {
}