blob: 369613c460406d444a8e2d65460ec001c6edf210 [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.
*/
/*
* User: anna
* Date: 22-Jun-2009
*/
package com.intellij.refactoring.extractMethod;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.controlFlow.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.TypeConversionUtil;
import com.intellij.refactoring.util.VariableData;
import com.intellij.refactoring.util.duplicates.DuplicatesFinder;
import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class InputVariables {
private final List<VariableData> myInputVariables;
private List<? extends PsiVariable> myInitialParameters;
private final Project myProject;
private final LocalSearchScope myScope;
private ParametersFolder myFolding;
private boolean myFoldingAvailable;
public InputVariables(final List<? extends PsiVariable> inputVariables,
Project project,
LocalSearchScope scope,
boolean foldingAvailable) {
myInitialParameters = inputVariables;
myProject = project;
myScope = scope;
myFoldingAvailable = foldingAvailable;
myFolding = new ParametersFolder();
myInputVariables = wrapInputVariables(inputVariables);
}
/**
* copy use only
*/
public InputVariables(List<VariableData> inputVariables,
Project project,
LocalSearchScope scope) {
myProject = project;
myScope = scope;
myInputVariables = new ArrayList<VariableData>(inputVariables);
}
public boolean isFoldable() {
return myFolding.isFoldable();
}
public ArrayList<VariableData> wrapInputVariables(final List<? extends PsiVariable> inputVariables) {
final ArrayList<VariableData> inputData = new ArrayList<VariableData>(inputVariables.size());
for (PsiVariable var : inputVariables) {
String name = var.getName();
if (!(var instanceof PsiParameter)) {
JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(myProject);
VariableKind kind = codeStyleManager.getVariableKind(var);
name = codeStyleManager.variableNameToPropertyName(name, kind);
name = codeStyleManager.propertyNameToVariableName(name, VariableKind.PARAMETER);
}
PsiType type = var.getType();
if (type instanceof PsiEllipsisType) {
type = ((PsiEllipsisType)type).toArrayType();
}
final Map<PsiCodeBlock, PsiType> casts = new HashMap<PsiCodeBlock, PsiType>();
for (PsiReference reference : ReferencesSearch.search(var, myScope)) {
final PsiElement element = reference.getElement();
final PsiElement parent = element.getParent();
final PsiCodeBlock block = PsiTreeUtil.getParentOfType(parent, PsiCodeBlock.class);
if (parent instanceof PsiTypeCastExpression) {
final PsiType currentType = casts.get(block);
final PsiType castType = ((PsiTypeCastExpression)parent).getType();
casts.put(block, casts.containsKey(block) && currentType == null ? null : getBroaderType(currentType, castType));
} else {
casts.put(block, null);
}
}
if (!casts.containsValue(null)) {
PsiType currentType = null;
for (PsiType psiType : casts.values()) {
currentType = getBroaderType(currentType, psiType);
if (currentType == null) {
break;
}
}
if (currentType != null) {
currentType = checkTopLevelInstanceOf(currentType);
if (currentType != null) {
type = currentType;
}
}
}
VariableData data = new VariableData(var, type);
data.name = name;
data.passAsParameter = true;
inputData.add(data);
if (myFoldingAvailable) myFolding.isParameterFoldable(data, myScope, inputVariables);
}
if (myFoldingAvailable) {
final Set<VariableData> toDelete = new HashSet<VariableData>();
for (int i = inputData.size() - 1; i >=0; i--) {
final VariableData data = inputData.get(i);
if (myFolding.isParameterSafeToDelete(data, myScope)) {
toDelete.add(data);
}
}
inputData.removeAll(toDelete);
}
return inputData;
}
@Nullable
private PsiType checkTopLevelInstanceOf(final PsiType currentType) {
final PsiElement[] scope = myScope.getScope();
if (scope.length == 1 && scope[0] instanceof PsiIfStatement) {
final PsiExpression condition = ((PsiIfStatement)scope[0]).getCondition();
if (condition != null) {
class CheckInstanceOf {
boolean check(PsiInstanceOfExpression expr) {
final PsiTypeElement checkType = expr.getCheckType();
return checkType == null || !checkType.getType().equals(currentType);
}
}
CheckInstanceOf checker = new CheckInstanceOf();
final PsiInstanceOfExpression[] expressions = PsiTreeUtil.getChildrenOfType(condition, PsiInstanceOfExpression.class);
if (expressions != null) {
for (PsiInstanceOfExpression instanceOfExpression : expressions) {
if (!checker.check(instanceOfExpression)) return null;
}
} else if (condition instanceof PsiInstanceOfExpression) {
if (!checker.check((PsiInstanceOfExpression)condition)) return null;
}
}
}
return currentType;
}
@Nullable
private static PsiType getBroaderType(PsiType currentType, PsiType castType) {
if (currentType != null) {
if (castType != null) {
if (TypeConversionUtil.isAssignable(castType, currentType)) {
return castType;
} else if (!TypeConversionUtil.isAssignable(currentType, castType)) {
for (PsiType superType : castType.getSuperTypes()) {
if (TypeConversionUtil.isAssignable(superType, currentType)) {
return superType;
}
}
return null;
}
}
}
else {
return castType;
}
return currentType;
}
public List<VariableData> getInputVariables() {
return myInputVariables;
}
public PsiExpression replaceWrappedReferences(PsiElement[] elements, PsiExpression expression) {
if (!myFoldingAvailable) return expression;
boolean update = elements[0] == expression;
for (VariableData inputVariable : myInputVariables) {
myFolding.foldParameterUsagesInBody(inputVariable, elements, myScope);
}
return update ? (PsiExpression)elements[0] : expression;
}
public boolean toDeclareInsideBody(PsiVariable variable) {
final ArrayList<VariableData> knownVars = new ArrayList<VariableData>(myInputVariables);
for (VariableData data : knownVars) {
if (data.variable.equals(variable)) {
return false;
}
}
return !myFolding.wasExcluded(variable);
}
public boolean contains(PsiVariable variable) {
for (VariableData data : myInputVariables) {
if (data.variable.equals(variable)) return true;
}
return false;
}
public void removeParametersUsedInExitsOnly(PsiElement codeFragment,
Collection<PsiStatement> exitStatements,
ControlFlow controlFlow,
int startOffset,
int endOffset) {
final LocalSearchScope scope = new LocalSearchScope(codeFragment);
Variables:
for (Iterator<VariableData> iterator = myInputVariables.iterator(); iterator.hasNext();) {
final VariableData data = iterator.next();
for (PsiReference ref : ReferencesSearch.search(data.variable, scope)) {
PsiElement element = ref.getElement();
int elementOffset = controlFlow.getStartOffset(element);
if (elementOffset >= startOffset && elementOffset <= endOffset) {
if (!isInExitStatements(element, exitStatements)) 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;
}
public InputVariables copy() {
final InputVariables inputVariables = new InputVariables(myInputVariables, myProject, myScope);
inputVariables.myFoldingAvailable = myFoldingAvailable;
inputVariables.myFolding = myFolding;
inputVariables.myInitialParameters = myInitialParameters;
return inputVariables;
}
public void appendCallArguments(VariableData data, StringBuilder buffer) {
if (myFoldingAvailable) {
buffer.append(myFolding.getGeneratedCallArgument(data));
} else {
if (!TypeConversionUtil.isAssignable(data.type, data.variable.getType())) {
buffer.append("(").append(data.type.getCanonicalText()).append(")");
}
buffer.append(data.variable.getName());
}
}
public void setFoldingAvailable(boolean foldingAvailable) {
myFoldingAvailable = foldingAvailable;
myFolding.clear();
myInputVariables.clear();
myInputVariables.addAll(wrapInputVariables(myInitialParameters));
}
public void annotateWithParameter(PsiJavaCodeReferenceElement reference) {
for (VariableData data : myInputVariables) {
final PsiElement element = reference.resolve();
if (data.variable.equals(element)) {
PsiType type = data.variable.getType();
final PsiMethodCallExpression methodCallExpression = PsiTreeUtil.getParentOfType(reference, PsiMethodCallExpression.class);
if (methodCallExpression != null) {
int idx = ArrayUtil.find(methodCallExpression.getArgumentList().getExpressions(), reference);
if (idx > -1) {
final PsiMethod psiMethod = methodCallExpression.resolveMethod();
if (psiMethod != null) {
final PsiParameter[] parameters = psiMethod.getParameterList().getParameters();
if (idx >= parameters.length) { //vararg parameter
idx = parameters.length - 1;
if (idx >= 0) { //incomplete code
type = parameters[idx].getType();
}
}
if (type instanceof PsiEllipsisType) {
type = ((PsiEllipsisType)type).getComponentType();
}
}
}
}
if (!myFoldingAvailable || !myFolding.annotateWithParameter(data, reference)) {
reference.putUserData(DuplicatesFinder.PARAMETER, Pair.create(data.variable, type));
}
}
}
}
public boolean isFoldingSelectedByDefault() {
return myFolding.isFoldingSelectedByDefault();
}
}