@@ -1,313 +0,0 @@
|
||||
package com.codechecker.api.parser;
|
||||
|
||||
import com.codechecker.api.model.EndpointSnapshot;
|
||||
import com.codechecker.api.model.MethodParameterSnapshot;
|
||||
import com.codechecker.parser.TypeNameUtils;
|
||||
import com.github.javaparser.StaticJavaParser;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.github.javaparser.ast.NodeList;
|
||||
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
|
||||
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.expr.AnnotationExpr;
|
||||
import com.github.javaparser.ast.expr.Expression;
|
||||
import com.github.javaparser.ast.expr.NormalAnnotationExpr;
|
||||
import com.github.javaparser.ast.type.Type;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 解析 Controller / Feign 接口完整快照(含入参明细)。
|
||||
*/
|
||||
public class EndpointSnapshotParser {
|
||||
private static final Set<String> MAPPING_ANNOTATIONS = Set.of(
|
||||
"GetMapping", "PostMapping", "PutMapping", "DeleteMapping", "PatchMapping", "RequestMapping"
|
||||
);
|
||||
private static final Map<String, String> MAPPING_DEFAULT_METHOD = Map.of(
|
||||
"GetMapping", "GET",
|
||||
"PostMapping", "POST",
|
||||
"PutMapping", "PUT",
|
||||
"DeleteMapping", "DELETE",
|
||||
"PatchMapping", "PATCH"
|
||||
);
|
||||
private static final Set<String> FRAMEWORK_PARAM_TYPES = Set.of(
|
||||
"HttpServletRequest", "HttpServletResponse", "BindingResult", "Principal",
|
||||
"Authentication", "Model", "ModelMap", "UriComponentsBuilder", "WebRequest",
|
||||
"NativeWebRequest", "Errors", "Locale"
|
||||
);
|
||||
|
||||
private final boolean excludeFrameworkParams;
|
||||
|
||||
public EndpointSnapshotParser(boolean excludeFrameworkParams) {
|
||||
this.excludeFrameworkParams = excludeFrameworkParams;
|
||||
}
|
||||
|
||||
public List<EndpointSnapshot> parseSource(String source, String sourceFile, boolean feignMode) {
|
||||
if (source == null || source.isBlank()) {
|
||||
return List.of();
|
||||
}
|
||||
CompilationUnit cu = StaticJavaParser.parse(source);
|
||||
List<EndpointSnapshot> snapshots = new ArrayList<>();
|
||||
for (TypeDeclaration<?> type : cu.getTypes()) {
|
||||
if (!(type instanceof ClassOrInterfaceDeclaration)) {
|
||||
continue;
|
||||
}
|
||||
ClassOrInterfaceDeclaration decl = (ClassOrInterfaceDeclaration) type;
|
||||
if (feignMode && !isFeignClient(decl)) {
|
||||
continue;
|
||||
}
|
||||
if (!feignMode && !isController(decl)) {
|
||||
continue;
|
||||
}
|
||||
String basePath = feignMode
|
||||
? joinPaths(extractFeignBasePath(decl), extractTypeLevelPath(decl))
|
||||
: extractTypeLevelPath(decl);
|
||||
String className = decl.getNameAsString();
|
||||
for (MethodDeclaration method : decl.getMethods()) {
|
||||
if (feignMode && !decl.isInterface()) {
|
||||
continue;
|
||||
}
|
||||
if (!feignMode && decl.isInterface()) {
|
||||
continue;
|
||||
}
|
||||
snapshots.addAll(parseMethod(method, basePath, sourceFile, className));
|
||||
}
|
||||
}
|
||||
return snapshots;
|
||||
}
|
||||
|
||||
private List<EndpointSnapshot> parseMethod(MethodDeclaration method, String basePath,
|
||||
String sourceFile, String className) {
|
||||
List<EndpointSnapshot> result = new ArrayList<>();
|
||||
for (AnnotationExpr annotation : method.getAnnotations()) {
|
||||
String annName = annotation.getNameAsString();
|
||||
if (!MAPPING_ANNOTATIONS.contains(annName)) {
|
||||
continue;
|
||||
}
|
||||
List<String> subPaths = readStringArray(annotation, "value", "path");
|
||||
List<String> httpMethods = extractHttpMethods(annotation, annName);
|
||||
List<MethodParameterSnapshot> params = extractParameters(method);
|
||||
String methodDescription = MethodDescriptionExtractor.extract(method);
|
||||
String fingerprint = EndpointSnapshot.buildFingerprint(sourceFile, method.getNameAsString());
|
||||
for (String httpMethod : httpMethods) {
|
||||
for (String subPath : subPaths) {
|
||||
String uri = joinPaths(basePath, subPath);
|
||||
result.add(new EndpointSnapshot(fingerprint, httpMethod, uri, sourceFile,
|
||||
className, method.getNameAsString(), methodDescription, params));
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<MethodParameterSnapshot> extractParameters(MethodDeclaration method) {
|
||||
Map<String, String> paramDescriptions = MethodParamJavadocExtractor.extract(method);
|
||||
List<MethodParameterSnapshot> params = new ArrayList<>();
|
||||
for (Parameter parameter : method.getParameters()) {
|
||||
String typeName = TypeNameUtils.typeToString(parameter.getType());
|
||||
String simple = TypeNameUtils.simpleName(typeName);
|
||||
if (excludeFrameworkParams && FRAMEWORK_PARAM_TYPES.contains(simple)) {
|
||||
continue;
|
||||
}
|
||||
String source = resolveParamSource(parameter);
|
||||
String paramName = parameter.getNameAsString();
|
||||
String bindingName = resolveBindingName(parameter, source, paramName);
|
||||
boolean required = resolveRequired(parameter, source);
|
||||
String dtoName = "body".equals(source) ? simple : "";
|
||||
String description = paramDescriptions.getOrDefault(paramName, "");
|
||||
params.add(new MethodParameterSnapshot(
|
||||
paramName,
|
||||
bindingName,
|
||||
typeName,
|
||||
source,
|
||||
required,
|
||||
description,
|
||||
dtoName
|
||||
));
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
private String resolveBindingName(Parameter parameter, String source, String paramName) {
|
||||
if (!"path".equals(source) && !"query".equals(source)) {
|
||||
return paramName;
|
||||
}
|
||||
String annName = "path".equals(source) ? "PathVariable" : "RequestParam";
|
||||
for (AnnotationExpr ann : parameter.getAnnotations()) {
|
||||
if (!annName.equals(ann.getNameAsString())) {
|
||||
continue;
|
||||
}
|
||||
List<String> bindings = readStringArray(ann, "value", "name");
|
||||
for (String binding : bindings) {
|
||||
if (binding != null && !binding.isBlank()) {
|
||||
return binding;
|
||||
}
|
||||
}
|
||||
}
|
||||
return paramName;
|
||||
}
|
||||
|
||||
private String resolveParamSource(Parameter parameter) {
|
||||
for (AnnotationExpr ann : parameter.getAnnotations()) {
|
||||
String name = ann.getNameAsString();
|
||||
if ("RequestBody".equals(name)) {
|
||||
return "body";
|
||||
}
|
||||
if ("PathVariable".equals(name)) {
|
||||
return "path";
|
||||
}
|
||||
if ("RequestParam".equals(name)) {
|
||||
return "query";
|
||||
}
|
||||
}
|
||||
return "simple";
|
||||
}
|
||||
|
||||
private boolean resolveRequired(Parameter parameter, String source) {
|
||||
if ("query".equals(source)) {
|
||||
for (AnnotationExpr ann : parameter.getAnnotations()) {
|
||||
if ("RequestParam".equals(ann.getNameAsString()) && ann.isNormalAnnotationExpr()) {
|
||||
for (var pair : ann.asNormalAnnotationExpr().getPairs()) {
|
||||
if ("required".equals(pair.getNameAsString())) {
|
||||
return !"false".equalsIgnoreCase(pair.getValue().toString().trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return !"query".equals(source);
|
||||
}
|
||||
|
||||
private boolean isController(ClassOrInterfaceDeclaration decl) {
|
||||
return decl.getAnnotations().stream()
|
||||
.anyMatch(ann -> {
|
||||
String n = ann.getNameAsString();
|
||||
return "RestController".equals(n) || "Controller".equals(n);
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isFeignClient(ClassOrInterfaceDeclaration decl) {
|
||||
return decl.isInterface() && decl.getAnnotations().stream()
|
||||
.anyMatch(ann -> "FeignClient".equals(ann.getNameAsString()));
|
||||
}
|
||||
|
||||
private String extractTypeLevelPath(ClassOrInterfaceDeclaration decl) {
|
||||
for (AnnotationExpr annotation : decl.getAnnotations()) {
|
||||
if ("RequestMapping".equals(annotation.getNameAsString())) {
|
||||
List<String> paths = readStringArray(annotation, "value", "path");
|
||||
if (!paths.isEmpty()) {
|
||||
return paths.get(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private String extractFeignBasePath(ClassOrInterfaceDeclaration decl) {
|
||||
for (AnnotationExpr annotation : decl.getAnnotations()) {
|
||||
if ("FeignClient".equals(annotation.getNameAsString())) {
|
||||
List<String> paths = readStringArray(annotation, "path");
|
||||
if (!paths.isEmpty()) {
|
||||
return paths.get(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private List<String> extractHttpMethods(AnnotationExpr annotation, String annName) {
|
||||
if (!"RequestMapping".equals(annName)) {
|
||||
return List.of(MAPPING_DEFAULT_METHOD.getOrDefault(annName, "GET"));
|
||||
}
|
||||
List<String> methods = readEnumArray(annotation, "method");
|
||||
return methods.isEmpty() ? List.of("GET") : methods;
|
||||
}
|
||||
|
||||
private String joinPaths(String base, String sub) {
|
||||
String normalizedBase = normalizePath(base);
|
||||
String normalizedSub = normalizePath(sub);
|
||||
if (normalizedBase.isEmpty()) {
|
||||
return normalizedSub.isEmpty() ? "/" : normalizedSub;
|
||||
}
|
||||
if (normalizedSub.isEmpty()) {
|
||||
return normalizedBase;
|
||||
}
|
||||
return (normalizedBase + "/" + normalizedSub.substring(1)).replaceAll("/+", "/");
|
||||
}
|
||||
|
||||
private String normalizePath(String path) {
|
||||
if (path == null || path.isBlank()) {
|
||||
return "";
|
||||
}
|
||||
String trimmed = path.trim();
|
||||
if (!trimmed.startsWith("/")) {
|
||||
trimmed = "/" + trimmed;
|
||||
}
|
||||
return trimmed.replaceAll("/+", "/");
|
||||
}
|
||||
|
||||
private List<String> readStringArray(AnnotationExpr annotation, String... keys) {
|
||||
NodeList<?> values = readArrayValues(annotation, keys);
|
||||
List<String> result = new ArrayList<>();
|
||||
for (Object value : values) {
|
||||
String text = value.toString().replace("\"", "").trim();
|
||||
if (!text.isBlank()) {
|
||||
result.add(text);
|
||||
}
|
||||
}
|
||||
if (result.isEmpty()) {
|
||||
result.add("");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<String> readEnumArray(AnnotationExpr annotation, String key) {
|
||||
NodeList<?> values = readArrayValues(annotation, key);
|
||||
List<String> result = new ArrayList<>();
|
||||
for (Object value : values) {
|
||||
String text = value.toString().trim();
|
||||
if (text.contains(".")) {
|
||||
text = text.substring(text.lastIndexOf('.') + 1);
|
||||
}
|
||||
result.add(text.toUpperCase(Locale.ROOT));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private NodeList<?> readArrayValues(AnnotationExpr annotation, String... keys) {
|
||||
if (annotation.isSingleMemberAnnotationExpr()) {
|
||||
Expression value = annotation.asSingleMemberAnnotationExpr().getMemberValue();
|
||||
if (value.isArrayInitializerExpr()) {
|
||||
return value.asArrayInitializerExpr().getValues();
|
||||
}
|
||||
return new NodeList<>(value);
|
||||
}
|
||||
if (annotation.isNormalAnnotationExpr()) {
|
||||
var pairs = annotation.asNormalAnnotationExpr().getPairs();
|
||||
for (var pair : pairs) {
|
||||
for (String key : keys) {
|
||||
if (pair.getNameAsString().equals(key)) {
|
||||
if (pair.getValue().isArrayInitializerExpr()) {
|
||||
return pair.getValue().asArrayInitializerExpr().getValues();
|
||||
}
|
||||
return new NodeList<>(pair.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var pair : pairs) {
|
||||
if ("value".equals(pair.getNameAsString())) {
|
||||
if (pair.getValue().isArrayInitializerExpr()) {
|
||||
return pair.getValue().asArrayInitializerExpr().getValues();
|
||||
}
|
||||
return new NodeList<>(pair.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
return new NodeList<>();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user