
org.remapper.service.MethodStatementMatcherService Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of remapper Show documentation
Show all versions of remapper Show documentation
An automated code entity matching algorithm
package org.remapper.service;
import info.debatty.java.stringsimilarity.NormalizedLevenshtein;
import org.apache.commons.lang3.tuple.Pair;
import org.eclipse.jdt.core.dom.*;
import org.remapper.dto.*;
import org.remapper.util.DiceFunction;
import org.remapper.util.StringUtils;
import java.util.*;
import java.util.stream.Collectors;
public class MethodStatementMatcherService {
public void matchStatements(MethodNode methodBefore, MethodNode methodAfter, MatchPair originalPair,
List> replacementsBefore, List> replacementsCurrent) {
MatchPair matchPair = new MatchPair();
matchControls(matchPair, methodBefore, methodAfter);
matchBlocks(matchPair, methodBefore, methodAfter);
matchOperations(originalPair, matchPair, methodBefore, methodAfter);
Map>> entityReplacements = getReplacements(originalPair);
matchByVariableReplacement(matchPair, methodBefore, methodAfter, originalPair, replacementsBefore, replacementsCurrent, entityReplacements);
iterativeMatching(originalPair, matchPair, methodBefore, methodAfter);
List unmatchedStatementsBefore = methodBefore.getUnmatchedStatements();
for (StatementNodeTree unmatchedStatement : unmatchedStatementsBefore) {
if (unmatchedStatement.isRefactored())
continue;
matchPair.addDeletedStatement(unmatchedStatement);
}
List duplicatedStatementsBefore = methodBefore.getDuplicatedStatements();
Set matchedStatementsLeft = matchPair.getMatchedStatementsLeft();
for (StatementNodeTree duplicatedStatement : duplicatedStatementsBefore) {
if (!matchedStatementsLeft.contains(duplicatedStatement))
matchPair.addDeletedStatement(duplicatedStatement);
}
List duplicatedStatementsAfter = methodAfter.getDuplicatedStatements();
Set matchedStatementsRight = matchPair.getMatchedStatementsRight();
for (StatementNodeTree duplicatedStatement : duplicatedStatementsAfter) {
if (!matchedStatementsRight.contains(duplicatedStatement))
matchPair.addAddedStatement(duplicatedStatement);
}
List unmatchedStatementsAfter = methodAfter.getUnmatchedStatements();
for (StatementNodeTree unmatchedStatement : unmatchedStatementsAfter) {
if (unmatchedStatement.isRefactored())
continue;
matchPair.addAddedStatement(unmatchedStatement);
}
additionalMatchByDice(originalPair, matchPair);
additionalMatchByChildren(originalPair, matchPair);
additionalMatchByContext(originalPair, matchPair, entityReplacements, replacementsBefore, replacementsCurrent);
additionalMatchByReturn(matchPair);
Set> matchedStatements = matchPair.getMatchedStatements();
for (Pair matchedStatement : matchedStatements) {
originalPair.addMatchedStatement(matchedStatement.getLeft(), matchedStatement.getRight());
matchedStatement.getLeft().setMatched();
matchedStatement.getRight().setMatched();
}
Set deletedStatements = matchPair.getDeletedStatements();
for (StatementNodeTree deletedStatement : deletedStatements) {
originalPair.addDeletedStatement(deletedStatement);
}
Set addedStatements = matchPair.getAddedStatements();
for (StatementNodeTree addedStatement : addedStatements) {
originalPair.addAddedStatement(addedStatement);
}
}
private Map>> getReplacements(MatchPair originalPair) {
Map>> replacements = new LinkedHashMap<>();
Set> matchedEntities = originalPair.getMatchedEntities();
for (Pair pair : matchedEntities) {
DeclarationNodeTree dntBefore = pair.getLeft();
DeclarationNodeTree dntCurrent = pair.getRight();
if (dntBefore.getType() == EntityType.CLASS && dntCurrent.getType() == EntityType.CLASS) {
if (!dntBefore.getName().equals(dntCurrent.getName())) {
if (replacements.containsKey(EntityType.CLASS)) {
List> list = replacements.get(EntityType.CLASS);
Pair replacement = Pair.of(dntBefore.getName(), dntCurrent.getName());
list.add(replacement);
} else {
List> list = new ArrayList<>();
Pair replacement = Pair.of(dntBefore.getName(), dntCurrent.getName());
list.add(replacement);
replacements.put(EntityType.CLASS, list);
}
}
} else if (dntBefore.getType() == EntityType.INTERFACE && dntCurrent.getType() == EntityType.INTERFACE) {
if (!dntBefore.getName().equals(dntCurrent.getName())) {
if (replacements.containsKey(EntityType.INTERFACE)) {
List> list = replacements.get(EntityType.INTERFACE);
Pair replacement = Pair.of(dntBefore.getName(), dntCurrent.getName());
list.add(replacement);
} else {
List> list = new ArrayList<>();
Pair replacement = Pair.of(dntBefore.getName(), dntCurrent.getName());
list.add(replacement);
replacements.put(EntityType.INTERFACE, list);
}
}
} else if (dntBefore.getType() == EntityType.ENUM && dntCurrent.getType() == EntityType.ENUM) {
if (!dntBefore.getName().equals(dntCurrent.getName())) {
if (replacements.containsKey(EntityType.ENUM)) {
List> list = replacements.get(EntityType.ENUM);
Pair replacement = Pair.of(dntBefore.getName(), dntCurrent.getName());
list.add(replacement);
} else {
List> list = new ArrayList<>();
Pair replacement = Pair.of(dntBefore.getName(), dntCurrent.getName());
list.add(replacement);
replacements.put(EntityType.ENUM, list);
}
}
} else if (dntBefore.getType() == EntityType.FIELD && dntCurrent.getType() == EntityType.FIELD) {
if (!dntBefore.getName().equals(dntCurrent.getName())) {
if (replacements.containsKey(EntityType.FIELD)) {
List> list = replacements.get(EntityType.FIELD);
Pair replacement = Pair.of(dntBefore.getName(), dntCurrent.getName());
list.add(replacement);
} else {
List> list = new ArrayList<>();
Pair replacement = Pair.of(dntBefore.getName(), dntCurrent.getName());
list.add(replacement);
replacements.put(EntityType.FIELD, list);
}
}
} else if (dntBefore.getType() == EntityType.METHOD && dntCurrent.getType() == EntityType.METHOD) {
if (!dntBefore.getName().equals(dntCurrent.getName())) {
if (replacements.containsKey(EntityType.METHOD)) {
List> list = replacements.get(EntityType.METHOD);
Pair replacement = Pair.of(dntBefore.getName(), dntCurrent.getName());
list.add(replacement);
} else {
List> list = new ArrayList<>();
Pair replacement = Pair.of(dntBefore.getName(), dntCurrent.getName());
list.add(replacement);
replacements.put(EntityType.METHOD, list);
}
}
} else if (dntBefore.getType() == EntityType.ENUM_CONSTANT && dntCurrent.getType() == EntityType.ENUM_CONSTANT) {
if (!dntBefore.getName().equals(dntCurrent.getName())) {
if (replacements.containsKey(EntityType.ENUM_CONSTANT)) {
List> list = replacements.get(EntityType.ENUM_CONSTANT);
Pair replacement = Pair.of(dntBefore.getName(), dntCurrent.getName());
list.add(replacement);
} else {
List> list = new ArrayList<>();
Pair replacement = Pair.of(dntBefore.getName(), dntCurrent.getName());
list.add(replacement);
replacements.put(EntityType.ENUM_CONSTANT, list);
}
}
}
}
return replacements;
}
private void matchControls(MatchPair matchPair, MethodNode methodBefore, MethodNode methodAfter) {
List allControlsBefore = methodBefore.getAllControls();
List allControlsAfter = methodAfter.getAllControls();
/**
* 1. n1.type = n2.type ^ node1.expression = node2.expression ^ n1.depth = n2.depth
*/
Map temp1 = new LinkedHashMap<>();
for (StatementNodeTree node1 : allControlsBefore) {
for (StatementNodeTree node2 : allControlsAfter) {
if (!node1.isMatchedOver() && !node2.isMatchedOver() && node1.getType() == node2.getType() &&
node1.getExpression().equals(node2.getExpression()) && node1.getDepth() == node2.getDepth()) {
processControlMap(temp1, node1, node2);
}
}
}
controlMap2SetOfMatchedStatements(matchPair, temp1);
/**
* 2. n1.type = n2.type ^ node1.expression = node2.expression
*/
Map temp2 = new LinkedHashMap<>();
for (StatementNodeTree node1 : allControlsBefore) {
for (StatementNodeTree node2 : allControlsAfter) {
if (!node1.isMatchedOver() && !node2.isMatchedOver() && node1.getType() == node2.getType() &&
node1.getExpression().equals(node2.getExpression())) {
processControlMap(temp2, node1, node2);
}
}
}
controlMap2SetOfMatchedStatements(matchPair, temp2);
}
private void processControlMap(Map temp, StatementNodeTree node1, StatementNodeTree node2) {
if (temp.containsKey(node1) || temp.containsValue(node2)) {
StatementNodeTree candidateValue = temp.get(node1);
StatementNodeTree candidateKey = getKeyByValue(temp, node2);
double previousValue = calculateTextSimilarity(node1, candidateValue);
double previousKey = calculateTextSimilarity(candidateKey, node2);
double current = calculateTextSimilarity(node1, node2);
if (current > previousValue && current > previousKey) {
temp.remove(node1, candidateValue);
temp.remove(candidateKey, node2);
temp.put(node1, node2);
}
} else
temp.put(node1, node2);
}
private double calculateTextSimilarity(StatementNodeTree node1, StatementNodeTree node2) {
double sim = 0.0;
if (node1 == null || node2 == null) return sim;
NormalizedLevenshtein nl = new NormalizedLevenshtein();
sim += (1 - nl.distance(node1.getStatement().toString(), node2.getStatement().toString()));
return sim;
}
private void controlMap2SetOfMatchedStatements(MatchPair matchPair, Map temp) {
for (StatementNodeTree control1 : temp.keySet()) {
StatementNodeTree control2 = temp.get(control1);
if (!control1.isMatchedOver() && !control2.isMatchedOver()) {
matchPair.addMatchedStatement(control1, control2);
control1.setMatched();
control2.setMatched();
if (control1.hasChildren() && control2.hasChildren())
matchSubStatements(matchPair, control1.getChildren(), control2.getChildren());
}
}
}
private void matchSubStatements(MatchPair matchPair, List childrenBefore, List childrenAfter) {
for (StatementNodeTree node1 : childrenBefore) {
for (StatementNodeTree node2 : childrenAfter) {
if (!node1.isMatchedOver() && !node2.isMatchedOver() &&
StringUtils.equals(node1.getStatement().toString(), node2.getStatement().toString())) {
matchPair.addMatchedStatement(node1, node2);
node1.setMatched();
node2.setMatched();
if (node1.hasChildren() && node2.hasChildren()) {
matchSubStatements(matchPair, node1.getChildren(), node2.getChildren());
}
break;
}
}
}
}
private void matchBlocks(MatchPair matchPair, MethodNode methodBefore, MethodNode methodAfter) {
List allBlocksBefore = methodBefore.getAllBlocks();
List allBlocksAfter = methodAfter.getAllBlocks();
/**
* 1. n1.text = n2.text ^ n1.depth = n2.depth
*/
Map temp1 = new LinkedHashMap<>();
for (StatementNodeTree node1 : allBlocksBefore) {
for (StatementNodeTree node2 : allBlocksAfter) {
if (!node1.isMatchedOver() && !node2.isMatchedOver() && StringUtils.equals(node1.getStatement().toString(), node2.getStatement().toString()) &&
node1.getDepth() == node2.getDepth()) {
processBlockMap(temp1, node1, node2);
}
}
}
blockMap2SetOfMatchedStatements(matchPair, temp1);
/**
* 2. n1.text = n2.text
*/
Map temp2 = new LinkedHashMap<>();
for (StatementNodeTree node1 : allBlocksBefore) {
for (StatementNodeTree node2 : allBlocksAfter) {
if (!node1.isMatchedOver() && !node2.isMatchedOver() && StringUtils.equals(node1.getStatement().toString(), node2.getStatement().toString())) {
processBlockMap(temp2, node1, node2);
}
}
}
blockMap2SetOfMatchedStatements(matchPair, temp2);
}
private void processBlockMap(Map temp, StatementNodeTree node1, StatementNodeTree node2) {
if (temp.containsKey(node1) || temp.containsValue(node2)) {
StatementNodeTree candidateValue = temp.get(node1);
StatementNodeTree candidateKey = getKeyByValue(temp, node2);
double previousValue = calculateWeight(node1, candidateValue);
double previousKey = calculateWeight(candidateKey, node2);
double current = calculateWeight(node1, node2);
if (current > previousValue && current > previousKey) {
temp.remove(node1, candidateValue);
temp.remove(candidateKey, node2);
temp.put(node1, node2);
}
} else
temp.put(node1, node2);
}
private double calculateWeight(StatementNodeTree node1, StatementNodeTree node2) {
double weight = 0.0;
if (node1 == null || node2 == null) return weight;
NormalizedLevenshtein nl = new NormalizedLevenshtein();
if (node1.getBlockType() == node2.getBlockType())
weight += 1.0;
if (node1.getBlockType() == BlockType.IF_BLOCK && node2.getBlockType() == BlockType.ELSE_BLOCK)
weight += 1.0;
if (node1.getBlockType() == BlockType.ELSE_BLOCK && node2.getBlockType() == BlockType.IF_BLOCK)
weight += 1.0;
weight += (1 - nl.distance(node1.getBlockExpression(), node2.getBlockExpression()));
return weight;
}
private void blockMap2SetOfMatchedStatements(MatchPair matchPair, Map temp) {
for (StatementNodeTree node1 : temp.keySet()) {
StatementNodeTree node2 = temp.get(node1);
if (!node1.isMatchedOver() && !node2.isMatchedOver()) {
matchPair.addMatchedStatement(node1, node2);
node1.setMatched();
node2.setMatched();
setChildrenMatched(matchPair, node1.getChildren(), node2.getChildren());
}
}
}
private void setChildrenMatched(MatchPair matchPair, List children1, List children2) {
if (children1.size() == children2.size()) {
for (int i = 0; i < children1.size(); i++) {
StatementNodeTree child1 = children1.get(i);
StatementNodeTree child2 = children2.get(i);
matchPair.addMatchedStatement(child1, child2);
child1.setMatched();
child2.setMatched();
if (child1.hasChildren() && child2.hasChildren()) {
setChildrenMatched(matchPair, child1.getChildren(), child2.getChildren());
}
}
} else {
matchSubStatements(matchPair, children1, children2);
}
}
private void matchOperations(MatchPair originalPair, MatchPair matchPair, MethodNode methodBefore, MethodNode methodAfter) {
List allOperationsBefore = methodBefore.getAllOperations();
List allOperationsAfter = methodAfter.getAllOperations();
/**
* 1. n1.text = n2.text ^ n1.depth = n2.depth
*/
Map temp1 = new LinkedHashMap<>();
for (StatementNodeTree node1 : allOperationsBefore) {
for (StatementNodeTree node2 : allOperationsAfter) {
if (!node1.isMatchedOver() && !node2.isMatchedOver() && isTheSameExpression(node1, node2) &&
node1.getDepth() == node2.getDepth()) {
processOperationMap(originalPair, matchPair, temp1, node1, node2);
}
}
}
stmtMap2SetOfMatchedStatements(matchPair, temp1);
/**
* 2. n1.text = n2.text
*/
Map temp2 = new LinkedHashMap<>();
for (StatementNodeTree node1 : allOperationsBefore) {
for (StatementNodeTree node2 : allOperationsAfter) {
if (!node1.isMatchedOver() && !node2.isMatchedOver() && isTheSameExpression(node1, node2)) {
processOperationMap(originalPair, matchPair, temp2, node1, node2);
}
}
}
stmtMap2SetOfMatchedStatements(matchPair, temp2);
/**
* 3. n1.variableDeclaration = n2.variableDeclaration
*/
Map temp3 = new LinkedHashMap<>();
for (StatementNodeTree node1 : allOperationsBefore) {
for (StatementNodeTree node2 : allOperationsAfter) {
if (!node1.isMatchedOver() && !node2.isMatchedOver() && node1.getType() == StatementType.VARIABLE_DECLARATION_STATEMENT &&
node2.getType() == StatementType.VARIABLE_DECLARATION_STATEMENT) {
VariableDeclarationStatement variableDeclaration1 = (VariableDeclarationStatement) node1.getStatement();
VariableDeclarationStatement variableDeclaration2 = (VariableDeclarationStatement) node2.getStatement();
if (StringUtils.type2String(variableDeclaration1.getType()).equals(StringUtils.type2String(variableDeclaration2.getType()))) {
VariableDeclarationFragment fragment1 = (VariableDeclarationFragment) variableDeclaration1.fragments().get(0);
VariableDeclarationFragment fragment2 = (VariableDeclarationFragment) variableDeclaration2.fragments().get(0);
if (fragment1.getName().getIdentifier().equals(fragment2.getName().getIdentifier()))
processOperationMap(originalPair, matchPair, temp3, node1, node2);
}
}
}
}
stmtMap2SetOfMatchedStatements(matchPair, temp3);
}
private boolean isTheSameExpression(StatementNodeTree node1, StatementNodeTree node2) {
if (node1.getType() == StatementType.EXPRESSION_STATEMENT && node2.getType() == StatementType.LAMBDA_EXPRESSION_BODY) {
String expression1 = node1.getExpression();
String expression2 = node2.getExpression();
return StringUtils.equals(expression1, expression2) || StringUtils.equals(expression1, expression2 + ";\n");
}
if (node1.getType() == StatementType.LAMBDA_EXPRESSION_BODY && node2.getType() == StatementType.EXPRESSION_STATEMENT) {
String expression1 = node1.getExpression();
String expression2 = node2.getExpression();
return StringUtils.equals(expression1, expression2) || StringUtils.equals(expression1 + ";\n", expression2);
}
return false;
}
private void stmtMap2SetOfMatchedStatements(MatchPair matchPair, Map temp) {
for (StatementNodeTree node1 : temp.keySet()) {
StatementNodeTree node2 = temp.get(node1);
if (!node1.isMatchedOver() && !node2.isMatchedOver()) {
matchPair.addMatchedStatement(node1, node2);
node1.setMatched();
node2.setMatched();
}
}
}
private void matchByVariableReplacement(MatchPair matchPair, MethodNode methodBefore, MethodNode methodAfter, MatchPair originalPair,
List> replacementsBefore, List> replacementsCurrent,
Map>> entityReplacements) {
List allControlsBefore = methodBefore.getAllControls();
List allControlsAfter = methodAfter.getAllControls();
List allOperationsBefore = methodBefore.getAllOperations();
List allOperationsAfter = methodAfter.getAllOperations();
List allVariablesBefore = allOperationsBefore.stream().filter(
node -> node.getType() == StatementType.VARIABLE_DECLARATION_STATEMENT && !node.isMatched()).collect(Collectors.toList());
List allVariablesAfter = allOperationsAfter.stream().filter(
node -> node.getType() == StatementType.VARIABLE_DECLARATION_STATEMENT && !node.isMatched()).collect(Collectors.toList());
Map temp1 = new LinkedHashMap<>();
for (StatementNodeTree node1 : allControlsBefore) {
for (StatementNodeTree node2 : allControlsAfter) {
if (!node1.isMatchedOver() && !node2.isMatchedOver() &&
equalsWithReplacements(matchPair, methodBefore, methodAfter, node1, node2, entityReplacements,
replacementsBefore, replacementsCurrent, allVariablesBefore, allVariablesAfter)) {
processControlMap(temp1, node1, node2);
List children1 = node1.getChildren();
List children2 = node2.getChildren();
if (children1.size() == 1 && children2.size() == 1) {
StatementNodeTree child1 = children1.get(0);
StatementNodeTree child2 = children2.get(0);
if (child1 instanceof BlockNode && child2 instanceof BlockNode)
processBlockMap(temp1, child1, child2);
}
}
}
}
stmtMap2SetOfMatchedStatements(matchPair, temp1);
Map temp2 = new LinkedHashMap<>();
for (StatementNodeTree node1 : allOperationsBefore) {
for (StatementNodeTree node2 : allOperationsAfter) {
if (!node1.isMatchedOver() && !node2.isMatchedOver() &&
equalsWithReplacements(matchPair, methodBefore, methodAfter, node1, node2, entityReplacements,
replacementsBefore, replacementsCurrent, allVariablesBefore, allVariablesAfter))
processOperationMap(originalPair, matchPair, temp2, node1, node2);
}
}
stmtMap2SetOfMatchedStatements(matchPair, temp2);
}
private boolean equalsWithReplacements(MatchPair matchPair, MethodNode methodBefore, MethodNode methodAfter,
StatementNodeTree node1, StatementNodeTree node2,
Map>> entityReplacements,
List> replacementsBefore, List> replacementsCurrent,
List allVariablesBefore, List allVariablesAfter) {
String statement1 = node1.getExpression();
String statement2 = node2.getExpression();
if (node1.getType() == StatementType.RETURN_STATEMENT && node2.getType() != StatementType.RETURN_STATEMENT &&
statement1.startsWith("return ")) {
MethodNode root = node1.getRoot();
if (root != methodBefore)
statement1 = statement1.substring("return ".length());
}
if (node2.getType() == StatementType.RETURN_STATEMENT && node1.getType() != StatementType.RETURN_STATEMENT &&
statement2.startsWith("return ")) {
MethodNode root = node2.getRoot();
if (root != methodAfter)
statement2 = statement2.substring("return ".length());
}
List astNodes1 = new ArrayList<>();
List astNodes2 = new ArrayList<>();
node1.getStatement().accept(new ASTVisitor() {
@Override
public boolean visit(SimpleName node) {
astNodes1.add(node.getIdentifier());
return true;
}
});
node2.getStatement().accept(new ASTVisitor() {
@Override
public boolean visit(SimpleName node) {
astNodes2.add(node.getIdentifier());
return true;
}
});
List> parameterReplacements = new ArrayList<>();
if (!methodBefore.getExpression().equals("initializer") && !methodAfter.getExpression().equals("initializer")) {
MethodDeclaration declaration1 = (MethodDeclaration) methodBefore.getMethodEntity().getDeclaration();
MethodDeclaration declaration2 = (MethodDeclaration) methodAfter.getMethodEntity().getDeclaration();
List parameters1 = declaration1.parameters();
List parameters2 = declaration2.parameters();
if (parameters1.size() == parameters2.size()) {
for (int i = 0; i < parameters1.size(); i++) {
SingleVariableDeclaration parameter1 = parameters1.get(i);
SingleVariableDeclaration parameter2 = parameters2.get(i);
Type type1 = parameter1.getType();
Type type2 = parameter2.getType();
String name1 = parameter1.getName().getIdentifier();
String name2 = parameter2.getName().getIdentifier();
if (StringUtils.equals(type1.toString(), type2.toString()) &&
!StringUtils.equals(name1, name2)) {
parameterReplacements.add(Pair.of(name1, name2));
}
}
}
}
return equalsWithReplacements(matchPair, statement1, statement2, astNodes1, astNodes2, entityReplacements, parameterReplacements,
replacementsBefore, replacementsCurrent, allVariablesBefore, allVariablesAfter);
}
private boolean equalsWithReplacements(MatchPair matchPair, String statement1, String statement2,
List astNodes1, List astNodes2,
Map>> entityReplacements,
List> parameterReplacements,
List> replacementsBefore, List> replacementsCurrent,
List allVariablesBefore, List allVariablesAfter) {
String replaced1 = statement1;
String replaced2 = statement2;
List> list = new ArrayList<>();
if (entityReplacements.get(EntityType.CLASS) != null)
list.addAll(entityReplacements.get(EntityType.CLASS));
if (entityReplacements.get(EntityType.INTERFACE) != null)
list.addAll(entityReplacements.get(EntityType.INTERFACE));
if (entityReplacements.get(EntityType.ENUM) != null)
list.addAll(entityReplacements.get(EntityType.ENUM));
if (entityReplacements.get(EntityType.FIELD) != null)
list.addAll(entityReplacements.get(EntityType.FIELD));
if (entityReplacements.get(EntityType.METHOD) != null)
list.addAll(entityReplacements.get(EntityType.METHOD));
if (entityReplacements.get(EntityType.ENUM_CONSTANT) != null)
list.addAll(entityReplacements.get(EntityType.ENUM_CONSTANT));
NormalizedLevenshtein nl = new NormalizedLevenshtein();
double distance = nl.distance(statement1, statement2);
for (Pair pair : list) {
String oldEntityName = pair.getLeft();
String newEntityName = pair.getRight();
if (astNodes1.contains(oldEntityName)) {
String replaced = replaced1.replace(oldEntityName, newEntityName);
double compared = nl.distance(replaced, replaced2);
if (compared == 0.0)
return true;
if (compared < distance) {
replaced1 = replaced;
distance = compared;
}
}
}
for (Pair pair : replacementsBefore) {
String name = pair.getLeft();
String initializer = pair.getRight();
if (astNodes1.contains(name)) {
if (replaced1.equals("this." + name + "=" + name + ";\n")) {
String replaced = "this." + name + "=" + initializer + ";\n";
double compared = nl.distance(replaced, replaced2);
if (compared == 0.0)
return true;
if (compared < distance) {
replaced1 = replaced;
distance = compared;
}
} else {
String replaced = replaceLast(replaced1, name, initializer);
double compared = nl.distance(replaced, replaced2);
if (compared == 0.0)
return true;
if (compared < distance) {
replaced1 = replaced;
distance = compared;
}
}
}
}
for (StatementNodeTree variable : allVariablesBefore) {
VariableDeclarationStatement statement = (VariableDeclarationStatement) variable.getStatement();
List fragments = statement.fragments();
for (VariableDeclarationFragment fragment : fragments) {
if (fragment.getInitializer() != null) {
String name = fragment.getName().getIdentifier();
String initializer = fragment.getInitializer().toString();
if (replaced1.contains(name + "="))
continue;
if (astNodes1.contains(name)) {
String replaced = replaced1.replace(name, initializer);
double compared = nl.distance(replaced, replaced2);
if (compared == 0.0) {
variable.setRefactored();
if (!variable.isMatched())
matchPair.addDeletedStatement(variable);
return true;
}
if (compared < distance) {
replaced1 = replaced;
distance = compared;
}
}
}
}
}
for (Pair pair : replacementsCurrent) {
String name = pair.getLeft();
String initializer = pair.getRight();
if (astNodes2.contains(name)) {
if (replaced2.equals("this." + name + "=" + name + ";\n")) {
String replaced = "this." + name + "=" + initializer + ";\n";
double compared = nl.distance(replaced1, replaced);
if (compared == 0.0)
return true;
if (compared < distance) {
replaced2 = replaced;
distance = compared;
}
} else {
String replaced = replaceLast(replaced2, name, initializer);
double compared = nl.distance(replaced1, replaced);
if (compared == 0.0)
return true;
if (compared < distance) {
replaced2 = replaced;
distance = compared;
}
}
}
}
for (StatementNodeTree variable : allVariablesAfter) {
VariableDeclarationStatement statement = (VariableDeclarationStatement) variable.getStatement();
List fragments = statement.fragments();
for (VariableDeclarationFragment fragment : fragments) {
if (fragment.getInitializer() != null) {
String name = fragment.getName().getIdentifier();
String initializer = fragment.getInitializer().toString();
if (replaced2.contains(name + "="))
continue;
if (astNodes2.contains(name)) {
String replaced = replaced2.replace(name, initializer);
double compared = nl.distance(replaced1, replaced);
if (compared == 0.0) {
variable.setRefactored();
if (!variable.isMatched())
matchPair.addAddedStatement(variable);
return true;
}
if (compared < distance) {
replaced2 = replaced;
distance = compared;
}
}
}
}
}
for (Pair pair : parameterReplacements) {
String oldParameter = pair.getLeft();
String newParameter = pair.getRight();
if (astNodes1.contains(oldParameter)) {
String replaced = replaced1.replace(oldParameter, newParameter);
double compared = nl.distance(replaced, replaced2);
if (compared == 0.0)
return true;
if (compared < distance) {
replaced1 = replaced;
distance = compared;
}
}
}
for (StatementNodeTree variable1 : allVariablesBefore) {
if (variable1.isRefactored() || variable1.isMatched())
continue;
for (StatementNodeTree variable2 : allVariablesAfter) {
if (variable2.isRefactored() || variable2.isMatched())
continue;
VariableDeclarationStatement variableDeclaration1 = (VariableDeclarationStatement) variable1.getStatement();
VariableDeclarationStatement variableDeclaration2 = (VariableDeclarationStatement) variable2.getStatement();
List fragments1 = variableDeclaration1.fragments();
List fragments2 = variableDeclaration2.fragments();
for (VariableDeclarationFragment fragment1 : fragments1) {
for (VariableDeclarationFragment fragment2 : fragments2) {
String name1 = fragment1.getName().getIdentifier();
String name2 = fragment2.getName().getIdentifier();
Expression initializer1 = fragment1.getInitializer();
Expression initializer2 = fragment2.getInitializer();
if (initializer1 == null || initializer2 == null)
continue;
if (!initializer1.toString().equals(initializer2.toString()))
continue;
if (!astNodes1.contains(name1) || !astNodes2.contains(name2))
continue;
if (StringUtils.equals(replaced1.replace(name1 + ".", initializer1.toString() + "."),
replaced2.replace(name2 + ".", initializer2.toString() + "."))) {
variable1.setMatched();
variable2.setMatched();
matchPair.addMatchedStatement(variable1, variable2);
return true;
}
}
}
}
}
return replaced1.equals(statement2) || statement1.equals(replaced2) || replaced1.equals(replaced2);
}
private String replaceLast(String originalString, String searchString, String replacementString) {
int lastIndex = originalString.lastIndexOf(searchString);
if (lastIndex != -1) {
String newString = originalString.substring(0, lastIndex) + replacementString +
originalString.substring(lastIndex + searchString.length());
return newString;
}
return originalString;
}
private void processOperationMap(MatchPair originalPair, MatchPair matchPair, Map temp, StatementNodeTree node1, StatementNodeTree node2) {
if (temp.containsKey(node1) || temp.containsValue(node2)) {
StatementNodeTree candidateValue = temp.get(node1);
StatementNodeTree candidateKey = getKeyByValue(temp, node2);
double previousValue = candidateValue == null ? 0.0 : DiceFunction.calculateContextSimilarity(originalPair, matchPair, node1, candidateValue);
double previousKey = candidateKey == null ? 0.0 : DiceFunction.calculateContextSimilarity(originalPair, matchPair, candidateKey, node2);
double current = DiceFunction.calculateContextSimilarity(originalPair, matchPair, node1, node2);
if (current > previousValue && current > previousKey) {
temp.remove(node1, candidateValue);
temp.remove(candidateKey, node2);
temp.put(node1, node2);
}
} else
temp.put(node1, node2);
}
private void iterativeMatching(MatchPair originalPair, MatchPair matchPair, MethodNode methodBefore, MethodNode methodAfter) {
Set> candidateStatements = matchByDiceCoefficient(originalPair, matchPair, methodBefore, methodAfter);
matchPair.setCandidateStatements(candidateStatements);
for (int i = 0; i < 5; i++) {
Set> temp = matchByDiceCoefficient(originalPair, matchPair, methodBefore, methodAfter);
if (matchPair.getCandidateStatements().size() == temp.size() && matchPair.getCandidateStatements().equals(temp)) {
// System.out.println("At the " + (i + 1) + "th iteration, the candidate set of statement mapping does not change.");
break;
}
matchPair.setCandidateStatements(temp);
}
for (Pair pair : matchPair.getCandidateStatements()) {
matchPair.getMatchedStatements().add(pair);
pair.getLeft().setMatched();
pair.getRight().setMatched();
}
matchPair.getCandidateStatements().clear();
}
private Set> matchByDiceCoefficient(MatchPair originalPair, MatchPair matchPair,
MethodNode methodBefore, MethodNode methodAfter) {
Set> temp = new HashSet<>();
List allOperationsBefore = methodBefore.getAllOperations();
List allOperationsAfter = methodAfter.getAllOperations();
/**
* 1. sim(n1.text, n2.text) +sim(n1.context, n2.context) > 1.0 ^ n1 instance operation
*/
Map temp1 = new HashMap<>();
for (StatementNodeTree node1 : allOperationsBefore) {
for (StatementNodeTree node2 : allOperationsAfter) {
if (!node1.isMatchedOver() && !node2.isMatchedOver()) {
if (!typeCompatible(originalPair, node1, node2))
continue;
double sim = DiceFunction.calculateSimilarity(originalPair, matchPair, node1, node2);
if (sim < DiceFunction.minSimilarity)
continue;
processStatementMap(originalPair, matchPair, temp1, node1, node2);
}
}
}
for (StatementNodeTree operation1 : temp1.keySet()) {
StatementNodeTree operation2 = temp1.get(operation1);
temp.add(Pair.of(operation1, operation2));
}
/**
* 2. sim(n1.text, n2.text) +sim(n1.context, n2.context) > 1.0 ^ n1 instance block
*/
Map temp2 = new HashMap<>();
List allBlocksBefore = methodBefore.getAllBlocks();
List allBlocksAfter = methodAfter.getAllBlocks();
for (StatementNodeTree node1 : allBlocksBefore) {
for (StatementNodeTree node2 : allBlocksAfter) {
if (!node1.isMatchedOver() && !node2.isMatchedOver()) {
if (!typeCompatible(originalPair, node1, node2))
continue;
if (DiceFunction.calculateSimilarity(originalPair, matchPair, node1, node2) < DiceFunction.minSimilarity)
continue;
processStatementMap(originalPair, matchPair, temp2, node1, node2);
}
}
}
for (StatementNodeTree block1 : temp2.keySet()) {
StatementNodeTree block2 = temp2.get(block1);
temp.add(Pair.of(block1, block2));
}
/**
* 3. sim(n1.text, n2.text) +sim(n1.context, n2.context) > 1.0 ^ n1 instance control
*/
Map temp3 = new HashMap<>();
List allControlsBefore = methodBefore.getAllControls();
List allControlsAfter = methodAfter.getAllControls();
for (StatementNodeTree node1 : allControlsBefore) {
for (StatementNodeTree node2 : allControlsAfter) {
if (!node1.isMatchedOver() && !node2.isMatchedOver()) {
if (!typeCompatible(originalPair, node1, node2))
continue;
if (DiceFunction.calculateSimilarity(originalPair, matchPair, node1, node2) < DiceFunction.minSimilarity)
continue;
processStatementMap(originalPair, matchPair, temp3, node1, node2);
}
}
}
for (StatementNodeTree control1 : temp3.keySet()) {
StatementNodeTree control2 = temp3.get(control1);
temp.add(Pair.of(control1, control2));
}
return temp;
}
private boolean typeCompatible(MatchPair matchPair, StatementNodeTree node1, StatementNodeTree node2) {
if (node1 instanceof BlockNode && node2 instanceof BlockNode)
return node1.getBlockType() == node2.getBlockType() || (node1.getBlockType() == BlockType.IF_BLOCK && node2.getBlockType() == BlockType.ELSE_BLOCK) ||
(node1.getBlockType() == BlockType.ELSE_BLOCK && node2.getBlockType() == BlockType.IF_BLOCK);
else {
boolean isSameType = node1.getType() == node2.getType();
boolean isLoopType = (node1.getType() == StatementType.FOR_STATEMENT || node1.getType() == StatementType.ENHANCED_FOR_STATEMENT ||
node1.getType() == StatementType.WHILE_STATEMENT || node1.getType() == StatementType.DO_STATEMENT) &&
(node2.getType() == StatementType.FOR_STATEMENT || node2.getType() == StatementType.ENHANCED_FOR_STATEMENT ||
node2.getType() == StatementType.WHILE_STATEMENT || node2.getType() == StatementType.DO_STATEMENT);
boolean isLambdaType = (node1.getType() == StatementType.LAMBDA_EXPRESSION_BODY || node1.getType() == StatementType.EXPRESSION_STATEMENT) &&
(node2.getType() == StatementType.LAMBDA_EXPRESSION_BODY || node2.getType() == StatementType.EXPRESSION_STATEMENT);
boolean isExtractedOrInlinedType = false;
if ((node1.getType() == StatementType.RETURN_STATEMENT && node2.getType() != StatementType.RETURN_STATEMENT) ||
(node1.getType() != StatementType.RETURN_STATEMENT && node2.getType() == StatementType.RETURN_STATEMENT)) {
DeclarationNodeTree entity1 = node1.getRoot().getMethodEntity();
DeclarationNodeTree entity2 = node2.getRoot().getMethodEntity();
if (!matchPair.getMatchedEntities().contains(Pair.of(entity1, entity2)))
isExtractedOrInlinedType = true;
}
boolean isSameExpression = false;
if (node1.getType() == StatementType.VARIABLE_DECLARATION_STATEMENT && node2.getType() == StatementType.EXPRESSION_STATEMENT) {
VariableDeclarationStatement statement1 = (VariableDeclarationStatement) node1.getStatement();
ExpressionStatement statement2 = (ExpressionStatement) node2.getStatement();
VariableDeclarationFragment fragment = (VariableDeclarationFragment) statement1.fragments().get(0);
if (fragment.getInitializer() != null && statement2.getExpression() != null &&
fragment.getInitializer().toString().equals(statement2.getExpression().toString()))
isSameExpression = true;
}
if (node1.getType() == StatementType.EXPRESSION_STATEMENT && node2.getType() == StatementType.VARIABLE_DECLARATION_STATEMENT) {
ExpressionStatement statement1 = (ExpressionStatement) node1.getStatement();
VariableDeclarationStatement statement2 = (VariableDeclarationStatement) node2.getStatement();
VariableDeclarationFragment fragment = (VariableDeclarationFragment) statement2.fragments().get(0);
if (fragment.getInitializer() != null && statement1.getExpression() != null &&
fragment.getInitializer().toString().equals(statement1.getExpression().toString()))
isSameExpression = true;
}
return isSameType || isLoopType || isLambdaType || isExtractedOrInlinedType || isSameExpression;
}
}
private void processStatementMap(MatchPair originalPair, MatchPair matchPair, Map temp, StatementNodeTree node1, StatementNodeTree node2) {
if (temp.containsKey(node1) || temp.containsValue(node2)) {
StatementNodeTree candidateValue = temp.get(node1);
StatementNodeTree candidateKey = getKeyByValue(temp, node2);
double previousValue = candidateValue == null ? 0.0 : DiceFunction.calculateSimilarity(originalPair, matchPair, node1, candidateValue);
double previousKey = candidateKey == null ? 0.0 : DiceFunction.calculateSimilarity(originalPair, matchPair, candidateKey, node2);
double current = DiceFunction.calculateSimilarity(originalPair, matchPair, node1, node2);
if (current > previousValue && current > previousKey) {
temp.remove(node1, candidateValue);
temp.remove(candidateKey, node2);
temp.put(node1, node2);
}
if (current == previousValue || current == previousKey) {
NormalizedLevenshtein levenshtein = new NormalizedLevenshtein();
double distance1 = candidateValue == null ? 1.0 : levenshtein.distance(node1.getExpression(), candidateValue.getExpression());
double distance2 = candidateKey == null ? 1.0 : levenshtein.distance(candidateKey.getExpression(), node2.getExpression());
double distance3 = levenshtein.distance(node1.getExpression(), node2.getExpression());
if (distance3 < distance2 && distance3 < distance1) {
temp.remove(node1, candidateValue);
temp.remove(candidateKey, node2);
temp.put(node1, node2);
}
}
} else
temp.put(node1, node2);
}
public K getKeyByValue(Map map, V value) {
return map.entrySet().stream()
.filter(entry -> Objects.equals(entry.getValue(), value))
.map(Map.Entry::getKey)
.findFirst()
.orElse(null);
}
private void additionalMatchByDice(MatchPair originalPair, MatchPair matchPair) {
Set deletedStatements = matchPair.getDeletedStatements();
Set addedStatements = matchPair.getAddedStatements();
Map temp = new HashMap<>();
for (StatementNodeTree node1 : deletedStatements) {
for (StatementNodeTree node2 : addedStatements) {
if (typeCompatible(originalPair, node1, node2) && node1 instanceof OperationNode && node2 instanceof OperationNode &&
!node1.isRefactored() && !node2.isRefactored()) {
double sim = DiceFunction.calculateDiceSimilarity(node1, node2);
if (sim < DiceFunction.minSimilarity)
continue;
processStatementMap(originalPair, matchPair, temp, node1, node2);
}
}
}
replacedMap2SetOfMatchedStatements(matchPair.getMatchedStatements(), deletedStatements, addedStatements, temp);
}
private void additionalMatchByChildren(MatchPair originalPair, MatchPair matchPair) {
Set deletedStatements = matchPair.getDeletedStatements();
Set addedStatements = matchPair.getAddedStatements();
Map temp = new HashMap<>();
for (StatementNodeTree node1 : deletedStatements) {
for (StatementNodeTree node2 : addedStatements) {
if (typeCompatible(originalPair, node1, node2) && node1 instanceof BlockNode && node2 instanceof BlockNode) {
if (DiceFunction.isMatchedGreaterThanAnyOne(matchPair, node1, node2)) {
StatementNodeTree parent1 = node1.getParent();
StatementNodeTree parent2 = node2.getParent();
if (typeCompatible(originalPair, parent1, parent2) && parent1 instanceof ControlNode && parent2 instanceof ControlNode) {
processStatementMap(originalPair, matchPair, temp, node1, node2);
processStatementMap(originalPair, matchPair, temp, parent1, parent2);
}
}
}
}
}
replacedMap2SetOfMatchedStatements(matchPair.getMatchedStatements(), deletedStatements, addedStatements, temp);
}
private void replacedMap2SetOfMatchedStatements(Set> matchedStatements,
Set deletedStatements, Set addedStatements,
Map temp) {
for (StatementNodeTree node1 : temp.keySet()) {
StatementNodeTree node2 = temp.get(node1);
node1.setMatched();
node2.setMatched();
matchedStatements.add(Pair.of(node1, node2));
deletedStatements.remove(node1);
addedStatements.remove(node2);
}
}
private void additionalMatchByContext(MatchPair originalPair, MatchPair matchPair, Map>> entityReplacements,
List> replacementsBefore, List> replacementsCurrent) {
Set> matchedStatements = matchPair.getMatchedStatements();
Set deletedStatements = matchPair.getDeletedStatements();
Set addedStatements = matchPair.getAddedStatements();
Map temp = new HashMap<>();
for (StatementNodeTree node1 : deletedStatements) {
for (StatementNodeTree node2 : addedStatements) {
if (node1.getType() == StatementType.VARIABLE_DECLARATION_STATEMENT &&
node2.getType() == StatementType.VARIABLE_DECLARATION_STATEMENT) {
double ctxSim = DiceFunction.calculateContextSimilarity(originalPair, matchPair, node1, node2);
if (ctxSim < 0.25)
continue;
VariableDeclarationStatement variableDeclaration1 = (VariableDeclarationStatement) node1.getStatement();
VariableDeclarationStatement variableDeclaration2 = (VariableDeclarationStatement) node2.getStatement();
VariableDeclarationFragment fragment1 = (VariableDeclarationFragment) variableDeclaration1.fragments().get(0);
VariableDeclarationFragment fragment2 = (VariableDeclarationFragment) variableDeclaration2.fragments().get(0);
String variableName1 = fragment1.getName().getIdentifier();
String variableName2 = fragment2.getName().getIdentifier();
Type type1 = variableDeclaration1.getType();
Type type2 = variableDeclaration2.getType();
Expression initializer1 = fragment1.getInitializer();
Expression initializer2 = fragment2.getInitializer();
if (initializer1 != null && initializer2 != null && initializer1.toString().equals(initializer2.toString()))
processStatementMap(originalPair, matchPair, temp, node1, node2);
for (Pair replacement : replacementsBefore) {
String left = replacement.getLeft();
String right = replacement.getRight();
if (initializer1 != null && initializer2 != null && initializer1.toString().replace(left, right).equals(initializer2.toString())) {
processStatementMap(originalPair, matchPair, temp, node1, node2);
break;
}
}
for (Pair replacement : replacementsCurrent) {
String left = replacement.getLeft();
String right = replacement.getRight();
if (initializer1 != null && initializer2 != null && initializer1.toString().equals(initializer2.toString().replace(left, right))) {
processStatementMap(originalPair, matchPair, temp, node1, node2);
break;
}
}
if (initializer1 instanceof ClassInstanceCreation && initializer2 instanceof ClassInstanceCreation) {
ClassInstanceCreation creation1 = (ClassInstanceCreation) initializer1;
ClassInstanceCreation creation2 = (ClassInstanceCreation) initializer2;
String classType1 = creation1.getType().toString();
String classType2 = creation2.getType().toString();
if (entityReplacements.get(EntityType.CLASS) != null && entityReplacements.get(EntityType.CLASS).contains(Pair.of(classType1, classType2)))
processStatementMap(originalPair, matchPair, temp, node1, node2);
}
if (!node1.isRefactored() && !node2.isRefactored()) {
double txtSim = DiceFunction.calculateDiceSimilarity(node1, node2);
if (txtSim < 0.15 && !StringUtils.equals(type1.toString(), type2.toString()) &&
!StringUtils.equals(fragment1.getName().getIdentifier(), fragment2.getName().getIdentifier()))
continue;
for (Pair pair : matchedStatements) {
StatementNodeTree left = pair.getLeft();
StatementNodeTree right = pair.getRight();
List astNodes1 = new ArrayList<>();
List astNodes2 = new ArrayList<>();
left.getStatement().accept(new ASTVisitor() {
@Override
public boolean visit(SimpleName node) {
astNodes1.add(node.getIdentifier());
return true;
}
});
right.getStatement().accept(new ASTVisitor() {
@Override
public boolean visit(SimpleName node) {
astNodes2.add(node.getIdentifier());
return true;
}
});
if (astNodes1.contains(variableName1) && astNodes2.contains(variableName2))
processStatementMap(originalPair, matchPair, temp, node1, node2);
}
}
}
}
}
replacedMap2SetOfMatchedStatements(matchedStatements, deletedStatements, addedStatements, temp);
}
private void additionalMatchByReturn(MatchPair matchPair) {
Set> matchedStatements = matchPair.getMatchedStatements();
Set deletedStatements = matchPair.getDeletedStatements();
Set addedStatements = matchPair.getAddedStatements();
List returnStatements1 = new ArrayList<>();
List returnStatements2 = new ArrayList<>();
for (StatementNodeTree node : deletedStatements) {
if (node.getType() == StatementType.RETURN_STATEMENT)
returnStatements1.add(node);
}
for (StatementNodeTree node : addedStatements) {
if (node.getType() == StatementType.RETURN_STATEMENT)
returnStatements2.add(node);
}
if (returnStatements1.size() == 1 && returnStatements2.size() == 1) {
StatementNodeTree returnStatement1 = returnStatements1.get(0);
StatementNodeTree returnStatement2 = returnStatements2.get(0);
if (returnStatement1.getDepth() == 1 && returnStatement2.getDepth() == 1) {
returnStatement1.setMatched();
returnStatement2.setMatched();
matchedStatements.add(Pair.of(returnStatement1, returnStatement2));
deletedStatements.remove(returnStatement1);
addedStatements.remove(returnStatement2);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy