/*******************************************************************************
* Copyright (c) 2000, 2011 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.che.ide.ext.java.jdt.internal.text.correction.proposals;
import org.eclipse.che.ide.ext.java.jdt.Images;
import org.eclipse.che.ide.ext.java.jdt.core.dom.AST;
import org.eclipse.che.ide.ext.java.jdt.core.dom.ASTNode;
import org.eclipse.che.ide.ext.java.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.che.ide.ext.java.jdt.core.dom.Assignment;
import org.eclipse.che.ide.ext.java.jdt.core.dom.Block;
import org.eclipse.che.ide.ext.java.jdt.core.dom.BodyDeclaration;
import org.eclipse.che.ide.ext.java.jdt.core.dom.ChildListPropertyDescriptor;
import org.eclipse.che.ide.ext.java.jdt.core.dom.CompilationUnit;
import org.eclipse.che.ide.ext.java.jdt.core.dom.EnumConstantDeclaration;
import org.eclipse.che.ide.ext.java.jdt.core.dom.EnumDeclaration;
import org.eclipse.che.ide.ext.java.jdt.core.dom.Expression;
import org.eclipse.che.ide.ext.java.jdt.core.dom.ExpressionStatement;
import org.eclipse.che.ide.ext.java.jdt.core.dom.FieldDeclaration;
import org.eclipse.che.ide.ext.java.jdt.core.dom.ForStatement;
import org.eclipse.che.ide.ext.java.jdt.core.dom.IBinding;
import org.eclipse.che.ide.ext.java.jdt.core.dom.ITypeBinding;
import org.eclipse.che.ide.ext.java.jdt.core.dom.Initializer;
import org.eclipse.che.ide.ext.java.jdt.core.dom.Javadoc;
import org.eclipse.che.ide.ext.java.jdt.core.dom.MethodDeclaration;
import org.eclipse.che.ide.ext.java.jdt.core.dom.MethodInvocation;
import org.eclipse.che.ide.ext.java.jdt.core.dom.Modifier;
import org.eclipse.che.ide.ext.java.jdt.core.dom.QualifiedName;
import org.eclipse.che.ide.ext.java.jdt.core.dom.SimpleName;
import org.eclipse.che.ide.ext.java.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.che.ide.ext.java.jdt.core.dom.Statement;
import org.eclipse.che.ide.ext.java.jdt.core.dom.StructuralPropertyDescriptor;
import org.eclipse.che.ide.ext.java.jdt.core.dom.TagElement;
import org.eclipse.che.ide.ext.java.jdt.core.dom.TextElement;
import org.eclipse.che.ide.ext.java.jdt.core.dom.Type;
import org.eclipse.che.ide.ext.java.jdt.core.dom.TypeDeclaration;
import org.eclipse.che.ide.ext.java.jdt.core.dom.VariableDeclarationExpression;
import org.eclipse.che.ide.ext.java.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.che.ide.ext.java.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.che.ide.ext.java.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.che.ide.ext.java.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.che.ide.ext.java.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext;
import org.eclipse.che.ide.ext.java.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.che.ide.ext.java.jdt.internal.corext.codemanipulation.ASTResolving;
import org.eclipse.che.ide.ext.java.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext;
import org.eclipse.che.ide.ext.java.jdt.internal.corext.dom.ASTNodeFactory;
import org.eclipse.che.ide.ext.java.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.che.ide.ext.java.jdt.internal.corext.dom.Bindings;
import org.eclipse.che.ide.ext.java.jdt.internal.corext.dom.LinkedNodeFinder;
import org.eclipse.che.ide.ext.java.jdt.internal.text.correction.JavadocTagsSubProcessor;
import org.eclipse.che.ide.ext.java.jdt.text.Document;
import org.eclipse.che.ide.runtime.Assert;
import org.eclipse.che.ide.runtime.CoreException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
public class NewVariableCorrectionProposal 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 NewVariableCorrectionProposal(String label, int variableKind, SimpleName node, ITypeBinding senderBinding,
int relevance, Document document, Images image) {
super(label, null, relevance, document, image);
if (senderBinding == null) {
Assert.isTrue(variableKind == PARAM || variableKind == LOCAL);
} else {
Assert.isTrue(Bindings.isDeclarationBinding(senderBinding));
}
fVariableKind = variableKind;
fOriginalNode = node;
fSenderBinding = senderBinding;
}
@Override
protected ASTRewrite getRewrite() throws CoreException {
CompilationUnit cu = ASTResolving.findParentCompilationUnit(fOriginalNode);
switch (fVariableKind) {
case PARAM:
return doAddParam(cu);
case FIELD:
case CONST_FIELD:
return doAddField(cu);
case LOCAL:
return doAddLocal(cu);
case ENUM_CONST:
return doAddEnumConst(cu);
default:
throw new IllegalArgumentException("Unsupported variable kind: " + fVariableKind); //$NON-NLS-1$
}
}
private ASTRewrite doAddParam(CompilationUnit cu) {
AST ast = cu.getAST();
SimpleName node = fOriginalNode;
BodyDeclaration decl = ASTResolving.findParentBodyDeclaration(node);
if (decl instanceof MethodDeclaration) {
MethodDeclaration methodDeclaration = (MethodDeclaration)decl;
ASTRewrite rewrite = ASTRewrite.create(ast);
ImportRewrite imports = createImportRewrite((CompilationUnit)decl.getRoot());
ImportRewriteContext importRewriteContext = new ContextSensitiveImportRewriteContext(decl, imports);
SingleVariableDeclaration newDecl = ast.newSingleVariableDeclaration();
newDecl.setType(evaluateVariableType(ast, imports, importRewriteContext, methodDeclaration.resolveBinding()));
newDecl.setName(ast.newSimpleName(node.getIdentifier()));
ListRewrite listRewriter = rewrite.getListRewrite(decl, MethodDeclaration.PARAMETERS_PROPERTY);
listRewriter.insertLast(newDecl, null);
// addLinkedPosition(rewrite.track(node), true, KEY_NAME);
// add javadoc tag
Javadoc javadoc = methodDeclaration.getJavadoc();
if (javadoc != null) {
HashSet<String> leadingNames = new HashSet<String>();
for (Iterator<SingleVariableDeclaration> iter = methodDeclaration.parameters().iterator(); iter.hasNext(); ) {
SingleVariableDeclaration curr = 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), false, KEY_NAME);
// addLinkedPosition(rewrite.track(commentStart), false, "comment_start"); //$NON-NLS-1$
ListRewrite tagsRewriter = rewrite.getListRewrite(javadoc, Javadoc.TAGS_PROPERTY);
JavadocTagsSubProcessor.insertTag(tagsRewriter, newTagElement, leadingNames);
}
// addLinkedPosition(rewrite.track(newDecl.getType()), false, KEY_TYPE);
// addLinkedPosition(rewrite.track(newDecl.getName()), false, KEY_NAME);
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<Expression> 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(CompilationUnit cu) {
AST ast = cu.getAST();
Block body;
BodyDeclaration decl = ASTResolving.findParentBodyDeclaration(fOriginalNode);
IBinding targetContext = null;
if (decl instanceof MethodDeclaration) {
body = (((MethodDeclaration)decl).getBody());
targetContext = ((MethodDeclaration)decl).resolveBinding();
} else if (decl instanceof Initializer) {
body = (((Initializer)decl).getBody());
targetContext = Bindings.getBindingOfParentType(decl);
} else {
return null;
}
ASTRewrite rewrite = ASTRewrite.create(ast);
ImportRewrite imports = createImportRewrite((CompilationUnit)decl.getRoot());
ImportRewriteContext importRewriteContext = new ContextSensitiveImportRewriteContext(decl, imports);
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, importRewriteContext, 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, importRewriteContext, 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, importRewriteContext, 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<? extends ASTNode> 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(Block body) {
SimpleName[] names = LinkedNodeFinder.findByProblems(body, fOriginalNode);
if (names == null) {
return new SimpleName[]{fOriginalNode};
}
if (names.length > 1) {
Arrays.sort(names, new Comparator<SimpleName>() {
public int compare(SimpleName s1, SimpleName s2) {
return s1.getStartPosition() - s2.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) {
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(CompilationUnit astRoot) {
SimpleName node = fOriginalNode;
boolean isInDifferentCU = false;
ASTNode newTypeDecl = astRoot.findDeclaringNode(fSenderBinding);
if (newTypeDecl == null) {
astRoot = ASTResolving.createQuickFixAST(document);
newTypeDecl = astRoot.findDeclaringNode(fSenderBinding.getKey());
isInDifferentCU = true;
}
ImportRewrite imports = createImportRewrite(astRoot);
ImportRewriteContext importRewriteContext =
new ContextSensitiveImportRewriteContext(ASTResolving.findParentBodyDeclaration(node), imports);
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, importRewriteContext, 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<BodyDeclaration> decls = (List<BodyDeclaration>)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<BodyDeclaration> decls, FieldDeclaration newDecl, int maxOffset) {
if (maxOffset != -1) {
for (int i = decls.size() - 1; i >= 0; i--) {
BodyDeclaration curr = 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, ImportRewriteContext importRewriteContext,
IBinding targetContext) {
if (fOriginalNode.getParent() instanceof MethodInvocation) {
MethodInvocation parent = (MethodInvocation)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, importRewriteContext);
}
}
}
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;
}
private ASTRewrite doAddEnumConst(CompilationUnit astRoot) {
SimpleName node = fOriginalNode;
ASTNode newTypeDecl = astRoot.findDeclaringNode(fSenderBinding);
if (newTypeDecl == null) {
astRoot = ASTResolving.createQuickFixAST(document);
newTypeDecl = astRoot.findDeclaringNode(fSenderBinding.getKey());
}
if (newTypeDecl != null) {
AST ast = newTypeDecl.getAST();
ASTRewrite rewrite = ASTRewrite.create(ast);
EnumConstantDeclaration constDecl = ast.newEnumConstantDeclaration();
constDecl.setName(ast.newSimpleName(node.getIdentifier()));
ListRewrite listRewriter = rewrite.getListRewrite(newTypeDecl, EnumDeclaration.ENUM_CONSTANTS_PROPERTY);
listRewriter.insertLast(constDecl, null);
// addLinkedPosition(rewrite.track(constDecl.getName()), false, KEY_NAME);
return rewrite;
}
return null;
}
/**
* Returns the variable kind.
*
* @return the variable kind
*/
public int getVariableKind() {
return fVariableKind;
}
}