package com.aicheck.analyzer; import com.aicheck.model.FieldChange; import com.aicheck.model.FieldInfo; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * 对比新旧字段列表,产出新增/删除/类型修改/重命名(纯注释变更忽略)。 */ public class FieldDiffEngine { /** * 按字段名对比;删除+新增且说明匹配时合并为重命名。 * 输出顺序:按新字段声明顺序,未配对的删除字段置于末尾。 */ public List diff(List oldFields, List newFields) { Map oldMap = toMap(oldFields); Map newMap = toMap(newFields); List modified = new ArrayList<>(); List added = new ArrayList<>(); List removed = new ArrayList<>(); for (FieldInfo newField : newFields) { FieldInfo oldField = oldMap.get(newField.getName()); if (oldField == null) { added.add(newField); } else if (!oldField.getType().equals(newField.getType())) { modified.add(FieldChange.modified(oldField, newField, buildTypeDetail(oldField, newField))); } // 仅 @Schema / 注释文案变化:不纳入字段变更 } for (FieldInfo oldField : oldFields) { if (!newMap.containsKey(oldField.getName())) { removed.add(oldField); } } List renamed = pairRenames(removed, added); return mergeInOrder(newFields, renamed, modified, added, removed); } /** * 将删除+新增配对为字段重命名。 * 优先:说明相同且类型相同;其次:说明相同但类型不同(重命名+改类型)。 */ private List pairRenames(List removed, List added) { List renames = new ArrayList<>(); Set matchedRemoved = new LinkedHashSet<>(); Set matchedAdded = new LinkedHashSet<>(); for (FieldInfo oldField : removed) { FieldInfo pair = findRenamePair(oldField, added, matchedAdded, true); if (pair == null) { pair = findRenamePair(oldField, added, matchedAdded, false); } if (pair != null) { renames.add(FieldChange.renamed(oldField, pair)); matchedRemoved.add(oldField); matchedAdded.add(pair); } } removed.removeIf(matchedRemoved::contains); added.removeIf(matchedAdded::contains); return renames; } private FieldInfo findRenamePair(FieldInfo removed, List added, Set excluded, boolean requireSameType) { for (FieldInfo candidate : added) { if (excluded.contains(candidate)) { continue; } if (!descriptionsMatch(removed, candidate)) { continue; } if (requireSameType && !removed.getType().equals(candidate.getType())) { continue; } if (!requireSameType && removed.getType().equals(candidate.getType())) { continue; } return candidate; } return null; } /** 说明相同(非空)或双方均为空时视为匹配 */ private boolean descriptionsMatch(FieldInfo oldField, FieldInfo newField) { String oldDesc = normalizeDescription(oldField.getDescription()); String newDesc = normalizeDescription(newField.getDescription()); if (oldDesc.isEmpty() && newDesc.isEmpty()) { return true; } if (oldDesc.isEmpty() || newDesc.isEmpty()) { return false; } return oldDesc.equals(newDesc); } private String normalizeDescription(String description) { return description == null ? "" : description.trim(); } /** 按新字段声明顺序合并各变更类型 */ private List mergeInOrder(List newFields, List renamed, List modified, List added, List removed) { Map renamedByNewName = new LinkedHashMap<>(); for (FieldChange change : renamed) { renamedByNewName.put(change.getFieldName(), change); } Map modifiedByName = new LinkedHashMap<>(); for (FieldChange change : modified) { modifiedByName.put(change.getFieldName(), change); } Set emitted = new LinkedHashSet<>(); List result = new ArrayList<>(); for (FieldInfo newField : newFields) { String name = newField.getName(); if (renamedByNewName.containsKey(name)) { result.add(renamedByNewName.get(name)); emitted.add(name); } else if (modifiedByName.containsKey(name)) { result.add(modifiedByName.get(name)); emitted.add(name); } else if (added.stream().anyMatch(f -> f.getName().equals(name))) { result.add(FieldChange.added(newField)); emitted.add(name); } } for (FieldInfo oldField : removed) { result.add(FieldChange.removed(oldField)); } return result; } /** 字段列表转 LinkedHashMap,保持声明顺序 */ private Map toMap(List fields) { Map map = new LinkedHashMap<>(); for (FieldInfo field : fields) { map.put(field.getName(), field); } return map; } /** 构造类型变化描述,如 Integer → String */ private String buildTypeDetail(FieldInfo oldField, FieldInfo newField) { if (oldField.getType().equals(newField.getType())) { return ""; } return oldField.getType() + " → " + newField.getType(); } }