gr.uom.java.xmi.diff.ExtractOperationDetection Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of refactoring-miner Show documentation
Show all versions of refactoring-miner Show documentation
RefactoringMiner is a library/API written in Java that can detect refactorings applied in the history of a Java project.
package gr.uom.java.xmi.diff;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import org.refactoringminer.api.RefactoringMinerTimedOutException;
import gr.uom.java.xmi.UMLOperation;
import gr.uom.java.xmi.UMLParameter;
import gr.uom.java.xmi.UMLType;
import gr.uom.java.xmi.decomposition.AbstractCodeFragment;
import gr.uom.java.xmi.decomposition.AbstractCodeMapping;
import gr.uom.java.xmi.decomposition.CompositeStatementObject;
import gr.uom.java.xmi.decomposition.LambdaExpressionObject;
import gr.uom.java.xmi.decomposition.OperationInvocation;
import gr.uom.java.xmi.decomposition.StatementObject;
import gr.uom.java.xmi.decomposition.UMLOperationBodyMapper;
import gr.uom.java.xmi.decomposition.replacement.Replacement.ReplacementType;
public class ExtractOperationDetection {
private UMLOperationBodyMapper mapper;
private List addedOperations;
private UMLClassBaseDiff classDiff;
private UMLModelDiff modelDiff;
private List operationInvocations;
private Map callTreeMap = new LinkedHashMap();
public ExtractOperationDetection(UMLOperationBodyMapper mapper, List addedOperations, UMLClassBaseDiff classDiff, UMLModelDiff modelDiff) {
this.mapper = mapper;
this.addedOperations = addedOperations;
this.classDiff = classDiff;
this.modelDiff = modelDiff;
this.operationInvocations = getInvocationsInSourceOperationAfterExtraction(mapper);
}
public List check(UMLOperation addedOperation) throws RefactoringMinerTimedOutException {
List refactorings = new ArrayList();
if(!mapper.getNonMappedLeavesT1().isEmpty() || !mapper.getNonMappedInnerNodesT1().isEmpty() ||
!mapper.getReplacementsInvolvingMethodInvocation().isEmpty()) {
List addedOperationInvocations = matchingInvocations(addedOperation, operationInvocations, mapper.getOperation2().variableTypeMap());
if(addedOperationInvocations.size() > 0) {
int otherAddedMethodsCalled = 0;
for(UMLOperation addedOperation2 : this.addedOperations) {
if(!addedOperation.equals(addedOperation2)) {
List addedOperationInvocations2 = matchingInvocations(addedOperation2, operationInvocations, mapper.getOperation2().variableTypeMap());
if(addedOperationInvocations2.size() > 0) {
otherAddedMethodsCalled++;
}
}
}
if(otherAddedMethodsCalled == 0) {
for(OperationInvocation addedOperationInvocation : addedOperationInvocations) {
processAddedOperation(mapper, addedOperation, refactorings, addedOperationInvocations, addedOperationInvocation);
}
}
else {
processAddedOperation(mapper, addedOperation, refactorings, addedOperationInvocations, addedOperationInvocations.get(0));
}
}
}
return refactorings;
}
private void processAddedOperation(UMLOperationBodyMapper mapper, UMLOperation addedOperation,
List refactorings,
List addedOperationInvocations, OperationInvocation addedOperationInvocation)
throws RefactoringMinerTimedOutException {
CallTreeNode root = new CallTreeNode(mapper.getOperation1(), addedOperation, addedOperationInvocation);
CallTree callTree = null;
if(callTreeMap.containsKey(root)) {
callTree = callTreeMap.get(root);
}
else {
callTree = new CallTree(root);
generateCallTree(addedOperation, root, callTree);
callTreeMap.put(root, callTree);
}
UMLOperationBodyMapper operationBodyMapper = createMapperForExtractedMethod(mapper, mapper.getOperation1(), addedOperation, addedOperationInvocation);
if(operationBodyMapper != null) {
List additionalExactMatches = new ArrayList();
List nodesInBreadthFirstOrder = callTree.getNodesInBreadthFirstOrder();
for(int i=1; i()) && extractMatchCondition(operationBodyMapper, additionalExactMatches)) {
List nestedMatchingInvocations = matchingInvocations(node.getInvokedOperation(), node.getOriginalOperation().getAllOperationInvocations(), node.getOriginalOperation().variableTypeMap());
ExtractOperationRefactoring nestedRefactoring = new ExtractOperationRefactoring(nestedMapper, mapper.getOperation2(), nestedMatchingInvocations);
refactorings.add(nestedRefactoring);
operationBodyMapper.addChildMapper(nestedMapper);
}
//add back to mapper non-exact matches
for(AbstractCodeMapping mapping : nestedMapper.getMappings()) {
if(!mapping.isExact() || mapping.getFragment1().getString().equals("{")) {
AbstractCodeFragment fragment1 = mapping.getFragment1();
if(fragment1 instanceof StatementObject) {
if(!mapper.getNonMappedLeavesT1().contains(fragment1)) {
mapper.getNonMappedLeavesT1().add((StatementObject)fragment1);
}
}
else if(fragment1 instanceof CompositeStatementObject) {
if(!mapper.getNonMappedInnerNodesT1().contains(fragment1)) {
mapper.getNonMappedInnerNodesT1().add((CompositeStatementObject)fragment1);
}
}
}
}
}
}
}
UMLOperation delegateMethod = findDelegateMethod(mapper.getOperation1(), addedOperation, addedOperationInvocation);
if(extractMatchCondition(operationBodyMapper, additionalExactMatches)) {
if(delegateMethod == null) {
refactorings.add(new ExtractOperationRefactoring(operationBodyMapper, mapper.getOperation2(), addedOperationInvocations));
}
else {
refactorings.add(new ExtractOperationRefactoring(operationBodyMapper, addedOperation,
mapper.getOperation1(), mapper.getOperation2(), addedOperationInvocations));
}
}
}
}
public static List getInvocationsInSourceOperationAfterExtraction(UMLOperationBodyMapper mapper) {
List operationInvocations = mapper.getOperation2().getAllOperationInvocations();
for(StatementObject statement : mapper.getNonMappedLeavesT2()) {
addStatementInvocations(operationInvocations, statement);
}
return operationInvocations;
}
public static void addStatementInvocations(List operationInvocations, StatementObject statement) {
Map> statementMethodInvocationMap = statement.getMethodInvocationMap();
for(String key : statementMethodInvocationMap.keySet()) {
for(OperationInvocation statementInvocation : statementMethodInvocationMap.get(key)) {
if(!containsInvocation(operationInvocations, statementInvocation)) {
operationInvocations.add(statementInvocation);
}
}
}
List lambdas = statement.getLambdas();
for(LambdaExpressionObject lambda : lambdas) {
if(lambda.getBody() != null) {
for(OperationInvocation statementInvocation : lambda.getBody().getAllOperationInvocations()) {
if(!containsInvocation(operationInvocations, statementInvocation)) {
operationInvocations.add(statementInvocation);
}
}
}
if(lambda.getExpression() != null) {
Map> methodInvocationMap = lambda.getExpression().getMethodInvocationMap();
for(String key : methodInvocationMap.keySet()) {
for(OperationInvocation statementInvocation : methodInvocationMap.get(key)) {
if(!containsInvocation(operationInvocations, statementInvocation)) {
operationInvocations.add(statementInvocation);
}
}
}
}
}
}
public static boolean containsInvocation(List operationInvocations, OperationInvocation invocation) {
for(OperationInvocation operationInvocation : operationInvocations) {
if(operationInvocation.getLocationInfo().equals(invocation.getLocationInfo())) {
return true;
}
}
return false;
}
private List matchingInvocations(UMLOperation operation,
List operationInvocations, Map variableTypeMap) {
List addedOperationInvocations = new ArrayList();
for(OperationInvocation invocation : operationInvocations) {
if(invocation.matchesOperation(operation, variableTypeMap, modelDiff)) {
addedOperationInvocations.add(invocation);
}
}
return addedOperationInvocations;
}
private void generateCallTree(UMLOperation operation, CallTreeNode parent, CallTree callTree) {
List invocations = operation.getAllOperationInvocations();
for(UMLOperation addedOperation : addedOperations) {
for(OperationInvocation invocation : invocations) {
if(invocation.matchesOperation(addedOperation, operation.variableTypeMap(), modelDiff)) {
if(!callTree.contains(addedOperation)) {
CallTreeNode node = new CallTreeNode(operation, addedOperation, invocation);
parent.addChild(node);
generateCallTree(addedOperation, node, callTree);
}
}
}
}
}
private UMLOperationBodyMapper createMapperForExtractedMethod(UMLOperationBodyMapper mapper,
UMLOperation originalOperation, UMLOperation addedOperation, OperationInvocation addedOperationInvocation) throws RefactoringMinerTimedOutException {
List originalMethodParameters = originalOperation.getParametersWithoutReturnType();
Map originalMethodParametersPassedAsArgumentsMappedToCalledMethodParameters = new LinkedHashMap();
List arguments = addedOperationInvocation.getArguments();
List parameters = addedOperation.getParametersWithoutReturnType();
Map parameterToArgumentMap = new LinkedHashMap();
//special handling for methods with varargs parameter for which no argument is passed in the matching invocation
int size = Math.min(arguments.size(), parameters.size());
for(int i=0; i(), parameterToArgumentMap, classDiff);
}
return null;
}
private boolean extractMatchCondition(UMLOperationBodyMapper operationBodyMapper, List additionalExactMatches) {
int mappings = operationBodyMapper.mappingsWithoutBlocks();
int nonMappedElementsT1 = operationBodyMapper.nonMappedElementsT1();
int nonMappedElementsT2 = operationBodyMapper.nonMappedElementsT2();
List exactMatchList = new ArrayList(operationBodyMapper.getExactMatches());
boolean exceptionHandlingExactMatch = false;
boolean throwsNewExceptionExactMatch = false;
if(exactMatchList.size() == 1) {
AbstractCodeMapping mapping = exactMatchList.get(0);
if(mapping.getFragment1() instanceof StatementObject && mapping.getFragment2() instanceof StatementObject) {
StatementObject statement1 = (StatementObject)mapping.getFragment1();
StatementObject statement2 = (StatementObject)mapping.getFragment2();
if(statement1.getParent().getString().startsWith("catch(") &&
statement2.getParent().getString().startsWith("catch(")) {
exceptionHandlingExactMatch = true;
}
}
if(mapping.getFragment1().throwsNewException() && mapping.getFragment2().throwsNewException()) {
throwsNewExceptionExactMatch = true;
}
}
exactMatchList.addAll(additionalExactMatches);
int exactMatches = exactMatchList.size();
return mappings > 0 && (mappings > nonMappedElementsT2 || (mappings > 1 && mappings >= nonMappedElementsT2) ||
(exactMatches >= mappings && nonMappedElementsT1 == 0) ||
(exactMatches == 1 && !throwsNewExceptionExactMatch && nonMappedElementsT2-exactMatches <= 10) ||
(!exceptionHandlingExactMatch && exactMatches > 1 && additionalExactMatches.size() < exactMatches && nonMappedElementsT2-exactMatches < 20) ||
(mappings == 1 && mappings > operationBodyMapper.nonMappedLeafElementsT2())) ||
argumentExtractedWithDefaultReturnAdded(operationBodyMapper);
}
private boolean argumentExtractedWithDefaultReturnAdded(UMLOperationBodyMapper operationBodyMapper) {
List totalMappings = new ArrayList(operationBodyMapper.getMappings());
List nonMappedInnerNodesT2 = new ArrayList(operationBodyMapper.getNonMappedInnerNodesT2());
ListIterator iterator = nonMappedInnerNodesT2.listIterator();
while(iterator.hasNext()) {
if(iterator.next().toString().equals("{")) {
iterator.remove();
}
}
List nonMappedLeavesT2 = operationBodyMapper.getNonMappedLeavesT2();
return totalMappings.size() == 1 && totalMappings.get(0).containsReplacement(ReplacementType.ARGUMENT_REPLACED_WITH_RETURN_EXPRESSION) &&
nonMappedInnerNodesT2.size() == 1 && nonMappedInnerNodesT2.get(0).toString().startsWith("if") &&
nonMappedLeavesT2.size() == 1 && nonMappedLeavesT2.get(0).toString().startsWith("return ");
}
private UMLOperation findDelegateMethod(UMLOperation originalOperation, UMLOperation addedOperation, OperationInvocation addedOperationInvocation) {
OperationInvocation delegateMethodInvocation = addedOperation.isDelegate();
if(originalOperation.isDelegate() == null && delegateMethodInvocation != null && !originalOperation.getAllOperationInvocations().contains(addedOperationInvocation)) {
for(UMLOperation operation : addedOperations) {
if(delegateMethodInvocation.matchesOperation(operation, addedOperation.variableTypeMap(), modelDiff)) {
return operation;
}
}
}
return null;
}
private boolean parameterTypesMatch(Map originalMethodParametersPassedAsArgumentsMappedToCalledMethodParameters) {
for(UMLParameter key : originalMethodParametersPassedAsArgumentsMappedToCalledMethodParameters.keySet()) {
UMLParameter value = originalMethodParametersPassedAsArgumentsMappedToCalledMethodParameters.get(key);
if(!key.getType().equals(value.getType()) && !key.getType().equalsWithSubType(value.getType()) &&
!modelDiff.isSubclassOf(key.getType().getClassType(), value.getType().getClassType())) {
return false;
}
}
return true;
}
}