| /******************************************************************************* |
| * Copyright (c) 2000, 2018 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * jens.lukowski@gmx.de - contributed code to convert prefix and postfix |
| * expressions into a combination of setter and getter calls. |
| * Nikolay Metchev <nikolaymetchev@gmail.com> - [encapsulate field] Encapsulating parenthesized field assignment yields compilation error - https://bugs.eclipse.org/177095 |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.corext.refactoring.sef; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.eclipse.core.runtime.Assert; |
| |
| import org.eclipse.text.edits.TextEditGroup; |
| |
| import org.eclipse.ltk.core.refactoring.RefactoringStatus; |
| |
| 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.ASTVisitor; |
| import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; |
| import org.eclipse.jdt.core.dom.Assignment; |
| 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.ITypeBinding; |
| import org.eclipse.jdt.core.dom.IVariableBinding; |
| import org.eclipse.jdt.core.dom.ImportDeclaration; |
| 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.ParenthesizedExpression; |
| import org.eclipse.jdt.core.dom.PostfixExpression; |
| import org.eclipse.jdt.core.dom.PrefixExpression; |
| import org.eclipse.jdt.core.dom.QualifiedName; |
| import org.eclipse.jdt.core.dom.SimpleName; |
| import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; |
| import org.eclipse.jdt.core.dom.rewrite.ImportRewrite; |
| |
| import org.eclipse.jdt.internal.core.manipulation.dom.NecessaryParenthesesChecker; |
| import org.eclipse.jdt.internal.corext.SourceRangeFactory; |
| import org.eclipse.jdt.internal.corext.dom.ASTNodes; |
| import org.eclipse.jdt.internal.corext.dom.Bindings; |
| import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages; |
| import org.eclipse.jdt.internal.corext.refactoring.util.JavaStatusContext; |
| |
| /** |
| * Analyzer to find all references to the field and to determine how to convert |
| * them into setter or getter calls. |
| */ |
| class AccessAnalyzer extends ASTVisitor { |
| |
| private ICompilationUnit fCUnit; |
| private IVariableBinding fFieldBinding; |
| private ITypeBinding fDeclaringClassBinding; |
| private String fGetter; |
| private String fSetter; |
| private ASTRewrite fRewriter; |
| private ImportRewrite fImportRewriter; |
| private List<TextEditGroup> fGroupDescriptions; |
| private RefactoringStatus fStatus; |
| private boolean fSetterMustReturnValue; |
| private boolean fEncapsulateDeclaringClass; |
| private boolean fIsFieldFinal; |
| |
| private boolean fRemoveStaticImport; |
| private boolean fReferencingGetter; |
| private boolean fReferencingSetter; |
| |
| private static final String READ_ACCESS= RefactoringCoreMessages.SelfEncapsulateField_AccessAnalyzer_encapsulate_read_access; |
| private static final String WRITE_ACCESS= RefactoringCoreMessages.SelfEncapsulateField_AccessAnalyzer_encapsulate_write_access; |
| private static final String PREFIX_ACCESS= RefactoringCoreMessages.SelfEncapsulateField_AccessAnalyzer_encapsulate_prefix_access; |
| private static final String POSTFIX_ACCESS= RefactoringCoreMessages.SelfEncapsulateField_AccessAnalyzer_encapsulate_postfix_access; |
| |
| public AccessAnalyzer(SelfEncapsulateFieldRefactoring refactoring, ICompilationUnit unit, IVariableBinding field, ITypeBinding declaringClass, ASTRewrite rewriter, ImportRewrite importRewrite) { |
| Assert.isNotNull(refactoring); |
| Assert.isNotNull(unit); |
| Assert.isNotNull(field); |
| Assert.isNotNull(declaringClass); |
| Assert.isNotNull(rewriter); |
| Assert.isNotNull(importRewrite); |
| fCUnit= unit; |
| fFieldBinding= field.getVariableDeclaration(); |
| fDeclaringClassBinding= declaringClass; |
| fRewriter= rewriter; |
| fImportRewriter= importRewrite; |
| fGroupDescriptions= new ArrayList<>(); |
| fGetter= refactoring.getGetterName(); |
| fSetter= refactoring.getSetterName(); |
| fEncapsulateDeclaringClass= refactoring.getEncapsulateDeclaringClass(); |
| try { |
| fIsFieldFinal= Flags.isFinal(refactoring.getField().getFlags()); |
| } catch (JavaModelException e) { |
| // assume non final field |
| } |
| fStatus= new RefactoringStatus(); |
| } |
| |
| public boolean getSetterMustReturnValue() { |
| return fSetterMustReturnValue; |
| } |
| |
| public RefactoringStatus getStatus() { |
| return fStatus; |
| } |
| |
| public List<TextEditGroup> getGroupDescriptions() { |
| return fGroupDescriptions; |
| } |
| |
| @Override |
| public boolean visit(Assignment node) { |
| Expression leftHandSide= node.getLeftHandSide(); |
| if (!considerBinding(resolveBinding(leftHandSide), leftHandSide)) |
| return true; |
| |
| checkParent(node); |
| Expression rightHandSide= node.getRightHandSide(); |
| if (!fIsFieldFinal) { |
| // Write access. |
| AST ast= node.getAST(); |
| MethodInvocation invocation= ast.newMethodInvocation(); |
| invocation.setName(ast.newSimpleName(fSetter)); |
| fReferencingSetter= true; |
| Expression receiver= getReceiver(leftHandSide); |
| if (receiver != null) |
| invocation.setExpression((Expression)fRewriter.createCopyTarget(receiver)); |
| List<Expression> arguments= invocation.arguments(); |
| if (node.getOperator() == Assignment.Operator.ASSIGN) { |
| arguments.add((Expression)fRewriter.createCopyTarget(rightHandSide)); |
| } else { |
| // This is the compound assignment case: field+= 10; |
| InfixExpression exp= ast.newInfixExpression(); |
| exp.setOperator(ASTNodes.convertToInfixOperator(node.getOperator())); |
| MethodInvocation getter= ast.newMethodInvocation(); |
| getter.setName(ast.newSimpleName(fGetter)); |
| fReferencingGetter= true; |
| if (receiver != null) |
| getter.setExpression((Expression)fRewriter.createCopyTarget(receiver)); |
| exp.setLeftOperand(getter); |
| Expression rhs= (Expression)fRewriter.createCopyTarget(rightHandSide); |
| if (NecessaryParenthesesChecker.needsParenthesesForRightOperand(rightHandSide, exp, leftHandSide.resolveTypeBinding())) { |
| ParenthesizedExpression p= ast.newParenthesizedExpression(); |
| p.setExpression(rhs); |
| rhs= p; |
| } |
| exp.setRightOperand(rhs); |
| arguments.add(exp); |
| } |
| fRewriter.replace(node, invocation, createGroupDescription(WRITE_ACCESS)); |
| } |
| rightHandSide.accept(this); |
| return false; |
| } |
| |
| @Override |
| public boolean visit(SimpleName node) { |
| if (!node.isDeclaration() && considerBinding(node.resolveBinding(), node)) { |
| fReferencingGetter= true; |
| fRewriter.replace( |
| node, |
| fRewriter.createStringPlaceholder(fGetter + "()", ASTNode.METHOD_INVOCATION), //$NON-NLS-1$ |
| createGroupDescription(READ_ACCESS)); |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean visit(ImportDeclaration node) { |
| if (considerBinding(node.resolveBinding(), node)) { |
| fRemoveStaticImport= true; |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean visit(PrefixExpression node) { |
| Expression operand= node.getOperand(); |
| if (!considerBinding(resolveBinding(operand), operand)) |
| return true; |
| |
| PrefixExpression.Operator operator= node.getOperator(); |
| if (operator != PrefixExpression.Operator.INCREMENT && operator != PrefixExpression.Operator.DECREMENT) |
| return true; |
| |
| checkParent(node); |
| |
| fRewriter.replace(node, |
| createInvocation(node.getAST(), node.getOperand(), node.getOperator().toString()), |
| createGroupDescription(PREFIX_ACCESS)); |
| return false; |
| } |
| |
| @Override |
| public boolean visit(PostfixExpression node) { |
| Expression operand= node.getOperand(); |
| if (!considerBinding(resolveBinding(operand), operand)) |
| return true; |
| |
| ASTNode parent= node.getParent(); |
| if (!(parent instanceof ExpressionStatement)) { |
| fStatus.addError(RefactoringCoreMessages.SelfEncapsulateField_AccessAnalyzer_cannot_convert_postfix_expression, |
| JavaStatusContext.create(fCUnit, SourceRangeFactory.create(node))); |
| return false; |
| } |
| fRewriter.replace(node, |
| createInvocation(node.getAST(), node.getOperand(), node.getOperator().toString()), |
| createGroupDescription(POSTFIX_ACCESS)); |
| return false; |
| } |
| |
| @Override |
| public boolean visit(MethodDeclaration node) { |
| String name= node.getName().getIdentifier(); |
| if (name.equals(fGetter) || name.equals(fSetter)) |
| return false; |
| return true; |
| } |
| |
| @Override |
| public void endVisit(CompilationUnit node) { |
| // If we don't had a static import to the field we don't |
| // have to add any, even if we generated a setter or |
| // getter access. |
| if (!fRemoveStaticImport) |
| return; |
| |
| ITypeBinding type= fFieldBinding.getDeclaringClass(); |
| String fieldName= fFieldBinding.getName(); |
| String typeName= type.getQualifiedName(); |
| if (fRemoveStaticImport) { |
| fImportRewriter.removeStaticImport(typeName + "." + fieldName); //$NON-NLS-1$ |
| } |
| if (fReferencingGetter) { |
| fImportRewriter.addStaticImport(typeName, fGetter, false); |
| } |
| if (fReferencingSetter) { |
| fImportRewriter.addStaticImport(typeName, fSetter, false); |
| } |
| } |
| |
| private boolean considerBinding(IBinding binding, ASTNode node) { |
| if (!(binding instanceof IVariableBinding)) |
| return false; |
| boolean result= Bindings.equals(fFieldBinding, ((IVariableBinding)binding).getVariableDeclaration()); |
| if (!result || fEncapsulateDeclaringClass) |
| return result; |
| |
| if (binding instanceof IVariableBinding) { |
| AbstractTypeDeclaration type= ASTNodes.getParent(node, AbstractTypeDeclaration.class); |
| if (type != null) { |
| ITypeBinding declaringType= type.resolveBinding(); |
| return !Bindings.equals(fDeclaringClassBinding, declaringType); |
| } |
| } |
| return true; |
| } |
| |
| private void checkParent(ASTNode node) { |
| ASTNode parent= node.getParent(); |
| if (!(parent instanceof ExpressionStatement)) |
| fSetterMustReturnValue= true; |
| } |
| |
| 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 ParenthesizedExpression) |
| return resolveBinding(((ParenthesizedExpression) expression).getExpression()); |
| return null; |
| } |
| |
| private Expression getReceiver(Expression expression) { |
| int type= expression.getNodeType(); |
| switch(type) { |
| case ASTNode.SIMPLE_NAME: |
| return null; |
| case ASTNode.QUALIFIED_NAME: |
| return ((QualifiedName)expression).getQualifier(); |
| case ASTNode.FIELD_ACCESS: |
| return ((FieldAccess)expression).getExpression(); |
| case ASTNode.PARENTHESIZED_EXPRESSION: |
| return getReceiver(((ParenthesizedExpression)expression).getExpression()); |
| } |
| return null; |
| } |
| |
| private MethodInvocation createInvocation(AST ast, Expression operand, String operator) { |
| Expression receiver= getReceiver(operand); |
| MethodInvocation invocation= ast.newMethodInvocation(); |
| invocation.setName(ast.newSimpleName(fSetter)); |
| if (receiver != null) |
| invocation.setExpression((Expression)fRewriter.createCopyTarget(receiver)); |
| InfixExpression argument= ast.newInfixExpression(); |
| invocation.arguments().add(argument); |
| if ("++".equals(operator)) { //$NON-NLS-1$ |
| argument.setOperator(InfixExpression.Operator.PLUS); |
| } else if ("--".equals(operator)) { //$NON-NLS-1$ |
| argument.setOperator(InfixExpression.Operator.MINUS); |
| } else { |
| Assert.isTrue(false, "Should not happen"); //$NON-NLS-1$ |
| } |
| MethodInvocation getter= ast.newMethodInvocation(); |
| getter.setName(ast.newSimpleName(fGetter)); |
| if (receiver != null) |
| getter.setExpression((Expression)fRewriter.createCopyTarget(receiver)); |
| argument.setLeftOperand(getter); |
| argument.setRightOperand(ast.newNumberLiteral("1")); //$NON-NLS-1$ |
| |
| fReferencingGetter= true; |
| fReferencingSetter= true; |
| |
| return invocation; |
| } |
| |
| private TextEditGroup createGroupDescription(String name) { |
| TextEditGroup result= new TextEditGroup(name); |
| fGroupDescriptions.add(result); |
| return result; |
| } |
| } |
| |