blob: d80780723819e12d8f8d8fd355af58b3ab44179e [file] [log] [blame]
/*
* Licensed Materials - Property of IBM,
* WebSphere Studio Workbench
* (c) Copyright IBM Corp 1999, 2000, 2001
*/
package org.eclipse.jdt.core.refactoring.code;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.refactoring.IChange;
import org.eclipse.jdt.core.refactoring.Refactoring;
import org.eclipse.jdt.core.refactoring.RefactoringStatus;
import org.eclipse.jdt.core.refactoring.text.ITextBuffer;
import org.eclipse.jdt.core.refactoring.text.ITextBufferChange;
import org.eclipse.jdt.core.refactoring.text.ITextBufferChangeCreator;
import org.eclipse.jdt.core.refactoring.text.SimpleReplaceTextChange;
import org.eclipse.jdt.core.refactoring.text.SimpleTextChange;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.env.IConstants;
import org.eclipse.jdt.internal.core.CompilationUnit;
import org.eclipse.jdt.internal.core.refactoring.Assert;
import org.eclipse.jdt.internal.core.refactoring.Checks;
import org.eclipse.jdt.internal.core.refactoring.ExtendedBuffer;
import org.eclipse.jdt.internal.core.refactoring.TextUtilities;
import org.eclipse.jdt.internal.core.util.HackFinder;
/**
* Extracts a method in a compilation unit based on a text selection range.
* [ToDo:
* - Exceptions (waiting for 1GBWRMY)
* - break statements (waiting for 1GBWRMY)
* ]
* <p>
* <bf>NOTE:<bf> This class/interface is part of an interim API that is still under development
* and expected to change significantly before reaching stability. It is being made available at
* this early stage to solicit feedback from pioneering adopters on the understanding that any
* code that uses this API will almost certainly be broken (repeatedly) as the API evolves.</p>
*/
public class ExtractMethodRefactoring extends Refactoring{
private int fTabWidth;
private ICompilationUnit fCUnit;
private ITextBufferChangeCreator fTextBufferChangeCreator;
private int fSelectionStart;
private int fSelectionLength;
private int fSelectionEnd;
private StatementAnalyzer fStatementAnalyzer;
private ExtendedBuffer fBuffer;
private String fMethodName;
private int fMethodFlags= IConstants.AccProtected;
/* (non-Javadoc)
* Method declared in IRefactoring
*/
public String getName(){
return "Extract Method";
}
/**
* Creates a new extract method refactoring.
*
* @param cu the compilation unit which is going to be modified.
* @param accessor a callback object to access the source this refactoring is working on.
*/
public ExtractMethodRefactoring(ICompilationUnit cu, ITextBufferChangeCreator creator,
int selectionStart, int selectionLength) {
fCUnit= cu;
Assert.isNotNull(fCUnit);
fTextBufferChangeCreator= creator;
Assert.isNotNull(fTextBufferChangeCreator);
fMethodName= "extracted";
fSelectionStart= selectionStart;
fSelectionLength= selectionLength;
fSelectionEnd= fSelectionStart + fSelectionLength - 1;
}
/**
* 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 checkActivation(IProgressMonitor pm) {
try {
pm.beginTask("Checking text selection", 1);
RefactoringStatus result= new RefactoringStatus();
if (fSelectionStart < 0 || fSelectionLength == 0)
return mergeTextSelectionStatus(result);
try {
fBuffer= new ExtendedBuffer(fCUnit.getBuffer());
fStatementAnalyzer= new StatementAnalyzer(fBuffer, fSelectionStart, fSelectionLength);
HackFinder.fixMeSoon("1GA9VPL: ITPJUI:WIN2000 - ICompilationUnit should provide method accept(Visitor)");
((CompilationUnit)fCUnit).accept(fStatementAnalyzer);
} catch (JavaModelException e) {
result.addFatalError(e.getStatus().getMessage());
return result;
}
fStatementAnalyzer.checkActivation(result);
return result;
} finally {
pm.worked(1);
pm.done();
}
}
/**
* 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;
}
/**
* Checks if the refactoring can work on the values provided by the refactoring
* client. The client defined value for the extract method refactoring is the
* new method name.
*/
public RefactoringStatus checkMethodName() {
return Checks.checkMethodName(fMethodName);
}
/* (non-Javadoc)
* Method declared in Refactoring
*/
public RefactoringStatus checkInput(IProgressMonitor pm) throws JavaModelException {
RefactoringStatus result= null;
AbstractMethodDeclaration node= fStatementAnalyzer.getEnclosingMethod();
if (node != null) {
pm.beginTask("Checking new method name", 4);
result= Checks.checkMethodName(fMethodName);
pm.worked(1);
IMethod method= (IMethod)fCUnit.getElementAt(node.sourceStart);
IType type= method.getDeclaringType();
LocalVariableAnalyzer localAnalyzer= fStatementAnalyzer.getLocalVariableAnalyzer();
String[] params= localAnalyzer.getParameterTypes();
result.merge(Checks.checkMethodInType(type, fMethodName, params, false));
pm.worked(1);
result.merge(Checks.checkMethodInHierarchy(new SubProgressMonitor(pm, 1),
type, fMethodName, params, false, fMethodFlags));
pm.worked(1);
pm.done();
} else {
result= new RefactoringStatus();
}
return result;
}
/**
* Creates the actual change object.
*
* @param pm progress monitor to report progress
* @return the change object.
* @see IChange
*/
public IChange createChange(IProgressMonitor pm) throws JavaModelException {
if (fMethodName == null)
return null;
ITextBufferChange result= fTextBufferChangeCreator.create("Extract Method", fCUnit);
AbstractMethodDeclaration method= fStatementAnalyzer.getEnclosingMethod();
final int start= method.declarationSourceStart;
final int end= method.declarationSourceEnd;
final int insertPosition= end + 1;
// Inserting the new method
result.addSimpleTextChange(new SimpleReplaceTextChange("Extracted method", insertPosition) {
public SimpleTextChange[] adjust(ITextBuffer buffer) {
int line= buffer.getLineOfOffset(start);
String delimiter= buffer.getLineDelimiter(line);
int indent= TextUtilities.getIndent(buffer.getLineContentOfOffset(start), fTabWidth);
setText(computeNewMethod(buffer, line, TextUtilities.createIndentString(indent), delimiter));
return null;
}
});
// Replacing the old statements with the new method call.
result.addSimpleTextChange(new SimpleReplaceTextChange("Changed method", fSelectionStart, fSelectionLength, null) {
public SimpleTextChange[] adjust(ITextBuffer buffer) {
String delimiter= buffer.getLineDelimiter(buffer.getLineOfOffset(start));
int indent= TextUtilities.getIndent(buffer.getLineContentOfOffset(start), fTabWidth);
setText(computeCall(buffer, delimiter));
return null;
}
});
return result;
}
private RefactoringStatus mergeTextSelectionStatus(RefactoringStatus status) {
status.addFatalError("TextSelection doesn't mark a set of statements");
return status;
}
private String computeNewMethod(ITextBuffer buffer, int lineNumber, String indent, String delimiter) {
LocalVariableAnalyzer localAnalyzer= fStatementAnalyzer.getLocalVariableAnalyzer();
StringBuffer result= new StringBuffer();
result.append(delimiter);
if (insertNewLineAfterMethodBody(buffer, lineNumber))
result.append(delimiter);
result.append(indent);
result.append("protected ");
result.append(fStatementAnalyzer.getSignature(fMethodName));
result.append(" {");
result.append(delimiter);
result.append(computeSource(buffer, indent + '\t', delimiter));
result.append(indent);
result.append("}");
return result.toString();
}
private boolean insertNewLineAfterMethodBody(ITextBuffer buffer, int lineNumber) {
String line= buffer.getLineContent(lineNumber + 1);
if (line != null) {
return TextUtilities.containsOnlyWhiteSpaces(line);
}
return true;
}
private String computeSource(ITextBuffer buffer, String indent, String delimiter) {
LocalVariableAnalyzer localAnalyzer= fStatementAnalyzer.getLocalVariableAnalyzer();
final String EMPTY_LINE= "";
StringBuffer result= new StringBuffer();
String[] lines= buffer.convertIntoLines(fSelectionStart, fSelectionLength);
// Format the first line with the right indent.
String firstLine= buffer.getLineContentOfOffset(fSelectionStart);
int firstLineIndent= TextUtilities.getIndent(firstLine, fTabWidth);
if (lines.length > 0)
lines[0]= TextUtilities.createIndentString(firstLineIndent) + TextUtilities.removeLeadingIndents(lines[0], fTabWidth);
// Compute the minimal indent.
int minIndent= Integer.MAX_VALUE;
for (int i= 0; i < lines.length; i++) {
String line= lines[i];
if (line.length() == 0 && i + 1 == lines.length) {
lines[i]= null;
} else if (!TextUtilities.containsOnlyWhiteSpaces(lines[i])) {
minIndent= Math.min(TextUtilities.getIndent(lines[i], fTabWidth), minIndent);
} else {
lines[i]= EMPTY_LINE;
}
}
// Remove the indent.
if (minIndent > 0) {
for (int i= 0; i < lines.length; i++) {
String line= lines[i];
if (line != null && line != EMPTY_LINE)
lines[i]= TextUtilities.removeIndent(minIndent, line, fTabWidth);
}
}
// Reformat and add to buffer
boolean isFirstLine= true;
for (int i= 0; i < lines.length; i++) {
String line= lines[i];
if (line != null) {
if (!isFirstLine)
result.append(delimiter);
else
isFirstLine= false;
result.append(indent);
result.append(line);
}
}
if (sourceNeedsSemicolon())
result.append(";");
result.append(delimiter);
String returnStatement= localAnalyzer.getReturnStatement();
if (returnStatement != null) {
result.append(indent);
result.append(returnStatement);
result.append(delimiter);
}
return result.toString();
}
private String computeCall(ITextBuffer buffer, String delimiter) {
String[] lines= buffer.convertIntoLines(fSelectionStart, fSelectionLength);
int indent= TextUtilities.getIndent(lines[0], fTabWidth);
StringBuffer result= new StringBuffer(TextUtilities.createIndentString(indent));
LocalVariableAnalyzer localAnalyzer= fStatementAnalyzer.getLocalVariableAnalyzer();
String local= localAnalyzer.getLocalDeclaration();
if (local != null) {
result.append(local);
result.append(delimiter);
String firstLine= buffer.getLineContentOfOffset(fSelectionStart);
indent= TextUtilities.getIndent(firstLine, fTabWidth);
result.append(TextUtilities.createIndentString(indent));
}
result.append(localAnalyzer.getCall(fMethodName));
if (callNeedsSemicolon())
result.append(";");
if (endsSelectionWithLineDelimiter(lines))
result.append(delimiter);
return result.toString();
}
private boolean endsSelectionWithLineDelimiter(String[] lines) {
return lines[lines.length - 1].length() == 0;
}
private boolean callNeedsSemicolon() {
int start= fStatementAnalyzer.getLastSelectedStatementEnd() + 1;
int length= fSelectionLength - (start - fSelectionStart);
if (length <= 0)
return true;
if (fBuffer.indexOf(';', start, length) != -1) {
return true;
} else {
return !fStatementAnalyzer.getNeedsSemicolon();
}
}
private boolean sourceNeedsSemicolon() {
int start= fStatementAnalyzer.getLastSelectedStatementEnd() + 1;
int length= fSelectionLength - (start - fSelectionStart);
if (length <= 0)
return false;
if (fBuffer.indexOf(';', start, length) != -1) {
return false;
} else {
return fStatementAnalyzer.getNeedsSemicolon();
}
}
}