blob: bf5295d49a811eb95b40b97be6f86d4033561dc5 [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.corext.refactoring.code;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.text.edits.TextEditGroup;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.ITextFileBufferManager;
import org.eclipse.core.resources.IFile;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.TextFileChange;
import org.eclipse.ltk.core.refactoring.participants.RefactoringArguments;
import org.eclipse.ltk.core.refactoring.participants.ResourceChangeChecker;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.FieldAccess;
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.MethodInvocation;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.TypeParameter;
import org.eclipse.jdt.core.dom.VariableDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
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.ASTFlattener;
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.BodyDeclarationRewrite;
import org.eclipse.jdt.internal.corext.dom.LinkedNodeFinder;
import org.eclipse.jdt.internal.corext.dom.Selection;
import org.eclipse.jdt.internal.corext.dom.StatementRewrite;
import org.eclipse.jdt.internal.corext.refactoring.Checks;
import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringArguments;
import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringDescriptor;
import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringDescriptorComment;
import org.eclipse.jdt.internal.corext.refactoring.ParameterInfo;
import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages;
import org.eclipse.jdt.internal.corext.refactoring.changes.CompilationUnitChange;
import org.eclipse.jdt.internal.corext.refactoring.changes.CompilationUnitDescriptorChange;
import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringASTParser;
import org.eclipse.jdt.internal.corext.refactoring.util.ResourceUtil;
import org.eclipse.jdt.internal.corext.refactoring.util.SelectionAwareSourceRangeComputer;
import org.eclipse.jdt.internal.corext.util.JdtFlags;
import org.eclipse.jdt.internal.corext.util.Messages;
import org.eclipse.jdt.ui.CodeGeneration;
import org.eclipse.jdt.ui.JavaElementLabels;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.viewsupport.BindingLabelProvider;
/**
* Extracts a method in a compilation unit based on a text selection range.
*/
public class ExtractMethodRefactoring extends ScriptableRefactoring {
private static final String ID_EXTRACT_METHOD= "org.eclipse.jdt.ui.extract.method"; //$NON-NLS-1$
private static final String ATTRIBUTE_VISIBILITY= "visibility"; //$NON-NLS-1$
private static final String ATTRIBUTE_DESTINATION= "destination"; //$NON-NLS-1$
private static final String ATTRIBUTE_COMMENTS= "comments"; //$NON-NLS-1$
private static final String ATTRIBUTE_REPLACE= "replace"; //$NON-NLS-1$
private static final String ATTRIBUTE_EXCEPTIONS= "exceptions"; //$NON-NLS-1$
private ICompilationUnit fCUnit;
private CompilationUnit fRoot;
private ImportRewrite fImportRewriter;
private int fSelectionStart;
private int fSelectionLength;
private AST fAST;
private ASTRewrite fRewriter;
private IDocument fDocument;
private ExtractMethodAnalyzer fAnalyzer;
private int fVisibility;
private String fMethodName;
private boolean fThrowRuntimeExceptions;
private List fParameterInfos;
private Set fUsedNames;
private boolean fGenerateJavadoc;
private boolean fReplaceDuplicates;
private SnippetFinder.Match[] fDuplicates;
private int fDestinationIndex= 0;
// either of type TypeDeclaration or AnonymousClassDeclaration
private ASTNode fDestination;
// either of type TypeDeclaration or AnonymousClassDeclaration
private ASTNode[] fDestinations;
private static final String EMPTY= ""; //$NON-NLS-1$
private static class UsedNamesCollector extends ASTVisitor {
private Set result= new HashSet();
private Set fIgnore= new HashSet();
public static Set perform(ASTNode[] nodes) {
UsedNamesCollector collector= new UsedNamesCollector();
for (int i= 0; i < nodes.length; i++) {
nodes[i].accept(collector);
}
return collector.result;
}
public boolean visit(FieldAccess node) {
Expression exp= node.getExpression();
if (exp != null)
fIgnore.add(node.getName());
return true;
}
public void endVisit(FieldAccess node) {
fIgnore.remove(node.getName());
}
public boolean visit(MethodInvocation node) {
Expression exp= node.getExpression();
if (exp != null)
fIgnore.add(node.getName());
return true;
}
public void endVisit(MethodInvocation node) {
fIgnore.remove(node.getName());
}
public boolean visit(QualifiedName node) {
fIgnore.add(node.getName());
return true;
}
public void endVisit(QualifiedName node) {
fIgnore.remove(node.getName());
}
public boolean visit(SimpleName node) {
if (!fIgnore.contains(node))
result.add(node.getIdentifier());
return true;
}
public boolean visit(TypeDeclaration node) {
return visitType(node);
}
public boolean visit(AnnotationTypeDeclaration node) {
return visitType(node);
}
public boolean visit(EnumDeclaration node) {
return visitType(node);
}
private boolean visitType(AbstractTypeDeclaration node) {
result.add(node.getName().getIdentifier());
// don't dive into type declaration since they open a new
// context.
return false;
}
}
/**
* Creates a new extract method refactoring
* @param unit the compilation unit, or <code>null</code> if invoked by scripting
* @param selectionStart
* @param selectionLength
*/
public ExtractMethodRefactoring(ICompilationUnit unit, int selectionStart, int selectionLength) throws CoreException {
fCUnit= unit;
fMethodName= "extracted"; //$NON-NLS-1$
fSelectionStart= selectionStart;
fSelectionLength= selectionLength;
fVisibility= -1;
if (unit != null)
initialize(unit);
}
private void initialize(ICompilationUnit cu) throws CoreException {
fImportRewriter= StubUtility.createImportRewrite(cu, true);
}
public String getName() {
return RefactoringCoreMessages.ExtractMethodRefactoring_name;
}
/**
* Checks if the refactoring can be activated. Activation typically means, if a
* corresponding menu entry can be added to the UI.
*
* @param pm a progress monitor to report progress during activation checking.
* @return the refactoring status describing the result of the activation check.
*/
public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException {
RefactoringStatus result= new RefactoringStatus();
pm.beginTask("", 100); //$NON-NLS-1$
if (fSelectionStart < 0 || fSelectionLength == 0)
return mergeTextSelectionStatus(result);
IFile[] changedFiles= ResourceUtil.getFiles(new ICompilationUnit[]{fCUnit});
result.merge(Checks.validateModifiesFiles(changedFiles, getValidationContext()));
if (result.hasFatalError())
return result;
result.merge(ResourceChangeChecker.checkFilesToBeChanged(changedFiles, new SubProgressMonitor(pm, 1)));
fRoot= RefactoringASTParser.parseWithASTProvider(fCUnit, true, new SubProgressMonitor(pm, 99));
fAST= fRoot.getAST();
fRoot.accept(createVisitor());
result.merge(fAnalyzer.checkInitialConditions(fImportRewriter));
if (result.hasFatalError())
return result;
if (fVisibility == -1) {
setVisibility(Modifier.PRIVATE);
}
initializeParameterInfos();
initializeUsedNames();
initializeDuplicates();
initializeDestinations();
return result;
}
private ASTVisitor createVisitor() throws JavaModelException {
fAnalyzer= new ExtractMethodAnalyzer(fCUnit, Selection.createFromStartLength(fSelectionStart, fSelectionLength));
return fAnalyzer;
}
/**
* Sets the method name to be used for the extracted method.
*
* @param name the new method name.
*/
public void setMethodName(String name) {
fMethodName= name;
}
/**
* Returns the method name to be used for the extracted method.
* @return the method name to be used for the extracted method.
*/
public String getMethodName() {
return fMethodName;
}
/**
* Sets the visibility of the new method.
*
* @param visibility the visibility of the new method. Valid values are
* "public", "protected", "", and "private"
*/
public void setVisibility(int visibility) {
fVisibility= visibility;
}
/**
* Returns the visibility of the new method.
*
* @return the visibility of the new method
*/
public int getVisibility() {
return fVisibility;
}
/**
* Returns the parameter infos.
* @return a list of parameter infos.
*/
public List getParameterInfos() {
return fParameterInfos;
}
/**
* Sets whether the new method signature throws runtime exceptions.
*
* @param throwRuntimeExceptions flag indicating if the new method
* throws runtime exceptions
*/
public void setThrowRuntimeExceptions(boolean throwRuntimeExceptions) {
fThrowRuntimeExceptions= throwRuntimeExceptions;
}
/**
* Checks if the new method name is a valid method name. This method doesn't
* check if a method with the same name already exists in the hierarchy. This
* check is done in <code>checkInput</code> since it is expensive.
*/
public RefactoringStatus checkMethodName() {
return Checks.checkMethodName(fMethodName);
}
public ASTNode[] getDestinations() {
return fDestinations;
}
public void setDestination(int index) {
fDestination= fDestinations[index];
fDestinationIndex= index;
}
/**
* Checks if the parameter names are valid.
*/
public RefactoringStatus checkParameterNames() {
RefactoringStatus result= new RefactoringStatus();
for (Iterator iter= fParameterInfos.iterator(); iter.hasNext();) {
ParameterInfo parameter= (ParameterInfo)iter.next();
result.merge(Checks.checkIdentifier(parameter.getNewName()));
for (Iterator others= fParameterInfos.iterator(); others.hasNext();) {
ParameterInfo other= (ParameterInfo) others.next();
if (parameter != other && other.getNewName().equals(parameter.getNewName())) {
result.addError(Messages.format(
RefactoringCoreMessages.ExtractMethodRefactoring_error_sameParameter,
other.getNewName()));
return result;
}
}
if (parameter.isRenamed() && fUsedNames.contains(parameter.getNewName())) {
result.addError(Messages.format(
RefactoringCoreMessages.ExtractMethodRefactoring_error_nameInUse,
parameter.getNewName()));
return result;
}
}
return result;
}
/**
* Checks if varargs are ordered correctly.
*/
public RefactoringStatus checkVarargOrder() {
for (Iterator iter= fParameterInfos.iterator(); iter.hasNext();) {
ParameterInfo info= (ParameterInfo)iter.next();
if (info.isOldVarargs() && iter.hasNext()) {
return RefactoringStatus.createFatalErrorStatus(Messages.format(
RefactoringCoreMessages.ExtractMethodRefactoring_error_vararg_ordering,
info.getOldName()));
}
}
return new RefactoringStatus();
}
/**
* Returns the names already in use in the selected statements/expressions.
*
* @return names already in use.
*/
public Set getUsedNames() {
return fUsedNames;
}
/* (non-Javadoc)
* Method declared in Refactoring
*/
public RefactoringStatus checkFinalConditions(IProgressMonitor pm) throws CoreException {
pm.beginTask(RefactoringCoreMessages.ExtractMethodRefactoring_checking_new_name, 2);
pm.subTask(EMPTY);
RefactoringStatus result= checkMethodName();
result.merge(checkParameterNames());
result.merge(checkVarargOrder());
pm.worked(1);
if (pm.isCanceled())
throw new OperationCanceledException();
BodyDeclaration node= fAnalyzer.getEnclosingBodyDeclaration();
if (node != null) {
fAnalyzer.checkInput(result, fMethodName, fAST);
pm.worked(1);
}
pm.done();
return result;
}
/* (non-Javadoc)
* Method declared in IRefactoring
*/
public Change createChange(IProgressMonitor pm) throws CoreException {
if (fMethodName == null)
return null;
pm.beginTask("", 2); //$NON-NLS-1$
fAnalyzer.aboutToCreateChange();
BodyDeclaration declaration= fAnalyzer.getEnclosingBodyDeclaration();
fRewriter= ASTRewrite.create(declaration.getAST());
final Map arguments= new HashMap();
String project= null;
IJavaProject javaProject= fCUnit.getJavaProject();
if (javaProject != null)
project= javaProject.getElementName();
ITypeBinding type= null;
if (fDestination instanceof AbstractTypeDeclaration) {
final AbstractTypeDeclaration decl= (AbstractTypeDeclaration) fDestination;
type= decl.resolveBinding();
} else if (fDestination instanceof AnonymousClassDeclaration) {
final AnonymousClassDeclaration decl= (AnonymousClassDeclaration) fDestination;
type= decl.resolveBinding();
}
IMethodBinding method= null;
final BodyDeclaration enclosing= fAnalyzer.getEnclosingBodyDeclaration();
if (enclosing instanceof MethodDeclaration) {
final MethodDeclaration node= (MethodDeclaration) enclosing;
method= node.resolveBinding();
}
final int flags= RefactoringDescriptor.STRUCTURAL_CHANGE | JavaRefactoringDescriptor.JAR_REFACTORABLE | JavaRefactoringDescriptor.JAR_SOURCE_ATTACHMENT;
final String description= Messages.format(RefactoringCoreMessages.ExtractMethodRefactoring_descriptor_description_short, fMethodName);
final String label= method != null ? BindingLabelProvider.getBindingLabel(method, JavaElementLabels.ALL_FULLY_QUALIFIED) : '{' + JavaElementLabels.ELLIPSIS_STRING + '}';
final String header= Messages.format(RefactoringCoreMessages.ExtractMethodRefactoring_descriptor_description, new String[] { getSignature(), label, BindingLabelProvider.getBindingLabel(type, JavaElementLabels.ALL_FULLY_QUALIFIED)});
final JavaRefactoringDescriptorComment comment= new JavaRefactoringDescriptorComment(this, header);
comment.addSetting(Messages.format(RefactoringCoreMessages.ExtractMethodRefactoring_name_pattern, fMethodName));
comment.addSetting(Messages.format(RefactoringCoreMessages.ExtractMethodRefactoring_destination_pattern, BindingLabelProvider.getBindingLabel(type, JavaElementLabels.ALL_FULLY_QUALIFIED)));
String visibility= JdtFlags.getVisibilityString(fVisibility);
if ("".equals(visibility)) //$NON-NLS-1$
visibility= RefactoringCoreMessages.ExtractMethodRefactoring_default_visibility;
comment.addSetting(Messages.format(RefactoringCoreMessages.ExtractMethodRefactoring_visibility_pattern, visibility));
if (fThrowRuntimeExceptions)
comment.addSetting(RefactoringCoreMessages.ExtractMethodRefactoring_declare_thrown_exceptions);
if (fReplaceDuplicates)
comment.addSetting(RefactoringCoreMessages.ExtractMethodRefactoring_replace_occurrences);
if (fGenerateJavadoc)
comment.addSetting(RefactoringCoreMessages.ExtractMethodRefactoring_generate_comment);
final JavaRefactoringDescriptor descriptor= new JavaRefactoringDescriptor(ID_EXTRACT_METHOD, project, description, comment.asString(), arguments, flags);
arguments.put(JavaRefactoringDescriptor.ATTRIBUTE_INPUT, descriptor.elementToHandle(fCUnit));
arguments.put(JavaRefactoringDescriptor.ATTRIBUTE_NAME, fMethodName);
arguments.put(JavaRefactoringDescriptor.ATTRIBUTE_SELECTION, new Integer(fSelectionStart).toString() + " " + new Integer(fSelectionLength).toString()); //$NON-NLS-1$
arguments.put(ATTRIBUTE_VISIBILITY, new Integer(fVisibility).toString());
arguments.put(ATTRIBUTE_DESTINATION, new Integer(fDestinationIndex).toString());
arguments.put(ATTRIBUTE_EXCEPTIONS, Boolean.valueOf(fThrowRuntimeExceptions).toString());
arguments.put(ATTRIBUTE_COMMENTS, Boolean.valueOf(fGenerateJavadoc).toString());
arguments.put(ATTRIBUTE_REPLACE, Boolean.valueOf(fReplaceDuplicates).toString());
final CompilationUnitDescriptorChange result= new CompilationUnitDescriptorChange(descriptor, RefactoringCoreMessages.ExtractMethodRefactoring_change_name, fCUnit);
result.setSaveMode(TextFileChange.KEEP_SAVE_STATE);
MultiTextEdit root= new MultiTextEdit();
result.setEdit(root);
// This is cheap since the compilation unit is already open in a editor.
IPath path= ((IFile)fCUnit.getPrimary().getResource()).getFullPath();
ITextFileBufferManager bufferManager= FileBuffers.getTextFileBufferManager();
try {
bufferManager.connect(path, new SubProgressMonitor(pm, 1));
fDocument= bufferManager.getTextFileBuffer(path).getDocument();
ASTNode[] selectedNodes= fAnalyzer.getSelectedNodes();
fRewriter.setTargetSourceRangeComputer(new SelectionAwareSourceRangeComputer(selectedNodes,
fDocument, fSelectionStart, fSelectionLength));
TextEditGroup substituteDesc= new TextEditGroup(Messages.format(RefactoringCoreMessages.ExtractMethodRefactoring_substitute_with_call, fMethodName));
result.addTextEditGroup(substituteDesc);
MethodDeclaration mm= createNewMethod(fMethodName, true, selectedNodes, fDocument.getLineDelimiter(0), substituteDesc);
TextEditGroup insertDesc= new TextEditGroup(Messages.format(RefactoringCoreMessages.ExtractMethodRefactoring_add_method, fMethodName));
result.addTextEditGroup(insertDesc);
if (fDestination == fDestinations[0]) {
ChildListPropertyDescriptor desc= (ChildListPropertyDescriptor)declaration.getLocationInParent();
ListRewrite container= fRewriter.getListRewrite(declaration.getParent(), desc);
container.insertAfter(mm, declaration, insertDesc);
} else {
BodyDeclarationRewrite container= BodyDeclarationRewrite.create(fRewriter, fDestination);
container.insert(mm, insertDesc);
}
replaceDuplicates(result);
if (fImportRewriter.hasRecordedChanges()) {
TextEdit edit= fImportRewriter.rewriteImports(null);
root.addChild(edit);
result.addTextEditGroup(new TextEditGroup(
RefactoringCoreMessages.ExtractMethodRefactoring_organize_imports,
new TextEdit[] {edit}
));
}
root.addChild(fRewriter.rewriteAST(fDocument, fCUnit.getJavaProject().getOptions(true)));
} catch (BadLocationException e) {
throw new CoreException(new Status(IStatus.ERROR, JavaPlugin.getPluginId(), IStatus.ERROR,
e.getMessage(), e));
} finally {
bufferManager.disconnect(path, new SubProgressMonitor(pm, 1));
pm.done();
}
return result;
}
/**
* Returns the signature of the new method.
*
* @return the signature of the extracted method
*/
public String getSignature() {
return getSignature(fMethodName);
}
/**
* Returns the signature of the new method.
*
* @param methodName the method name used for the new method
* @return the signature of the extracted method
*/
public String getSignature(String methodName) {
MethodDeclaration method= null;
try {
method= createNewMethod(methodName, false, null, StubUtility.getLineDelimiterUsed(fCUnit), null);
} catch (CoreException cannotHappen) {
// we don't generate a code block and java comments.
Assert.isTrue(false);
} catch (BadLocationException e) {
// we don't generate a code block and java comments.
Assert.isTrue(false);
}
method.setBody(fAST.newBlock());
ASTFlattener flattener= new ASTFlattener() {
public boolean visit(Block node) {
return false;
}
};
method.accept(flattener);
return flattener.getResult();
}
/**
* Returns the number of duplicate code snippets found.
*
* @return the number of duplicate code fragments
*/
public int getNumberOfDuplicates() {
if (fDuplicates == null)
return 0;
int result=0;
for (int i= 0; i < fDuplicates.length; i++) {
if (!fDuplicates[i].isMethodBody())
result++;
}
return result;
}
public boolean getReplaceDuplicates() {
return fReplaceDuplicates;
}
public void setReplaceDuplicates(boolean replace) {
fReplaceDuplicates= replace;
}
public void setGenerateJavadoc(boolean generate) {
fGenerateJavadoc= generate;
}
public boolean getGenerateJavadoc() {
return fGenerateJavadoc;
}
//---- Helper methods ------------------------------------------------------------------------
private void initializeParameterInfos() {
IVariableBinding[] arguments= fAnalyzer.getArguments();
fParameterInfos= new ArrayList(arguments.length);
ASTNode root= fAnalyzer.getEnclosingBodyDeclaration();
ParameterInfo vararg= null;
for (int i= 0; i < arguments.length; i++) {
IVariableBinding argument= arguments[i];
if (argument == null)
continue;
VariableDeclaration declaration= ASTNodes.findVariableDeclaration(argument, root);
boolean isVarargs= declaration instanceof SingleVariableDeclaration
? ((SingleVariableDeclaration)declaration).isVarargs()
: false;
ParameterInfo info= new ParameterInfo(argument, getType(declaration, isVarargs), argument.getName(), i);
if (isVarargs) {
vararg= info;
} else {
fParameterInfos.add(info);
}
}
if (vararg != null) {
fParameterInfos.add(vararg);
}
}
private void initializeUsedNames() {
fUsedNames= UsedNamesCollector.perform(fAnalyzer.getSelectedNodes());
for (Iterator iter= fParameterInfos.iterator(); iter.hasNext();) {
ParameterInfo parameter= (ParameterInfo)iter.next();
fUsedNames.remove(parameter.getOldName());
}
}
private void initializeDuplicates() {
ASTNode start= fAnalyzer.getEnclosingBodyDeclaration();
while(!(start instanceof AbstractTypeDeclaration) && !(start instanceof AnonymousClassDeclaration)) {
start= start.getParent();
}
fDuplicates= SnippetFinder.perform(start, fAnalyzer.getSelectedNodes());
fReplaceDuplicates= fDuplicates.length > 0 && ! fAnalyzer.isLiteralNodeSelected();
}
private void initializeDestinations() {
List result= new ArrayList();
BodyDeclaration decl= fAnalyzer.getEnclosingBodyDeclaration();
ASTNode current= getNextParent(decl);
result.add(current);
if (decl instanceof MethodDeclaration) {
ITypeBinding binding= ASTNodes.getEnclosingType(current);
ASTNode next= getNextParent(current);
while (next != null && binding != null && binding.isNested() && !Modifier.isStatic(binding.getDeclaredModifiers())) {
result.add(next);
current= next;
binding= ASTNodes.getEnclosingType(current);
next= getNextParent(next);
}
}
fDestinations= (ASTNode[])result.toArray(new ASTNode[result.size()]);
fDestination= fDestinations[fDestinationIndex];
}
private ASTNode getNextParent(ASTNode node) {
do {
node= node.getParent();
} while (node != null && !((node instanceof AbstractTypeDeclaration) || (node instanceof AnonymousClassDeclaration)));
return node;
}
private RefactoringStatus mergeTextSelectionStatus(RefactoringStatus status) {
status.addFatalError(RefactoringCoreMessages.ExtractMethodRefactoring_no_set_of_statements);
return status;
}
private String getType(VariableDeclaration declaration, boolean isVarargs) {
String type= ASTNodes.asString(ASTNodeFactory.newType(declaration.getAST(), declaration));
if (isVarargs)
return type + ParameterInfo.ELLIPSIS;
else
return type;
}
//---- Code generation -----------------------------------------------------------------------
private ASTNode[] createCallNodes(SnippetFinder.Match duplicate) {
List result= new ArrayList(2);
IVariableBinding[] locals= fAnalyzer.getCallerLocals();
for (int i= 0; i < locals.length; i++) {
result.add(createDeclaration(locals[i], null));
}
MethodInvocation invocation= fAST.newMethodInvocation();
invocation.setName(fAST.newSimpleName(fMethodName));
List arguments= invocation.arguments();
for (int i= 0; i < fParameterInfos.size(); i++) {
ParameterInfo parameter= ((ParameterInfo)fParameterInfos.get(i));
arguments.add(ASTNodeFactory.newName(fAST, getMappedName(duplicate, parameter)));
}
ASTNode call;
int returnKind= fAnalyzer.getReturnKind();
switch (returnKind) {
case ExtractMethodAnalyzer.ACCESS_TO_LOCAL:
IVariableBinding binding= fAnalyzer.getReturnLocal();
if (binding != null) {
VariableDeclarationStatement decl= createDeclaration(getMappedBinding(duplicate, binding), invocation);
call= decl;
} else {
Assignment assignment= fAST.newAssignment();
assignment.setLeftHandSide(ASTNodeFactory.newName(fAST,
getMappedBinding(duplicate, fAnalyzer.getReturnValue()).getName()));
assignment.setRightHandSide(invocation);
call= assignment;
}
break;
case ExtractMethodAnalyzer.RETURN_STATEMENT_VALUE:
ReturnStatement rs= fAST.newReturnStatement();
rs.setExpression(invocation);
call= rs;
break;
default:
call= invocation;
}
if (call instanceof Expression && !fAnalyzer.isExpressionSelected()) {
call= fAST.newExpressionStatement((Expression)call);
}
result.add(call);
// We have a void return statement. The code looks like
// extracted();
// return;
if (returnKind == ExtractMethodAnalyzer.RETURN_STATEMENT_VOID && !fAnalyzer.isLastStatementSelected()) {
result.add(fAST.newReturnStatement());
}
return (ASTNode[])result.toArray(new ASTNode[result.size()]);
}
private IVariableBinding getMappedBinding(SnippetFinder.Match duplicate, IVariableBinding org) {
if (duplicate == null)
return org;
return duplicate.getMappedBinding(org);
}
private String getMappedName(SnippetFinder.Match duplicate, ParameterInfo paramter) {
if (duplicate == null)
return paramter.getOldName();
return duplicate.getMappedName(paramter.getOldBinding()).getIdentifier();
}
private void replaceDuplicates(CompilationUnitChange result) {
int numberOf= getNumberOfDuplicates();
if (numberOf == 0 || !fReplaceDuplicates)
return;
String label= null;
if (numberOf == 1)
label= Messages.format(RefactoringCoreMessages.ExtractMethodRefactoring_duplicates_single, fMethodName);
else
label= Messages.format(RefactoringCoreMessages.ExtractMethodRefactoring_duplicates_multi, fMethodName);
TextEditGroup description= new TextEditGroup(label);
result.addTextEditGroup(description);
for (int d= 0; d < fDuplicates.length; d++) {
SnippetFinder.Match duplicate= fDuplicates[d];
if (!duplicate.isMethodBody()) {
ASTNode[] callNodes= createCallNodes(duplicate);
new StatementRewrite(fRewriter, duplicate.getNodes()).replace(callNodes, description);
}
}
}
private MethodDeclaration createNewMethod(String name, boolean code, ASTNode[] selectedNodes, String lineDelimiter, TextEditGroup substitute) throws CoreException, BadLocationException {
MethodDeclaration result= fAST.newMethodDeclaration();
int modifiers= fVisibility;
if (Modifier.isStatic(fAnalyzer.getEnclosingBodyDeclaration().getModifiers()) || fAnalyzer.getForceStatic()) {
modifiers|= Modifier.STATIC;
}
ITypeBinding[] typeVariables= computeLocalTypeVariables();
List typeParameters= result.typeParameters();
for (int i= 0; i < typeVariables.length; i++) {
TypeParameter parameter= fAST.newTypeParameter();
parameter.setName(fAST.newSimpleName(typeVariables[i].getName()));
typeParameters.add(parameter);
}
result.modifiers().addAll(ASTNodeFactory.newModifiers(fAST, modifiers));
result.setReturnType2((Type)ASTNode.copySubtree(fAST, fAnalyzer.getReturnType()));
result.setName(fAST.newSimpleName(name));
List parameters= result.parameters();
for (int i= 0; i < fParameterInfos.size(); i++) {
ParameterInfo info= (ParameterInfo)fParameterInfos.get(i);
VariableDeclaration infoDecl= getVariableDeclaration(info);
SingleVariableDeclaration parameter= fAST.newSingleVariableDeclaration();
parameter.modifiers().addAll(ASTNodeFactory.newModifiers(fAST, ASTNodes.getModifiers(infoDecl)));
parameter.setType(ASTNodeFactory.newType(fAST, infoDecl));
parameter.setName(fAST.newSimpleName(info.getNewName()));
parameter.setVarargs(info.isNewVarargs());
parameters.add(parameter);
}
List exceptions= result.thrownExceptions();
ITypeBinding[] exceptionTypes= fAnalyzer.getExceptions(fThrowRuntimeExceptions, fAST);
for (int i= 0; i < exceptionTypes.length; i++) {
ITypeBinding exceptionType= exceptionTypes[i];
exceptions.add(ASTNodeFactory.newName(fAST, fImportRewriter.addImport(exceptionType)));
}
if (code) {
result.setBody(createMethodBody(result, selectedNodes, substitute));
if (fGenerateJavadoc) {
AbstractTypeDeclaration enclosingType=
(AbstractTypeDeclaration)ASTNodes.getParent(fAnalyzer.getEnclosingBodyDeclaration(), AbstractTypeDeclaration.class);
String string= CodeGeneration.getMethodComment(fCUnit, enclosingType.getName().getIdentifier(), result, null, lineDelimiter);
if (string != null) {
Javadoc javadoc= (Javadoc)fRewriter.createStringPlaceholder(string, ASTNode.JAVADOC);
result.setJavadoc(javadoc);
}
}
}
return result;
}
private ITypeBinding[] computeLocalTypeVariables() {
List result= new ArrayList(Arrays.asList(fAnalyzer.getTypeVariables()));
for (int i= 0; i < fParameterInfos.size(); i++) {
ParameterInfo info= (ParameterInfo)fParameterInfos.get(i);
processVariable(result, info.getOldBinding());
}
IVariableBinding[] methodLocals= fAnalyzer.getMethodLocals();
for (int i= 0; i < methodLocals.length; i++) {
processVariable(result, methodLocals[i]);
}
return (ITypeBinding[])result.toArray(new ITypeBinding[result.size()]);
}
private void processVariable(List result, IVariableBinding variable) {
if (variable == null)
return;
ITypeBinding binding= variable.getType();
if (binding != null && binding.isParameterizedType()) {
ITypeBinding[] typeArgs= binding.getTypeArguments();
for (int args= 0; args < typeArgs.length; args++) {
ITypeBinding arg= typeArgs[args];
if (arg.isTypeVariable() && !result.contains(arg)) {
ASTNode decl= fRoot.findDeclaringNode(arg);
if (decl != null && decl.getParent() instanceof MethodDeclaration) {
result.add(arg);
}
}
}
}
}
private Block createMethodBody(MethodDeclaration method, ASTNode[] selectedNodes, TextEditGroup substitute) throws BadLocationException, CoreException {
Block result= fAST.newBlock();
ListRewrite statements= fRewriter.getListRewrite(result, Block.STATEMENTS_PROPERTY);
// Locals that are not passed as an arguments since the extracted method only
// writes to them
IVariableBinding[] methodLocals= fAnalyzer.getMethodLocals();
for (int i= 0; i < methodLocals.length; i++) {
if (methodLocals[i] != null) {
result.statements().add(createDeclaration(methodLocals[i], null));
}
}
for (Iterator iter= fParameterInfos.iterator(); iter.hasNext();) {
ParameterInfo parameter= (ParameterInfo)iter.next();
if (parameter.isRenamed()) {
for (int n= 0; n < selectedNodes.length; n++) {
SimpleName[] oldNames= LinkedNodeFinder.findByBinding(selectedNodes[n], parameter.getOldBinding());
for (int i= 0; i < oldNames.length; i++) {
fRewriter.replace(oldNames[i], fAST.newSimpleName(parameter.getNewName()), null);
}
}
}
}
boolean extractsExpression= fAnalyzer.isExpressionSelected();
ASTNode[] callNodes= createCallNodes(null);
ASTNode replacementNode;
if (callNodes.length == 1) {
replacementNode= callNodes[0];
} else {
replacementNode= fRewriter.createGroupNode(callNodes);
}
if (extractsExpression) {
// if we have an expression then only one node is selected.
ITypeBinding binding= fAnalyzer.getExpressionBinding();
if (binding != null && (!binding.isPrimitive() || !"void".equals(binding.getName()))) { //$NON-NLS-1$
ReturnStatement rs= fAST.newReturnStatement();
rs.setExpression((Expression)fRewriter.createMoveTarget(selectedNodes[0]));
statements.insertLast(rs, null);
} else {
ExpressionStatement st= fAST.newExpressionStatement((Expression)fRewriter.createMoveTarget(selectedNodes[0]));
statements.insertLast(st, null);
}
fRewriter.replace(selectedNodes[0], replacementNode, substitute);
} else {
if (selectedNodes.length == 1) {
statements.insertLast(fRewriter.createMoveTarget(selectedNodes[0]), substitute);
fRewriter.replace(selectedNodes[0], replacementNode, substitute);
} else {
ListRewrite source= fRewriter.getListRewrite(
selectedNodes[0].getParent(),
(ChildListPropertyDescriptor)selectedNodes[0].getLocationInParent());
ASTNode toMove= source.createMoveTarget(
selectedNodes[0], selectedNodes[selectedNodes.length - 1],
replacementNode, substitute);
statements.insertLast(toMove, substitute);
}
IVariableBinding returnValue= fAnalyzer.getReturnValue();
if (returnValue != null) {
ReturnStatement rs= fAST.newReturnStatement();
rs.setExpression(fAST.newSimpleName(getName(returnValue)));
statements.insertLast(rs, null);
}
}
return result;
}
private String getName(IVariableBinding binding) {
for (Iterator iter= fParameterInfos.iterator(); iter.hasNext();) {
ParameterInfo info= (ParameterInfo)iter.next();
if (Bindings.equals(binding, info.getOldBinding())) {
return info.getNewName();
}
}
return binding.getName();
}
private VariableDeclaration getVariableDeclaration(ParameterInfo parameter) {
return ASTNodes.findVariableDeclaration(parameter.getOldBinding(), fAnalyzer.getEnclosingBodyDeclaration());
}
private VariableDeclarationStatement createDeclaration(IVariableBinding binding, Expression intilizer) {
VariableDeclaration original= ASTNodes.findVariableDeclaration(binding, fAnalyzer.getEnclosingBodyDeclaration());
VariableDeclarationFragment fragment= fAST.newVariableDeclarationFragment();
fragment.setName((SimpleName)ASTNode.copySubtree(fAST, original.getName()));
fragment.setInitializer(intilizer);
VariableDeclarationStatement result= fAST.newVariableDeclarationStatement(fragment);
result.modifiers().addAll(ASTNode.copySubtrees(fAST, ASTNodes.getModifiers(original)));
result.setType(ASTNodeFactory.newType(fAST, original));
return result;
}
public ICompilationUnit getCompilationUnit() {
return fCUnit;
}
public RefactoringStatus initialize(final RefactoringArguments arguments) {
if (arguments instanceof JavaRefactoringArguments) {
final JavaRefactoringArguments extended= (JavaRefactoringArguments) arguments;
final String selection= extended.getAttribute(JavaRefactoringDescriptor.ATTRIBUTE_SELECTION);
if (selection != null) {
int offset= -1;
int length= -1;
final StringTokenizer tokenizer= new StringTokenizer(selection);
if (tokenizer.hasMoreTokens())
offset= Integer.valueOf(tokenizer.nextToken()).intValue();
if (tokenizer.hasMoreTokens())
length= Integer.valueOf(tokenizer.nextToken()).intValue();
if (offset >= 0 && length >= 0) {
fSelectionStart= offset;
fSelectionLength= length;
} else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_illegal_argument, new Object[] { selection, JavaRefactoringDescriptor.ATTRIBUTE_SELECTION}));
} else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptor.ATTRIBUTE_SELECTION));
final String handle= extended.getAttribute(JavaRefactoringDescriptor.ATTRIBUTE_INPUT);
if (handle != null) {
final IJavaElement element= JavaRefactoringDescriptor.handleToElement(extended.getProject(), handle, false);
if (element == null || !element.exists() || element.getElementType() != IJavaElement.COMPILATION_UNIT)
return createInputFatalStatus(element, ID_EXTRACT_METHOD);
else {
fCUnit= (ICompilationUnit) element;
try {
initialize(fCUnit);
} catch (CoreException exception) {
JavaPlugin.log(exception);
}
}
} else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptor.ATTRIBUTE_INPUT));
final String visibility= extended.getAttribute(ATTRIBUTE_VISIBILITY);
if (visibility != null && !"".equals(visibility)) {//$NON-NLS-1$
int flag= 0;
try {
flag= Integer.parseInt(visibility);
} catch (NumberFormatException exception) {
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_VISIBILITY));
}
fVisibility= flag;
}
final String name= extended.getAttribute(JavaRefactoringDescriptor.ATTRIBUTE_NAME);
if (name != null && !"".equals(name)) //$NON-NLS-1$
fMethodName= name;
else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptor.ATTRIBUTE_NAME));
final String destination= extended.getAttribute(ATTRIBUTE_DESTINATION);
if (destination != null && !"".equals(destination)) {//$NON-NLS-1$
int index= 0;
try {
index= Integer.parseInt(destination);
} catch (NumberFormatException exception) {
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_DESTINATION));
}
fDestinationIndex= index;
}
final String replace= extended.getAttribute(ATTRIBUTE_REPLACE);
if (replace != null) {
fReplaceDuplicates= Boolean.valueOf(replace).booleanValue();
} else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_REPLACE));
final String comments= extended.getAttribute(ATTRIBUTE_COMMENTS);
if (comments != null)
fGenerateJavadoc= Boolean.valueOf(comments).booleanValue();
else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_COMMENTS));
final String exceptions= extended.getAttribute(ATTRIBUTE_EXCEPTIONS);
if (exceptions != null)
fThrowRuntimeExceptions= Boolean.valueOf(exceptions).booleanValue();
else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_EXCEPTIONS));
} else
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.InitializableRefactoring_inacceptable_arguments);
return new RefactoringStatus();
}
}