136 lines
5.5 KiB
Java
136 lines
5.5 KiB
Java
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("[]");
|
|
}
|
|
}
|