content: /*******************************************************************************
   * Copyright (c) 2000, 2009 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.
   *******************************************************************************/
  package org.eclipse.wst.jsdt.internal.corext.refactoring.sef;
  
  import java.util.ArrayList;
  import java.util.List;
  
  import org.eclipse.core.runtime.Assert;
  import org.eclipse.ltk.core.refactoring.RefactoringStatus;
  import org.eclipse.text.edits.TextEditGroup;
  import org.eclipse.wst.jsdt.core.IJavaScriptUnit;
  import org.eclipse.wst.jsdt.core.dom.AST;
  import org.eclipse.wst.jsdt.core.dom.ASTNode;
  import org.eclipse.wst.jsdt.core.dom.ASTVisitor;
  import org.eclipse.wst.jsdt.core.dom.AbstractTypeDeclaration;
  import org.eclipse.wst.jsdt.core.dom.Assignment;
  import org.eclipse.wst.jsdt.core.dom.Expression;
  import org.eclipse.wst.jsdt.core.dom.ExpressionStatement;
  import org.eclipse.wst.jsdt.core.dom.FieldAccess;
  import org.eclipse.wst.jsdt.core.dom.FunctionDeclaration;
  import org.eclipse.wst.jsdt.core.dom.FunctionInvocation;
  import org.eclipse.wst.jsdt.core.dom.IBinding;
  import org.eclipse.wst.jsdt.core.dom.ITypeBinding;
  import org.eclipse.wst.jsdt.core.dom.IVariableBinding;
  import org.eclipse.wst.jsdt.core.dom.ImportDeclaration;
  import org.eclipse.wst.jsdt.core.dom.InfixExpression;
  import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit;
  import org.eclipse.wst.jsdt.core.dom.ParenthesizedExpression;
  import org.eclipse.wst.jsdt.core.dom.PostfixExpression;
  import org.eclipse.wst.jsdt.core.dom.PrefixExpression;
  import org.eclipse.wst.jsdt.core.dom.QualifiedName;
  import org.eclipse.wst.jsdt.core.dom.SimpleName;
  import org.eclipse.wst.jsdt.core.dom.rewrite.ASTRewrite;
  import org.eclipse.wst.jsdt.core.dom.rewrite.ImportRewrite;
  import org.eclipse.wst.jsdt.internal.corext.SourceRange;
  import org.eclipse.wst.jsdt.internal.corext.dom.ASTNodes;
  import org.eclipse.wst.jsdt.internal.corext.dom.Bindings;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.RefactoringCoreMessages;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.base.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 IJavaScriptUnit fCUnit;
  	private IVariableBinding fFieldBinding;
  	private ITypeBinding fDeclaringClassBinding;	
  	private String fGetter;
  	private String fSetter;
  	private ASTRewrite fRewriter;
  	private ImportRewrite fImportRewriter;
  	private List 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, IJavaScriptUnit 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();
  		fIsFieldFinal= false;
  		
  		fStatus= new RefactoringStatus();
  	}
  
  	public boolean getSetterMustReturnValue() {
  		return fSetterMustReturnValue;
  	}
  
  	public RefactoringStatus getStatus() {
  		return fStatus;
  	}
  	
  	public List getGroupDescriptions() {
  		return fGroupDescriptions;
  	}
  	
  	public boolean visit(Assignment node) {
  		Expression lhs= node.getLeftHandSide();
  		if (!considerBinding(resolveBinding(lhs), lhs))
  			return true;
  			
  		checkParent(node);
  		if (!fIsFieldFinal) {
  			// Write access.
  			AST ast= node.getAST();
  			FunctionInvocation invocation= ast.newFunctionInvocation();
  			invocation.setName(ast.newSimpleName(fSetter));
  			fReferencingSetter= true;
  			Expression receiver= getReceiver(lhs);
  			if (receiver != null)
  				invocation.setExpression((Expression)fRewriter.createCopyTarget(receiver));
  			List arguments= invocation.arguments();
  			if (node.getOperator() == Assignment.Operator.ASSIGN) {
  				arguments.add(fRewriter.createCopyTarget(node.getRightHandSide()));
  			} else {
  				// This is the compound assignment case: field+= 10;
  				boolean needsParentheses= ASTNodes.needsParentheses(node.getRightHandSide());
  				InfixExpression exp= ast.newInfixExpression();
  				exp.setOperator(ASTNodes.convertToInfixOperator(node.getOperator()));
  				FunctionInvocation getter= ast.newFunctionInvocation();
  				getter.setName(ast.newSimpleName(fGetter));
  				fReferencingGetter= true;
  				if (receiver != null)
  					getter.setExpression((Expression)fRewriter.createCopyTarget(receiver));
  				exp.setLeftOperand(getter);
  				Expression rhs= (Expression)fRewriter.createCopyTarget(node.getRightHandSide());
  				if (needsParentheses) {
  					ParenthesizedExpression p= ast.newParenthesizedExpression();
  					p.setExpression(rhs);
  					rhs= p;
  				}
  				exp.setRightOperand(rhs);
  				arguments.add(exp);
  			}
  			fRewriter.replace(node, invocation, createGroupDescription(WRITE_ACCESS));
  		}
  		node.getRightHandSide().accept(this);
  		return false;
  	}
  
  	public boolean visit(SimpleName node) {
  		if (!node.isDeclaration() && considerBinding(node.resolveBinding(), node)) {
  			fReferencingGetter= true;
  			fRewriter.replace(
  				node, 
  				fRewriter.createStringPlaceholder(fGetter + "()", ASTNode.FUNCTION_INVOCATION), //$NON-NLS-1$
  				createGroupDescription(READ_ACCESS));
  		}
  		return true;
  	}
  	
  	public boolean visit(ImportDeclaration node) {
  		if (considerBinding(node.resolveBinding(), node)) {
  			fRemoveStaticImport= true;
  		}
  		return false;
  	}
  	
  	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;
  	}
  	
  	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, new SourceRange(node)));
  			return false;
  		}
  		fRewriter.replace(node, 
  			createInvocation(node.getAST(), node.getOperand(), node.getOperator().toString()), 
  			createGroupDescription(POSTFIX_ACCESS));
  		return false;
  	}
  	
  	public boolean visit(FunctionDeclaration node) {
  		String name= node.getName().getIdentifier();
  		if (name.equals(fGetter) || name.equals(fSetter))
  			return false;
  		return true;
  	}
  	
  	public void endVisit(JavaScriptUnit 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= (AbstractTypeDeclaration)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();
  		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();
  		}
  		return null;
  	}
  		
  	private FunctionInvocation createInvocation(AST ast, Expression operand, String operator) {
  		Expression receiver= getReceiver(operand);
  		FunctionInvocation invocation= ast.newFunctionInvocation();
  		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$
  		}
  		FunctionInvocation getter= ast.newFunctionInvocation();
  		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;
  	}
  }
  
  