package com.codechecker.api.parser; import com.codechecker.git.GitChangeScanner; import com.codechecker.model.FieldInfo; import com.codechecker.parser.ClassFieldParser; import com.codechecker.parser.TypeNameUtils; import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; /** * 递归展开 Dto/Vo 嵌套字段(dot path),与类变更字段解析解耦但复用 ClassFieldParser。 */ public class NestedDtoFieldParser { private static final Set LEAF_TYPES = Set.of( "String", "Integer", "int", "Long", "long", "Boolean", "boolean", "Double", "double", "Float", "float", "Short", "short", "Byte", "byte", "Character", "char", "BigDecimal", "BigInteger", "Date", "LocalDate", "LocalDateTime", "LocalTime", "Instant", "Timestamp", "Object", "Void", "void" ); private final ClassFieldParser classFieldParser = new ClassFieldParser(); private final JavaSourceLocator sourceLocator; private final GitChangeScanner gitScanner; private final String oldSha; private final String newSha; private final int maxDepth; public NestedDtoFieldParser(Path repoRoot, List searchDirs, GitChangeScanner gitScanner, String oldSha, String newSha, int maxDepth) { this.sourceLocator = new JavaSourceLocator(repoRoot, searchDirs); this.gitScanner = gitScanner; this.oldSha = oldSha; this.newSha = newSha; this.maxDepth = maxDepth; } public List parseNestedFieldsAtOldCommit(String dtoClassName) throws IOException { return parseNestedFields(dtoClassName, oldSha); } public List parseNestedFieldsAtNewCommit(String dtoClassName) throws IOException { return parseNestedFields(dtoClassName, newSha); } private List parseNestedFields(String dtoClassName, String sha) throws IOException { Set visiting = new HashSet<>(); List result = new ArrayList<>(); collectFields(dtoClassName, "", visiting, result, sha, 1); return result; } private void collectFields(String className, String prefix, Set visiting, List out, String sha, int depth) throws IOException { if (className == null || className.isBlank() || visiting.contains(className) || depth > maxDepth) { return; } visiting.add(className); Optional source = readSource(className, sha); if (source.isEmpty()) { visiting.remove(className); return; } List fields = classFieldParser.parseFields(source.get(), className); for (FieldInfo field : fields) { String path = prefix.isBlank() ? field.getName() : prefix + "." + field.getName(); Set nestedTypes = TypeNameUtils.peelDirectTypeNames(field.getType()); boolean expanded = false; for (String nestedType : nestedTypes) { if (isLeafType(nestedType) || nestedType.equals(className)) { continue; } expanded = true; // 嵌套字段路径用类型简单类名(如 UserSelfDto.nickName),不用成员名(userDtos.nickName) collectFields(nestedType, nestedType, visiting, out, sha, depth + 1); } if (!expanded) { out.add(new NestedFieldInfo(path, field.getType(), field.getDescription())); } } visiting.remove(className); } private Optional readSource(String className, String sha) throws IOException { if (sha != null && gitScanner != null) { return sourceLocator.readSourceAtCommit(gitScanner, sha, className); } return sourceLocator.readSourceBySimpleName(className); } private boolean isLeafType(String simpleType) { return LEAF_TYPES.contains(simpleType) || simpleType.endsWith("[]"); } }