@@ -1,135 +0,0 @@
|
||||
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<String> 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<String, Set<String>> ancestorsOf = new LinkedHashMap<>();
|
||||
private final Map<String, String> 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<Path> 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<String, String> entry : index.sourceByClass.entrySet()) {
|
||||
String rootClass = entry.getKey();
|
||||
List<FieldInfo> fields = fieldParser.parseFields(entry.getValue(), rootClass);
|
||||
Set<String> visiting = new LinkedHashSet<>();
|
||||
index.walkNested(rootClass, fields, rootClass, 1, visiting, fieldParser);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
/** 自身 + 所有祖先 Dto/Vo 类名(用于接口影响匹配) */
|
||||
public Set<String> expandImpactNames(String className) {
|
||||
Set<String> 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<String> ancestors = ancestorsOf.get(className);
|
||||
return ancestors != null && !ancestors.isEmpty();
|
||||
}
|
||||
|
||||
/** 嵌套类型的 @RequestBody 根 Dto 祖先(仅 Dto 后缀) */
|
||||
public Set<String> findRequestBodyRoots(String className) {
|
||||
Set<String> 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<FieldInfo> fields, String rootAncestor,
|
||||
int depth, Set<String> 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<FieldInfo> 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("[]");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user