package com.codechecker.analyzer; import com.codechecker.config.AppConfig; import com.codechecker.model.ClassType; import com.codechecker.model.FieldInfo; import com.codechecker.parser.ClassFieldParser; import com.codechecker.parser.TypeNameUtils; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Stream; /** * Dto/Vo 嵌套关系索引:反向查找祖先容器(用于影响分析与 API 跟进)。 */ public class DtoNestIndex { 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 int maxDepth; private final Map> ancestorsOf = new LinkedHashMap<>(); private final Map sourceByClass = new HashMap<>(); private DtoNestIndex(int maxDepth) { this.maxDepth = maxDepth; } public static DtoNestIndex build(Path repoRoot, AppConfig config) throws IOException { DtoNestIndex index = new DtoNestIndex(config.getNestMaxDepth()); ClassFieldParser fieldParser = new ClassFieldParser(); for (String dir : config.getModelDirs()) { Path root = repoRoot.resolve(dir.replace('\\', '/')); if (!Files.exists(root)) { continue; } try (Stream paths = Files.walk(root)) { paths.filter(path -> path.toString().endsWith(".java")) .forEach(path -> { String className = path.getFileName().toString().replace(".java", ""); if (ClassType.fromClassName(className) != ClassType.DTO && ClassType.fromClassName(className) != ClassType.VO) { return; } try { String source = Files.readString(path, StandardCharsets.UTF_8); index.sourceByClass.put(className, source); } catch (IOException ignored) { // 跳过无法读取的文件 } }); } } for (Map.Entry entry : index.sourceByClass.entrySet()) { String rootClass = entry.getKey(); List fields = fieldParser.parseFields(entry.getValue(), rootClass); Set visiting = new LinkedHashSet<>(); index.walkNested(rootClass, fields, rootClass, 1, visiting, fieldParser); } return index; } /** 自身 + 所有祖先 Dto/Vo 类名(用于接口影响匹配) */ public Set expandImpactNames(String className) { Set names = new LinkedHashSet<>(); if (className != null && !className.isBlank()) { names.add(className); names.addAll(ancestorsOf.getOrDefault(className, Set.of())); } return names; } /** 是否被其他 Dto/Vo 嵌套引用(存在至少一个祖先容器) */ public boolean hasAncestors(String className) { Set ancestors = ancestorsOf.get(className); return ancestors != null && !ancestors.isEmpty(); } /** 嵌套类型的 @RequestBody 根 Dto 祖先(仅 Dto 后缀) */ public Set findRequestBodyRoots(String className) { Set roots = new LinkedHashSet<>(); if (className != null && className.endsWith("Dto")) { roots.add(className); } for (String ancestor : ancestorsOf.getOrDefault(className, Set.of())) { if (ancestor.endsWith("Dto")) { roots.add(ancestor); } } return roots; } public int getMaxDepth() { return maxDepth; } private void walkNested(String ownerClass, List fields, String rootAncestor, int depth, Set visiting, ClassFieldParser fieldParser) { if (depth > maxDepth) { return; } for (FieldInfo field : fields) { for (String nestedType : TypeNameUtils.peelDirectTypeNames(field.getType())) { if (isLeafType(nestedType) || nestedType.equals(ownerClass)) { continue; } ancestorsOf.computeIfAbsent(nestedType, k -> new LinkedHashSet<>()).add(rootAncestor); if (!visiting.add(nestedType)) { continue; } String nestedSource = sourceByClass.get(nestedType); if (nestedSource != null) { List nestedFields = fieldParser.parseFields(nestedSource, nestedType); walkNested(nestedType, nestedFields, rootAncestor, depth + 1, visiting, fieldParser); } visiting.remove(nestedType); } } } private boolean isLeafType(String simpleType) { return LEAF_TYPES.contains(simpleType) || simpleType.endsWith("[]"); } }