| /******************************************************************************* |
| * 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; |
| } |
| |
| } |