package com.freetmp.mbg.merge;
import com.freetmp.mbg.merge.comment.BlockCommentMerger;
import com.freetmp.mbg.merge.comment.JavadocCommentMerger;
import com.freetmp.mbg.merge.comment.LineCommentMerger;
import com.freetmp.mbg.merge.declaration.*;
import com.freetmp.mbg.merge.expression.*;
import com.freetmp.mbg.merge.parameter.MultiTypeParameterMerger;
import com.freetmp.mbg.merge.parameter.ParameterMerger;
import com.freetmp.mbg.merge.parameter.TypeParameterMerger;
import com.freetmp.mbg.merge.statement.*;
import com.freetmp.mbg.merge.type.*;
import com.freetmp.mbg.merge.variable.VariableDeclaratorIdMerger;
import com.freetmp.mbg.merge.variable.VariableDeclaratorMerger;
import com.github.javaparser.ast.*;
import com.github.javaparser.ast.body.*;
import com.github.javaparser.ast.comments.BlockComment;
import com.github.javaparser.ast.comments.Comment;
import com.github.javaparser.ast.comments.JavadocComment;
import com.github.javaparser.ast.comments.LineComment;
import com.github.javaparser.ast.expr.*;
import com.github.javaparser.ast.stmt.*;
import com.github.javaparser.ast.type.*;
import info.debatty.java.stringsimilarity.Levenshtein;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
/**
* Created by LiuPin on 2015/3/27.
*/
@SuppressWarnings("unchecked")
public abstract class AbstractMerger<M extends Node> {
protected static ConcurrentHashMap<Class, AbstractMerger> map = new ConcurrentHashMap<>();
static {
// comment
map.put(BlockComment.class, new BlockCommentMerger());
map.put(JavadocComment.class, new JavadocCommentMerger());
map.put(LineComment.class, new LineCommentMerger());
// declaration
map.put(AnnotationDeclaration.class, new AnnotationDeclarationMerger());
map.put(AnnotationMemberDeclaration.class, new AnnotationDeclarationMerger());
map.put(BodyDeclaration.class, new BodyDeclarationMerger());
map.put(ClassOrInterfaceDeclaration.class, new ClassOrInterfaceDeclarationMerger());
map.put(ConstructorDeclaration.class, new ConstructorDeclarationMerger());
map.put(EmptyMemberDeclaration.class, new EmptyMemberDeclarationMerger());
map.put(EmptyTypeDeclaration.class, new EmptyTypeDeclarationMerger());
map.put(EnumConstantDeclaration.class, new EnumConstantDeclarationMerger());
map.put(EnumDeclaration.class, new EnumDeclarationMerger());
map.put(FieldDeclaration.class, new FieldDeclarationMerger());
map.put(InitializerDeclaration.class, new InitializerDeclarationMerger());
map.put(MethodDeclaration.class, new MethodDeclarationMerger());
map.put(PackageDeclaration.class, new PackageDeclarationMerger());
map.put(ImportDeclaration.class, new ImportDeclarationMerger());
// expression
map.put(Expression.class, new ExpressionMerger());
map.put(MarkerAnnotationExpr.class, new MarkerAnnotationExprMerger());
map.put(NormalAnnotationExpr.class, new NormalAnnotationExprMerger());
map.put(SingleMemberAnnotationExpr.class, new SingleMemberAnnotationExprMerger());
map.put(ArrayAccessExpr.class, new ArrayAccessExprMerger());
map.put(ArrayCreationExpr.class, new ArrayAccessExprMerger());
map.put(ArrayInitializerExpr.class, new ArrayInitializerExprMerger());
map.put(AssignExpr.class, new AssignExprMerger());
map.put(BinaryExpr.class, new BinaryExprMerger());
map.put(BooleanLiteralExpr.class, new BooleanLiteralExprMerger());
map.put(CastExpr.class, new CastExprMerger());
map.put(CharLiteralExpr.class, new CharLiteralExprMerger());
map.put(ClassExpr.class, new ClassExprMerger());
map.put(ConditionalExpr.class, new ConditionalExprMerger());
map.put(DoubleLiteralExpr.class, new DoubleLiteralExprMerger());
map.put(EnclosedExpr.class, new EnclosedExprMerger());
map.put(FieldAccessExpr.class, new FieldAccessExprMerger());
map.put(InstanceOfExpr.class, new InstanceOfExprMerger());
map.put(IntegerLiteralExpr.class, new IntegerLiteralExprMerger());
map.put(IntegerLiteralMinValueExpr.class, new IntegerLiteralMinValueExprMerger());
map.put(LambdaExpr.class, new LambdaExprMerger());
map.put(LongLiteralExpr.class, new LongLiteralExprMerger());
map.put(LongLiteralMinValueExpr.class, new LongLiteralMinValueExprMerger());
map.put(MemberValuePair.class, new MemberValuePairMerger());
map.put(MethodCallExpr.class, new MethodCallExprMerger());
map.put(MethodReferenceExpr.class, new MethodReferenceExprMerger());
map.put(NameExpr.class, new NameExprMerger());
map.put(NullLiteralExpr.class, new NullLiteralExprMerger());
map.put(ObjectCreationExpr.class, new ObjectCreationExprMerger());
map.put(QualifiedNameExpr.class, new QualifiedNameExprMerger());
map.put(StringLiteralExpr.class, new StringLiteralExprMerger());
map.put(SuperExpr.class, new SuperExprMerger());
map.put(ThisExpr.class, new ThisExprMerger());
map.put(TypeExpr.class, new TypeExprMerger());
map.put(UnaryExpr.class, new UnaryExprMerger());
map.put(VariableDeclarationExpr.class, new VariableDeclarationExprMerger());
// parameter
map.put(Parameter.class, new ParameterMerger());
map.put(MultiTypeParameter.class, new MultiTypeParameterMerger());
map.put(TypeParameter.class, new TypeParameterMerger());
// statement
map.put(BlockStmt.class, new BlockStmtMerger());
map.put(AssertStmt.class, new AssertStmtMerger());
map.put(BreakStmt.class, new BreakStmtMerger());
map.put(CatchClause.class, new CatchClauseMerger());
map.put(ContinueStmt.class, new ContinueStmtMerger());
map.put(DoStmt.class, new DoStmtMerger());
map.put(EmptyStmt.class, new EmptyStmtMerger());
map.put(ExplicitConstructorInvocationStmt.class, new ExplicitConstructorInvocationStmtMerger());
map.put(ExpressionStmt.class, new ExpressionStmtMerger());
map.put(ForeachStmt.class, new ForeachStmtMerger());
map.put(ForStmt.class, new ForStmtMerger());
map.put(IfStmt.class, new IfStmtMerger());
map.put(LabeledStmt.class, new LabeledStmtMerger());
map.put(ReturnStmt.class, new ReturnStmtMerger());
map.put(SwitchEntryStmt.class, new SwitchEntryStmtMerger());
map.put(SwitchStmt.class, new SwitchStmtMerger());
map.put(SynchronizedStmt.class, new SynchronizedStmtMerger());
map.put(ThrowStmt.class, new ThrowStmtMerger());
map.put(TryStmt.class, new TryStmtMerger());
map.put(TypeDeclarationStmt.class, new TypeDeclarationStmtMerger());
map.put(WhileStmt.class, new WhileStmtMerger());
// type
map.put(ClassOrInterfaceType.class, new ClassOrInterfaceTypeMerger());
map.put(PrimitiveType.class, new PrimitiveTypeMerger());
map.put(ReferenceType.class, new ReferenceTypeMerger());
map.put(VoidType.class, new VoidTypeMerger());
map.put(WildcardType.class, new WildcardTypeMerger());
//variable
map.put(VariableDeclaratorId.class, new VariableDeclaratorIdMerger());
map.put(VariableDeclarator.class, new VariableDeclaratorMerger());
// compile unit
map.put(CompilationUnit.class, new CompilationUnitMerger());
}
public <T> boolean isAllNull(T one, T two) {
return one == null ? two == null : false;
}
public <T> boolean isAllNotNull(T one, T two) {
return one != null && two != null;
}
public <T> T findFirstNotNull(T... types) {
for (T type : types) {
if (type != null) return type;
}
return null;
}
public <T> int indexOf(int start, List<T> datas, T target) {
int index = -1;
for (int i = start; i < datas.size(); i++) {
if (datas.get(i).equals(target)) {
index = i;
break;
}
}
return index;
}
public <T> T mergeSelective(T one, T two) {
T t = null;
if (isAllNull(one, two)) {
return t;
}
t = findFirstNotNull(one, two);
return t;
}
public <T extends BaseParameter> boolean isParametersEquals(List<T> one, List<T> two) {
if (one == two) return true;
if (one == null || two == null) return false;
if (one.size() != two.size()) return false;
for (int i = 0; i < one.size(); i++) {
T o = one.get(i);
T t = two.get(i);
AbstractMerger merger = getMerger(o.getClass());
if (!merger.isEquals(o, t)) return false;
}
return true;
}
public boolean isTypeParameterEquals(List<TypeParameter> first, List<TypeParameter> second) {
if (first == second) return true;
if (first == null || second == null) return false;
if (first.size() != second.size()) return false;
for (int i = 0; i < first.size(); i++) {
AbstractMerger<TypeParameter> merger = getMerger(TypeParameter.class);
if (!merger.isEquals(first.get(i), second.get(i))) return false;
}
return true;
}
public <T extends Node> boolean isSmallerHasEqualsInBigger(List<T> first, List<T> second, boolean useOrigin) {
if (first == second) return true;
if (first == null || second == null) return true;
List<T> smaller = null;
List<T> bigger = null;
if (first.size() > second.size()) {
smaller = second;
bigger = first;
} else {
smaller = first;
bigger = second;
}
for (T st : smaller) {
if (useOrigin) {
if (!bigger.contains(st)) return false;
} else {
AbstractMerger merger = getMerger(st.getClass());
boolean found = false;
for (T bt : bigger) {
if (merger.isEquals(st, bt)) {
found = true;
break;
}
}
if (!found) return false;
}
}
return true;
}
public int mergeModifiers(int one, int two) {
return ModifierSet.addModifier(one, two);
}
public <T extends Node> List<T> mergeListNoDuplicate(List<T> one, List<T> two, boolean useMerger) {
if (one == two) return one;
if (one == null) return two;
if (two == null) return one;
List<T> results = new ArrayList<>();
if (useMerger) {
List<T> twoCopy = new ArrayList<>();
twoCopy.addAll(two);
for (T ot : one) {
AbstractMerger merger = getMerger(ot.getClass());
T found = null;
for (T tt : twoCopy) {
if (ot.getClass().equals(tt.getClass()) && merger.isEquals(ot, tt)) {
found = tt;
break;
}
}
if (found != null) {
twoCopy.remove(found);
results.add((T) merger.merge(ot, found));
} else {
results.add(ot);
}
}
results.addAll(twoCopy);
} else {
TreeSet<T> treeSet = new TreeSet<>();
treeSet.addAll(one);
treeSet.addAll(two);
results.addAll(treeSet);
}
return results;
}
public <T> List<T> mergeListInOrder(List<T> one, List<T> two) {
List<T> results = new ArrayList<>();
if (isAllNull(one, two)) return null;
if (isAllNotNull(one, two)) {
int start = 0;
for (int i = 0; i < one.size(); i++) {
T t = one.get(i);
int index = indexOf(start, two, t);
if (index == -1 || index == start) {
results.add(t);
start += 1;
} else {
results.addAll(two.subList(start, ++index));
start = index;
}
}
if (start < two.size()) {
results.addAll(two.subList(start, two.size()));
}
} else {
results.addAll(findFirstNotNull(one, two));
}
return results;
}
/**
* first check if mapper of the type T exist, if existed return it
* else check if mapper of the supper type exist, then return it
*
* @param <T> the generic type which extends from Node
* @param clazz The class of type T
* @return null if not found else the merger of the type T
*/
public static <T extends Node> AbstractMerger<T> getMerger(Class<T> clazz) {
AbstractMerger<T> merger = null;
Class<?> type = clazz;
while (merger == null && type != null) {
merger = map.get(type);
type = type.getSuperclass();
}
return merger;
}
protected static <T extends Node> void register(Class<T> clazz, AbstractMerger<T> abstractMerger) {
map.put(clazz, abstractMerger);
}
protected <T extends Node> List<T> mergeCollections(List<T> first, List<T> second) {
if (first == null) return second;
if (second == null) return first;
List<T> nodes = new ArrayList<>();
List<T> copies = new ArrayList<>();
copies.addAll(second);
for (T node : first) {
AbstractMerger merger = getMerger(node.getClass());
T found = null;
for (T anotherNode : copies) {
if (node.getClass().equals(anotherNode.getClass())) {
if (merger.isEquals(node, anotherNode)) {
found = anotherNode;
break;
}
}
}
if (found != null) {
nodes.add((T) merger.merge(node, found));
copies.remove(found);
} else {
nodes.add(node);
}
}
if (!copies.isEmpty()) {
nodes.addAll(copies);
}
return nodes;
}
protected <T extends Node> List<T> mergeCollectionsInOrder(List<T> first, List<T> second) {
if (first == null) return second;
if (second == null) return first;
List<T> nodes = new ArrayList<>();
int max = Math.max(first.size(), second.size());
for (int i = 0; i < max; i++) {
T f = i < first.size() ? first.get(i) : null;
T s = i < second.size() ? second.get(i) : null;
if (isAllNotNull(f, s)) {
AbstractMerger merger = getMerger(f.getClass());
nodes.add((T) merger.merge(f, s));
} else {
nodes.add(f != null ? f : s);
}
}
return nodes;
}
protected <T extends Node> T mergeSingle(T first, T second) {
/**
* ensure the parameter passed to the merge is either not null
*/
if (first == null) return second;
if (second == null) return first;
if (first.getClass().equals(second.getClass())) {
AbstractMerger merger = getMerger(first.getClass());
if (merger.isEquals(first, second)) {
return (T) merger.merge(first, second);
}
}
return null;
}
protected <T extends Node> boolean isEqualsUseMerger(T first, T second) {
if (first == second) return true;
if (first == null || second == null) return false;
if (first.getClass().equals(second.getClass())) {
AbstractMerger merger = getMerger(first.getClass());
return merger.isEquals(first, second);
}
return false;
}
protected <T extends Node> boolean isEqualsUseMerger(List<T> first, List<T> second) {
if (first == second) return true;
if (first == null || second == null) return false;
if (first.size() != second.size()) return false;
for (int i = 0; i < first.size(); i++) {
if (!isEqualsUseMerger(first.get(i), second.get(i))) {
return false;
}
}
return true;
}
protected <T extends Node> void mergeOrphanComments(T first, T second, T third) {
List<Comment> comments = mergeCollections(first.getOrphanComments(), second.getOrphanComments());
if (comments != null && !comments.isEmpty()) {
for (Comment comment : comments) {
third.addOrphanComment(comment);
}
}
}
protected double similarity(String first, String second) {
if (first == null || second == null) return 0d;
return 1.0 - distance(first, second);
}
public double distance(String s1, String s2) {
return (double) distanceAbsolute(s1, s2) / Math.max(s1.length(), s2.length());
}
/**
* The Levenshtein distance, or edit distance, between two words is the
* minimum number of single-character edits (insertions, deletions or
* substitutions) required to change one word into the other.
*
* http://en.wikipedia.org/wiki/Levenshtein_distance
*
* It is always at least the difference of the sizes of the two strings.
* It is at most the length of the longer string.
* It is zero if and only if the strings are equal.
* If the strings are the same size, the Hamming distance is an upper bound
* on the Levenshtein distance.
* The Levenshtein distance verifies the triangle inequality (the distance
* between two strings is no greater than the sum Levenshtein distances from
* a third string).
*
* Implementation uses dynamic programming (Wagner–Fischer algorithm), with
* only 2 rows of data. The space requirement is thus O(m) and the algorithm
* runs in O(mn).
*
* @param s1
* @param s2
* @return
*/
public int distanceAbsolute(String s1, String s2) {
if (s1.equals(s2)) {
return 0;
}
if (s1.length() == 0) {
return s2.length();
}
if (s2.length() == 0) {
return s1.length();
}
// create two work vectors of integer distances
int[] v0 = new int[s2.length() + 1];
int[] v1 = new int[s2.length() + 1];
int[] vtemp;
// initialize v0 (the previous row of distances)
// this row is A[0][i]: edit distance for an empty s
// the distance is just the number of characters to delete from t
for (int i = 0; i < v0.length; i++) {
v0[i] = i;
}
for (int i = 0; i < s1.length(); i++) {
// calculate v1 (current row distances) from the previous row v0
// first element of v1 is A[i+1][0]
// edit distance is delete (i+1) chars from s to match empty t
v1[0] = i + 1;
// use formula to fill in the rest of the row
for (int j = 0; j < s2.length(); j++) {
int cost = (s1.charAt(i) == s2.charAt(j)) ? 0 : 1;
v1[j + 1] = Math.min(
v1[j] + 1, // Cost of insertion
Math.min(
v0[j + 1] + 1, // Cost of remove
v0[j] + cost)); // Cost of substitution
}
// copy v1 (current row) to v0 (previous row) for next iteration
//System.arraycopy(v1, 0, v0, 0, v0.length);
// Flip references to current and previous row
vtemp = v0;
v0 = v1;
v1 = vtemp;
}
return v0[s2.length()];
}
protected <T extends Node> void copyPosition(T source, T dest) {
dest.setBeginColumn(source.getBeginColumn());
dest.setBeginLine(source.getBeginLine());
dest.setEndColumn(source.getEndColumn());
dest.setEndLine(source.getEndLine());
}
public abstract M doMerge(M first, M second);
public M merge(M first, M second) {
if (first == null) return second;
if (second == null) return first;
M m = doMerge(first, second);
m.setComment(mergeSingle(first.getComment(), second.getComment()));
mergeOrphanComments(first, second, m);
return m;
}
public abstract boolean doIsEquals(M first, M second);
public boolean isEquals(M first, M second) {
if (first == second) return true;
if (first == null || second == null) return false;
return doIsEquals(first, second);
}
}