blob: 37d12132a4bc3d8cd48526bf1902bee8b4fd55cf [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2006 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.jdt.internal.ui.text.correction;
import java.util.ArrayList;
import java.util.HashSet;
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.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.NamingConventions;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ArrayType;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.Javadoc;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.TagElement;
import org.eclipse.jdt.core.dom.TextElement;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility;
import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.Bindings;
import org.eclipse.jdt.internal.corext.dom.LinkedNodeFinder;
import org.eclipse.jdt.internal.corext.dom.ScopeAnalyzer;
public class ChangeMethodSignatureProposal extends LinkedCorrectionProposal {
public static interface ChangeDescription {
}
public static class SwapDescription implements ChangeDescription {
final int index;
public SwapDescription(int index) {
this.index= index;
}
}
public static class RemoveDescription implements ChangeDescription {
}
static class ModifyDescription implements ChangeDescription {
public final String name;
public final ITypeBinding type;
Type resultingParamType;
SimpleName[] resultingParamName;
SimpleName resultingTagArg;
private ModifyDescription(ITypeBinding type, String name) {
this.type= type;
this.name= name;
}
}
public static class EditDescription extends ModifyDescription {
String orginalName;
public EditDescription(ITypeBinding type, String name) {
super(type, name);
}
}
public static class InsertDescription extends ModifyDescription {
public InsertDescription(ITypeBinding type, String name) {
super(type, name);
}
}
private ASTNode fInvocationNode;
private IMethodBinding fSenderBinding;
private ChangeDescription[] fParameterChanges;
private ChangeDescription[] fExceptionChanges;
public ChangeMethodSignatureProposal(String label, ICompilationUnit targetCU, ASTNode invocationNode, IMethodBinding binding, ChangeDescription[] paramChanges, ChangeDescription[] exceptionChanges, int relevance, Image image) {
super(label, targetCU, null, relevance, image);
Assert.isTrue(binding != null && Bindings.isDeclarationBinding(binding));
fInvocationNode= invocationNode;
fSenderBinding= binding;
fParameterChanges= paramChanges;
fExceptionChanges= exceptionChanges;
}
protected ASTRewrite getRewrite() throws CoreException {
CompilationUnit astRoot= (CompilationUnit) fInvocationNode.getRoot();
ASTNode methodDecl= astRoot.findDeclaringNode(fSenderBinding);
ASTNode newMethodDecl= null;
boolean isInDifferentCU;
if (methodDecl != null) {
isInDifferentCU= false;
newMethodDecl= methodDecl;
} else {
isInDifferentCU= true;
astRoot= ASTResolving.createQuickFixAST(getCompilationUnit(), null);
newMethodDecl= astRoot.findDeclaringNode(fSenderBinding.getKey());
}
createImportRewrite(astRoot);
if (newMethodDecl instanceof MethodDeclaration) {
MethodDeclaration decl= (MethodDeclaration) newMethodDecl;
ASTRewrite rewrite= ASTRewrite.create(astRoot.getAST());
if (fParameterChanges != null) {
modifyParameters(rewrite, decl, isInDifferentCU);
}
if (fExceptionChanges != null) {
modifyExceptions(rewrite, decl);
}
return rewrite;
}
return null;
}
private void modifyParameters(ASTRewrite rewrite, MethodDeclaration methodDecl, boolean isInDifferentCU) throws CoreException {
AST ast= methodDecl.getAST();
ArrayList usedNames= new ArrayList();
boolean hasCreatedVariables= false;
IVariableBinding[] declaredFields= fSenderBinding.getDeclaringClass().getDeclaredFields();
for (int i= 0; i < declaredFields.length; i++) { // avoid to take parameter names that are equal to field names
usedNames.add(declaredFields[i].getName());
}
ImportRewrite imports= getImportRewrite();
ListRewrite listRewrite= rewrite.getListRewrite(methodDecl, MethodDeclaration.PARAMETERS_PROPERTY);
List parameters= methodDecl.parameters(); // old parameters
int k= 0; // index over the oldParameters
for (int i= 0; i < fParameterChanges.length; i++) {
ChangeDescription curr= fParameterChanges[i];
if (curr == null) {
SingleVariableDeclaration oldParam= (SingleVariableDeclaration) parameters.get(k);
usedNames.add(oldParam.getName().getIdentifier());
k++;
} else if (curr instanceof InsertDescription) {
InsertDescription desc= (InsertDescription) curr;
SingleVariableDeclaration newNode= ast.newSingleVariableDeclaration();
newNode.setType(imports.addImport(desc.type, ast));
newNode.setName(ast.newSimpleName("x")); //$NON-NLS-1$
// remember to set name later
desc.resultingParamName= new SimpleName[] {newNode.getName()};
desc.resultingParamType= newNode.getType();
hasCreatedVariables= true;
listRewrite.insertAt(newNode, i, null);
Javadoc javadoc= methodDecl.getJavadoc();
if (javadoc != null) {
TagElement newTagElement= ast.newTagElement();
newTagElement.setTagName(TagElement.TAG_PARAM);
SimpleName arg= ast.newSimpleName("x"); //$NON-NLS-1$
newTagElement.fragments().add(arg);
insertTabStop(rewrite, newTagElement.fragments(), "param_tagcomment" + i); //$NON-NLS-1$
insertParamTag(rewrite.getListRewrite(javadoc, Javadoc.TAGS_PROPERTY), parameters, k, newTagElement);
desc.resultingTagArg= arg; // set the name later
} else {
desc.resultingTagArg= null;
}
} else if (curr instanceof RemoveDescription) {
SingleVariableDeclaration decl= (SingleVariableDeclaration) parameters.get(k);
listRewrite.remove(decl, null);
k++;
TagElement tagNode= findParamTag(methodDecl, decl);
if (tagNode != null) {
rewrite.remove(tagNode, null);
}
} else if (curr instanceof EditDescription) {
EditDescription desc= (EditDescription) curr;
Type newType= imports.addImport(desc.type, ast);
SingleVariableDeclaration decl= (SingleVariableDeclaration) parameters.get(k);
rewrite.replace(decl.getType(), newType, null);
IBinding binding= decl.getName().resolveBinding();
if (binding != null) {
SimpleName[] names= LinkedNodeFinder.findByBinding(decl.getRoot(), binding);
SimpleName[] newNames= new SimpleName[names.length];
for (int j= 0; j < names.length; j++) {
SimpleName newName= ast.newSimpleName("x"); //$NON-NLS-1$ // name will be set later
newNames[j]= newName;
rewrite.replace(names[j], newName, null);
}
desc.resultingParamName= newNames;
} else {
SimpleName newName= ast.newSimpleName("x"); //$NON-NLS-1$ // name will be set later
rewrite.replace(decl.getName(), newName, null);
// remember to set name later
desc.resultingParamName= new SimpleName[] {newName};
}
desc.resultingParamType= newType;
desc.orginalName= decl.getName().getIdentifier();
hasCreatedVariables= true;
k++;
TagElement tagNode= findParamTag(methodDecl, decl);
if (tagNode != null) {
List fragments= tagNode.fragments();
if (!fragments.isEmpty()) {
SimpleName arg= ast.newSimpleName("x"); //$NON-NLS-1$
rewrite.replace((ASTNode) fragments.get(0), arg, null);
desc.resultingTagArg= arg;
}
}
} else if (curr instanceof SwapDescription) {
SingleVariableDeclaration decl1= (SingleVariableDeclaration) parameters.get(k);
SingleVariableDeclaration decl2= (SingleVariableDeclaration) parameters.get(((SwapDescription) curr).index);
rewrite.replace(decl1, rewrite.createCopyTarget(decl2), null);
rewrite.replace(decl2, rewrite.createCopyTarget(decl1), null);
usedNames.add(decl1.getName().getIdentifier());
k++;
TagElement tagNode1= findParamTag(methodDecl, decl1);
TagElement tagNode2= findParamTag(methodDecl, decl2);
if (tagNode1 != null && tagNode2 != null) {
rewrite.replace(tagNode1, rewrite.createCopyTarget(tagNode2), null);
rewrite.replace(tagNode2, rewrite.createCopyTarget(tagNode1), null);
}
}
}
if (!hasCreatedVariables) {
return;
}
if (methodDecl.getBody() != null) {
// avoid take a name of a local variable inside
CompilationUnit root= (CompilationUnit) methodDecl.getRoot();
IBinding[] bindings= (new ScopeAnalyzer(root)).getDeclarationsAfter(methodDecl.getBody().getStartPosition(), ScopeAnalyzer.VARIABLES);
for (int i= 0; i < bindings.length; i++) {
usedNames.add(bindings[i].getName());
}
}
fixupNames(rewrite, usedNames, methodDecl, isInDifferentCU);
}
private void fixupNames(ASTRewrite rewrite, ArrayList usedNames, MethodDeclaration methodDecl, boolean isInDifferentCU) {
AST ast= rewrite.getAST();
// set names for new parameters
for (int i= 0; i < fParameterChanges.length; i++) {
ChangeDescription curr= fParameterChanges[i];
if (curr instanceof ModifyDescription) {
ModifyDescription desc= (ModifyDescription) curr;
String typeKey= getParamTypeGroupId(i);
String nameKey= getParamNameGroupId(i);
// collect name suggestions
String favourite= null;
String[] excludedNames= (String[]) usedNames.toArray(new String[usedNames.size()]);
String suggestedName= desc.name;
if (suggestedName != null) {
favourite= StubUtility.suggestArgumentName(getCompilationUnit().getJavaProject(), suggestedName, excludedNames);
addLinkedPositionProposal(nameKey, favourite, null);
}
Type type= desc.resultingParamType;
int dim= 0;
if (type.isArrayType()) {
dim= ((ArrayType) type).getDimensions();
type= ((ArrayType) type).getElementType();
}
if (desc instanceof EditDescription) {
addLinkedPositionProposal(nameKey, ((EditDescription)desc).orginalName, null);
}
String[] suggestedNames= NamingConventions.suggestArgumentNames(getCompilationUnit().getJavaProject(), "", ASTNodes.asString(type), dim, excludedNames); //$NON-NLS-1$
for (int k= 0; k < suggestedNames.length; k++) {
addLinkedPositionProposal(nameKey, suggestedNames[k], null);
}
if (favourite == null) {
favourite= suggestedNames[0];
}
usedNames.add(favourite);
SimpleName[] names= desc.resultingParamName;
for (int j= 0; j < names.length; j++) {
names[j].setIdentifier(favourite);
addLinkedPosition(rewrite.track(names[j]), false, nameKey);
}
addLinkedPosition(rewrite.track(desc.resultingParamType), true, typeKey);
// collect type suggestions
ITypeBinding[] bindings= ASTResolving.getRelaxingTypes(ast, desc.type);
for (int k= 0; k < bindings.length; k++) {
addLinkedPositionProposal(typeKey, bindings[k]);
}
SimpleName tagArg= desc.resultingTagArg;
if (tagArg != null) {
tagArg.setIdentifier(favourite);
addLinkedPosition(rewrite.track(tagArg), false, nameKey);
}
}
}
}
private TagElement findParamTag(MethodDeclaration decl, SingleVariableDeclaration param) {
Javadoc javadoc= decl.getJavadoc();
if (javadoc != null) {
return JavadocTagsSubProcessor.findParamTag(javadoc, param.getName().getIdentifier());
}
return null;
}
private TagElement insertParamTag(ListRewrite tagRewriter, List parameters, int currentIndex, TagElement newTagElement) {
HashSet previousNames= new HashSet();
for (int n = 0; n < currentIndex; n++) {
SingleVariableDeclaration var= (SingleVariableDeclaration) parameters.get(n);
previousNames.add(var.getName().getIdentifier());
}
JavadocTagsSubProcessor.insertTag(tagRewriter, newTagElement, previousNames);
return newTagElement;
}
private void modifyExceptions(ASTRewrite rewrite, MethodDeclaration methodDecl) throws CoreException {
AST ast= methodDecl.getAST();
ImportRewrite imports= getImportRewrite();
ListRewrite listRewrite= rewrite.getListRewrite(methodDecl, MethodDeclaration.THROWN_EXCEPTIONS_PROPERTY);
List exceptions= methodDecl.thrownExceptions(); // old exceptions
int k= 0; // index over the old exceptions
for (int i= 0; i < fExceptionChanges.length; i++) {
ChangeDescription curr= fExceptionChanges[i];
if (curr == null) {
k++;
} else if (curr instanceof InsertDescription) {
InsertDescription desc= (InsertDescription) curr;
String type= imports.addImport(desc.type);
ASTNode newNode= ASTNodeFactory.newName(ast, type);
listRewrite.insertAt(newNode, i, null);
String key= getExceptionTypeGroupId(i);
addLinkedPosition(rewrite.track(newNode), false, key);
Javadoc javadoc= methodDecl.getJavadoc();
if (javadoc != null) {
TagElement newTagElement= ast.newTagElement();
newTagElement.setTagName(TagElement.TAG_THROWS);
ASTNode newRef= ASTNodeFactory.newName(ast, type);
newTagElement.fragments().add(newRef);
insertTabStop(rewrite, newTagElement.fragments(), "throws_tagcomment" + i); //$NON-NLS-1$
insertThrowsTag(rewrite.getListRewrite(javadoc, Javadoc.TAGS_PROPERTY), exceptions, k, newTagElement);
addLinkedPosition(rewrite.track(newRef), false, key);
}
} else if (curr instanceof RemoveDescription) {
Name node= (Name) exceptions.get(k);
listRewrite.remove(node, null);
k++;
TagElement tagNode= findThrowsTag(methodDecl, node);
if (tagNode != null) {
rewrite.remove(tagNode, null);
}
} else if (curr instanceof EditDescription) {
EditDescription desc= (EditDescription) curr;
Name oldNode= (Name) exceptions.get(k);
String type= imports.addImport(desc.type);
ASTNode newNode= ASTNodeFactory.newName(ast, type);
listRewrite.replace(oldNode, newNode, null);
String key= getExceptionTypeGroupId(i);
addLinkedPosition(rewrite.track(newNode), false, key);
k++;
TagElement tagNode= findThrowsTag(methodDecl, oldNode);
if (tagNode != null) {
ASTNode newRef= ASTNodeFactory.newName(ast, type);
rewrite.replace((ASTNode) tagNode.fragments().get(0), newRef, null);
addLinkedPosition(rewrite.track(newRef), false, key);
}
} else if (curr instanceof SwapDescription) {
Name decl1= (Name) exceptions.get(k);
Name decl2= (Name) exceptions.get(((SwapDescription) curr).index);
rewrite.replace(decl1, rewrite.createCopyTarget(decl2), null);
rewrite.replace(decl2, rewrite.createCopyTarget(decl1), null);
k++;
TagElement tagNode1= findThrowsTag(methodDecl, decl1);
TagElement tagNode2= findThrowsTag(methodDecl, decl2);
if (tagNode1 != null && tagNode2 != null) {
rewrite.replace(tagNode1, rewrite.createCopyTarget(tagNode2), null);
rewrite.replace(tagNode2, rewrite.createCopyTarget(tagNode1), null);
}
}
}
}
private void insertTabStop(ASTRewrite rewriter, List fragments, String linkedName) {
TextElement textElement= rewriter.getAST().newTextElement();
textElement.setText(""); //$NON-NLS-1$
fragments.add(textElement);
addLinkedPosition(rewriter.track(textElement), false, linkedName);
}
private TagElement findThrowsTag(MethodDeclaration decl, Name exception) {
Javadoc javadoc= decl.getJavadoc();
if (javadoc != null) {
String name= ASTNodes.getSimpleNameIdentifier(exception);
return JavadocTagsSubProcessor.findThrowsTag(javadoc, name);
}
return null;
}
private TagElement insertThrowsTag(ListRewrite tagRewriter, List exceptions, int currentIndex, TagElement newTagElement) {
HashSet previousNames= new HashSet();
for (int n = 0; n < currentIndex; n++) {
Name curr= (Name) exceptions.get(n);
previousNames.add(ASTNodes.getSimpleNameIdentifier(curr));
}
JavadocTagsSubProcessor.insertTag(tagRewriter, newTagElement, previousNames);
return newTagElement;
}
public String getParamNameGroupId(int idx) {
return "param_name_" + idx; //$NON-NLS-1$
}
public String getParamTypeGroupId(int idx) {
return "param_type_" + idx; //$NON-NLS-1$
}
public String getExceptionTypeGroupId(int idx) {
return "exc_type_" + idx; //$NON-NLS-1$
}
}