feat: 项目整体命名修改cache-schema-checker
All checks were successful
缓存序列化结构检查 / cache-schema-check (push) Has been skipped
All checks were successful
缓存序列化结构检查 / cache-schema-check (push) Has been skipped
This commit is contained in:
143
src/main/java/com/codechecker/cache/key/RedisKeyResolver.java
vendored
Normal file
143
src/main/java/com/codechecker/cache/key/RedisKeyResolver.java
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
package com.codechecker.cache.key;
|
||||
|
||||
import com.codechecker.cache.schema.SourceIndex;
|
||||
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
|
||||
import com.github.javaparser.ast.body.FieldDeclaration;
|
||||
import com.github.javaparser.ast.body.MethodDeclaration;
|
||||
import com.github.javaparser.ast.body.VariableDeclarator;
|
||||
import com.github.javaparser.ast.expr.BinaryExpr;
|
||||
import com.github.javaparser.ast.expr.Expression;
|
||||
import com.github.javaparser.ast.expr.FieldAccessExpr;
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr;
|
||||
import com.github.javaparser.ast.expr.NameExpr;
|
||||
import com.github.javaparser.ast.expr.StringLiteralExpr;
|
||||
import com.github.javaparser.ast.stmt.ReturnStmt;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 尽力将 Redis key 表达式静态推断为一个「key 模式」,动态部分以 {@code *} 表示。
|
||||
*/
|
||||
public class RedisKeyResolver {
|
||||
|
||||
private static final int MAX_DEPTH = 6;
|
||||
|
||||
private final SourceIndex index;
|
||||
|
||||
public RedisKeyResolver(SourceIndex index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public String resolve(Expression keyExpr, ClassOrInterfaceDeclaration enclosingClass,
|
||||
SourceIndex.IndexedType context) {
|
||||
String raw = resolveExpr(keyExpr, enclosingClass, context, 0);
|
||||
return normalize(raw);
|
||||
}
|
||||
|
||||
private String resolveExpr(Expression expr, ClassOrInterfaceDeclaration enclosingClass,
|
||||
SourceIndex.IndexedType context, int depth) {
|
||||
if (expr == null || depth > MAX_DEPTH) {
|
||||
return "*";
|
||||
}
|
||||
if (expr instanceof StringLiteralExpr) {
|
||||
return ((StringLiteralExpr) expr).asString();
|
||||
}
|
||||
if (expr instanceof BinaryExpr) {
|
||||
BinaryExpr be = (BinaryExpr) expr;
|
||||
if (be.getOperator() == BinaryExpr.Operator.PLUS) {
|
||||
return resolveExpr(be.getLeft(), enclosingClass, context, depth + 1)
|
||||
+ resolveExpr(be.getRight(), enclosingClass, context, depth + 1);
|
||||
}
|
||||
return "*";
|
||||
}
|
||||
if (expr instanceof NameExpr) {
|
||||
String name = ((NameExpr) expr).getNameAsString();
|
||||
String constVal = lookupConstant(enclosingClass, name);
|
||||
if (constVal != null) {
|
||||
return constVal;
|
||||
}
|
||||
String methodVal = lookupMethodReturn(enclosingClass, name, context, depth);
|
||||
return methodVal != null ? methodVal : "*";
|
||||
}
|
||||
if (expr instanceof FieldAccessExpr) {
|
||||
FieldAccessExpr fae = (FieldAccessExpr) expr;
|
||||
String fieldName = fae.getNameAsString();
|
||||
String scope = fae.getScope().toString();
|
||||
String external = lookupExternalConstant(scope, fieldName, context);
|
||||
if (external != null) {
|
||||
return external;
|
||||
}
|
||||
String local = lookupConstant(enclosingClass, fieldName);
|
||||
return local != null ? local : "*";
|
||||
}
|
||||
if (expr instanceof MethodCallExpr) {
|
||||
MethodCallExpr call = (MethodCallExpr) expr;
|
||||
String name = call.getNameAsString();
|
||||
if ("format".equals(name) && !call.getArguments().isEmpty()) {
|
||||
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) {
|
||||
if (clazz == null) {
|
||||
return null;
|
||||
}
|
||||
for (FieldDeclaration field : clazz.getFields()) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String lookupExternalConstant(String scopeName, String fieldName, SourceIndex.IndexedType context) {
|
||||
String fqn = index.resolveFqn(scopeName, context);
|
||||
if (fqn == null) {
|
||||
return null;
|
||||
}
|
||||
SourceIndex.IndexedType type = index.get(fqn);
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
return lookupConstant(type.getDeclaration(), fieldName);
|
||||
}
|
||||
|
||||
private String lookupMethodReturn(ClassOrInterfaceDeclaration clazz, String methodName,
|
||||
SourceIndex.IndexedType context, int depth) {
|
||||
if (clazz == null || depth > MAX_DEPTH) {
|
||||
return null;
|
||||
}
|
||||
for (MethodDeclaration method : clazz.getMethods()) {
|
||||
if (method.getNameAsString().equals(methodName) && method.getBody().isPresent()) {
|
||||
for (ReturnStmt ret : method.getBody().get().findAll(ReturnStmt.class)) {
|
||||
if (ret.getExpression().isPresent()) {
|
||||
return resolveExpr(ret.getExpression().get(), clazz, context, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String normalize(String raw) {
|
||||
if (raw == null || raw.isEmpty()) {
|
||||
return "unknown-key";
|
||||
}
|
||||
String collapsed = raw.replaceAll("\\*+", "*");
|
||||
if (collapsed.equals("*")) {
|
||||
return "unknown-key";
|
||||
}
|
||||
return collapsed;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user