blob: 980b71aa50f05d28d96f4a6686f37b130a8ecceb [file] [log] [blame]
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.refactoring.extractMethod;
import com.intellij.codeInsight.PsiEquivalenceUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.controlFlow.*;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.util.containers.HashSet;
import com.intellij.util.containers.IntArrayList;
import java.util.*;
public class ControlFlowWrapper {
private static final Logger LOG = Logger.getInstance("#" + ControlFlowWrapper.class.getName());
private final ControlFlow myControlFlow;
private int myFlowStart;
private int myFlowEnd;
private boolean myGenerateConditionalExit;
private Collection<PsiStatement> myExitStatements;
private PsiStatement myFirstExitStatementCopy;
private IntArrayList myExitPoints;
public ControlFlowWrapper(Project project, PsiElement codeFragment, PsiElement[] elements) throws PrepareFailedException {
try {
myControlFlow =
ControlFlowFactory.getInstance(project).getControlFlow(codeFragment, new LocalsControlFlowPolicy(codeFragment), false, false);
}
catch (AnalysisCanceledException e) {
throw new PrepareFailedException(RefactoringBundle.message("extract.method.control.flow.analysis.failed"), e.getErrorElement());
}
if (LOG.isDebugEnabled()) {
LOG.debug(myControlFlow.toString());
}
myFlowStart = -1;
int index = 0;
while (index < elements.length) {
myFlowStart = myControlFlow.getStartOffset(elements[index]);
if (myFlowStart >= 0) break;
index++;
}
if (myFlowStart < 0) {
// no executable code
myFlowStart = 0;
myFlowEnd = 0;
}
else {
index = elements.length - 1;
while (true) {
myFlowEnd = myControlFlow.getEndOffset(elements[index]);
if (myFlowEnd >= 0) break;
index--;
}
}
if (LOG.isDebugEnabled()) {
LOG.debug("start offset:" + myFlowStart);
LOG.debug("end offset:" + myFlowEnd);
}
}
public PsiStatement getFirstExitStatementCopy() {
return myFirstExitStatementCopy;
}
public Collection<PsiStatement> prepareExitStatements(final PsiElement[] elements) throws ExitStatementsNotSameException {
myExitPoints = new IntArrayList();
myExitStatements = ControlFlowUtil
.findExitPointsAndStatements(myControlFlow, myFlowStart, myFlowEnd, myExitPoints, ControlFlowUtil.DEFAULT_EXIT_STATEMENTS_CLASSES);
if (LOG.isDebugEnabled()) {
LOG.debug("exit points:");
for (int i = 0; i < myExitPoints.size(); i++) {
LOG.debug(" " + myExitPoints.get(i));
}
LOG.debug("exit statements:");
for (PsiStatement exitStatement : myExitStatements) {
LOG.debug(" " + exitStatement);
}
}
if (myExitPoints.isEmpty()) {
// if the fragment never exits assume as if it exits in the end
myExitPoints.add(myControlFlow.getEndOffset(elements[elements.length - 1]));
}
if (myExitPoints.size() != 1) {
areExitStatementsTheSame();
myGenerateConditionalExit = true;
}
return myExitStatements;
}
private void areExitStatementsTheSame() throws ExitStatementsNotSameException {
if (myExitStatements.isEmpty()) {
throw new ExitStatementsNotSameException();
}
PsiStatement first = null;
for (PsiStatement statement : myExitStatements) {
if (first == null) {
first = statement;
continue;
}
if (!PsiEquivalenceUtil.areElementsEquivalent(first, statement)) {
throw new ExitStatementsNotSameException();
}
}
myFirstExitStatementCopy = (PsiStatement)first.copy();
}
public boolean isGenerateConditionalExit() {
return myGenerateConditionalExit;
}
public Collection<PsiStatement> getExitStatements() {
return myExitStatements;
}
public static class ExitStatementsNotSameException extends Exception {}
public PsiVariable[] getOutputVariables() {
PsiVariable[] myOutputVariables = ControlFlowUtil.getOutputVariables(myControlFlow, myFlowStart, myFlowEnd, myExitPoints.toArray());
if (myGenerateConditionalExit) {
//variables declared in selected block used in return statements are to be considered output variables when extracting guard methods
final Set<PsiVariable> outputVariables = new HashSet<PsiVariable>(Arrays.asList(myOutputVariables));
for (PsiStatement statement : myExitStatements) {
statement.accept(new JavaRecursiveElementVisitor() {
@Override
public void visitReferenceExpression(PsiReferenceExpression expression) {
super.visitReferenceExpression(expression);
final PsiElement resolved = expression.resolve();
if (resolved instanceof PsiVariable) {
final PsiVariable variable = (PsiVariable)resolved;
if (isWrittenInside(variable)) {
outputVariables.add(variable);
}
}
}
private boolean isWrittenInside(final PsiVariable variable) {
final List<Instruction> instructions = myControlFlow.getInstructions();
for (int i = myFlowStart; i < myFlowEnd; i++) {
Instruction instruction = instructions.get(i);
if (instruction instanceof WriteVariableInstruction && variable.equals(((WriteVariableInstruction)instruction).variable)) {
return true;
}
}
return false;
}
});
}
myOutputVariables = outputVariables.toArray(new PsiVariable[outputVariables.size()]);
}
Arrays.sort(myOutputVariables, PsiUtil.BY_POSITION);
return myOutputVariables;
}
public boolean isReturnPresentBetween() {
return ControlFlowUtil.returnPresentBetween(myControlFlow, myFlowStart, myFlowEnd);
}
private void removeParametersUsedInExitsOnly(PsiElement codeFragment, List<PsiVariable> inputVariables) {
LocalSearchScope scope = new LocalSearchScope(codeFragment);
Variables:
for (Iterator<PsiVariable> iterator = inputVariables.iterator(); iterator.hasNext();) {
PsiVariable variable = iterator.next();
for (PsiReference ref : ReferencesSearch.search(variable, scope)) {
PsiElement element = ref.getElement();
int elementOffset = myControlFlow.getStartOffset(element);
if (elementOffset >= myFlowStart && elementOffset <= myFlowEnd) {
if (!isInExitStatements(element, myExitStatements)) continue Variables;
}
if (elementOffset == -1) { //references in local/anonymous classes should not be skipped
final PsiClass psiClass = PsiTreeUtil.getParentOfType(element, PsiClass.class);
if (psiClass != null) {
final TextRange textRange = psiClass.getTextRange();
if (myControlFlow.getElement(myFlowStart).getTextOffset() <= textRange.getStartOffset() &&
textRange.getEndOffset() <= myControlFlow.getElement(myFlowEnd).getTextRange().getEndOffset()) {
continue Variables;
}
}
}
}
iterator.remove();
}
}
private static boolean isInExitStatements(PsiElement element, Collection<PsiStatement> exitStatements) {
for (PsiStatement exitStatement : exitStatements) {
if (PsiTreeUtil.isAncestor(exitStatement, element, false)) return true;
}
return false;
}
private boolean needExitStatement(final PsiStatement exitStatement) {
if (exitStatement instanceof PsiContinueStatement) {
//IDEADEV-11748
PsiStatement statement = ((PsiContinueStatement)exitStatement).findContinuedStatement();
if (statement == null) return true;
if (statement instanceof PsiLoopStatement) statement = ((PsiLoopStatement)statement).getBody();
int endOffset = myControlFlow.getEndOffset(statement);
return endOffset > myFlowEnd;
}
return true;
}
public List<PsiVariable> getInputVariables(final PsiElement codeFragment, PsiElement[] elements) {
final List<PsiVariable> inputVariables = ControlFlowUtil.getInputVariables(myControlFlow, myFlowStart, myFlowEnd);
List<PsiVariable> myInputVariables;
if (myGenerateConditionalExit) {
List<PsiVariable> inputVariableList = new ArrayList<PsiVariable>(inputVariables);
removeParametersUsedInExitsOnly(codeFragment, inputVariableList);
myInputVariables = inputVariableList;
}
else {
List<PsiVariable> inputVariableList = new ArrayList<PsiVariable>(inputVariables);
for (Iterator<PsiVariable> iterator = inputVariableList.iterator(); iterator.hasNext(); ) {
PsiVariable variable = iterator.next();
for (PsiElement element : elements) {
if (PsiTreeUtil.isAncestor(element, variable, false)) {
iterator.remove();
break;
}
}
}
myInputVariables = inputVariableList;
}
//varargs variables go last, otherwise order is induced by original ordering
Collections.sort(myInputVariables, new Comparator<PsiVariable>() {
public int compare(final PsiVariable v1, final PsiVariable v2) {
if (v1.getType() instanceof PsiEllipsisType) {
return 1;
}
if (v2.getType() instanceof PsiEllipsisType) {
return -1;
}
return v1.getTextOffset() - v2.getTextOffset();
}
});
return myInputVariables;
}
public PsiStatement getExitStatementCopy(PsiElement returnStatement,
final PsiElement[] elements) {
PsiStatement exitStatementCopy = null;
// replace all exit-statements such as break's or continue's with appropriate return
for (PsiStatement exitStatement : myExitStatements) {
if (exitStatement instanceof PsiReturnStatement) {
if (!myGenerateConditionalExit) continue;
}
else if (exitStatement instanceof PsiBreakStatement) {
PsiStatement statement = ((PsiBreakStatement)exitStatement).findExitedStatement();
if (statement == null) continue;
int startOffset = myControlFlow.getStartOffset(statement);
int endOffset = myControlFlow.getEndOffset(statement);
if (myFlowStart <= startOffset && endOffset <= myFlowEnd) continue;
}
else if (exitStatement instanceof PsiContinueStatement) {
PsiStatement statement = ((PsiContinueStatement)exitStatement).findContinuedStatement();
if (statement == null) continue;
int startOffset = myControlFlow.getStartOffset(statement);
int endOffset = myControlFlow.getEndOffset(statement);
if (myFlowStart <= startOffset && endOffset <= myFlowEnd) continue;
}
else {
LOG.error(String.valueOf(exitStatement));
continue;
}
int index = -1;
for (int j = 0; j < elements.length; j++) {
if (exitStatement.equals(elements[j])) {
index = j;
break;
}
}
if (exitStatementCopy == null) {
if (needExitStatement(exitStatement)) {
exitStatementCopy = (PsiStatement)exitStatement.copy();
}
}
PsiElement result = exitStatement.replace(returnStatement);
if (index >= 0) {
elements[index] = result;
}
}
return exitStatementCopy;
}
public List<PsiVariable> getUsedVariables(int start) {
return getUsedVariables(start, myControlFlow.getSize());
}
public List<PsiVariable> getUsedVariables(int start, int end) {
return ControlFlowUtil.getUsedVariables(myControlFlow, start, end);
}
public Collection<ControlFlowUtil.VariableInfo> getInitializedTwice(int start) {
return ControlFlowUtil.getInitializedTwice(myControlFlow, start, myControlFlow.getSize());
}
public List<PsiVariable> getUsedVariables() {
return getUsedVariables(myFlowEnd);
}
public List<PsiVariable> getUsedVariablesInBody() {
return getUsedVariables(myFlowStart, myFlowEnd);
}
public Collection<ControlFlowUtil.VariableInfo> getInitializedTwice() {
return getInitializedTwice(myFlowEnd);
}
public void setGenerateConditionalExit(boolean generateConditionalExit) {
myGenerateConditionalExit = generateConditionalExit;
}
}