package mit.edu.concurrencyrefactorings.refactorings; import java.util.ArrayList; import java.util.Collection; import java.util.List; import mit.edu.concurrencyrefactorings.util.ReferencesFinder; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jdt.core.Flags; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; import org.eclipse.jdt.core.dom.Assignment; import org.eclipse.jdt.core.dom.Block; import org.eclipse.jdt.core.dom.CastExpression; import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor; import org.eclipse.jdt.core.dom.ClassInstanceCreation; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.ExpressionStatement; import org.eclipse.jdt.core.dom.FieldAccess; import org.eclipse.jdt.core.dom.IBinding; import org.eclipse.jdt.core.dom.IVariableBinding; import org.eclipse.jdt.core.dom.IfStatement; import org.eclipse.jdt.core.dom.InfixExpression; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.MethodInvocation; import org.eclipse.jdt.core.dom.Modifier; import org.eclipse.jdt.core.dom.Name; import org.eclipse.jdt.core.dom.NullLiteral; import org.eclipse.jdt.core.dom.ParameterizedType; import org.eclipse.jdt.core.dom.ParenthesizedExpression; import org.eclipse.jdt.core.dom.PrefixExpression; import org.eclipse.jdt.core.dom.PrimitiveType; import org.eclipse.jdt.core.dom.QualifiedName; import org.eclipse.jdt.core.dom.ReturnStatement; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SimpleType; import org.eclipse.jdt.core.dom.Statement; import org.eclipse.jdt.core.dom.SuperFieldAccess; import org.eclipse.jdt.core.dom.SynchronizedStatement; import org.eclipse.jdt.core.dom.ThisExpression; import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.TypeLiteral; import org.eclipse.jdt.core.dom.VariableDeclaration; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.jdt.core.dom.VariableDeclarationStatement; import org.eclipse.jdt.core.dom.InfixExpression.Operator; import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; import org.eclipse.jdt.core.dom.rewrite.ImportRewrite; import org.eclipse.jdt.core.dom.rewrite.ListRewrite; import org.eclipse.jdt.core.refactoring.CompilationUnitChange; import org.eclipse.jdt.internal.compiler.lookup.FieldBinding; import org.eclipse.jdt.internal.core.search.matching.SecondaryTypeDeclarationPattern; import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory; import org.eclipse.jdt.internal.corext.dom.ASTNodes; import org.eclipse.jdt.internal.corext.dom.Bindings; import org.eclipse.jdt.internal.corext.dom.JdtASTMatcher; import org.eclipse.jdt.internal.corext.dom.ModifierRewrite; import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages; import org.eclipse.jdt.internal.corext.refactoring.code.ExtractMethodRefactoring; import org.eclipse.jdt.internal.corext.util.Messages; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; import org.eclipse.ltk.core.refactoring.Change; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.ltk.core.refactoring.TextEditBasedChangeGroup; import org.eclipse.text.edits.InsertEdit; import org.eclipse.text.edits.MultiTextEdit; import org.eclipse.text.edits.TextEdit; import org.eclipse.text.edits.TextEditGroup; public class AccessAnalyzerForConcurrentHashMap extends ASTVisitor { private static final String METHOD_INVOCATION = "Method Invocation"; private static final String INITIALIZATION = "Initialization"; private static final String REPLACE_WITH_PUT_IF_ABSENT = "Replace with putIfAbsent()"; private static final String REMOVE_STATEMENT = "Remove Statement"; private static final String REMOVE_SYNCHRONIZED_BLOCK = "Remove Synchronized Block"; private static final String REMOVE_SYNCHRONIZED_MODIFIER = "Remove Synchronized Modifier"; private ICompilationUnit fCUnit; private IVariableBinding fFieldBinding; private AbstractTypeDeclaration fDeclaringClass; private ASTRewrite fRewriter; private ImportRewrite fImportRewriter; private List<TextEditGroup> fGroupDescriptions; private boolean fIsFieldFinal; private RefactoringStatus fStatus; public boolean usingCHMOnlyMethods = false; private boolean fSetterMustReturnValue; private final CompilationUnit cuRoot; private final ConvertToConcurrentHashMapRefactoring refactoring; private String fMethodName; public AccessAnalyzerForConcurrentHashMap( ConvertToConcurrentHashMapRefactoring refactoring, ICompilationUnit unit, IVariableBinding field, AbstractTypeDeclaration declaringClass, ASTRewrite rewriter, ImportRewrite importRewrite, CompilationUnit root) { this.refactoring = refactoring; fCUnit= unit; this.cuRoot = root; fFieldBinding= field.getVariableDeclaration(); fDeclaringClass= declaringClass; fRewriter= rewriter; fImportRewriter= importRewrite; fGroupDescriptions= new ArrayList<TextEditGroup>(); fMethodName = null; try { fIsFieldFinal= Flags.isFinal(refactoring.getField().getFlags()); } catch (JavaModelException e) { // assume non final field } fStatus= new RefactoringStatus(); } public RefactoringStatus getStatus() { return fStatus; } public Collection<TextEditGroup> getGroupDescriptions() { return fGroupDescriptions; } public boolean visit(ClassInstanceCreation node) { // is HashMap // is part of assignment // if left-hand binds to our variable // then change type AST ast = node.getAST(); Type instanceCreationType = node.getType(); if(instanceCreationType instanceof ParameterizedType) { instanceCreationType = (Type) ASTNode.copySubtree(ast, ((ParameterizedType)instanceCreationType).getType()); } if(instanceCreationType instanceof SimpleType) { String fqName = ((SimpleType)instanceCreationType).getName().getFullyQualifiedName(); if(fqName.equals("HashMap") || fqName.equals("java.util.HashMap")) { ASTNode assignment = ASTNodes.getParent(node, Assignment.class); if(assignment != null) { Expression leftHandSide = ((Assignment)assignment).getLeftHandSide(); if (considerBinding(resolveBinding(leftHandSide))) { Expression rightHandSide = ((Assignment)assignment).getRightHandSide(); if (rightHandSide != null) { Type newTypeRS = ast.newSimpleType(ASTNodeFactory.newName(ast, "ConcurrentHashMap")); if(rightHandSide instanceof ClassInstanceCreation) { ClassInstanceCreation concurrentInstanceCreation = ast.newClassInstanceCreation(); Type rhsType = ((ClassInstanceCreation)rightHandSide).getType(); if(rhsType instanceof ParameterizedType) { newTypeRS = ast.newParameterizedType(newTypeRS); Type oldTypeRS = ((ParameterizedType) rhsType).getType(); List<Type> oldTypeArgumentsRS = ((ParameterizedType) rhsType).typeArguments(); List<Type> newTypeArgumentsRS = ((ParameterizedType)newTypeRS).typeArguments(); newTypeArgumentsRS.addAll(ASTNode.copySubtrees(ast, oldTypeArgumentsRS)); } concurrentInstanceCreation.setType(newTypeRS); fRewriter.replace(rightHandSide, concurrentInstanceCreation, createGroupDescription(INITIALIZATION)); } } } } } } return true; } @Override public boolean visit(IfStatement ifStatement) { Expression ifExpression = ifStatement.getExpression(); // Remove the parentheses. while (ifExpression instanceof ParenthesizedExpression) { ifExpression = ((ParenthesizedExpression)ifExpression).getExpression(); } if(ifExpression instanceof InfixExpression) { // Operator conditional: if(something operator something) handleOperatorConditional(ifStatement, ifExpression); } else { // Direct conditional: if(something) handleDirectConditional(ifStatement, ifExpression); } return false; } private void handleOperatorConditional(IfStatement ifStatement, Expression ifExpression) { Operator operator = ((InfixExpression)ifExpression).getOperator(); InfixExpression testExpression = (InfixExpression) ifExpression; Expression leftOperand = testExpression.getLeftOperand(); Expression rightOperand = testExpression.getRightOperand(); // TODO - (cleanup) can factor this out some, but ensure we don't accept other operators if(rightOperand instanceof NullLiteral) { // if(? operator null) if(operator.equals(InfixExpression.Operator.EQUALS)) { // if(? == null) if(leftOperand instanceof MethodInvocation) { // if(method() == null) handleMethodAndNullConditional(ifStatement, leftOperand, false); } else if(leftOperand instanceof SimpleName) { // if(variable == null) handleVariableAndNullConditional(ifStatement, leftOperand, false); } } else if(operator.equals(InfixExpression.Operator.NOT_EQUALS)) { // if(? != null) if(leftOperand instanceof MethodInvocation) { // if(method() != null) handleMethodAndNullConditional(ifStatement, leftOperand, true); } else if(leftOperand instanceof SimpleName) { // if(variable != null) handleVariableAndNullConditional(ifStatement, leftOperand, true); } } } } private void handleDirectConditional(IfStatement ifStatement, Expression ifExpression) { boolean expressionIsNegation = false; // Remove the prefix but record its presence. if(ifExpression instanceof PrefixExpression) { if (((PrefixExpression)ifExpression).getOperator().equals(PrefixExpression.Operator.NOT)) { PrefixExpression negationExpression = (PrefixExpression) ifExpression; ifExpression = negationExpression.getOperand(); expressionIsNegation = true; } } // Remove the parentheses. while (ifExpression instanceof ParenthesizedExpression) { ifExpression = ((ParenthesizedExpression)ifExpression).getExpression(); } // if(something) or if(!something) if (ifExpression instanceof MethodInvocation) { // if(method()) or if(!method()) handleMethodDirectConditional(ifStatement, ifExpression, expressionIsNegation); } else if (ifExpression instanceof SimpleName) { // if(variable) or if(!variable) handleVariableDirectConditional(ifStatement, ifExpression, expressionIsNegation); } } private void handleVariableDirectConditional(IfStatement ifStatement, Expression operand, boolean hasNegationPrefix) { // TODO (possible feature, low priority) // for the generalization, I think this needs to allow for "get" - might need to restructure, // factor out "instanceof MethodInvocation". // if(variable) or if(!variable) SimpleName booleanVariableCheck = (SimpleName) operand; Block enclosingBlock = (Block) ASTNodes.getParent(ifStatement, Block.class); if(enclosingBlock != null) { List statementsInEnclosingBlock = enclosingBlock.statements(); int indexOfIfStatement = statementsInEnclosingBlock.indexOf(ifStatement); if (indexOfIfStatement > 0) { ASTNode statementBeforeIf = (ASTNode) statementsInEnclosingBlock.get(indexOfIfStatement-1); if(statementBeforeIf instanceof VariableDeclarationStatement) { VariableDeclarationStatement declarationStatementBeforeIf = (VariableDeclarationStatement)statementBeforeIf; if (declarationStatementBeforeIf.getType().isPrimitiveType()) { PrimitiveType declPrimitive = (PrimitiveType)declarationStatementBeforeIf.getType(); if(declPrimitive.getPrimitiveTypeCode().equals(PrimitiveType.BOOLEAN)) { // if(boolean) or if(!boolean) VariableDeclarationFragment declarationFragmentBeforeIf = (VariableDeclarationFragment) declarationStatementBeforeIf.fragments().get(0); if(Bindings.equals(declarationFragmentBeforeIf.getName().resolveBinding(), booleanVariableCheck.resolveBinding())) { ASTNode booleanVariableInitializer = declarationFragmentBeforeIf.getInitializer(); if(booleanVariableInitializer instanceof MethodInvocation) { MethodInvocation booleanVariableInitializerMethod = (MethodInvocation)booleanVariableInitializer; Expression booleanVariableInitializerExpression = booleanVariableInitializerMethod.getExpression(); if(booleanVariableInitializerExpression != null && booleanVariableInitializerExpression instanceof MethodInvocation) { if(checkMethodNameAndBinding((MethodInvocation)booleanVariableInitializerExpression, "get") && booleanVariableInitializerMethod.getName().getIdentifier().equals("equals")) { // boolean = get().equals(); if(boolean) or boolean = get().equals(); if(!boolean) ASTNode hashMapKey = (ASTNode) ((MethodInvocation) booleanVariableInitializerExpression).arguments().get(0); ASTNode equalsValue = (ASTNode) booleanVariableInitializerMethod.arguments().get(0); Statement thenStatement = ifStatement.getThenStatement(); if(thenStatement instanceof Block) { List blockStatements = ((Block)thenStatement).statements(); if(blockStatements.size() == 1) { Statement statement = (Statement) blockStatements.get(0); // boolean = get().equals(); if(boolean) { method(); } or !boolean form handleSingleInvocationInThenClauseForDirectConditional(ifStatement, hasNegationPrefix, hashMapKey, equalsValue, statement, true); } else { // boolean = get().equals(); if(boolean) { ... method(); } or !boolean form handleMultipleStatementsInThenClause(ifStatement, hashMapKey, equalsValue, blockStatements, true); } } else if(thenStatement instanceof ExpressionStatement) { // boolean = get().equals(); if(boolean) method(); or !boolean form handleSingleInvocationInThenClauseForDirectConditional(ifStatement, hasNegationPrefix, hashMapKey, equalsValue, thenStatement, true); } } } else if(checkMethodNameAndBinding(booleanVariableInitializerMethod, "containsKey")) { // boolean = containsKey(); if(boolean) or boolean = containsKey(); if(!boolean) ASTNode hashMapKey = (ASTNode) booleanVariableInitializerMethod.arguments().get(0); ASTNode equalsValue = null; Statement thenStatement = ifStatement.getThenStatement(); if(thenStatement instanceof Block) { List blockStatements = ((Block)thenStatement).statements(); if(blockStatements.size() == 1) { Statement statement = (Statement) blockStatements.get(0); // boolean = containsKey(); if(boolean) { method(); } or !boolean form handleSingleInvocationInThenClauseForDirectConditional(ifStatement, hasNegationPrefix, hashMapKey, equalsValue, statement, true); } else { // boolean = containsKey(); if(boolean) { ... method(); } or !boolean form handleMultipleStatementsInThenClause(ifStatement, hashMapKey, equalsValue, blockStatements, true); } } else if(thenStatement instanceof ExpressionStatement) { // boolean = containsKey(); if(boolean) method(); or !boolean form handleSingleInvocationInThenClauseForDirectConditional(ifStatement, hasNegationPrefix, hashMapKey, equalsValue, thenStatement, true); } } } } } } else if(declarationStatementBeforeIf.getType().isSimpleType()) { // TODO This can be anything other than simple. Don't even really need to check it. // if(Object) or if(!Object) // TODO (possible feature, low priority) } } } } } private void handleMethodDirectConditional(IfStatement ifStatement, Expression operand, boolean hasNegationPrefix) { // if(method()) or if(!method()) MethodInvocation mapInvocation; mapInvocation = (MethodInvocation) operand; Expression mapInvocationExpression = mapInvocation.getExpression(); // TODO (cleanup) can factor the common operations between these two clauses if(mapInvocationExpression != null && mapInvocationExpression instanceof MethodInvocation) { if(checkMethodNameAndBinding((MethodInvocation)mapInvocationExpression, "get") && mapInvocation.getName().getIdentifier().equals("equals")) { ASTNode hashMapKey = (ASTNode) ((MethodInvocation) mapInvocationExpression).arguments().get(0); ASTNode equalsValue = (ASTNode) mapInvocation.arguments().get(0); Statement thenStatement = ifStatement.getThenStatement(); if(thenStatement instanceof Block) { List blockStatements = ((Block)thenStatement).statements(); if(blockStatements.size() == 1) { Statement statement = (Statement) blockStatements.get(0); // if(method()) { method2(); } or !method() form handleSingleInvocationInThenClauseForDirectConditional(ifStatement, hasNegationPrefix, hashMapKey, equalsValue, statement, false); } else { // if(method()) { ... method2(); } or !method() form handleMultipleStatementsInThenClause(ifStatement, hashMapKey, equalsValue, blockStatements, false); } } else if(thenStatement instanceof ExpressionStatement) { // if(method()) method2(); or !method() form handleSingleInvocationInThenClauseForDirectConditional(ifStatement, hasNegationPrefix, hashMapKey, equalsValue, thenStatement, false); } } } else if (checkMethodNameAndBinding(mapInvocation, "containsKey")) { ASTNode hashMapKey = (ASTNode) mapInvocation.arguments().get(0); ASTNode equalsValue = null; Statement thenStatement = ifStatement.getThenStatement(); if(thenStatement instanceof Block) { List blockStatements = ((Block)thenStatement).statements(); if(blockStatements.size() == 1) { Statement statement = (Statement) blockStatements.get(0); // if(method()) { method2(); } or !method() form handleSingleInvocationInThenClauseForDirectConditional(ifStatement, hasNegationPrefix, hashMapKey, equalsValue, statement, false); } else { // if(method()) { ... method2(); } or !method() form handleMultipleStatementsInThenClause(ifStatement, hashMapKey, equalsValue, blockStatements, false); } } else if(thenStatement instanceof ExpressionStatement) { // if(method()) method2(); or !method() form handleSingleInvocationInThenClauseForDirectConditional(ifStatement, hasNegationPrefix, hashMapKey, equalsValue, thenStatement, false); } } } private void handleSingleInvocationInThenClauseForDirectConditional(IfStatement ifStatement, boolean hasNegationPrefix, ASTNode hashMapKey, ASTNode equalsValue, Statement statement, boolean removeStatementBeforeIf) { if(statement instanceof ExpressionStatement) { Expression expression = ((ExpressionStatement)statement).getExpression(); if(expression instanceof MethodInvocation) { if(checkMethodNameAndBinding((MethodInvocation)expression, "put")) { if(hasNegationPrefix) { // boolean = containsKey(); if(!boolean) { put(); } -> putIfAbsent(); replaceIfStatement(ifStatement, hashMapKey, equalsValue, statement, "putIfAbsent", null, null, removeStatementBeforeIf, false, false, null); } else { // boolean = containsKey(); if(boolean) { put(); } -> replace(); replaceIfStatement(ifStatement, hashMapKey, equalsValue, statement, "replace", null, null, removeStatementBeforeIf, false, false, null); } } else if(checkMethodNameAndBinding((MethodInvocation)expression, "remove")) { if(!hasNegationPrefix) { // boolean = containsKey(); if(boolean) { remove(); } -> remove(); replaceIfStatement(ifStatement, hashMapKey, equalsValue, statement, "remove", null, null, removeStatementBeforeIf, false, false, null); } } } } } private void handleSingleInvocationInThenClauseForOperatorConditional(IfStatement ifStatement, boolean testNotNull, ASTNode hashMapKey, ASTNode equalsValue, Statement statement, boolean removeStatementBeforeIf) { if(statement instanceof ExpressionStatement) { Expression expression = ((ExpressionStatement)statement).getExpression(); if(expression instanceof MethodInvocation) { if(checkMethodNameAndBinding((MethodInvocation)expression, "put")) { if(testNotNull) { // if(method() != null) { put(); } -> replace(); replaceIfStatement(ifStatement, hashMapKey, equalsValue, statement, "replace", null, null, removeStatementBeforeIf, false, false, null); } else { // if(method() == null) { put(); } -> putIfAbsent(); replaceIfStatement(ifStatement, hashMapKey, equalsValue, statement, "putIfAbsent", null, null, removeStatementBeforeIf, false, false, null); } } else if(checkMethodNameAndBinding((MethodInvocation)expression, "remove")) { if(testNotNull) { // if(method() != null) { remove(); } -> remove(); replaceIfStatement(ifStatement, hashMapKey, equalsValue, statement, "remove", null, null, removeStatementBeforeIf, false, false, null); } } } } } private void handleVariableAndNullConditional( IfStatement ifStatement, Expression leftOperand, boolean testNotNull) { SimpleName nullVariableCheck = (SimpleName) leftOperand; Block enclosingBlock = (Block) ASTNodes.getParent(ifStatement, Block.class); if(enclosingBlock != null) { List statementsInEnclosingBlock = enclosingBlock.statements(); int indexOfIfStatement = statementsInEnclosingBlock.indexOf(ifStatement); if (indexOfIfStatement > 0) { // TODO TODO - should be able to acquire this statement no matter where it is relative to the if? (Is there another such statement elsewhere in this code?) ASTNode statementBeforeIf = (ASTNode) statementsInEnclosingBlock.get(indexOfIfStatement-1); if(statementBeforeIf instanceof VariableDeclarationStatement) { VariableDeclarationStatement declarationStatementBeforeIf = (VariableDeclarationStatement)statementBeforeIf; // TODO (mandatory add?) This can be anything, excluding primitive and wildcard. We don't handle any other cases besides simple. if (declarationStatementBeforeIf.getType().isSimpleType()) { SimpleType declSimple = (SimpleType)declarationStatementBeforeIf.getType(); VariableDeclarationFragment declarationFragmentBeforeIf = (VariableDeclarationFragment) declarationStatementBeforeIf.fragments().get(0); if(Bindings.equals(declarationFragmentBeforeIf.getName().resolveBinding(), nullVariableCheck.resolveBinding())) { ASTNode nullVariableInitializer = declarationFragmentBeforeIf.getInitializer(); if(nullVariableInitializer instanceof CastExpression) { nullVariableInitializer = ((CastExpression)nullVariableInitializer).getExpression(); } if(nullVariableInitializer instanceof MethodInvocation) { MethodInvocation nullVariableInitializerMethod = (MethodInvocation)nullVariableInitializer; if(checkMethodNameAndBinding(nullVariableInitializerMethod, "get")) { // variable = get(); if(variable == null) or variable = get()); if(variable != null) ASTNode hashMapKey = (ASTNode) nullVariableInitializerMethod.arguments().get(0); ASTNode equalsValue = null; Statement thenStatement = ifStatement.getThenStatement(); if(thenStatement instanceof Block) { List blockStatements = ((Block)thenStatement).statements(); if(blockStatements.size() == 1) { Statement statement = (Statement) blockStatements.get(0); if(statement instanceof ExpressionStatement) { // variable = get(); if(variable == null) { method(); } or != form handleSingleInvocationInThenClauseForOperatorConditional(ifStatement, testNotNull, hashMapKey, equalsValue, statement, true); } } else { // variable = get(); if(variable == null) { ... method(); } or != form handleMultipleStatementsInThenClause(ifStatement, hashMapKey, equalsValue, blockStatements, true); } } else if(thenStatement instanceof ExpressionStatement) { // variable = get(); if(variable == null) method(); or != form handleSingleInvocationInThenClauseForOperatorConditional(ifStatement, testNotNull, hashMapKey, equalsValue, thenStatement, true); } } } } } } } } } private void handleMethodAndNullConditional(IfStatement ifStatement, Expression leftOperand, boolean testNotNull) { MethodInvocation mapInvocation = (MethodInvocation)leftOperand; if(checkMethodNameAndBinding(mapInvocation, "get")) { // if(get() == null) or != form ASTNode hashMapKey = (ASTNode) mapInvocation.arguments().get(0); ASTNode equalsValue = null; Statement thenStatement = ifStatement.getThenStatement(); if(thenStatement instanceof Block) { List blockStatements = ((Block)thenStatement).statements(); if(blockStatements.size() == 1) { Statement statement = (Statement) blockStatements.get(0); // if(method() == null) { method2(); } or != form handleSingleInvocationInThenClauseForOperatorConditional(ifStatement, testNotNull, hashMapKey, equalsValue, statement, false); } else { // if(method() == null) { ... method2(); } or != form handleMultipleStatementsInThenClause(ifStatement, hashMapKey, equalsValue, blockStatements, false); } } else if(thenStatement instanceof ExpressionStatement) { // if(method() == null) method2(); or != form handleSingleInvocationInThenClauseForOperatorConditional(ifStatement, testNotNull, hashMapKey, equalsValue, thenStatement, false); } } } @SuppressWarnings("restriction") private boolean handleMultipleStatementsInThenClause( IfStatement ifStatement, ASTNode hashMapKey, ASTNode equalsValue, List blockStatements, boolean removeStatementBeforeIf) { String methodInvocationText = null; String codeForCreateValueMethod = null; ASTNode replacementForValue = null; int numberOfStatementsBeforePut = 0; boolean otherStatementsBeforePut = false; int beginningOfThenBlockIndex = ((Statement)blockStatements.get(0)).getStartPosition(); Expression invocationOfPut = null; Statement statement = null; for(Object object : blockStatements) { statement = (Statement)object; numberOfStatementsBeforePut++; if(statement instanceof ExpressionStatement) { invocationOfPut = ((ExpressionStatement) statement).getExpression(); if(invocationOfPut instanceof MethodInvocation) { if(checkMethodNameAndBinding((MethodInvocation)invocationOfPut, "put")) { numberOfStatementsBeforePut--; break; } } } } Statement invocationOfPutStatement = (Statement) ASTNodes.getParent(invocationOfPut, Statement.class); final ASTNode secondArgumentToPut = (ASTNode) ((MethodInvocation)invocationOfPut).arguments().get(((MethodInvocation)invocationOfPut).arguments().size()-1); // Handle statements before put (createValue) if(beginningOfThenBlockIndex < invocationOfPut.getStartPosition()) { int beforePutStatementIndex = invocationOfPut.getStartPosition()-1; // If size of statements in BEFORE_PUT == 1, don't create a new method, but use that statement directly ReferencesFinder rf = new ReferencesFinder(); if(secondArgumentToPut instanceof SimpleName) { rf.findReferences(ifStatement, (SimpleName) secondArgumentToPut, ifStatement.getStartPosition(), invocationOfPut.getStartPosition()); } if(rf.getWriteReferences() != null && rf.hasWriteReferences()) { if(numberOfStatementsBeforePut > 1) { if(rf.getWriteReferences().size() == 1) { ASTNode theWrite = rf.getWriteReferences().get(0); Statement theWriteStatement = (Statement) ASTNodes.getParent(theWrite, Statement.class); fRewriter.remove(theWriteStatement, createGroupDescription(REMOVE_STATEMENT)); } fMethodName = "create" + secondArgumentToPut; Block enclosingBlock = (Block) ASTNodes.getParent(invocationOfPut, Block.class); checkSideEffects(fCUnit, enclosingBlock, beginningOfThenBlockIndex, beforePutStatementIndex - beginningOfThenBlockIndex); ExtractMethodRefactoring extractMethodRefactoring = new ExtractMethodRefactoring(fCUnit, beginningOfThenBlockIndex, beforePutStatementIndex - beginningOfThenBlockIndex); extractMethodRefactoring.setMethodName(fMethodName); extractMethodRefactoring.setVisibility(Modifier.PRIVATE); try { extractMethodRefactoring.checkAllConditions(new NullProgressMonitor()); Change extractMethodChanges = extractMethodRefactoring.createChange(new NullProgressMonitor()); TextEditGroup textEditGroupForNewCreateValueMethod= extractCreateValueMethod((CompilationUnitChange)extractMethodChanges); TextEditGroup methodInvocationTextEditGroup= getMethodInvocationTextEdit((CompilationUnitChange)extractMethodChanges); refactoring.setExtractMethodTextEdit(textEditGroupForNewCreateValueMethod); refactoring.setRemoveSetMethodInvocationEdit(methodInvocationTextEditGroup); TextEdit[] textEditsForMethodInvocation = methodInvocationTextEditGroup.getTextEdits(); for (TextEdit editForMethodInvocation : textEditsForMethodInvocation) { if (editForMethodInvocation instanceof InsertEdit) { InsertEdit insertMethodInvocation = (InsertEdit) editForMethodInvocation; String textToBeInserted = insertMethodInvocation.getText(); int startMethodInvocation = textToBeInserted.indexOf(fMethodName); int endMethodInvocation = textToBeInserted.indexOf(";"); methodInvocationText = textToBeInserted.substring(startMethodInvocation, endMethodInvocation); } } MultiTextEdit muxEdit = (MultiTextEdit) textEditsForMethodInvocation[0].getParent().getParent(); IDocument scratchDocument = new Document(fCUnit.getSource()); muxEdit.apply(scratchDocument); ASTParser parser = ASTParser.newParser(AST.JLS3); parser.setSource(scratchDocument.get().toCharArray()); CompilationUnit scratchCU = (CompilationUnit)parser.createAST(null); final TypeDeclaration[] declaringClass = new TypeDeclaration[1]; scratchCU.accept(new ASTVisitor() { public boolean visit(TypeDeclaration typedecl){ if (typedecl.getName().getIdentifier().equals(fFieldBinding.getDeclaringClass().getName())) { declaringClass[0] = typedecl; } return true; } }); MethodDeclaration[] methodsInRefactoredClass = declaringClass[0].getMethods(); for (MethodDeclaration methodDeclaration : methodsInRefactoredClass) { if (methodDeclaration.getName().getIdentifier().equals(fMethodName)) { int startPositionForMethodCode = methodDeclaration.getStartPosition(); int lengthOfMethod = methodDeclaration.getLength(); codeForCreateValueMethod = scratchDocument.get(startPositionForMethodCode, lengthOfMethod); break; } } } catch (Exception e) { fStatus.addFatalError("Error occurred while handling then-clause with multiple statements: " + e.getMessage()); return false; } } else if(numberOfStatementsBeforePut == 1) { if(rf.getWriteReferences().size() == 1) { ASTNode writeStatement = rf.getWriteReferences().get(0); fRewriter.remove(writeStatement, createGroupDescription(REMOVE_STATEMENT)); } Block thenStatement = ((Block)ifStatement.getThenStatement()); List oldThenStatements = thenStatement.statements(); rf = new ReferencesFinder(); if(secondArgumentToPut instanceof SimpleName) { rf.findReferences(ifStatement, (SimpleName) secondArgumentToPut, invocationOfPut.getStartPosition() + invocationOfPut.getLength(), thenStatement.getStartPosition() + thenStatement.getLength()); // If this value is read in AFTER_PUT or AFTER_IF, can't do this if(!rf.hasReadReferences()) { // TODO can generalize this to a search - try to find only one write of this variable ASTNode blockStat = (ASTNode) blockStatements.get(blockStatements.indexOf(invocationOfPutStatement) - 1); if(blockStat instanceof VariableDeclarationStatement) { VariableDeclarationFragment vdf = (VariableDeclarationFragment) ((VariableDeclarationStatement)blockStat).fragments().get(0); // If the left side matches the value argument, set replacementForValue to the right side if(secondArgumentToPut.subtreeMatch(new JdtASTMatcher(), vdf.getName())) { replacementForValue = vdf.getInitializer(); } } else if(blockStat instanceof ExpressionStatement) { ExpressionStatement createdValueInit = (ExpressionStatement)blockStat; if(createdValueInit.getExpression() instanceof Assignment) { Assignment assignment = (Assignment) createdValueInit.getExpression(); // If the left side matches the value argument, set replacementForValue to the right side if(secondArgumentToPut.subtreeMatch(new JdtASTMatcher(), assignment.getLeftHandSide())) { replacementForValue = assignment.getRightHandSide(); } } } } } } } } if(numberOfStatementsBeforePut > 0) { otherStatementsBeforePut = true; } // Handle statements after put Statement thenStatement = ifStatement.getThenStatement(); if(thenStatement instanceof Block) { List thenStatements = ((Block)thenStatement).statements(); int indexOfPutInvocation = thenStatements.indexOf(invocationOfPutStatement); if(indexOfPutInvocation != thenStatements.size()-1) { replaceIfStatement(ifStatement, hashMapKey, equalsValue, statement, "putIfAbsent", methodInvocationText, codeForCreateValueMethod, removeStatementBeforeIf, otherStatementsBeforePut, true, replacementForValue); } else { replaceIfStatement(ifStatement, hashMapKey, equalsValue, statement, "putIfAbsent", methodInvocationText, codeForCreateValueMethod, removeStatementBeforeIf, otherStatementsBeforePut, false, replacementForValue); } } else { replaceIfStatement(ifStatement, hashMapKey, equalsValue, statement, "putIfAbsent", methodInvocationText, codeForCreateValueMethod, removeStatementBeforeIf, otherStatementsBeforePut, false, replacementForValue); } MethodDeclaration methodDeclaringTheIfStatement = (MethodDeclaration) ASTNodes.getParent(ifStatement, MethodDeclaration.class); insertCreateValueMethod(codeForCreateValueMethod, methodDeclaringTheIfStatement); return true; } private void checkSideEffects(ICompilationUnit unit, Block enclosingBlock, int indexBeginningStatement, int indexEndStatement) { SideEffectsFinder sideEffectsFinder = new SideEffectsFinder (unit, enclosingBlock, indexBeginningStatement, indexEndStatement, fFieldBinding, fStatus); sideEffectsFinder.findEffects(); } private void insertCreateValueMethod(String codeForCreateValueMethod, MethodDeclaration methodContainingPutIfAbsent) { if(codeForCreateValueMethod == null) return; // TODO use CodeFormatter instead of the replaceAll below //CodeFormatter cf = new DefaultCodeFormatter(); // Gives TextEdits, then apply the TextEdits // Removes excess indentation. String codeForCreateValueMethodUnindented = codeForCreateValueMethod.replaceAll("\n\t", "\n"); ChildListPropertyDescriptor descriptor= getBodyDeclarationsProperty(fDeclaringClass); ASTNode nodeForCreateValueMethod = fRewriter.createStringPlaceholder(codeForCreateValueMethodUnindented, ASTNode.METHOD_DECLARATION); // TODO Use ReferencesFinder to find possible side-effects // ReferencesFinder rf = new ReferencesFinder(); //Assert.isTrue(fDeclaringClass != null, "null declaring class"); fRewriter.getListRewrite(fDeclaringClass, descriptor).insertAfter(nodeForCreateValueMethod, methodContainingPutIfAbsent, createGroupDescription("CreateValue() Method")); } private ChildListPropertyDescriptor getBodyDeclarationsProperty(ASTNode declaration) { if (declaration instanceof AnonymousClassDeclaration) return AnonymousClassDeclaration.BODY_DECLARATIONS_PROPERTY; else if (declaration instanceof AbstractTypeDeclaration) return ((AbstractTypeDeclaration) declaration).getBodyDeclarationsProperty(); else if(declaration instanceof MethodDeclaration) { return ((MethodDeclaration) declaration).getBody().STATEMENTS_PROPERTY; } Assert.isTrue(false); return null; } private void replaceIfStatement(IfStatement ifStatement, ASTNode hashMapKey, ASTNode equalsValue, Statement statement, String methodToReplaceWithName, String textForInvokingCreateValue, String codeForCreateValueMethod, boolean removeStatementBeforeIf, boolean statementsBeforePut, boolean statementsAfterPut, ASTNode replacementForValue ) { // TODO - Factor out all the common code. Right now, a lot of the branches are very similar. AST ast = ifStatement.getAST(); Expression thenStatementMethodInvocation = ((ExpressionStatement)statement).getExpression(); if(thenStatementMethodInvocation instanceof MethodInvocation) { MethodInvocation methodInvoc = (MethodInvocation)thenStatementMethodInvocation; Statement methodInvocStatement = (Statement) ASTNodes.getParent(methodInvoc, Statement.class); MethodInvocation newMethodInvoc = ast.newMethodInvocation(); ASTNode firstArgument = (ASTNode) methodInvoc.arguments().get(0); if(textForInvokingCreateValue != null) { List newListOfArguments = new ArrayList(); ASTNode copyOfFirstArgument = ASTNode.copySubtree(ast, firstArgument); MethodInvocation newCreateValueMethodInvocation = ast.newMethodInvocation(); String createValueMethodName = textForInvokingCreateValue.substring(0, textForInvokingCreateValue.indexOf("(")); String createValueArgumentList = textForInvokingCreateValue.substring(textForInvokingCreateValue.indexOf("(")+1, textForInvokingCreateValue.indexOf(")")); if(!createValueArgumentList.isEmpty()) { String[] listOfArguments = createValueArgumentList.split(","); for (String argumentName : listOfArguments) { newCreateValueMethodInvocation.arguments().add(ast.newSimpleName(argumentName.trim())); } } newCreateValueMethodInvocation.setName(ast.newSimpleName(createValueMethodName)); newMethodInvoc.arguments().add(copyOfFirstArgument); newMethodInvoc.arguments().add(newCreateValueMethodInvocation); replacementForValue = newCreateValueMethodInvocation; } else { newMethodInvoc.arguments().addAll(ASTNode.copySubtrees(ast, methodInvoc.arguments())); if(equalsValue != null) { if(methodToReplaceWithName.equals("replace")) newMethodInvoc.arguments().add(newMethodInvoc.arguments().size()-1, ASTNode.copySubtree(ast, equalsValue)); else newMethodInvoc.arguments().add(ASTNode.copySubtree(ast, equalsValue)); } } // Replace directly with the replacement for value if there are no reads in AFTER_PUT or AFTER_IF if(replacementForValue != null) { newMethodInvoc.arguments().remove(newMethodInvoc.arguments().size()-1); newMethodInvoc.arguments().add(ASTNode.copySubtree(ast, replacementForValue)); } if(firstArgument.subtreeMatch(new JdtASTMatcher(), hashMapKey)) { newMethodInvoc.setName(ast.newSimpleName(methodToReplaceWithName)); newMethodInvoc.setExpression(ast.newSimpleName(fFieldBinding.getName())); Block enclosingBlock = (Block) ASTNodes.getParent(ifStatement, Block.class); int indexOfIfStatement = enclosingBlock.statements().indexOf(ifStatement); int indexOfMethodInvoc = -1; Statement thenStatement = ifStatement.getThenStatement(); if(thenStatement instanceof Block) { indexOfMethodInvoc = ((Block)thenStatement).statements().indexOf(methodInvocStatement); } else { indexOfMethodInvoc = 0; } ReferencesFinder rf = new ReferencesFinder(); ReferencesFinder wf = new ReferencesFinder(); ASTNode secondPutArgument = (ASTNode) methodInvoc.arguments().get(methodInvoc.arguments().size()-1); if(secondPutArgument instanceof Name) { wf.findReferences(ifStatement, (Name)secondPutArgument, ifStatement.getThenStatement().getStartPosition(), methodInvocStatement.getStartPosition()); rf.findReferences(enclosingBlock, (Name)secondPutArgument, methodInvocStatement.getStartPosition() + methodInvocStatement.getLength(), enclosingBlock.getStartPosition() + enclosingBlock.getLength()); } if(!statementsAfterPut && (!statementsBeforePut || (statementsBeforePut && indexOfMethodInvoc == 1 && wf.getWriteReferences() != null && wf.getWriteReferences().size() == 1 && rf.getReadReferences() != null && !rf.hasReadReferences()))) { // if() { // put(); -> putIfAbsent(); // } ExpressionStatement newMethodInvocStatement = ast.newExpressionStatement(newMethodInvoc); // Removes the statement before if if(removeStatementBeforeIf) { // TODO TODO - rewrite to find get statement ASTNode statementBeforeIf = (ASTNode) enclosingBlock.statements().get(indexOfIfStatement-1); fRewriter.remove(statementBeforeIf, createGroupDescription(REMOVE_STATEMENT)); } fRewriter.replace(ifStatement, newMethodInvocStatement, createGroupDescription(METHOD_INVOCATION)); checkSynchronizedBlock(ifStatement, newMethodInvocStatement, METHOD_INVOCATION); checkSynchronizedMethod(ifStatement, newMethodInvocStatement, METHOD_INVOCATION); } else { IfStatement newIfStatement = (IfStatement) ASTNode.copySubtree(ast, ifStatement); Block thenBlock = ((Block)ifStatement.getThenStatement()); List oldThenStatements = thenBlock.statements(); List newThenStatements = ((Block)newIfStatement.getThenStatement()).statements(); int indexOfMethodInvocation = oldThenStatements.indexOf(methodInvocStatement); if(indexOfMethodInvocation == -1) throw new RuntimeException("Cannot find method invocation."); if(codeForCreateValueMethod == null) { // No extract method, possibly creation with one statement or no creation if(indexOfMethodInvocation == 1) { // If: BEFORE_PUT creates value with one statement and either AFTER_PUT uses it or does not // // value = get() // createdValue = [SINGLE_STATEMENT_FROM_BEFORE_PUT]; // if(putIfAbsent(createdValue) == null) { // +[value = createdValue] // AFTER_PUT // } ReferencesFinder readFinder = new ReferencesFinder(); MethodDeclaration methDecl = (MethodDeclaration) ASTNodes.getParent(ifStatement, MethodDeclaration.class); if(secondPutArgument instanceof Name) { readFinder.findReferences(methDecl, (Name)secondPutArgument, methodInvocStatement.getStartPosition() + methodInvocStatement.getLength(), methDecl.getStartPosition() + methDecl.getLength()); } // Creates the if(putIfAbsent(...) == null) { ... } InfixExpression isNullExpression = ast.newInfixExpression(); isNullExpression.setLeftOperand(newMethodInvoc); isNullExpression.setOperator(Operator.EQUALS); isNullExpression.setRightOperand(ast.newNullLiteral()); newIfStatement.setExpression(isNullExpression); rf = new ReferencesFinder(); if(secondPutArgument instanceof Name) { rf.findReferences(ifStatement, (Name)secondPutArgument, thenBlock.getStartPosition(), methodInvocStatement.getStartPosition()); if(rf.getWriteReferences().size() == 1) { ASTNode theCreateValueWrite = rf.getWriteReferences().get(0); ASTNode theCreateValueWriteStatement = ASTNodes.getParent(theCreateValueWrite, Statement.class); fRewriter.remove(theCreateValueWriteStatement, createGroupDescription(REMOVE_STATEMENT)); } } // Remove all BEFORE_PUT statements and the method invocation for(int i = 0; i <= indexOfMethodInvocation; i++) { newThenStatements.remove(0); } // AFTER_PUT uses the single-statement creation if(readFinder.getReadReferences() != null && readFinder.hasReadReferences()) { Block newEnclosingBlock = (Block) ASTNode.copySubtree(ast, enclosingBlock); List newEnclosingBlockStatements = newEnclosingBlock.statements(); Statement theCreateValueStat = (Statement) ASTNodes.getParent(rf.getWriteReferences().get(0), Statement.class); Expression theCreateValueExpStat = null; if(theCreateValueStat instanceof ExpressionStatement) { theCreateValueExpStat = ((ExpressionStatement)theCreateValueStat).getExpression(); } else if(theCreateValueStat instanceof VariableDeclarationStatement) { VariableDeclarationFragment vdf = (VariableDeclarationFragment) ((VariableDeclarationStatement)theCreateValueStat).fragments().get(0); theCreateValueExpStat = vdf.getInitializer(); } if(theCreateValueExpStat instanceof Assignment) { theCreateValueExpStat = ((Assignment)theCreateValueExpStat).getRightHandSide(); } // Create items relevant to <Type> value = createValue(...); ASTNode valueArgument = (ASTNode) methodInvoc.arguments().get(1); VariableDeclarationFragment newVDF = ast.newVariableDeclarationFragment(); SimpleName nameOfCreatedValue = (SimpleName) ASTNode.copySubtree(ast, getName(valueArgument)); SimpleName newNameOfCreatedValue = (SimpleName) ASTNode.copySubtree(ast, getName(valueArgument)); newNameOfCreatedValue.setIdentifier("created" + newNameOfCreatedValue.getIdentifier()); newVDF.setName(newNameOfCreatedValue); newVDF.setInitializer((Expression) ASTNode.copySubtree(ast,theCreateValueExpStat)); VariableDeclarationStatement newVariableFromCreateValueDeclStatement = ast.newVariableDeclarationStatement(newVDF); if(valueArgument instanceof MethodInvocation) { String newType = ((MethodInvocation)valueArgument).resolveTypeBinding().getName(); SimpleName newSimpleName = ast.newSimpleName(newType); newVariableFromCreateValueDeclStatement.setType(ast.newSimpleType(newSimpleName)); } else if(valueArgument instanceof SimpleName) { String newType = ((SimpleName)valueArgument).resolveTypeBinding().getName(); SimpleName newSimpleName = ast.newSimpleName(newType); newVariableFromCreateValueDeclStatement.setType(ast.newSimpleType(newSimpleName)); } else { newVariableFromCreateValueDeclStatement.setType(ast.newSimpleType(ast.newName("Object"))); } Assignment setOldValueToCreatedValueExp = ast.newAssignment(); setOldValueToCreatedValueExp.setLeftHandSide(nameOfCreatedValue); setOldValueToCreatedValueExp.setRightHandSide((Expression) ASTNode.copySubtree(ast, newNameOfCreatedValue)); Statement setOldValueToCreatedValueStatement = ast.newExpressionStatement(setOldValueToCreatedValueExp); newThenStatements.add(0, setOldValueToCreatedValueStatement); newEnclosingBlockStatements.add(indexOfIfStatement, newVariableFromCreateValueDeclStatement); newMethodInvoc.arguments().remove(1); newMethodInvoc.arguments().add(ASTNode.copySubtree(ast, newNameOfCreatedValue)); newEnclosingBlockStatements.remove(indexOfIfStatement+1); newEnclosingBlockStatements.add(indexOfIfStatement+1, newIfStatement); fRewriter.replace(enclosingBlock, newEnclosingBlock, createGroupDescription(METHOD_INVOCATION)); } else { if(!statementsBeforePut) { if(indexOfIfStatement > 0) { ASTNode statementBeforeIf = (ASTNode) enclosingBlock.statements().get(indexOfIfStatement-1); fRewriter.remove(statementBeforeIf, createGroupDescription(REMOVE_STATEMENT)); } ExpressionStatement newMethodInvocStatement = ast.newExpressionStatement((Expression) ASTNode.copySubtree(ast, newMethodInvoc)); fRewriter.replace(ifStatement, newMethodInvocStatement, createGroupDescription(METHOD_INVOCATION)); checkSynchronizedBlock(ifStatement, newMethodInvocStatement, METHOD_INVOCATION); checkSynchronizedMethod(ifStatement, newMethodInvocStatement, METHOD_INVOCATION); } else { if(replacementForValue == null) { newThenStatements.add(ASTNode.copySubtree(ast, (ASTNode) oldThenStatements.get(0))); } if(indexOfIfStatement > 0) { ASTNode statementBeforeIf = (ASTNode) enclosingBlock.statements().get(indexOfIfStatement-1); fRewriter.remove(statementBeforeIf, createGroupDescription(REMOVE_STATEMENT)); } ExpressionStatement newMethodInvocStatement = ast.newExpressionStatement((Expression) ASTNode.copySubtree(ast, newMethodInvoc)); fRewriter.replace(ifStatement, newIfStatement, createGroupDescription(METHOD_INVOCATION)); } } } else { // If: BEFORE_PUT does not create value and either AFTER_PUT uses it or does not // if(putIfAbsent() == null) { // BEFORE_PUT // AFTER_PUT // } // Creates the if(putIfAbsent(...) == null) { ... } InfixExpression isNullExpression = ast.newInfixExpression(); isNullExpression.setLeftOperand(newMethodInvoc); isNullExpression.setOperator(Operator.EQUALS); isNullExpression.setRightOperand(ast.newNullLiteral()); newIfStatement.setExpression(isNullExpression); // Remove the statement before if if(removeStatementBeforeIf) { // TODO TODO - rewrite to find get statement ASTNode statementBeforeIf = (ASTNode) enclosingBlock.statements().get(indexOfIfStatement-1); fRewriter.remove(statementBeforeIf, createGroupDescription(REMOVE_STATEMENT)); } newThenStatements.remove(indexOfMethodInvocation); if(replacementForValue != null) { if(rf.getWriteReferences() != null && rf.getWriteReferences().size() == 1) { Statement theWriteStatement = (Statement) ASTNodes.getParent(rf.getWriteReferences().get(0), Statement.class); int indexOfWriteStatement = ((Block)ifStatement.getThenStatement()).statements().indexOf(theWriteStatement); ((Block)newIfStatement.getThenStatement()).statements().remove(indexOfWriteStatement); } } fRewriter.replace(ifStatement, newIfStatement, createGroupDescription(METHOD_INVOCATION)); } } else { // If: BEFORE_PUT extracts method, creates value, and either AFTER_PUT uses it or does not Expression ifExpression = ifStatement.getExpression(); if(ifExpression instanceof PrefixExpression) { ifExpression = ((PrefixExpression)ifExpression).getOperand(); } if(ifExpression instanceof MethodInvocation && ((MethodInvocation)ifExpression).getName().getIdentifier().equals("containsKey")) { // if(containsKey()) { // put(); -> putIfAbsent(); // } ReferencesFinder readFinder = new ReferencesFinder(); MethodDeclaration methDecl = (MethodDeclaration) ASTNodes.getParent(ifStatement, MethodDeclaration.class); if(secondPutArgument instanceof Name) { readFinder.findReferences(methDecl, (Name)secondPutArgument, methodInvocStatement.getStartPosition() + methodInvocStatement.getLength(), methDecl.getStartPosition() + methDecl.getLength()); } if(statementsAfterPut || (readFinder.getReadReferences() != null && readFinder.hasReadReferences())) { InfixExpression isNullExpression = ast.newInfixExpression(); isNullExpression.setLeftOperand(newMethodInvoc); isNullExpression.setOperator(Operator.EQUALS); isNullExpression.setRightOperand(ast.newNullLiteral()); newIfStatement.setExpression(isNullExpression); rf = new ReferencesFinder(); if(secondPutArgument instanceof Name) { rf.findReferences(ifStatement, (Name)secondPutArgument, thenBlock.getStartPosition(), methodInvocStatement.getStartPosition()); if(rf.getWriteReferences().size() == 1) { ASTNode theCreateValueWrite = rf.getWriteReferences().get(0); ASTNode theCreateValueWriteStatement = ASTNodes.getParent(theCreateValueWrite, Statement.class); fRewriter.remove(theCreateValueWriteStatement, createGroupDescription(REMOVE_STATEMENT)); } } Block newEnclosingBlock = (Block) ASTNode.copySubtree(ast, enclosingBlock); List newEnclosingBlockStatements = newEnclosingBlock.statements(); // Remove all BEFORE_PUT statements and the method invocation for(int i = 0; i <= indexOfMethodInvocation; i++) { newThenStatements.remove(0); } if(codeForCreateValueMethod != null) { if(!getCreateValueReturnType(codeForCreateValueMethod).equals("void")) { // Create items relevant to <Type> value = createValue(...); ASTNode valueArgument = (ASTNode) methodInvoc.arguments().get(1); MethodInvocation newCreateValueMethodInvocation = (MethodInvocation) fRewriter.createStringPlaceholder(textForInvokingCreateValue, ASTNode.METHOD_INVOCATION); VariableDeclarationFragment newVDF = ast.newVariableDeclarationFragment(); SimpleName nameOfCreatedValue = (SimpleName) ASTNode.copySubtree(ast, getName(valueArgument)); SimpleName newNameOfCreatedValue = (SimpleName) ASTNode.copySubtree(ast, getName(valueArgument)); newNameOfCreatedValue.setIdentifier("created" + newNameOfCreatedValue.getIdentifier()); newVDF.setName(newNameOfCreatedValue); newVDF.setInitializer(newCreateValueMethodInvocation); VariableDeclarationStatement newVariableFromCreateValueDeclStatement = ast.newVariableDeclarationStatement(newVDF); String returnType = getCreateValueReturnType(codeForCreateValueMethod); newVariableFromCreateValueDeclStatement.setType(ast.newSimpleType(ast.newName(returnType))); ReferencesFinder readF = new ReferencesFinder(); readF.findReferences(ifStatement, (Name)secondPutArgument, methodInvocStatement.getStartPosition() + methodInvocStatement.getLength(), ifStatement.getStartPosition() + ifStatement.getLength()); if(!readF.hasReadReferences()) { newEnclosingBlockStatements.remove(indexOfIfStatement); newEnclosingBlockStatements.add(indexOfIfStatement, newIfStatement); fRewriter.replace(enclosingBlock, newEnclosingBlock, createGroupDescription(METHOD_INVOCATION)); } else { Assignment setOldValueToCreatedValueExp = ast.newAssignment(); setOldValueToCreatedValueExp.setLeftHandSide(nameOfCreatedValue); setOldValueToCreatedValueExp.setRightHandSide((Expression) ASTNode.copySubtree(ast, newNameOfCreatedValue)); Statement setOldValueToCreatedValueStatement = ast.newExpressionStatement(setOldValueToCreatedValueExp); newThenStatements.add(0, setOldValueToCreatedValueStatement); newEnclosingBlockStatements.add(indexOfIfStatement, newVariableFromCreateValueDeclStatement); newMethodInvoc.arguments().remove(1); newMethodInvoc.arguments().add(ASTNode.copySubtree(ast, newNameOfCreatedValue)); newEnclosingBlockStatements.remove(indexOfIfStatement+1); newEnclosingBlockStatements.add(indexOfIfStatement+1, newIfStatement); fRewriter.replace(enclosingBlock, newEnclosingBlock, createGroupDescription(METHOD_INVOCATION)); } } else { fRewriter.replace(ifStatement, newIfStatement, createGroupDescription(METHOD_INVOCATION)); } } else { if(codeForCreateValueMethod == null) { // putIfAbsent(replacementForValue) { // AFTER_PUT // } newMethodInvoc.arguments().remove(newMethodInvoc.arguments().size()-1); newMethodInvoc.arguments().add(ASTNode.copySubtree(ast, replacementForValue)); } else { // TODO anything here? } fRewriter.replace(ifStatement, newIfStatement, createGroupDescription(METHOD_INVOCATION)); } } else { ExpressionStatement newMethodInvocStatement = ast.newExpressionStatement(newMethodInvoc); fRewriter.replace(ifStatement, newMethodInvocStatement, createGroupDescription(METHOD_INVOCATION)); checkSynchronizedBlock(ifStatement, newMethodInvocStatement, METHOD_INVOCATION); checkSynchronizedMethod(ifStatement, newMethodInvocStatement, METHOD_INVOCATION); } } else{ // value = get() // createdValue = createValue(); // if(putIfAbsent(createdValue) == null) { // BEFORE_PUT -> extract method // +[value = createdValue] // AFTER_PUT // } ReferencesFinder readFinder = new ReferencesFinder(); MethodDeclaration methDecl = (MethodDeclaration) ASTNodes.getParent(ifStatement, MethodDeclaration.class); readFinder.findReferences(methDecl, (Name)secondPutArgument, methodInvocStatement.getStartPosition() + methodInvocStatement.getLength(), methDecl.getStartPosition() + methDecl.getLength()); if(statementsAfterPut || (readFinder.getReadReferences() != null && readFinder.hasReadReferences())) { // Creates the if(putIfAbsent(...) == null) { ... } InfixExpression isNullExpression = ast.newInfixExpression(); isNullExpression.setLeftOperand(newMethodInvoc); isNullExpression.setOperator(Operator.EQUALS); isNullExpression.setRightOperand(ast.newNullLiteral()); newIfStatement.setExpression(isNullExpression); rf = new ReferencesFinder(); if(secondPutArgument instanceof Name) { rf.findReferences(ifStatement, (Name)secondPutArgument, thenBlock.getStartPosition(), methodInvocStatement.getStartPosition()); if(rf.getWriteReferences().size() == 1) { ASTNode theCreateValueWrite = rf.getWriteReferences().get(0); ASTNode theCreateValueWriteStatement = ASTNodes.getParent(theCreateValueWrite, Statement.class); fRewriter.remove(theCreateValueWriteStatement, createGroupDescription(REMOVE_STATEMENT)); } } Block newEnclosingBlock = (Block) ASTNode.copySubtree(ast, enclosingBlock); List newEnclosingBlockStatements = newEnclosingBlock.statements(); // Remove all BEFORE_PUT statements and the method invocation for(int i = 0; i <= indexOfMethodInvocation; i++) { newThenStatements.remove(0); } if(codeForCreateValueMethod != null) { if(!getCreateValueReturnType(codeForCreateValueMethod).equals("void")) { // Create items relevant to <Type> value = createValue(...); ASTNode valueArgument = (ASTNode) methodInvoc.arguments().get(1); MethodInvocation newCreateValueMethodInvocation = (MethodInvocation) fRewriter.createStringPlaceholder(textForInvokingCreateValue, ASTNode.METHOD_INVOCATION); VariableDeclarationFragment newVDF = ast.newVariableDeclarationFragment(); SimpleName nameOfCreatedValue = (SimpleName) ASTNode.copySubtree(ast, getName(valueArgument)); SimpleName newNameOfCreatedValue = (SimpleName) ASTNode.copySubtree(ast, getName(valueArgument)); newNameOfCreatedValue.setIdentifier("created" + newNameOfCreatedValue.getIdentifier()); newVDF.setName(newNameOfCreatedValue); newVDF.setInitializer(newCreateValueMethodInvocation); VariableDeclarationStatement newVariableFromCreateValueDeclStatement = ast.newVariableDeclarationStatement(newVDF); String returnType = getCreateValueReturnType(codeForCreateValueMethod); newVariableFromCreateValueDeclStatement.setType(ast.newSimpleType(ast.newName(returnType))); Assignment setOldValueToCreatedValueExp = ast.newAssignment(); setOldValueToCreatedValueExp.setLeftHandSide(nameOfCreatedValue); setOldValueToCreatedValueExp.setRightHandSide((Expression) ASTNode.copySubtree(ast, newNameOfCreatedValue)); Statement setOldValueToCreatedValueStatement = ast.newExpressionStatement(setOldValueToCreatedValueExp); newThenStatements.add(0, setOldValueToCreatedValueStatement); newEnclosingBlockStatements.add(indexOfIfStatement, newVariableFromCreateValueDeclStatement); newMethodInvoc.arguments().remove(1); newMethodInvoc.arguments().add(ASTNode.copySubtree(ast, newNameOfCreatedValue)); newEnclosingBlockStatements.remove(indexOfIfStatement+1); newEnclosingBlockStatements.add(indexOfIfStatement+1, newIfStatement); fRewriter.replace(enclosingBlock, newEnclosingBlock, createGroupDescription(METHOD_INVOCATION)); } else { fRewriter.replace(ifStatement, newIfStatement, createGroupDescription(METHOD_INVOCATION)); } } else { if(codeForCreateValueMethod == null) { // putIfAbsent(replacementForValue) { // AFTER_PUT // } newMethodInvoc.arguments().remove(newMethodInvoc.arguments().size()-1); newMethodInvoc.arguments().add(ASTNode.copySubtree(ast, replacementForValue)); } else { throw new RuntimeException("Unexpected code path"); } fRewriter.replace(ifStatement, newIfStatement, createGroupDescription(METHOD_INVOCATION)); } } else { // value = get() // createdValue = createValue(); // if(putIfAbsent(createdValue) == null) { // +[value = createdValue] // } // Creates the if(putIfAbsent(...) == null) { ... } InfixExpression isNullExpression = ast.newInfixExpression(); isNullExpression.setLeftOperand(newMethodInvoc); isNullExpression.setOperator(Operator.EQUALS); isNullExpression.setRightOperand(ast.newNullLiteral()); newIfStatement.setExpression(isNullExpression); Block newEnclosingBlock = (Block) ASTNode.copySubtree(ast, enclosingBlock); List newEnclosingBlockStatements = newEnclosingBlock.statements(); // Remove all BEFORE_PUT statements and the method invocation for(int i = 0; i <= indexOfMethodInvocation; i++) { newThenStatements.remove(0); } if(codeForCreateValueMethod != null) { if(!getCreateValueReturnType(codeForCreateValueMethod).equals("void")) { // Create items relevant to <Type> value = createValue(...); ASTNode valueArgument = (ASTNode) methodInvoc.arguments().get(1); MethodInvocation newCreateValueMethodInvocation = (MethodInvocation) fRewriter.createStringPlaceholder(textForInvokingCreateValue, ASTNode.METHOD_INVOCATION); VariableDeclarationFragment newVDF = ast.newVariableDeclarationFragment(); SimpleName nameOfCreatedValue = (SimpleName) ASTNode.copySubtree(ast, getName(valueArgument)); SimpleName newNameOfCreatedValue = (SimpleName) ASTNode.copySubtree(ast, getName(valueArgument)); newNameOfCreatedValue.setIdentifier("created" + newNameOfCreatedValue.getIdentifier()); newVDF.setName(newNameOfCreatedValue); newVDF.setInitializer(newCreateValueMethodInvocation); VariableDeclarationStatement newVariableFromCreateValueDeclStatement = ast.newVariableDeclarationStatement(newVDF); String returnType = getCreateValueReturnType(codeForCreateValueMethod); newVariableFromCreateValueDeclStatement.setType(ast.newSimpleType(ast.newName(returnType))); Assignment setOldValueToCreatedValueExp = ast.newAssignment(); setOldValueToCreatedValueExp.setLeftHandSide(nameOfCreatedValue); setOldValueToCreatedValueExp.setRightHandSide((Expression) ASTNode.copySubtree(ast, newNameOfCreatedValue)); Statement setOldValueToCreatedValueStatement = ast.newExpressionStatement(setOldValueToCreatedValueExp); newThenStatements.add(0, setOldValueToCreatedValueStatement); newEnclosingBlockStatements.add(indexOfIfStatement, newVariableFromCreateValueDeclStatement); newMethodInvoc.arguments().remove(1); newMethodInvoc.arguments().add(ASTNode.copySubtree(ast, newNameOfCreatedValue)); newEnclosingBlockStatements.remove(indexOfIfStatement+1); newEnclosingBlockStatements.add(indexOfIfStatement+1, newIfStatement); fRewriter.replace(enclosingBlock, newEnclosingBlock, createGroupDescription(METHOD_INVOCATION)); } else { fRewriter.replace(ifStatement, newIfStatement, createGroupDescription(METHOD_INVOCATION)); } } else { if(codeForCreateValueMethod == null) { // putIfAbsent(replacementForValue) { // AFTER_PUT // } newMethodInvoc.arguments().remove(newMethodInvoc.arguments().size()-1); newMethodInvoc.arguments().add(ASTNode.copySubtree(ast, replacementForValue)); } else { throw new RuntimeException("Unexpected code path"); } fRewriter.replace(ifStatement, newIfStatement, createGroupDescription(METHOD_INVOCATION)); } } } } } usingCHMOnlyMethods = true; } } } private String getCreateValueReturnType(String codeForCreateValueMethod) { for(String s : codeForCreateValueMethod.split(" ")) { if(s.equals("public") || s.equals("private") || s.equals("protected") || s.equals("package") || s.equals("static") || s.equals("final") || s.equals("abstract") || s.equals("transient") || s.equals("volatile")) { continue; } else { return s; } } return null; } private TextEditGroup getMethodInvocationTextEdit( CompilationUnitChange extractMethodChanges) { TextEditBasedChangeGroup[] changeGroups = extractMethodChanges.getChangeGroups(); for (TextEditBasedChangeGroup textEditBasedChangeGroup : changeGroups) { if(textEditBasedChangeGroup.getName().indexOf(Messages.format(RefactoringCoreMessages.ExtractMethodRefactoring_substitute_with_call, fMethodName)) != -1) { return textEditBasedChangeGroup.getTextEditGroup(); } } return null; } private TextEditGroup extractCreateValueMethod(CompilationUnitChange extractMethodChanges) { TextEditBasedChangeGroup[] changeGroups = extractMethodChanges.getChangeGroups(); for (TextEditBasedChangeGroup textEditBasedChangeGroup : changeGroups) { if(textEditBasedChangeGroup.getName().indexOf(Messages.format(RefactoringCoreMessages.ExtractMethodRefactoring_add_method, fMethodName)) != -1) { return textEditBasedChangeGroup.getTextEditGroup(); } } return null; } private boolean checkMethodNameAndBinding( MethodInvocation methodInvocation, String methodName) { return considerBinding(resolveBinding(methodInvocation.getExpression())) && methodInvocation.getName().getIdentifier().equals(methodName); } public boolean visit(MethodInvocation methodInvocNode) { Expression expression = methodInvocNode.getExpression(); if(!(expression instanceof SimpleName)) { return false; } SimpleName expressionName = (SimpleName) expression; if(!considerBinding(expressionName.resolveBinding())) { return false; } String methodIdentifier = methodInvocNode.getName().getIdentifier(); // clone() is the only method HashMap has that ConcurrentHashMap does not if(methodIdentifier.equals("clone")) { fStatus.addFatalError("Cannot refactor a method invocation for clone(): " + "ConcurrentHashMap has no such method."); return false; } boolean checkSynchronizedBlock = checkSynchronizedBlock(methodInvocNode, methodInvocNode, METHOD_INVOCATION); boolean checkSynchronizedMethod = checkSynchronizedMethod(methodInvocNode, methodInvocNode, METHOD_INVOCATION); // TODO check return type (medium priority) throw Exception if false? return true; } private boolean checkSynchronizedBlock(ASTNode node, ASTNode invocation, String accessType) { AST ast = node.getAST(); ASTNode syncStatement = ASTNodes.getParent(node, SynchronizedStatement.class); final MethodDeclaration parentMethod = (MethodDeclaration) ASTNodes.getParent(node, MethodDeclaration.class); if(syncStatement == null) return false; Block parentBlockForSyncStatement = (Block) syncStatement.getParent(); List parentBlockStatements = parentBlockForSyncStatement.statements(); final int lineOfOldSyncStatement = parentBlockStatements.indexOf(syncStatement); final SynchronizedStatement[] newSyncStatement = new SynchronizedStatement[1]; try { TextEdit rewrites = fRewriter.rewriteAST(); IDocument scratchDocument = new Document(fCUnit.getSource()); rewrites.apply(scratchDocument); ASTParser parser = ASTParser.newParser(AST.JLS3); parser.setSource(scratchDocument.get().toCharArray()); CompilationUnit scratchCU = (CompilationUnit)parser.createAST(null); scratchCU.accept(new ASTVisitor() { public boolean visit(SynchronizedStatement syncStat){ MethodDeclaration parentMeth = (MethodDeclaration) ASTNodes.getParent(syncStat, MethodDeclaration.class); if(parentMeth.getName().getIdentifier().equals(parentMethod.getName().getIdentifier())) { Block parentBlock = (Block) syncStat.getParent(); List parentBlockStatements = parentBlock.statements(); if(parentBlockStatements.indexOf(syncStat) == lineOfOldSyncStatement) { newSyncStatement[0] = syncStat; return false; } } return true; } }); } catch (Exception e) { fStatus.addFatalError("Error occurred while checking for synchronized block: " + e.getMessage()); return false; } if(syncStatement != null) { Block syncBody = newSyncStatement[0].getBody(); List syncBodyStatements = syncBody.statements(); if(syncBodyStatements.size() > 1) { fRewriter.replace(node, invocation, createGroupDescription(accessType)); } else { ASTNode nodeParent = node.getParent(); if(nodeParent instanceof ExpressionStatement) { fRewriter.replace(syncStatement, nodeParent, createGroupDescription(REMOVE_SYNCHRONIZED_BLOCK)); } else { if(invocation instanceof Block) { fRewriter.replace(parentBlockForSyncStatement, invocation, createGroupDescription(REMOVE_SYNCHRONIZED_BLOCK)); } else { fRewriter.replace(syncStatement, invocation, createGroupDescription(REMOVE_SYNCHRONIZED_BLOCK)); } } } return true; } return false; } private boolean checkSynchronizedMethod(ASTNode node, ASTNode invocation, String accessType) { fRewriter.replace(node, invocation, createGroupDescription(accessType)); final MethodDeclaration methodDecl = (MethodDeclaration) ASTNodes.getParent(node, MethodDeclaration.class); final MethodDeclaration[] newMethodWithSync = new MethodDeclaration[1]; try { TextEdit rewrites = fRewriter.rewriteAST(); IDocument scratchDocument = new Document(fCUnit.getSource()); rewrites.apply(scratchDocument); ASTParser parser = ASTParser.newParser(AST.JLS3); parser.setSource(scratchDocument.get().toCharArray()); CompilationUnit scratchCU = (CompilationUnit)parser.createAST(null); scratchCU.accept(new ASTVisitor() { public boolean visit(MethodDeclaration methDecl){ if (methDecl.getName().getIdentifier().equals(methodDecl.getName().getIdentifier())) { newMethodWithSync[0] = methDecl; } return true; } }); } catch (Exception e) { fStatus.addFatalError("Error occurred while checking for synchronized method: " + e.getMessage()); return false; } int modifiers = methodDecl.getModifiers(); if(Modifier.isSynchronized(modifiers)) { List methodBodyStatements = newMethodWithSync[0].getBody().statements(); if(methodBodyStatements.size() == 1) { ModifierRewrite methodRewriter = ModifierRewrite.create(fRewriter, methodDecl); int synchronized1 = Modifier.SYNCHRONIZED; synchronized1 = ~ synchronized1; int newModifiersWithoutSync = modifiers & synchronized1; methodRewriter.setModifiers(newModifiersWithoutSync, createGroupDescription(REMOVE_SYNCHRONIZED_MODIFIER)); } return true; } return false; } private boolean considerBinding(IBinding binding) { if (!(binding instanceof IVariableBinding)) return false; boolean result = Bindings.equals(fFieldBinding, ((IVariableBinding)binding).getVariableDeclaration()); return result; } private IBinding resolveBinding(Expression expression) { if (expression instanceof SimpleName) return ((SimpleName)expression).resolveBinding(); else if (expression instanceof QualifiedName) return ((QualifiedName)expression).resolveBinding(); else if (expression instanceof FieldAccess) return ((FieldAccess)expression).getName().resolveBinding(); else if (expression instanceof SuperFieldAccess) return ((SuperFieldAccess)expression).getName().resolveBinding(); return null; } public void endVisit(CompilationUnit node) { fImportRewriter.addImport("java.util.concurrent.ConcurrentHashMap"); } private TextEditGroup createGroupDescription(String name) { TextEditGroup result= new TextEditGroup(name); fGroupDescriptions.add(result); return result; } private SimpleName getName(ASTNode node) { int type= node.getNodeType(); switch(type) { case ASTNode.SIMPLE_NAME: return ((SimpleName)node); case ASTNode.QUALIFIED_NAME: return ((QualifiedName)node).getName(); case ASTNode.FIELD_ACCESS: return ((FieldAccess)node).getName(); case ASTNode.SUPER_FIELD_ACCESS: return ((SuperFieldAccess)node).getName(); case ASTNode.THIS_EXPRESSION: return (SimpleName) ((ThisExpression)node).getQualifier(); } return null; } }