blob: 5f7320ee35894cf8c66715a4e26c62e22c5a3077 [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 java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;
import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.refactoring.RefactoringStatus;
import org.eclipse.jdt.internal.compiler.IProblem;
import org.eclipse.jdt.internal.compiler.ast.*;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
import org.eclipse.jdt.internal.compiler.lookup.MethodScope;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
import org.eclipse.jdt.internal.compiler.util.CharOperation;
import org.eclipse.jdt.internal.core.refactoring.ASTEndVisitAdapter;
import org.eclipse.jdt.internal.core.refactoring.Assert;
import org.eclipse.jdt.internal.core.refactoring.TextUtilities;
import org.eclipse.jdt.internal.core.util.HackFinder;
/**
* Checks whether the source range denoted by <code>start</code> and <code>end</code>
* selects a set of statements.
*/
/* package */ class StatementAnalyzer extends ASTEndVisitAdapter {
static final int UNDEFINED= 0;
static final int BEFORE= 1;
static final int SELECTED= 2;
static final int AFTER= 3;
// The selection's start and end position
private IBuffer fBuffer;
private Selection fSelection;
// internal state.
private int fMode;
private int fLastEnd;
private int fCheckIntersectStart= -1;
private Statement fFirstSelectedStatement;
private Statement fLastSelectedStatement;
private boolean fIsCompleteStatementRange;
private AbstractMethodDeclaration fEnclosingMethod;
// private BlockScope fEnclosingScope;
// private BlockScope fInnerScope;
private RefactoringStatus fStatus= new RefactoringStatus();
private LocalVariableAnalyzer fLocalVariableAnalyzer;
private LocalTypeAnalyzer fLocalTypeAnalyzer;
// Handling label and branch statements.
private Stack fImplicitBranchTargets= new Stack();
private List fLabeledStatements= new ArrayList(2);
public StatementAnalyzer(IBuffer buffer, int start, int length) {
// System.out.println("Start: " + start + " length: " + length);
fBuffer= buffer;
Assert.isTrue(fBuffer != null);
fSelection= new Selection(start, start + length - 1);
fLocalVariableAnalyzer= new LocalVariableAnalyzer(this);
fLocalTypeAnalyzer= new LocalTypeAnalyzer();
}
/**
* Checks if the refactoring can be activated.
*/
public void checkActivation(RefactoringStatus status) {
if (fEnclosingMethod == null || fLastSelectedStatement == null) {
status.addFatalError("TextSelection doesn't mark a text range that can be extracted");
} else {
if (!fIsCompleteStatementRange)
status.addError("TextSelection doesn't completely cover a set of statements");
}
status.merge(fStatus);
fLocalVariableAnalyzer.checkActivation(status);
fLocalTypeAnalyzer.checkActivation(status);
}
/**
* Returns the local variable analyzer used by this statement analyzer.
*/
public LocalVariableAnalyzer getLocalVariableAnalyzer() {
return fLocalVariableAnalyzer;
}
/**
* Returns the method that encloses the text selection. Returns <code>null</code>
* is the text selection isn't enclosed by a method or is the text selection doesn't
* mark a valid set of statements.
* @return the method that encloses the text selection.
*/
public AbstractMethodDeclaration getEnclosingMethod() {
return fEnclosingMethod;
}
/**
* Returns <code>true</code> if the given AST node is selected in the text editor.
* Otherwise <code>false</code> is returned.
* @return whether or not the given AST node is selected in the editor.
*/
public boolean isSelected(AstNode node) {
return fSelection.start <= node.sourceStart && node.sourceEnd <= fSelection.end;
}
/**
* Returns the ending position of the last selected statement.
*/
public int getLastSelectedStatementEnd() {
return fLastSelectedStatement.sourceEnd;
}
private void reset() {
fMode= UNDEFINED;
fFirstSelectedStatement= null;
fLastSelectedStatement= null;
fEnclosingMethod= null;
fStatus= new RefactoringStatus();
fIsCompleteStatementRange= false;
fCheckIntersectStart= -1;
}
private boolean visitStatement(Statement statement, BlockScope scope) {
boolean result= true;
switch(fMode) {
case UNDEFINED:
result= false;
break;
case BEFORE:
if (fLastEnd < fSelection.start) {
if (fSelection.covers(statement)) {
startFound(statement);
fIsCompleteStatementRange= true;
} else {
fCheckIntersectStart= -1;
}
}
break;
case SELECTED:
/*
if (fCheckIntersectStart != -1) {
if (fSelection.intersects(fCheckIntersectStart, statement.sourceStart - 1)) {
reset();
fLastEnd= Integer.MAX_VALUE;
}
} else
*/
if (fSelection.endsIn(statement)) { // Selection ends in the middle of a statement
fMode= AFTER;
fLastSelectedStatement= statement;
fIsCompleteStatementRange= false;
} else if (statement.sourceEnd > fSelection.end) {
fMode= AFTER;
} else {
fLastSelectedStatement= statement;
}
break;
case AFTER:
break;
}
trackLastEnd(statement);
return result;
}
private void startFound(Statement statement) {
fMode= SELECTED;
fFirstSelectedStatement= statement;
fLastSelectedStatement= statement;
}
private void trackLastEnd(Statement statement) {
if (statement.sourceEnd > fLastEnd)
fLastEnd= statement.sourceEnd;
}
private boolean visitAbstractMethodDeclaration(AbstractMethodDeclaration node, Scope scope) {
// node.bodyStart is the first character after the {
// node.bodyEnd is the last character before the }
HackFinder.fixMeSoon("1GCSJPZ: ITPJCORE:WIN2000 - AbstractMethodDeclaration: bodyStart body End inconsistent");
node.bodyEnd= node.bodyEnd - 1;
boolean result= fSelection.enclosedBy(node);
if (result) {
reset();
fEnclosingMethod= node;
fMode= BEFORE;
}
return result;
}
private boolean visitLocalTypeDeclaration(TypeDeclaration declaration, BlockScope scope) {
boolean result= visitStatement(declaration, scope);
fLocalTypeAnalyzer.visitLocalTypeDeclaration(declaration, scope, fMode);
return result;
}
private boolean visitTypeReference(TypeReference reference, BlockScope scope) {
fLocalTypeAnalyzer.visitTypeReference(reference, scope, fMode);
return false;
}
private boolean visitImplicitBranchTarget(Statement statement, BlockScope scope) {
fImplicitBranchTargets.push(statement);
return visitStatement(statement, scope);
}
private void endVisitImplicitBranchTarget(Statement statement, BlockScope scope) {
fImplicitBranchTargets.pop();
}
private boolean visitBranchStatement(BranchStatement statement, BlockScope scope, String name) {
boolean result= visitStatement(statement, scope);
Statement target= findTarget(statement);
String label= "label"; //* new String(statement.label);
if (target != null) {
if (isSelected(target)) {
if (fMode != SELECTED)
fStatus.addError("Selected block contains a " + name + " target but not all corresponding " + name + " statements are selected");
} else {
if (fMode == SELECTED)
fStatus.addError("Selected block contains a " + name + " statement but the corresponding " + name + " target isn't selected");
}
} else {
fStatus.addFatalError("Can not find break target");
}
return result;
}
private Statement findTarget(BranchStatement statement) {
if (statement.label == null)
return (Statement)fImplicitBranchTargets.peek();
char[] label= statement.label;
for (Iterator iter= fLabeledStatements.iterator(); iter.hasNext(); ) {
LabeledStatement ls= (LabeledStatement)iter.next();
if (CharOperation.equals(label, ls.label))
return ls;
}
return null;
}
private void endVisitCompoundStatement(AstNode node, Scope scope) {
int realEnd= TextUtilities.indexOfNextStatementCharacter(fBuffer, node.sourceEnd + 1) - 1;
if (realEnd < 0 || fSelection.intersects(node.sourceStart, realEnd)) {
reset();
fLastEnd= Integer.MAX_VALUE;
}
fLastEnd= node.sourceEnd;
}
//---- Problem management -----------------------------------------------------
public void acceptProblem(IProblem problem) {
}
//---- Compilation Unit -------------------------------------------------------
public boolean visit(CompilationUnitDeclaration compilationUnitDeclaration, CompilationUnitScope scope) {
return fSelection.enclosedBy(compilationUnitDeclaration);
}
public boolean visit(ImportReference importRef, CompilationUnitScope scope) {
return false;
}
public boolean visit(TypeDeclaration typeDeclaration, CompilationUnitScope scope) {
return fSelection.enclosedBy(typeDeclaration);
}
//---- Type -------------------------------------------------------------------
public boolean visit(Clinit clinit, ClassScope scope) {
return fSelection.enclosedBy(clinit);
}
public boolean visit(TypeDeclaration typeDeclaration, ClassScope scope) {
return fSelection.enclosedBy(typeDeclaration);
}
public boolean visit(MemberTypeDeclaration memberTypeDeclaration, ClassScope scope) {
return fSelection.enclosedBy(memberTypeDeclaration);
}
public boolean visit(FieldDeclaration fieldDeclaration, MethodScope scope) {
return false;
}
public boolean visit(Initializer initializer, MethodScope scope) {
return false;
}
public boolean visit(ConstructorDeclaration constructorDeclaration, ClassScope scope) {
return visitAbstractMethodDeclaration(constructorDeclaration, scope);
}
public boolean visit(MethodDeclaration methodDeclaration, ClassScope scope) {
return visitAbstractMethodDeclaration(methodDeclaration, scope);
}
public boolean visit(SingleTypeReference singleTypeReference, ClassScope scope) {
return false;
}
public boolean visit(QualifiedTypeReference qualifiedTypeReference, ClassScope scope) {
return false;
}
public boolean visit(ArrayTypeReference arrayTypeReference, ClassScope scope) {
return false;
}
public boolean visit(ArrayQualifiedTypeReference arrayQualifiedTypeReference, ClassScope scope) {
return false;
}
//---- Methods ----------------------------------------------------------------
public boolean visit(LocalTypeDeclaration localTypeDeclaration, MethodScope scope) {
return visitLocalTypeDeclaration(localTypeDeclaration, scope);
}
public boolean visit(Argument argument, BlockScope scope) {
return false;
}
//---- Methods / Block --------------------------------------------------------
public boolean visit(TypeDeclaration typeDeclaration, BlockScope scope) {
return visitLocalTypeDeclaration(typeDeclaration, scope);
}
public boolean visit(AnonymousLocalTypeDeclaration anonymousTypeDeclaration, BlockScope scope) {
return visitStatement(anonymousTypeDeclaration, scope);
}
public boolean visit(LocalDeclaration localDeclaration, BlockScope scope) {
return visitStatement(localDeclaration, scope);
}
public boolean visit(FieldReference fieldReference, BlockScope scope) {
return false;
}
public boolean visit(ArrayReference arrayReference, BlockScope scope) {
return false;
}
public boolean visit(ArrayTypeReference arrayTypeReference, BlockScope scope) {
return visitTypeReference(arrayTypeReference, scope);
}
public boolean visit(ArrayQualifiedTypeReference arrayQualifiedTypeReference, BlockScope scope) {
return visitTypeReference(arrayQualifiedTypeReference, scope);
}
public boolean visit(SingleTypeReference singleTypeReference, BlockScope scope) {
return visitTypeReference(singleTypeReference, scope);
}
public boolean visit(QualifiedTypeReference qualifiedTypeReference, BlockScope scope) {
return visitTypeReference(qualifiedTypeReference, scope);
}
public boolean visit(QualifiedNameReference qualifiedNameReference, BlockScope scope) {
boolean result= visitStatement(qualifiedNameReference, scope);
if (result) {
fLocalVariableAnalyzer.visit(qualifiedNameReference, scope, fMode);
}
return result;
}
public boolean visit(QualifiedSuperReference qualifiedSuperReference, BlockScope scope) {
return false;
}
public boolean visit(QualifiedThisReference qualifiedThisReference, BlockScope scope) {
return false;
}
public boolean visit(SingleNameReference singleNameReference, BlockScope scope) {
boolean result= visitStatement(singleNameReference, scope);
if (result) {
fLocalVariableAnalyzer.visit(singleNameReference, scope, fMode);
fLocalTypeAnalyzer.visit(singleNameReference, scope, fMode);
}
return result;
}
public boolean visit(SuperReference superReference, BlockScope scope) {
return false;
}
public boolean visit(ThisReference thisReference, BlockScope scope) {
return false;
}
//---- Statements -------------------------------------------------------------
public boolean visit(AllocationExpression allocationExpression, BlockScope scope) {
return visitStatement(allocationExpression, scope);
}
public boolean visit(AND_AND_Expression and_and_Expression, BlockScope scope) {
return visitStatement(and_and_Expression, scope);
}
public boolean visit(ArrayAllocationExpression arrayAllocationExpression, BlockScope scope) {
return visitStatement(arrayAllocationExpression, scope);
}
public boolean visit(ArrayInitializer arrayInitializer, BlockScope scope) {
return visitStatement(arrayInitializer, scope);
}
public boolean visit(Assignment assignment, BlockScope scope) {
boolean result= visitStatement(assignment, scope);
fLocalVariableAnalyzer.visitAssignment(assignment, scope, fMode);
return result;
}
public boolean visit(BinaryExpression binaryExpression, BlockScope scope) {
return visitStatement(binaryExpression, scope);
}
public boolean visit(Block block, BlockScope scope) {
boolean result= visitStatement(block, scope);
if (result && fSelection.end >= block.sourceStart) {
fLastEnd= block.sourceStart;
}
return result;
}
public void endVisit(Block block, BlockScope scope) {
if (fSelection.intersects(block)) {
reset();
fLastEnd= Integer.MAX_VALUE;
} else {
fLastEnd= block.sourceEnd;
}
}
public boolean visit(Break breakStatement, BlockScope scope) {
HackFinder.fixMeSoon("1GCU7OH: ITPJCORE:WIN2000 - Break.sourceEnd contains tailing comments");
if (breakStatement.label == null) {
breakStatement.sourceEnd= breakStatement.sourceStart + "break".length() - 1;
}
return visitBranchStatement(breakStatement, scope, "break");
}
public boolean visit(Case caseStatement, BlockScope scope) {
return visitStatement(caseStatement, scope);
}
public boolean visit(CastExpression castExpression, BlockScope scope) {
return visitStatement(castExpression, scope);
}
public boolean visit(CharLiteral charLiteral, BlockScope scope) {
return visitStatement(charLiteral, scope);
}
public boolean visit(ClassLiteralAccess classLiteral, BlockScope scope) {
return visitStatement(classLiteral, scope);
}
public boolean visit(CompoundAssignment compoundAssignment, BlockScope scope) {
boolean result= visitStatement(compoundAssignment, scope);
fLocalVariableAnalyzer.visitAssignment(compoundAssignment, scope, fMode);
return result;
}
public boolean visit(ConditionalExpression conditionalExpression, BlockScope scope) {
return visitStatement(conditionalExpression, scope);
}
public boolean visit(Continue continueStatement, BlockScope scope) {
HackFinder.fixMeSoon("1GCU7OH: ITPJCORE:WIN2000 - Break.sourceEnd contains tailing comments");
if (continueStatement.label == null) {
continueStatement.sourceEnd= continueStatement.sourceStart + "continue".length() - 1;
}
return visitBranchStatement(continueStatement, scope, "continue");
}
public boolean visit(DefaultCase defaultCaseStatement, BlockScope scope) {
return visitStatement(defaultCaseStatement, scope);
}
public boolean visit(DoStatement doStatement, BlockScope scope) {
boolean result= visitImplicitBranchTarget(doStatement, scope);
if (result && fSelection.end >= doStatement.sourceStart) {
// skip the do.
fLastEnd= doStatement.sourceStart + 1;
}
return result;
}
public void endVisit(DoStatement doStatement, BlockScope scope) {
endVisitImplicitBranchTarget(doStatement, scope);
endVisitCompoundStatement(doStatement, scope);
}
public boolean visit(DoubleLiteral doubleLiteral, BlockScope scope) {
return visitStatement(doubleLiteral, scope);
}
public boolean visit(EqualExpression equalExpression, BlockScope scope) {
return visitStatement(equalExpression, scope);
}
public boolean visit(ExplicitConstructorCall explicitConstructor, BlockScope scope) {
return visitStatement(explicitConstructor, scope);
}
public boolean visit(ExtendedStringLiteral extendedStringLiteral, BlockScope scope) {
return visitStatement(extendedStringLiteral, scope);
}
public boolean visit(FalseLiteral falseLiteral, BlockScope scope) {
return visitStatement(falseLiteral, scope);
}
public boolean visit(FloatLiteral floatLiteral, BlockScope scope) {
return visitStatement(floatLiteral, scope);
}
public boolean visit(ForStatement forStatement, BlockScope scope) {
boolean result= visitImplicitBranchTarget(forStatement, scope);
// forStatement.sourceEnd includes the statement's action. Since the
// selection can be the statements body adjust last end if so.
if (result && fSelection.end >= forStatement.sourceStart) {
int start= forStatement.sourceStart;
if (forStatement.increments != null) {
start= forStatement.increments[forStatement.increments.length - 1].sourceEnd;
} else if (forStatement.condition != null) {
start= forStatement.condition.sourceEnd;
} else if (forStatement.initializations != null) {
start= forStatement.initializations[forStatement.initializations.length - 1].sourceEnd;
}
fLastEnd= TextUtilities.indexOf(fBuffer, start + 1, ')');
}
return result;
}
public void endVisit(ForStatement forStatement, BlockScope scope) {
endVisitImplicitBranchTarget(forStatement, scope);
endVisitCompoundStatement(forStatement, scope);
}
public boolean visit(IfStatement ifStatement, BlockScope scope) {
return visitStatement(ifStatement, scope);
}
public boolean visit(InstanceOfExpression instanceOfExpression, BlockScope scope) {
return visitStatement(instanceOfExpression, scope);
}
public boolean visit(IntLiteral intLiteral, BlockScope scope) {
return visitStatement(intLiteral, scope);
}
public boolean visit(LabeledStatement labeledStatement, BlockScope scope) {
fLabeledStatements.add(labeledStatement);
return visitStatement(labeledStatement, scope);
}
public boolean visit(LongLiteral longLiteral, BlockScope scope) {
return visitStatement(longLiteral, scope);
}
public boolean visit(MessageSend messageSend, BlockScope scope) {
return visitStatement(messageSend, scope);
}
public boolean visit(NullLiteral nullLiteral, BlockScope scope) {
return visitStatement(nullLiteral, scope);
}
public boolean visit(OR_OR_Expression or_or_Expression, BlockScope scope) {
return visitStatement(or_or_Expression, scope);
}
public boolean visit(PostfixExpression postfixExpression, BlockScope scope) {
boolean result= visitStatement(postfixExpression, scope);
fLocalVariableAnalyzer.visitPostfixPrefixExpression(postfixExpression, scope, fMode);
return result;
}
public boolean visit(PrefixExpression prefixExpression, BlockScope scope) {
boolean result= visitStatement(prefixExpression, scope);
fLocalVariableAnalyzer.visitPostfixPrefixExpression(prefixExpression, scope, fMode);
return result;
}
public boolean visit(QualifiedAllocationExpression qualifiedAllocationExpression, BlockScope scope) {
return visitStatement(qualifiedAllocationExpression, scope);
}
public boolean visit(ReturnStatement returnStatement, BlockScope scope) {
boolean result= visitStatement(returnStatement, scope);
if (fMode == SELECTED)
fStatus.addFatalError("Selected block contains a return statement");
return result;
}
public boolean visit(StringLiteral stringLiteral, BlockScope scope) {
return visitStatement(stringLiteral, scope);
}
public boolean visit(SwitchStatement switchStatement, BlockScope scope) {
return visitImplicitBranchTarget(switchStatement, scope);
}
public void endVisit(SwitchStatement switchStatement, BlockScope scope) {
endVisitImplicitBranchTarget(switchStatement, scope);
}
public boolean visit(SynchronizedStatement synchronizedStatement, BlockScope scope) {
return visitStatement(synchronizedStatement, scope);
}
public boolean visit(ThrowStatement throwStatement, BlockScope scope) {
return visitStatement(throwStatement, scope);
}
public boolean visit(TrueLiteral trueLiteral, BlockScope scope) {
return visitStatement(trueLiteral, scope);
}
public boolean visit(TryStatement tryStatement, BlockScope scope) {
return visitStatement(tryStatement, scope);
}
public boolean visit(UnaryExpression unaryExpression, BlockScope scope) {
return visitStatement(unaryExpression, scope);
}
public boolean visit(WhileStatement whileStatement, BlockScope scope) {
boolean result= visitImplicitBranchTarget(whileStatement, scope);
if (result && fSelection.end >= whileStatement.sourceStart) {
int start= whileStatement.sourceStart;
if (whileStatement.condition != null) {
start= whileStatement.condition.sourceEnd;
}
fLastEnd= TextUtilities.indexOf(fBuffer, start, ')');
}
return result;
}
public void endVisit(WhileStatement whileStatement, BlockScope scope) {
endVisitImplicitBranchTarget(whileStatement, scope);
endVisitCompoundStatement(whileStatement, scope);
}
}