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
   *******************************************************************************/
  
  package org.eclipse.wst.jsdt.internal.ui.text.correction;
  
  import java.util.Arrays;
  import java.util.Comparator;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.List;
  
  import org.eclipse.core.runtime.Assert;
  import org.eclipse.core.runtime.CoreException;
  import org.eclipse.swt.graphics.Image;
  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.AnonymousClassDeclaration;
  import org.eclipse.wst.jsdt.core.dom.Assignment;
  import org.eclipse.wst.jsdt.core.dom.BodyDeclaration;
  import org.eclipse.wst.jsdt.core.dom.ChildListPropertyDescriptor;
  import org.eclipse.wst.jsdt.core.dom.Expression;
  import org.eclipse.wst.jsdt.core.dom.ExpressionStatement;
  import org.eclipse.wst.jsdt.core.dom.FieldDeclaration;
  import org.eclipse.wst.jsdt.core.dom.ForStatement;
  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.Initializer;
  import org.eclipse.wst.jsdt.core.dom.JSdoc;
  import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit;
  import org.eclipse.wst.jsdt.core.dom.Modifier;
  import org.eclipse.wst.jsdt.core.dom.QualifiedName;
  import org.eclipse.wst.jsdt.core.dom.SimpleName;
  import org.eclipse.wst.jsdt.core.dom.SingleVariableDeclaration;
  import org.eclipse.wst.jsdt.core.dom.Statement;
  import org.eclipse.wst.jsdt.core.dom.StructuralPropertyDescriptor;
  import org.eclipse.wst.jsdt.core.dom.TagElement;
  import org.eclipse.wst.jsdt.core.dom.TextElement;
  import org.eclipse.wst.jsdt.core.dom.Type;
  import org.eclipse.wst.jsdt.core.dom.VariableDeclarationExpression;
  import org.eclipse.wst.jsdt.core.dom.VariableDeclarationFragment;
  import org.eclipse.wst.jsdt.core.dom.VariableDeclarationStatement;
  import org.eclipse.wst.jsdt.core.dom.rewrite.ASTRewrite;
  import org.eclipse.wst.jsdt.core.dom.rewrite.ImportRewrite;
  import org.eclipse.wst.jsdt.core.dom.rewrite.ListRewrite;
  import org.eclipse.wst.jsdt.internal.corext.dom.ASTNodeFactory;
  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.dom.LinkedNodeFinder;
  
  public class NewVariableCompletionProposal extends LinkedCorrectionProposal {
  
  	public static final int LOCAL= 1;
  	public static final int FIELD= 2;
  	public static final int PARAM= 3;
  
  	public static final int CONST_FIELD= 4;
  	public static final int ENUM_CONST= 5;
  
  	private static final String KEY_NAME= "name"; //$NON-NLS-1$
  	private static final String KEY_TYPE= "type"; //$NON-NLS-1$
  	private static final String KEY_INITIALIZER= "initializer"; //$NON-NLS-1$
  
  	final private int  fVariableKind;
  	final private SimpleName fOriginalNode;
  	final private ITypeBinding fSenderBinding;
  
  	public NewVariableCompletionProposal(String label, IJavaScriptUnit cu, int variableKind, SimpleName node, ITypeBinding senderBinding, int relevance, Image image) {
  		super(label, cu, null, relevance, image);
  		if (senderBinding == null) {
  			Assert.isTrue(variableKind == PARAM || variableKind == LOCAL);
  		} else {
  			Assert.isTrue(Bindings.isDeclarationBinding(senderBinding));
  		}
  
  		fVariableKind= variableKind;
  		fOriginalNode= node;
  		fSenderBinding= senderBinding;
  	}
  
  	protected ASTRewrite getRewrite() throws CoreException {
  		JavaScriptUnit cu= ASTResolving.findParentCompilationUnit(fOriginalNode);
  		switch (fVariableKind) {
  			case PARAM:
  				return doAddParam(cu);
  			case FIELD:
  			case CONST_FIELD:
  				return doAddField(cu);
  			case LOCAL:
  				return doAddLocal(cu);
  			default:
  				throw new IllegalArgumentException("Unsupported variable kind: " + fVariableKind); //$NON-NLS-1$
  		}
  	}
  
  	private ASTRewrite doAddParam(JavaScriptUnit cu) throws CoreException {
  		AST ast= cu.getAST();
  		SimpleName node= fOriginalNode;
  
  		BodyDeclaration decl= ASTResolving.findParentBodyDeclaration(node);
  		if (decl instanceof FunctionDeclaration) {
  			FunctionDeclaration methodDeclaration= (FunctionDeclaration) decl;
  
  			ASTRewrite rewrite= ASTRewrite.create(ast);
  			
  			ImportRewrite imports= createImportRewrite((JavaScriptUnit) decl.getRoot());
  
  			SingleVariableDeclaration newDecl= ast.newSingleVariableDeclaration();
  			newDecl.setType(evaluateVariableType(ast, imports, methodDeclaration.resolveBinding()));
  			newDecl.setName(ast.newSimpleName(node.getIdentifier()));
  
  			ListRewrite listRewriter= rewrite.getListRewrite(decl, FunctionDeclaration.PARAMETERS_PROPERTY);
  			listRewriter.insertLast(newDecl, null);
  
  			addLinkedPosition(rewrite.track(newDecl.getType()), false, KEY_TYPE);
  			addLinkedPosition(rewrite.track(node), true, KEY_NAME);
  			addLinkedPosition(rewrite.track(newDecl.getName()), false, KEY_NAME);
  
  			// add javadoc tag
  			JSdoc javadoc= methodDeclaration.getJavadoc();
  			if (javadoc != null) {
  				HashSet leadingNames= new HashSet();
  				for (Iterator iter= methodDeclaration.parameters().iterator(); iter.hasNext();) {
  					SingleVariableDeclaration curr= (SingleVariableDeclaration) iter.next();
  					leadingNames.add(curr.getName().getIdentifier());
  				}
  				SimpleName newTagRef= ast.newSimpleName(node.getIdentifier());
  
  				TagElement newTagElement= ast.newTagElement();
  				newTagElement.setTagName(TagElement.TAG_PARAM);
  				newTagElement.fragments().add(newTagRef);
  				TextElement commentStart= ast.newTextElement();
  				newTagElement.fragments().add(commentStart);
  
  				addLinkedPosition(rewrite.track(newTagRef), true, KEY_NAME);
  				addLinkedPosition(rewrite.track(commentStart), false, "comment_start"); //$NON-NLS-1$
  
  				ListRewrite tagsRewriter= rewrite.getListRewrite(javadoc, JSdoc.TAGS_PROPERTY);
  				JavadocTagsSubProcessor.insertTag(tagsRewriter, newTagElement, leadingNames);
  			}
  
  			return rewrite;
  		}
  		return null;
  	}
  
  	private boolean isAssigned(Statement statement, SimpleName name) {
  		if (statement instanceof ExpressionStatement) {
  			ExpressionStatement exstat= (ExpressionStatement) statement;
  			if (exstat.getExpression() instanceof Assignment) {
  				Assignment assignment= (Assignment) exstat.getExpression();
  				return assignment.getLeftHandSide() == name;
  			}
  		}
  		return false;
  	}
  
  	private boolean isForStatementInit(Statement statement, SimpleName name) {
  		if (statement instanceof ForStatement) {
  			ForStatement forStatement= (ForStatement) statement;
  			List list = forStatement.initializers();
  			if (list.size() == 1 && list.get(0) instanceof Assignment) {
  				Assignment assignment= (Assignment) list.get(0);
  				return assignment.getLeftHandSide() == name;
  			}
  		}
  		return false;
  	}
  
  
  	private ASTRewrite doAddLocal(JavaScriptUnit cu) throws CoreException {
  		AST ast= cu.getAST();
  
  		ASTNode body;
  		BodyDeclaration decl= ASTResolving.findParentBodyDeclaration(fOriginalNode);
  		IBinding targetContext= null;
  		if (decl instanceof FunctionDeclaration) {
  			body= (((FunctionDeclaration) decl).getBody());
  			targetContext= ((FunctionDeclaration) decl).resolveBinding();
  		} else if (decl instanceof Initializer) {
  			body= (((Initializer) decl).getBody());
  			targetContext= Bindings.getBindingOfParentType(decl);
  		} else if (decl ==null ) {
  			body= cu;
  			targetContext= cu.resolveBinding();
  		} else {
  			return null;
  		}
  		ASTRewrite rewrite= ASTRewrite.create(ast);
  		
  		ImportRewrite imports= createImportRewrite(cu);
  
  		SimpleName[] names= getAllReferences(body);
  		ASTNode dominant= getDominantNode(names);
  
  		Statement dominantStatement= ASTResolving.findParentStatement(dominant);
  		if (ASTNodes.isControlStatementBody(dominantStatement.getLocationInParent())) {
  			dominantStatement= (Statement) dominantStatement.getParent();
  		}
  
  		SimpleName node= names[0];
  
  		if (isAssigned(dominantStatement, node)) {
  			// x = 1; -> int x = 1;
  			Assignment assignment= (Assignment) node.getParent();
  
  			// trick to avoid comment removal around the statement: keep the expression statement
  			// and replace the assignment with an VariableDeclarationExpression
  			VariableDeclarationFragment newDeclFrag= ast.newVariableDeclarationFragment();
  			VariableDeclarationExpression newDecl= ast.newVariableDeclarationExpression(newDeclFrag);
  			newDecl.setType(evaluateVariableType(ast, imports, targetContext));
  
  			Expression placeholder= (Expression) rewrite.createCopyTarget(assignment.getRightHandSide());
  			newDeclFrag.setInitializer(placeholder);
  			newDeclFrag.setName(ast.newSimpleName(node.getIdentifier()));
  			rewrite.replace(assignment, newDecl, null);
  
  			addLinkedPosition(rewrite.track(newDecl.getType()), false, KEY_TYPE);
  			addLinkedPosition(rewrite.track(newDeclFrag.getName()), true, KEY_NAME);
  
  			setEndPosition(rewrite.track(assignment.getParent()));
  
  			return rewrite;
  		} else if ((dominant != dominantStatement) && isForStatementInit(dominantStatement, node)) {
  			//	for (x = 1;;) ->for (int x = 1;;)
  
  			Assignment assignment= (Assignment) node.getParent();
  
  			VariableDeclarationFragment frag= ast.newVariableDeclarationFragment();
  			VariableDeclarationExpression expression= ast.newVariableDeclarationExpression(frag);
  			frag.setName(ast.newSimpleName(node.getIdentifier()));
  			Expression placeholder= (Expression) rewrite.createCopyTarget(assignment.getRightHandSide());
  			frag.setInitializer(placeholder);
  			expression.setType(evaluateVariableType(ast, imports, targetContext));
  
  			rewrite.replace(assignment, expression, null);
  
  			addLinkedPosition(rewrite.track(expression.getType()), false, KEY_TYPE);
  			addLinkedPosition(rewrite.track(frag.getName()), true, KEY_NAME);
  
  			setEndPosition(rewrite.track(expression));
  
  			return rewrite;
  		}
  		//	foo(x) -> int x; foo(x)
  
  		VariableDeclarationFragment newDeclFrag= ast.newVariableDeclarationFragment();
  		VariableDeclarationStatement newDecl= ast.newVariableDeclarationStatement(newDeclFrag);
  
  		newDeclFrag.setName(ast.newSimpleName(node.getIdentifier()));
  		newDecl.setType(evaluateVariableType(ast, imports, targetContext));
  //		newDeclFrag.setInitializer(ASTNodeFactory.newDefaultExpression(ast, newDecl.getType(), 0));
  
  		addLinkedPosition(rewrite.track(newDecl.getType()), false, KEY_TYPE);
  		addLinkedPosition(rewrite.track(node), true, KEY_NAME);
  		addLinkedPosition(rewrite.track(newDeclFrag.getName()), false, KEY_NAME);
  
  		Statement statement= dominantStatement;
  		List list= ASTNodes.getContainingList(statement);
  		while (list == null && statement.getParent() instanceof Statement) { // parent must be if, for or while
  			statement= (Statement) statement.getParent();
  			list= ASTNodes.getContainingList(statement);
  		}
  		if (list != null) {
  			ASTNode parent= statement.getParent();
  			StructuralPropertyDescriptor childProperty= statement.getLocationInParent();
  			if (childProperty.isChildListProperty()) {
  				rewrite.getListRewrite(parent, (ChildListPropertyDescriptor) childProperty).insertBefore(newDecl, statement, null);
  				return rewrite;
  			} else {
  				return null;
  			}
  		}
  		return rewrite;
  	}
  
  	private SimpleName[] getAllReferences(ASTNode body) {
  		SimpleName[] names= LinkedNodeFinder.findByProblems(body, fOriginalNode);
  		if (names == null) {
  			return new SimpleName[] { fOriginalNode };
  		}
  		if (names.length > 1) {
  			Arrays.sort(names, new Comparator() {
  				public int compare(Object o1, Object o2) {
  					return ((SimpleName) o1).getStartPosition() - ((SimpleName) o2).getStartPosition();
  				}
  			});
  		}
  		return names;
  	}
  
  
  	private ASTNode getDominantNode(SimpleName[] names) {
  		ASTNode dominator= names[0]; //ASTResolving.findParentStatement(names[0]);
  		for (int i= 1; i < names.length; i++) {
  			ASTNode curr= names[i];// ASTResolving.findParentStatement(names[i]);
  			if (curr != dominator) {
  				ASTNode parent= getCommonParent(curr, dominator);
  
  				if (curr.getStartPosition() < dominator.getStartPosition()) {
  					dominator= curr;
  				}
  				while (dominator.getParent() != parent) {
  					dominator= dominator.getParent();
  				}
  			}
  		}
  		int parentKind= dominator.getParent().getNodeType();
  		if (parentKind != ASTNode.BLOCK && parentKind != ASTNode.FOR_STATEMENT && parentKind != ASTNode.FOR_IN_STATEMENT) {
  			return dominator.getParent();
  		}
  		return dominator;
  	}
  
  	private ASTNode getCommonParent(ASTNode node1, ASTNode node2) {
  		ASTNode parent= node1.getParent();
  		while (parent != null && !ASTNodes.isParent(node2, parent)) {
  			parent= parent.getParent();
  		}
  		return parent;
  	}
  
  	private ASTRewrite doAddField(JavaScriptUnit astRoot) throws CoreException {
  		SimpleName node= fOriginalNode;
  		boolean isInDifferentCU= false;
  
  		ASTNode newTypeDecl= astRoot.findDeclaringNode(fSenderBinding);
  		if (newTypeDecl == null) {
  			astRoot= ASTResolving.createQuickFixAST(getCompilationUnit(), null);
  			newTypeDecl= astRoot.findDeclaringNode(fSenderBinding.getKey());
  			isInDifferentCU= true;
  		}
  		ImportRewrite imports= createImportRewrite(astRoot);
  
  		if (newTypeDecl != null) {
  			AST ast= newTypeDecl.getAST();
  
  			ASTRewrite rewrite= ASTRewrite.create(ast);
  
  			VariableDeclarationFragment fragment= ast.newVariableDeclarationFragment();
  			fragment.setName(ast.newSimpleName(node.getIdentifier()));
  
  			Type type= evaluateVariableType(ast, imports, fSenderBinding);
  
  			FieldDeclaration newDecl= ast.newFieldDeclaration(fragment);
  			newDecl.setType(type);
  			newDecl.modifiers().addAll(ASTNodeFactory.newModifiers(ast, evaluateFieldModifiers(newTypeDecl)));
  
  			if (fVariableKind == CONST_FIELD) {
  				fragment.setInitializer(ASTNodeFactory.newDefaultExpression(ast, type, 0));
  			}
  
  			ChildListPropertyDescriptor property= ASTNodes.getBodyDeclarationsProperty(newTypeDecl);
  			List decls= (List) newTypeDecl.getStructuralProperty(property);
  
  			int maxOffset= isInDifferentCU ? -1 : node.getStartPosition();
  			
  			int insertIndex= findFieldInsertIndex(decls, newDecl, maxOffset);
  
  			ListRewrite listRewriter= rewrite.getListRewrite(newTypeDecl, property);
  			listRewriter.insertAt(newDecl, insertIndex, null);
  
  			ModifierCorrectionSubProcessor.installLinkedVisibilityProposals(getLinkedProposalModel(), rewrite, newDecl.modifiers(), false);
  			
  			addLinkedPosition(rewrite.track(newDecl.getType()), false, KEY_TYPE);
  			if (!isInDifferentCU) {
  				addLinkedPosition(rewrite.track(node), true, KEY_NAME);
  			}
  			addLinkedPosition(rewrite.track(fragment.getName()), false, KEY_NAME);
  
  			if (fragment.getInitializer() != null) {
  				addLinkedPosition(rewrite.track(fragment.getInitializer()), false, KEY_INITIALIZER);
  			}
  			return rewrite;
  		}
  		return null;
  	}
  
  	private int findFieldInsertIndex(List decls, FieldDeclaration newDecl, int maxOffset) {
  		if (maxOffset != -1) {
  			for (int i= decls.size() - 1; i >= 0; i--) {
  				ASTNode curr= (ASTNode) decls.get(i);
  				if (maxOffset > curr.getStartPosition() + curr.getLength()) {
  					return ASTNodes.getInsertionIndex(newDecl, decls.subList(0, i + 1));
  				}
  			}
  			return 0;
  		}
  		return ASTNodes.getInsertionIndex(newDecl, decls);
  	}
  
  	private Type evaluateVariableType(AST ast, ImportRewrite imports, IBinding targetContext) throws CoreException {
  		if (fOriginalNode.getParent() instanceof FunctionInvocation) {
  			FunctionInvocation parent= (FunctionInvocation) fOriginalNode.getParent();
  			if (parent.getExpression() == fOriginalNode) {
  				// _x_.foo() -> guess qualifier type by looking for a type with method 'foo'
  				ITypeBinding[] bindings= ASTResolving.getQualifierGuess(fOriginalNode.getRoot(), parent.getName().getIdentifier(), parent.arguments(), targetContext);
  				if (bindings.length > 0) {
  					for (int i= 0; i < bindings.length; i++) {
  						addLinkedPositionProposal(KEY_TYPE, bindings[i]);
  					}
  					return imports.addImport(bindings[0], ast);
  				}
  			}
  		}
  
  		ITypeBinding binding= ASTResolving.guessBindingForReference(fOriginalNode);
  		if (binding != null) {
  			
  			if (isVariableAssigned()) {
  				ITypeBinding[] typeProposals= ASTResolving.getRelaxingTypes(ast, binding);
  				for (int i= 0; i < typeProposals.length; i++) {
  					addLinkedPositionProposal(KEY_TYPE, typeProposals[i]);
  				}
  			}
  			return imports.addImport(binding, ast);
  		}
  		// no binding, find type AST node instead -> ABC a= x-> use 'ABC' as is
  		Type type= ASTResolving.guessTypeForReference(ast, fOriginalNode);
  		if (type != null) {
  			return type;
  		}
  		if (fVariableKind == CONST_FIELD) {
  			return ast.newSimpleType(ast.newSimpleName("String")); //$NON-NLS-1$
  		}
  		return ast.newSimpleType(ast.newSimpleName("Object")); //$NON-NLS-1$
  	}
  
  	private boolean isVariableAssigned() {
  		ASTNode parent= fOriginalNode.getParent();
  		return (parent instanceof Assignment) && (fOriginalNode == ((Assignment) parent).getLeftHandSide());
  	}
  
  
  	private int evaluateFieldModifiers(ASTNode newTypeDecl) {
  		int modifiers= 0;
  
  		if (fVariableKind == CONST_FIELD) {
  			modifiers |= Modifier.FINAL | Modifier.STATIC;
  		} else {
  			ASTNode parent= fOriginalNode.getParent();
  			if (parent instanceof QualifiedName) {
  				IBinding qualifierBinding= ((QualifiedName)parent).getQualifier().resolveBinding();
  				if (qualifierBinding instanceof ITypeBinding) {
  					modifiers |= Modifier.STATIC;
  				}
  			} else if (ASTResolving.isInStaticContext(fOriginalNode)) {
  				modifiers |= Modifier.STATIC;
  			}
  		}
  		ASTNode node= ASTResolving.findParentType(fOriginalNode, true);
  		if (newTypeDecl.equals(node)) {
  			modifiers |= Modifier.PRIVATE;
  		} else if (node instanceof AnonymousClassDeclaration) {
  			modifiers |= Modifier.PROTECTED;
  		} else {
  			modifiers |= Modifier.PUBLIC;
  		}
  
  		return modifiers;
  	}
  
  
  
  	/**
  	 * Returns the variable kind.
  	 * @return int
  	 */
  	public int getVariableKind() {
  		return fVariableKind;
  	}
  
  }
  