blob: dcbb8df2d8877df603c1de952d8c2b4177bd0b95 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2008 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.TypeDeclaration;
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 (fSenderBinding.isInterface() || 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(), fSenderBinding.isInterface());
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 (binding.isWildcardType()) {
binding= ASTResolving.normalizeWildcardType(binding, isVariableAssigned(), ast);
if (binding == null) {
// only null binding applies
binding= ast.resolveWellKnownType("java.lang.Object"); //$NON-NLS-1$
}
}
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) {
if (fSenderBinding.isAnnotation()) {
return 0;
}
if (fSenderBinding.isInterface()) {
// for interface members copy the modifiers from an existing field
FieldDeclaration[] fieldDecls= ((TypeDeclaration) newTypeDecl).getFields();
if (fieldDecls.length > 0) {
return fieldDecls[0].getModifiers();
}
return 0;
}
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;
}
}