content: /*******************************************************************************
   * Copyright (c) 2000, 2015 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
   *******************************************************************************/
  package org.eclipse.wst.jsdt.internal.corext.fix;
  
  import java.util.ArrayList;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.List;
  
  import org.eclipse.core.runtime.CoreException;
  import org.eclipse.text.edits.TextEditGroup;
  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.Assignment;
  import org.eclipse.wst.jsdt.core.dom.ClassInstanceCreation;
  import org.eclipse.wst.jsdt.core.dom.ConditionalExpression;
  import org.eclipse.wst.jsdt.core.dom.Expression;
  import org.eclipse.wst.jsdt.core.dom.FunctionInvocation;
  import org.eclipse.wst.jsdt.core.dom.ITypeBinding;
  import org.eclipse.wst.jsdt.core.dom.InfixExpression;
  import org.eclipse.wst.jsdt.core.dom.InfixExpression.Operator;
  import org.eclipse.wst.jsdt.core.dom.InstanceofExpression;
  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.rewrite.ASTRewrite;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
  
  public class ExpressionsFix extends AbstractFix {
  
  	private static final class MissingParenthesisVisitor extends ASTVisitor {
  		
  		private final ArrayList fNodes;
  
  		private MissingParenthesisVisitor(ArrayList nodes) {
  			fNodes= nodes;
  		}
  
  		public void postVisit(ASTNode node) {
  			// check that parent is && or ||
  			if (!(node.getParent() instanceof InfixExpression))
  				return;
  			
  			// we want to add parenthesis around arithmetic operators and instanceof
  			boolean needParenthesis = false;
  			if (node instanceof InfixExpression) {
  				InfixExpression expression = (InfixExpression) node;
  				InfixExpression.Operator operator = expression.getOperator();
  
  				InfixExpression parentExpression = (InfixExpression) node.getParent();
  				InfixExpression.Operator parentOperator = parentExpression.getOperator();
  				
  				if (parentOperator == operator)
  					return;
  				
  				needParenthesis= (operator == InfixExpression.Operator.LESS)
  						|| (operator == InfixExpression.Operator.GREATER)
  						|| (operator == InfixExpression.Operator.LESS_EQUALS)
  						|| (operator == InfixExpression.Operator.GREATER_EQUALS)
  						|| (operator == InfixExpression.Operator.EQUALS)
  						|| (operator == InfixExpression.Operator.NOT_EQUALS)
  						
  						|| (operator == InfixExpression.Operator.CONDITIONAL_AND)
  						|| (operator == InfixExpression.Operator.CONDITIONAL_OR);
  			}
  			if (node instanceof InstanceofExpression) {
  				needParenthesis = true;
  			}
  			if (!needParenthesis) {
  				return;
  			}
  			fNodes.add(node);
  		}
  	}
  	
  	private static final class UnnecessaryParenthesisVisitor extends ASTVisitor {
  		private final ArrayList fNodes;
  
  		private UnnecessaryParenthesisVisitor(ArrayList nodes) {
  			fNodes= nodes;
  		}
  
  		public void postVisit(ASTNode node) {
  			if (!(node instanceof ParenthesizedExpression)) {
  				return;
  			}
  			ParenthesizedExpression parenthesizedExpression= (ParenthesizedExpression) node;
  			Expression expression= parenthesizedExpression.getExpression();
  			while (expression instanceof ParenthesizedExpression) {
  				expression= ((ParenthesizedExpression) expression).getExpression();
  			}
  			// if this is part of another expression, check for this and parent precedences
  			if (parenthesizedExpression.getParent() instanceof Expression) {
  				Expression parentExpression= (Expression) parenthesizedExpression.getParent();
  				int expressionPrecedence= getExpressionPrecedence(expression);
  				int parentPrecedence= getExpressionPrecedence(parentExpression);
  				if ((expressionPrecedence > parentPrecedence)
  					&& !(parenthesizedExpression.getParent() instanceof ParenthesizedExpression)) {
  					return;
  				}
  				// check for case when precedences for expression and parent are same
  				if ((expressionPrecedence == parentPrecedence) && (parentExpression instanceof InfixExpression)) {
  					//we have expr infix (expr infix expr) removing the parenthesis is equal to (expr infix expr) infix expr
  					InfixExpression parentInfix= (InfixExpression) parentExpression;
   					Operator parentOperator= parentInfix.getOperator();
  					if (parentInfix.getLeftOperand() == parenthesizedExpression) {
  						fNodes.add(node);
  					} else if (isAssoziative(parentOperator)) {
  						if (parentOperator == InfixExpression.Operator.PLUS) {
  							if (isStringExpression(parentInfix.getLeftOperand())
  								|| isStringExpression(parentInfix.getRightOperand())) {
  								return;
  							}
  							for (Iterator J= parentInfix.extendedOperands().iterator(); J.hasNext();) {
  								Expression operand= (Expression) J.next();
  								if (isStringExpression(operand)) {
  									return;
  								}
  							}
  						}
  						fNodes.add(node);
  					}
  					return;
  				} else if (expressionPrecedence == parentPrecedence && parentExpression instanceof ConditionalExpression) {
  					if (((ConditionalExpression)parentExpression).getElseExpression() != parenthesizedExpression)
  						return;
  				}
  			}
  			fNodes.add(node);
  		}
  
  		//is e1 op (e2 op e3) == (e1 op e2) op e3 == e1 op e2 op e3 for 'operator'? 
  		private boolean isAssoziative(Operator operator) {
  			if (operator == InfixExpression.Operator.PLUS)
  				return true;
  			
  			if (operator == InfixExpression.Operator.CONDITIONAL_AND)
  				return true;
  			
  			if (operator == InfixExpression.Operator.CONDITIONAL_OR)
  				return true;
  			
  			if (operator == InfixExpression.Operator.AND)
  				return true;
  			
  			if (operator == InfixExpression.Operator.OR)
  				return true;
  			
  			if (operator == InfixExpression.Operator.XOR)
  				return true;
  			
  			if (operator == InfixExpression.Operator.TIMES)
  				return true;
  			
  			return false;
  		}
  
  		private static int getExpressionPrecedence(Expression expression) {
  			if (expression instanceof PostfixExpression || expression instanceof FunctionInvocation) {
  				return 0;
  			}
  			if (expression instanceof PrefixExpression) {
  				return 1;
  			}
  			if (expression instanceof ClassInstanceCreation) {
  				return 2;
  			}
  			if (expression instanceof InfixExpression) {
  				InfixExpression infixExpression = (InfixExpression) expression;
  				InfixExpression.Operator operator = infixExpression.getOperator();
  				return getInfixOperatorPrecedence(operator);
  			}
  			if (expression instanceof InstanceofExpression) {
  				return 6;
  			}
  			if (expression instanceof ConditionalExpression) {
  				return 13;
  			}
  			if (expression instanceof Assignment) {
  				return 14;
  			}
  			return -1;
  		}
  		
  		private static int getInfixOperatorPrecedence(InfixExpression.Operator operator) {
  			if ((operator == InfixExpression.Operator.TIMES) || (operator == InfixExpression.Operator.DIVIDE)
  					|| (operator == InfixExpression.Operator.REMAINDER)) {
  				return 3;
  			}
  			if ((operator == InfixExpression.Operator.PLUS) || (operator == InfixExpression.Operator.MINUS)) {
  				return 4;
  			}
  			if ((operator == InfixExpression.Operator.LEFT_SHIFT)
  					|| (operator == InfixExpression.Operator.RIGHT_SHIFT_SIGNED)
  					|| (operator == InfixExpression.Operator.RIGHT_SHIFT_UNSIGNED)) {
  				return 5;
  			}
  			if ((operator == InfixExpression.Operator.LESS) || (operator == InfixExpression.Operator.GREATER)
  					|| (operator == InfixExpression.Operator.LESS_EQUALS)
  					|| (operator == InfixExpression.Operator.GREATER_EQUALS)) {
  				return 6;
  			}
  			if ((operator == InfixExpression.Operator.EQUALS) || (operator == InfixExpression.Operator.NOT_EQUALS)) {
  				return 7;
  			}
  			if (operator == InfixExpression.Operator.AND) {
  				return 8;
  			}
  			if (operator == InfixExpression.Operator.XOR) {
  				return 9;
  			}
  			if (operator == InfixExpression.Operator.OR) {
  				return 10;
  			}
  			if (operator == InfixExpression.Operator.CONDITIONAL_AND) {
  				return 11;
  			}
  			if (operator == InfixExpression.Operator.CONDITIONAL_OR) {
  				return 12;
  			}
  			return -1;
  		}
  		
  	}
  
  	private static class AddParenthesisOperation extends AbstractFixRewriteOperation {
  
  		private final Expression[] fExpressions;
  
  		public AddParenthesisOperation(Expression[] expressions) {
  			fExpressions= expressions;
  		}
  
  		/**
  		 * {@inheritDoc}
  		 */
  		public void rewriteAST(CompilationUnitRewrite cuRewrite, List textEditGroups) throws CoreException {
  			TextEditGroup group= createTextEditGroup(FixMessages.ExpressionsFix_addParanoiacParenthesis_description);
  			textEditGroups.add(group);
  			
  			ASTRewrite rewrite= cuRewrite.getASTRewrite();
  			AST ast= cuRewrite.getRoot().getAST();
  
  			for (int i= 0; i < fExpressions.length; i++) {
  				// add parenthesis around expression
  				Expression expression= fExpressions[i];
  				
  				ParenthesizedExpression parenthesizedExpression= ast.newParenthesizedExpression();
  				parenthesizedExpression.setExpression((Expression) rewrite.createCopyTarget(expression));
  				rewrite.replace(expression, parenthesizedExpression, group);
  			}
  		}
  	}
  	
  	private static class RemoveParenthesisOperation extends AbstractFixRewriteOperation {
  
  		private final HashSet/*<ParenthesizedExpression>*/ fExpressions;
  
  		public RemoveParenthesisOperation(HashSet expressions) {
  			fExpressions= expressions;
  		}
  
  		/**
  		 * {@inheritDoc}
  		 */
  		public void rewriteAST(CompilationUnitRewrite cuRewrite, List textEditGroups) throws CoreException {
  			TextEditGroup group= createTextEditGroup(FixMessages.ExpressionsFix_removeUnnecessaryParenthesis_description);
  			textEditGroups.add(group);
  			
  			ASTRewrite rewrite= cuRewrite.getASTRewrite();
  						
  			while (fExpressions.size() > 0) {
  				ParenthesizedExpression parenthesizedExpression= (ParenthesizedExpression)fExpressions.iterator().next();
  				fExpressions.remove(parenthesizedExpression);
  				ParenthesizedExpression down= parenthesizedExpression;
  				while (fExpressions.contains(down.getExpression())) {
  					down= (ParenthesizedExpression)down.getExpression();
  					fExpressions.remove(down);
  				}
  				
  				ASTNode move= rewrite.createMoveTarget(down.getExpression());
  				
  				ParenthesizedExpression top= parenthesizedExpression;
  				while (fExpressions.contains(top.getParent())) {
  					top= (ParenthesizedExpression)top.getParent();
  					fExpressions.remove(top);
  				}
  				
  				rewrite.replace(top, move, group);
  			}
  		}
  	}
  	
  	public static IFix createAddParanoidalParenthesisFix(JavaScriptUnit compilationUnit, ASTNode[] coveredNodes) throws CoreException {
  		if (coveredNodes == null)
  			return null;
  		
  		if (coveredNodes.length == 0)
  			return null;
  		// check sub-expressions in fully covered nodes
  		final ArrayList changedNodes = new ArrayList();
  		for (int i= 0; i < coveredNodes.length; i++) {
  			ASTNode covered = coveredNodes[i];
  			if (covered instanceof InfixExpression)
  				covered.accept(new MissingParenthesisVisitor(changedNodes));
  		}
  		if (changedNodes.isEmpty())
  			return null;
  		
  
  		IFixRewriteOperation op= new AddParenthesisOperation((Expression[])changedNodes.toArray(new Expression[changedNodes.size()]));
  		return new ExpressionsFix(FixMessages.ExpressionsFix_addParanoiacParenthesis_description, compilationUnit, new IFixRewriteOperation[] {op});
  	}
  	
  	public static IFix createRemoveUnnecessaryParenthesisFix(JavaScriptUnit compilationUnit, ASTNode[] nodes) {
  		// check sub-expressions in fully covered nodes
  		final ArrayList changedNodes= new ArrayList();
  		for (int i= 0; i < nodes.length; i++) {
  			ASTNode covered= nodes[i];
  			if (covered instanceof ParenthesizedExpression || covered instanceof InfixExpression)
  				covered.accept(new UnnecessaryParenthesisVisitor(changedNodes));
  		}
  		if (changedNodes.isEmpty())
  			return null;
  		
  		HashSet expressions= new HashSet(changedNodes);
  		RemoveParenthesisOperation op= new RemoveParenthesisOperation(expressions);
  		return new ExpressionsFix(FixMessages.ExpressionsFix_removeUnnecessaryParenthesis_description, compilationUnit, new IFixRewriteOperation[] {op});
  	}
  	
  	public static IFix createCleanUp(JavaScriptUnit compilationUnit, 
  			boolean addParanoicParentesis,
  			boolean removeUnnecessaryParenthesis) {
  		
  		if (addParanoicParentesis) {
  			final ArrayList changedNodes = new ArrayList();
  			compilationUnit.accept(new MissingParenthesisVisitor(changedNodes));
  
  			if (changedNodes.isEmpty())
  				return null;
  			
  			IFixRewriteOperation op= new AddParenthesisOperation((Expression[])changedNodes.toArray(new Expression[changedNodes.size()]));
  			return new ExpressionsFix(FixMessages.ExpressionsFix_add_parenthesis_change_name, compilationUnit, new IFixRewriteOperation[] {op});
  		} else if (removeUnnecessaryParenthesis) {
  			final ArrayList changedNodes = new ArrayList();
  			compilationUnit.accept(new UnnecessaryParenthesisVisitor(changedNodes));
  
  			if (changedNodes.isEmpty())
  				return null;
  			
  			HashSet expressions= new HashSet(changedNodes);
  			IFixRewriteOperation op= new RemoveParenthesisOperation(expressions);
  			return new ExpressionsFix(FixMessages.ExpressionsFix_remove_parenthesis_change_name, compilationUnit, new IFixRewriteOperation[] {op});
  		}
  		return null;
  	}
  	
  	private static boolean isStringExpression(Expression expression) {
  		ITypeBinding binding = expression.resolveTypeBinding();
  		return binding.getQualifiedName().equals("String"); //$NON-NLS-1$
  	}
  
  	protected ExpressionsFix(String name, JavaScriptUnit compilationUnit, IFixRewriteOperation[] fixRewriteOperations) {
  		super(name, compilationUnit, fixRewriteOperations);
  	}
  
  }
  