feat: 完成二阶段的优化处理
All checks were successful
缓存序列化结构检查 / cache-schema-check (push) Has been skipped

This commit is contained in:
2026-07-14 15:49:40 +08:00
parent 875a5edbd1
commit cab3fea666
17 changed files with 623 additions and 118 deletions

View File

@@ -31,22 +31,24 @@ public class FileScanner {
public Map<String, String> scan() {
Map<String, String> result = new LinkedHashMap<>();
try (Stream<Path> stream = Files.walk(repoRoot)) {
stream.filter(Files::isRegularFile)
List<Path> javaFiles = stream
.filter(Files::isRegularFile)
.filter(p -> p.toString().endsWith(".java"))
.forEach(p -> {
.filter(p -> {
String rel = repoRoot.relativize(p).toString().replace('\\', '/');
if (!rel.contains("/src/main/java/")) {
return;
}
if (!moduleAllowed(rel)) {
return;
}
try {
result.put(rel, new String(Files.readAllBytes(p), StandardCharsets.UTF_8));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
return rel.contains("/src/main/java/") && moduleAllowed(rel);
})
.collect(java.util.stream.Collectors.toList());
javaFiles.parallelStream().forEach(p -> {
String rel = repoRoot.relativize(p).toString().replace('\\', '/');
try {
synchronized (result) {
result.put(rel, new String(Files.readAllBytes(p), StandardCharsets.UTF_8));
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
} catch (IOException e) {
throw new UncheckedIOException(e);
}

View File

@@ -111,6 +111,8 @@ public class SchemaCheckAnalyzer {
List<WritePoint> newWps = detectorNew.detect(path, newContent);
List<WritePoint> oldWps = oldContent == null
? new ArrayList<>() : detectorOld.detect(path, oldContent);
newWps.forEach(this::applyManualMappings);
oldWps.forEach(this::applyManualMappings);
Map<String, WritePoint> oldBySig = new LinkedHashMap<>();
for (WritePoint wp : oldWps) {
@@ -399,12 +401,30 @@ public class SchemaCheckAnalyzer {
private SourceIndex buildIndex(Iterable<String> contents) {
SourceIndex index = new SourceIndex();
List<String> list = new ArrayList<>();
for (String content : contents) {
index.addSource(content);
list.add(content);
}
index.addSources(list);
return index;
}
private void applyManualMappings(WritePoint wp) {
String location = wp.getEnclosingClass() + "#" + wp.getEnclosingMethod();
for (CheckerConfig.ManualMapping mapping : config.getManualMappings()) {
if (mapping.getWriterMethod() == null || !mapping.getWriterMethod().equals(location)) {
continue;
}
if (mapping.getKeyPattern() != null && !mapping.getKeyPattern().isEmpty()) {
wp.setResolvedKeyPattern(mapping.getKeyPattern());
}
if (mapping.getValueType() != null && !mapping.getValueType().isEmpty()) {
wp.setResolvedValueType(mapping.getValueType());
wp.setConfidence(Math.max(wp.getConfidence(), 1.0));
}
}
}
private void collectTypeNames(String content, Set<String> out) {
if (content == null || content.isEmpty()) {
return;

View File

@@ -11,10 +11,17 @@ import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.Parameter;
import com.github.javaparser.ast.body.TypeDeclaration;
import com.github.javaparser.ast.body.VariableDeclarator;
import com.github.javaparser.ast.expr.BinaryExpr;
import com.github.javaparser.ast.expr.BooleanLiteralExpr;
import com.github.javaparser.ast.expr.CastExpr;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.IntegerLiteralExpr;
import com.github.javaparser.ast.expr.LongLiteralExpr;
import com.github.javaparser.ast.expr.MethodCallExpr;
import com.github.javaparser.ast.expr.NameExpr;
import com.github.javaparser.ast.expr.NullLiteralExpr;
import com.github.javaparser.ast.expr.ObjectCreationExpr;
import com.github.javaparser.ast.expr.StringLiteralExpr;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import com.github.javaparser.ast.type.Type;
@@ -26,13 +33,19 @@ import java.util.Optional;
import java.util.Set;
/**
* 从单个 Java 源文件中检测 Redis value 写入点W01~W03JSON 字符串写入)。
* 从单个 Java 源文件中检测 Redis value 写入点W01~W05)。
*/
public class RedisWritePointDetector {
private static final Set<String> SERIALIZE_METHODS = new HashSet<>(Arrays.asList(
"toJSONString", "toJsonString", "getObjectToString", "toJsonStr", "writeValueAsString"));
private static final Set<String> WRITE_METHODS = new HashSet<>(Arrays.asList("set", "insert"));
private static final Set<String> VALUE_WRITE_METHODS = new HashSet<>(Arrays.asList("set", "insert"));
private static final Set<String> SKIP_METHODS = new HashSet<>(Arrays.asList(
"setIfAbsent", "setIfPresent", "increment", "decrement", "delete", "remove",
"expire", "get", "hasKey", "exists", "setnx", "getAndSet", "getAndDelete",
"multiGet", "multiSet", "keys", "scan"));
private static final Set<String> TRIVIAL_VALUE_CALLS = new HashSet<>(Arrays.asList(
"randomUUID", "toString", "valueOf"));
private static final Set<String> COLLECTION_SIMPLE = new HashSet<>(Arrays.asList(
"List", "ArrayList", "LinkedList", "Set", "HashSet", "Collection"));
@@ -59,58 +72,133 @@ public class RedisWritePointDetector {
}
for (MethodCallExpr mce : cu.findAll(MethodCallExpr.class)) {
String method = mce.getNameAsString();
if (!WRITE_METHODS.contains(method)) {
continue;
WritePoint wp = tryDetectValueWrite(mce, filePath);
if (wp == null) {
wp = tryDetectHashWrite(mce, filePath);
}
String scope = mce.getScope().map(Expression::toString).orElse("");
if (!isRedisScope(scope)) {
continue;
if (wp != null) {
result.add(wp);
}
if (mce.getArguments().size() < 2) {
continue;
}
Expression valueArg = mce.getArgument(1);
Expression serialized = unwrapSerializer(valueArg);
if (serialized == null) {
continue; // 非 JSON 字符串写入W04/W05 归后续阶段)
}
String pattern = classify(method, valueArg);
if (!enabledPatterns.contains(pattern)) {
continue;
}
WritePoint wp = new WritePoint();
wp.setFilePath(filePath);
wp.setLineNumber(mce.getBegin().map(p -> p.line).orElse(0));
wp.setPattern(pattern);
wp.setKeyExpression(mce.getArgument(0).toString());
wp.setValueExpression(valueArg.toString());
fillEnclosing(mce, wp);
SourceIndex.IndexedType context = index.get(wp.getEnclosingClass());
ClassOrInterfaceDeclaration enclosingDecl = mce
.findAncestor(ClassOrInterfaceDeclaration.class).orElse(null);
wp.setResolvedKeyPattern(keyResolver.resolve(mce.getArgument(0), enclosingDecl, context));
InferredType inferred = inferType(serialized, mce, context);
if (inferred != null) {
wp.setResolvedValueType(inferred.fqn);
wp.setRootArray(inferred.isArray);
wp.setConfidence(inferred.fqn == null ? 0.4 : 1.0);
} else {
wp.setConfidence(0.4);
}
result.add(wp);
}
return result;
}
private boolean isRedisScope(String scope) {
private WritePoint tryDetectValueWrite(MethodCallExpr mce, String filePath) {
String method = mce.getNameAsString();
if (!VALUE_WRITE_METHODS.contains(method) || SKIP_METHODS.contains(method)) {
return null;
}
if (!isValueOpsScope(mce)) {
return null;
}
if (mce.getArguments().size() < 2) {
return null;
}
Expression valueArg = mce.getArgument(1);
Expression serialized = unwrapSerializer(valueArg);
String pattern;
Expression typeExpr;
if (serialized != null) {
pattern = classifySerialized(method, valueArg);
typeExpr = serialized;
} else if (enabledPatterns.contains("W04") && !isTrivialValue(valueArg)) {
pattern = "W04";
typeExpr = valueArg;
} else {
return null;
}
if (!enabledPatterns.contains(pattern)) {
return null;
}
return buildWritePoint(mce, filePath, pattern, mce.getArgument(0), valueArg, typeExpr);
}
private WritePoint tryDetectHashWrite(MethodCallExpr mce, String filePath) {
if (!"put".equals(mce.getNameAsString()) || !enabledPatterns.contains("W05")) {
return null;
}
if (!isHashOpsScope(mce) || mce.getArguments().size() < 3) {
return null;
}
Expression valueArg = mce.getArgument(2);
if (isTrivialValue(valueArg)) {
return null;
}
Expression serialized = unwrapSerializer(valueArg);
Expression typeExpr = serialized != null ? serialized : valueArg;
return buildWritePoint(mce, filePath, "W05", mce.getArgument(0), valueArg, typeExpr);
}
private WritePoint buildWritePoint(MethodCallExpr mce, String filePath, String pattern,
Expression keyArg, Expression valueArg, Expression typeExpr) {
WritePoint wp = new WritePoint();
wp.setFilePath(filePath);
wp.setLineNumber(mce.getBegin().map(p -> p.line).orElse(0));
wp.setPattern(pattern);
wp.setKeyExpression(keyArg.toString());
wp.setValueExpression(valueArg.toString());
fillEnclosing(mce, wp);
SourceIndex.IndexedType context = index.get(wp.getEnclosingClass());
ClassOrInterfaceDeclaration enclosingDecl = mce
.findAncestor(ClassOrInterfaceDeclaration.class).orElse(null);
wp.setResolvedKeyPattern(keyResolver.resolve(keyArg, enclosingDecl, context));
InferredType inferred = inferType(typeExpr, mce, context);
if (inferred != null) {
wp.setResolvedValueType(inferred.fqn);
wp.setRootArray(inferred.isArray);
wp.setConfidence(inferred.fqn == null ? 0.4 : 1.0);
} else {
wp.setConfidence(0.4);
}
return wp;
}
private boolean isValueOpsScope(MethodCallExpr mce) {
String scope = mce.getScope().map(Expression::toString).orElse("");
String lower = scope.toLowerCase();
return lower.contains("redis") || lower.contains("opsforvalue") || lower.contains("boundvalueops");
}
private boolean isHashOpsScope(MethodCallExpr mce) {
String scope = mce.getScope().map(Expression::toString).orElse("");
String lower = scope.toLowerCase();
return lower.contains("opsforhash") || lower.contains("boundhashops");
}
private boolean isTrivialValue(Expression expr) {
if (expr instanceof StringLiteralExpr
|| expr instanceof IntegerLiteralExpr
|| expr instanceof LongLiteralExpr
|| expr instanceof BooleanLiteralExpr
|| expr instanceof NullLiteralExpr) {
return true;
}
if (expr instanceof BinaryExpr) {
BinaryExpr be = (BinaryExpr) expr;
if (be.getOperator() == BinaryExpr.Operator.PLUS) {
return isTrivialValue(be.getLeft()) && isTrivialValue(be.getRight());
}
}
if (expr instanceof MethodCallExpr) {
MethodCallExpr call = (MethodCallExpr) expr;
String name = call.getNameAsString();
if (TRIVIAL_VALUE_CALLS.contains(name)) {
return true;
}
if ("valueOf".equals(name) && !call.getArguments().isEmpty()) {
return isTrivialValue(call.getArgument(0));
}
}
if (expr instanceof ObjectCreationExpr) {
ObjectCreationExpr oce = (ObjectCreationExpr) expr;
String typeName = oce.getType().getNameAsString();
return "UUID".equals(typeName);
}
return false;
}
private Expression unwrapSerializer(Expression valueArg) {
if (valueArg instanceof MethodCallExpr) {
MethodCallExpr call = (MethodCallExpr) valueArg;
@@ -121,7 +209,7 @@ public class RedisWritePointDetector {
return null;
}
private String classify(String method, Expression valueArg) {
private String classifySerialized(String method, Expression valueArg) {
String serializer = valueArg instanceof MethodCallExpr
? ((MethodCallExpr) valueArg).getNameAsString() : "";
if ("insert".equals(method)) {
@@ -145,6 +233,9 @@ public class RedisWritePointDetector {
}
private InferredType inferType(Expression expr, MethodCallExpr contextCall, SourceIndex.IndexedType context) {
if (expr instanceof CastExpr) {
return resolveTypeNode(((CastExpr) expr).getType(), context);
}
if (expr instanceof ObjectCreationExpr) {
ClassOrInterfaceType t = ((ObjectCreationExpr) expr).getType();
return resolveTypeNode(t, context);
@@ -157,6 +248,18 @@ public class RedisWritePointDetector {
}
return null;
}
if (expr instanceof MethodCallExpr) {
MethodCallExpr call = (MethodCallExpr) expr;
Optional<ClassOrInterfaceDeclaration> clazz = contextCall
.findAncestor(ClassOrInterfaceDeclaration.class);
if (clazz.isPresent()) {
for (MethodDeclaration md : clazz.get().getMethods()) {
if (md.getNameAsString().equals(call.getNameAsString())) {
return resolveTypeNode(md.getType(), context);
}
}
}
}
return null;
}
@@ -164,20 +267,17 @@ public class RedisWritePointDetector {
Optional<CallableDeclaration> callable = contextCall.findAncestor(CallableDeclaration.class);
if (callable.isPresent()) {
CallableDeclaration<?> decl = callable.get();
// 局部变量
for (VariableDeclarator var : decl.findAll(VariableDeclarator.class)) {
if (var.getNameAsString().equals(name)) {
return var.getType();
}
}
// 方法参数
for (Parameter p : decl.getParameters()) {
if (p.getNameAsString().equals(name)) {
return p.getType();
}
}
}
// 类字段
Optional<ClassOrInterfaceDeclaration> clazz = contextCall.findAncestor(ClassOrInterfaceDeclaration.class);
if (clazz.isPresent()) {
for (FieldDeclaration field : clazz.get().getFields()) {

View File

@@ -20,7 +20,7 @@ import java.util.Optional;
*/
public class RedisKeyResolver {
private static final int MAX_DEPTH = 6;
private static final int MAX_DEPTH = 8;
private final SourceIndex index;
@@ -52,7 +52,7 @@ public class RedisKeyResolver {
}
if (expr instanceof NameExpr) {
String name = ((NameExpr) expr).getNameAsString();
String constVal = lookupConstant(enclosingClass, name);
String constVal = lookupConstant(enclosingClass, name, context, depth);
if (constVal != null) {
return constVal;
}
@@ -62,12 +62,23 @@ public class RedisKeyResolver {
if (expr instanceof FieldAccessExpr) {
FieldAccessExpr fae = (FieldAccessExpr) expr;
String fieldName = fae.getNameAsString();
if (fae.getScope() instanceof NameExpr) {
String scope = ((NameExpr) fae.getScope()).getNameAsString();
String local = lookupConstant(enclosingClass, fieldName, context, depth);
if (local != null && scope.equals(enclosingClass == null ? "" : enclosingClass.getNameAsString())) {
return local;
}
String external = lookupExternalConstant(scope, fieldName, context, depth);
if (external != null) {
return external;
}
}
String scope = fae.getScope().toString();
String external = lookupExternalConstant(scope, fieldName, context);
String external = lookupExternalConstant(scope, fieldName, context, depth);
if (external != null) {
return external;
}
String local = lookupConstant(enclosingClass, fieldName);
String local = lookupConstant(enclosingClass, fieldName, context, depth);
return local != null ? local : "*";
}
if (expr instanceof MethodCallExpr) {
@@ -77,23 +88,26 @@ public class RedisKeyResolver {
String fmt = resolveExpr(call.getArgument(0), enclosingClass, context, depth + 1);
return fmt.replaceAll("%[-0-9.]*[sdxDX]", "*");
}
// buildCacheKey(...) 等本类方法:解析其 return 表达式
String methodVal = lookupMethodReturn(enclosingClass, name, context, depth);
return methodVal != null ? methodVal : "*";
}
return "*";
}
private String lookupConstant(ClassOrInterfaceDeclaration clazz, String name) {
private String lookupConstant(ClassOrInterfaceDeclaration clazz, String name,
SourceIndex.IndexedType context, int depth) {
if (clazz == null) {
return null;
}
for (FieldDeclaration field : clazz.getFields()) {
if (!field.isStatic()) {
continue;
}
for (VariableDeclarator var : field.getVariables()) {
if (var.getNameAsString().equals(name)) {
Optional<Expression> init = var.getInitializer();
if (init.isPresent() && init.get() instanceof StringLiteralExpr) {
return ((StringLiteralExpr) init.get()).asString();
if (init.isPresent()) {
return resolveExpr(init.get(), clazz, context, depth + 1);
}
}
}
@@ -101,7 +115,8 @@ public class RedisKeyResolver {
return null;
}
private String lookupExternalConstant(String scopeName, String fieldName, SourceIndex.IndexedType context) {
private String lookupExternalConstant(String scopeName, String fieldName,
SourceIndex.IndexedType context, int depth) {
String fqn = index.resolveFqn(scopeName, context);
if (fqn == null) {
return null;
@@ -110,7 +125,7 @@ public class RedisKeyResolver {
if (type == null) {
return null;
}
return lookupConstant(type.getDeclaration(), fieldName);
return lookupConstant(type.getDeclaration(), fieldName, type, depth);
}
private String lookupMethodReturn(ClassOrInterfaceDeclaration clazz, String methodName,

View File

@@ -15,7 +15,7 @@ import java.util.Set;
* 将 {@link CheckReport} 渲染为企微 Markdown / 控制台文本。
* <ul>
* <li>企微:按 key 展示位置/类型/序列化骨架变更,不含字段明细,不分 P0/P1/P2</li>
* <li>未解析 key 展示源码表达式 + 灰色「key 解析」提示</li>
* <li>未解析 key 展示源码表达式 + 灰色「key 无法解析」提示</li>
* <li>多 key 优先拼成一条;超过企微上限则按 key 拆成多条</li>
* <li>CI先打字段明细再完整输出企微 Markdown拆分后的每条</li>
* </ul>

View File

@@ -1,12 +1,19 @@
package com.codechecker.cache.schema;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.expr.AnnotationExpr;
import com.github.javaparser.ast.expr.ArrayInitializerExpr;
import com.github.javaparser.ast.expr.BooleanLiteralExpr;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.MemberValuePair;
import com.github.javaparser.ast.expr.NormalAnnotationExpr;
import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr;
import com.github.javaparser.ast.expr.StringLiteralExpr;
import java.util.HashSet;
import java.util.Set;
/**
* 处理 Fastjson / Jackson 序列化相关注解:字段忽略与字段名映射。
*/
@@ -20,17 +27,12 @@ public final class AnnotationSupport {
*/
public static boolean isSerialized(FieldDeclaration field) {
for (AnnotationExpr annotation : field.getAnnotations()) {
String name = annotation.getNameAsString();
if (name.equals("JsonIgnore")) {
String name = simpleName(annotation);
if (isIgnoreAnnotation(name)) {
return false;
}
if (name.equals("JSONField") && annotation instanceof NormalAnnotationExpr) {
for (MemberValuePair pair : ((NormalAnnotationExpr) annotation).getPairs()) {
if (pair.getNameAsString().equals("serialize")
&& pair.getValue().toString().equals("false")) {
return false;
}
}
if (name.equals("JSONField") && isJsonFieldSerializeDisabled(annotation)) {
return false;
}
}
return true;
@@ -41,38 +43,93 @@ public final class AnnotationSupport {
*/
public static String jsonName(FieldDeclaration field, String defaultName) {
for (AnnotationExpr annotation : field.getAnnotations()) {
String name = annotation.getNameAsString();
String name = simpleName(annotation);
if (name.equals("JsonProperty")) {
String v = singleStringValue(annotation);
String v = stringMember(annotation, "value");
if (v != null && !v.isEmpty()) {
return v;
}
}
if (name.equals("JSONField") && annotation instanceof NormalAnnotationExpr) {
for (MemberValuePair pair : ((NormalAnnotationExpr) annotation).getPairs()) {
if (pair.getNameAsString().equals("name")
&& pair.getValue() instanceof StringLiteralExpr) {
String v = ((StringLiteralExpr) pair.getValue()).asString();
if (!v.isEmpty()) {
return v;
}
}
if (name.equals("JSONField")) {
String v = stringMember(annotation, "name");
if (v != null && !v.isEmpty()) {
return v;
}
}
}
return defaultName;
}
private static String singleStringValue(AnnotationExpr annotation) {
/**
* 类级别 @JsonIgnoreProperties 声明的忽略字段名。
*/
public static Set<String> ignoredProperties(ClassOrInterfaceDeclaration type) {
Set<String> ignored = new HashSet<>();
for (AnnotationExpr annotation : type.getAnnotations()) {
if (!"JsonIgnoreProperties".equals(simpleName(annotation))) {
continue;
}
collectIgnoredNames(annotation, ignored);
}
return ignored;
}
private static boolean isIgnoreAnnotation(String name) {
return name.equals("JsonIgnore")
|| name.equals("Transient")
|| name.equals("JsonIgnoreType");
}
private static boolean isJsonFieldSerializeDisabled(AnnotationExpr annotation) {
if (annotation instanceof NormalAnnotationExpr) {
for (MemberValuePair pair : ((NormalAnnotationExpr) annotation).getPairs()) {
if (pair.getNameAsString().equals("serialize")
&& isFalseLiteral(pair.getValue())) {
return true;
}
}
}
return false;
}
private static void collectIgnoredNames(AnnotationExpr annotation, Set<String> out) {
if (annotation instanceof SingleMemberAnnotationExpr) {
if (((SingleMemberAnnotationExpr) annotation).getMemberValue() instanceof StringLiteralExpr) {
return ((StringLiteralExpr) ((SingleMemberAnnotationExpr) annotation)
.getMemberValue()).asString();
addStringArray(((SingleMemberAnnotationExpr) annotation).getMemberValue(), out);
return;
}
if (annotation instanceof NormalAnnotationExpr) {
for (MemberValuePair pair : ((NormalAnnotationExpr) annotation).getPairs()) {
if ("value".equals(pair.getNameAsString())) {
addStringArray(pair.getValue(), out);
}
}
}
}
private static void addStringArray(Expression expr, Set<String> out) {
if (expr instanceof StringLiteralExpr) {
out.add(((StringLiteralExpr) expr).asString());
return;
}
if (expr instanceof ArrayInitializerExpr) {
for (Expression value : ((ArrayInitializerExpr) expr).getValues()) {
if (value instanceof StringLiteralExpr) {
out.add(((StringLiteralExpr) value).asString());
}
}
}
}
private static String stringMember(AnnotationExpr annotation, String member) {
if (annotation instanceof SingleMemberAnnotationExpr) {
Expression value = ((SingleMemberAnnotationExpr) annotation).getMemberValue();
if (value instanceof StringLiteralExpr) {
return ((StringLiteralExpr) value).asString();
}
}
if (annotation instanceof NormalAnnotationExpr) {
for (MemberValuePair pair : ((NormalAnnotationExpr) annotation).getPairs()) {
if (pair.getNameAsString().equals("value")
if (pair.getNameAsString().equals(member)
&& pair.getValue() instanceof StringLiteralExpr) {
return ((StringLiteralExpr) pair.getValue()).asString();
}
@@ -80,4 +137,14 @@ public final class AnnotationSupport {
}
return null;
}
private static boolean isFalseLiteral(Expression expr) {
return expr instanceof BooleanLiteralExpr && !((BooleanLiteralExpr) expr).getValue();
}
private static String simpleName(AnnotationExpr annotation) {
String name = annotation.getNameAsString();
int dot = name.lastIndexOf('.');
return dot >= 0 ? name.substring(dot + 1) : name;
}
}

View File

@@ -79,6 +79,7 @@ public class JavaSchemaExtractor {
Set<String> nextAncestors = new LinkedHashSet<>(ancestors);
nextAncestors.add(type.getFqn());
Set<String> classIgnored = AnnotationSupport.ignoredProperties(type.getDeclaration());
for (FieldDeclaration field : collectFields(type, new HashSet<>())) {
if (field.isStatic() || field.isTransient()) {
continue;
@@ -88,6 +89,9 @@ public class JavaSchemaExtractor {
}
for (VariableDeclarator var : field.getVariables()) {
String jsonName = AnnotationSupport.jsonName(field, var.getNameAsString());
if (classIgnored.contains(jsonName) || classIgnored.contains(var.getNameAsString())) {
continue;
}
String path = prefix.isEmpty() ? jsonName : prefix + "." + jsonName;
expandType(var.getType(), path, type, schema, nextAncestors, depth);
}

View File

@@ -8,9 +8,10 @@ import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.TypeDeclaration;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 一次提交快照下的源码类型索引。仅索引本仓库源码(不含依赖 jar供类型解析与字段展开使用。
@@ -21,9 +22,9 @@ import java.util.Map;
public class SourceIndex {
/** FQN以 . 分隔,含内部类) -> 类型信息 */
private final Map<String, IndexedType> byFqn = new LinkedHashMap<>();
private final Map<String, IndexedType> byFqn = new ConcurrentHashMap<>();
/** 简单类名 -> FQN 列表(兜底解析) */
private final Map<String, List<String>> bySimpleName = new LinkedHashMap<>();
private final Map<String, List<String>> bySimpleName = new ConcurrentHashMap<>();
static {
ParserConfiguration config = new ParserConfiguration()
@@ -31,6 +32,20 @@ public class SourceIndex {
StaticJavaParser.setConfiguration(config);
}
/**
* 批量解析源文件并建立索引。
* <p>文件读取可在 {@link com.codechecker.cache.analyze.FileScanner} 中并行;
* AST 解析使用 StaticJavaParser 串行写入,避免其全局配置的线程安全问题。</p>
*/
public void addSources(Collection<String> contents) {
if (contents == null || contents.isEmpty()) {
return;
}
for (String content : contents) {
addSource(content);
}
}
/**
* 解析并加入一个 Java 源文件内容。解析失败时静默跳过(返回 false
*/
@@ -65,7 +80,6 @@ public class SourceIndex {
byFqn.put(fqn, indexed);
bySimpleName.computeIfAbsent(simpleName, k -> new ArrayList<>()).add(fqn);
}
// 递归内部类型
for (Object member : type.getMembers()) {
if (member instanceof TypeDeclaration) {
registerType((TypeDeclaration<?>) member, packageName, imports, fqn);