167 lines
6.2 KiB
Java
167 lines
6.2 KiB
Java
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<FieldChange> diff(List<FieldInfo> oldFields, List<FieldInfo> newFields) {
|
||
Map<String, FieldInfo> oldMap = toMap(oldFields);
|
||
Map<String, FieldInfo> newMap = toMap(newFields);
|
||
|
||
List<FieldChange> modified = new ArrayList<>();
|
||
List<FieldInfo> added = new ArrayList<>();
|
||
List<FieldInfo> 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<FieldChange> renamed = pairRenames(removed, added);
|
||
return mergeInOrder(newFields, renamed, modified, added, removed);
|
||
}
|
||
|
||
/**
|
||
* 将删除+新增配对为字段重命名。
|
||
* 优先:说明相同且类型相同;其次:说明相同但类型不同(重命名+改类型)。
|
||
*/
|
||
private List<FieldChange> pairRenames(List<FieldInfo> removed, List<FieldInfo> added) {
|
||
List<FieldChange> renames = new ArrayList<>();
|
||
Set<FieldInfo> matchedRemoved = new LinkedHashSet<>();
|
||
Set<FieldInfo> 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<FieldInfo> added,
|
||
Set<FieldInfo> 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<FieldChange> mergeInOrder(List<FieldInfo> newFields, List<FieldChange> renamed,
|
||
List<FieldChange> modified, List<FieldInfo> added,
|
||
List<FieldInfo> removed) {
|
||
Map<String, FieldChange> renamedByNewName = new LinkedHashMap<>();
|
||
for (FieldChange change : renamed) {
|
||
renamedByNewName.put(change.getFieldName(), change);
|
||
}
|
||
|
||
Map<String, FieldChange> modifiedByName = new LinkedHashMap<>();
|
||
for (FieldChange change : modified) {
|
||
modifiedByName.put(change.getFieldName(), change);
|
||
}
|
||
|
||
Set<String> emitted = new LinkedHashSet<>();
|
||
List<FieldChange> 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<String, FieldInfo> toMap(List<FieldInfo> fields) {
|
||
Map<String, FieldInfo> 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();
|
||
}
|
||
}
|