package com.codechecker.analyzer; import com.codechecker.config.AppConfig; import com.codechecker.model.ApiEndpoint; import com.codechecker.model.ClassChangeReport; import com.codechecker.model.ClassType; import java.util.ArrayList; import com.codechecker.parser.ConversionParser; import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * 根据变更报告匹配受影响的 HTTP 接口与 Dto→Entity 转换目标。 */ public class ImpactAnalyzer { private final ConversionParser conversionParser = new ConversionParser(); /** * 填充 report 的影响列表;新旧类名均参与匹配;Entity/Model 不匹配接口。 */ public void analyze(ClassChangeReport report, Map endpointIndex, AppConfig config, Path repoRoot, String newSource, String oldSource, DtoNestIndex nestIndex) throws IOException { Set matchNames = namesForMatching(report, nestIndex); if (report.getClassType() != ClassType.ENTITY && report.getClassType() != ClassType.MODEL) { matchEndpoints(report, endpointIndex, matchNames); } report.setObjectRoleLabels(NestedObjectRoleResolver.resolve(report, nestIndex, endpointIndex)); if (!config.isDtoEntityConversionEnabled()) { return; } analyzeConversion(report, config, repoRoot, newSource, oldSource, matchNames); } /** 收集新旧类名及嵌套祖先 Dto/Vo,用于接口/转换匹配 */ private Set namesForMatching(ClassChangeReport report, DtoNestIndex nestIndex) { Set names = new LinkedHashSet<>(); names.add(report.getClassName()); if (report.getOldClassName() != null && !report.getOldClassName().isBlank()) { names.add(report.getOldClassName()); } if (nestIndex != null && (report.getClassType() == ClassType.DTO || report.getClassType() == ClassType.VO)) { for (String name : new ArrayList<>(names)) { names.addAll(nestIndex.expandImpactNames(name)); } } return names; } /** 在接口索引中匹配入参/返回类型 */ private void matchEndpoints(ClassChangeReport report, Map endpointIndex, Set matchNames) { List inputImpacts = new ArrayList<>(); List frontendImpacts = new ArrayList<>(); for (ApiEndpoint endpoint : endpointIndex.values()) { if (matchesAnyType(endpoint.getParamTypes(), matchNames)) { inputImpacts.add(endpoint); } if (matchesAnyType(endpoint.getReturnTypes(), matchNames)) { frontendImpacts.add(endpoint); } } inputImpacts.forEach(report::addInputImpact); frontendImpacts.forEach(report::addFrontendImpact); } /** 扫描 convert 方法与 BeanUtils.copyProperties 关联的 Entity */ private void analyzeConversion(ClassChangeReport report, AppConfig config, Path repoRoot, String newSource, String oldSource, Set matchNames) throws IOException { for (String name : matchNames) { if (newSource != null && !newSource.isBlank()) { conversionParser.findConvertTargetsInClass(newSource, name) .forEach(report::addConversionEntity); } if (oldSource != null && !oldSource.isBlank() && !oldSource.equals(newSource)) { conversionParser.findConvertTargetsInClass(oldSource, name) .forEach(report::addConversionEntity); } for (String scanDir : config.getConversionScanDirs()) { conversionParser.findBeanUtilsTargets(repoRoot.resolve(scanDir), name) .forEach(report::addConversionEntity); } } } /** 类型集合中是否包含任一目标类名 */ private boolean matchesAnyType(Collection types, Set classNames) { if (types == null) { return false; } for (String type : types) { if (classNames.contains(type)) { return true; } } return false; } }