blob: 5046a30fb94fbadb0a2d44caca26f9da6450bf4f [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.inline;
import com.intellij.codeInsight.ExceptionUtil;
import com.intellij.codeInspection.sameParameterValue.SameParameterValueInspection;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.*;
import com.intellij.psi.controlFlow.DefUseUtil;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.util.InlineUtil;
import com.intellij.refactoring.util.RefactoringUIUtil;
import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewDescriptor;
import com.intellij.usageView.UsageViewUtil;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author yole
*/
public class InlineParameterExpressionProcessor extends BaseRefactoringProcessor {
private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.inline.InlineParameterExpressionProcessor");
public static final Key<Boolean> CREATE_LOCAL_FOR_TESTS = Key.create("CREATE_INLINE_PARAMETER_LOCAL_FOR_TESTS");
private final PsiCallExpression myMethodCall;
private final PsiMethod myMethod;
private final PsiParameter myParameter;
private PsiExpression myInitializer;
private final boolean mySameClass;
private final PsiCodeBlock myCallingBlock;
private final boolean myCreateLocal;
public InlineParameterExpressionProcessor(final PsiCallExpression methodCall,
final PsiMethod method,
final PsiParameter parameter,
final PsiExpression initializer,
boolean createLocal) {
super(method.getProject());
myMethodCall = methodCall;
myMethod = method;
myParameter = parameter;
myInitializer = initializer;
myCreateLocal = createLocal;
PsiClass callingClass = PsiTreeUtil.getParentOfType(methodCall, PsiClass.class);
mySameClass = (callingClass == myMethod.getContainingClass());
myCallingBlock = PsiTreeUtil.getTopmostParentOfType(myMethodCall, PsiCodeBlock.class);
}
@Override
protected String getCommandName() {
return InlineParameterHandler.REFACTORING_NAME;
}
@NotNull
@Override
protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) {
return new InlineViewDescriptor(myParameter);
}
@NotNull
@Override
protected UsageInfo[] findUsages() {
int parameterIndex = myMethod.getParameterList().getParameterIndex(myParameter);
final Map<PsiVariable, PsiElement> localToParamRef = new HashMap<PsiVariable, PsiElement>();
final PsiExpression[] arguments = myMethodCall.getArgumentList().getExpressions();
for (int i = 0; i < arguments.length; i++) {
if (i != parameterIndex && arguments[i] instanceof PsiReferenceExpression) {
final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)arguments[i];
final PsiElement element = referenceExpression.resolve();
if (element instanceof PsiLocalVariable || element instanceof PsiParameter) {
final PsiParameter param = myMethod.getParameterList().getParameters()[i];
final PsiExpression paramRef =
JavaPsiFacade.getInstance(myMethod.getProject()).getElementFactory().createExpressionFromText(param.getName(), myMethod);
localToParamRef.put((PsiVariable)element, paramRef);
}
}
}
final List<UsageInfo> result = new ArrayList<UsageInfo>();
myInitializer.accept(new JavaRecursiveElementVisitor() {
@Override
public void visitReferenceExpression(final PsiReferenceExpression expression) {
super.visitReferenceExpression(expression);
final PsiElement element = expression.resolve();
if (element instanceof PsiLocalVariable) {
final PsiLocalVariable localVariable = (PsiLocalVariable)element;
final PsiElement[] elements = DefUseUtil.getDefs(myCallingBlock, localVariable, expression);
if (elements.length == 1) {
PsiExpression localInitializer = null;
if (elements[0] instanceof PsiLocalVariable) {
localInitializer = ((PsiLocalVariable)elements[0]).getInitializer();
}
else if (elements[0] instanceof PsiAssignmentExpression) {
localInitializer = ((PsiAssignmentExpression)elements[0]).getRExpression();
}
else if (elements[0] instanceof PsiReferenceExpression) {
final PsiReferenceExpression refElement = (PsiReferenceExpression)elements[0];
final PsiElement parent = refElement.getParent();
if (parent instanceof PsiAssignmentExpression && ((PsiAssignmentExpression)parent).getLExpression() == refElement) {
localInitializer = ((PsiAssignmentExpression)parent).getRExpression();
}
}
if (localInitializer != null) {
final PsiElement replacement;
if (localToParamRef.containsKey(localVariable)) {
replacement = localToParamRef.get(localVariable);
}
else {
replacement = replaceArgs(localToParamRef, localInitializer.copy());
}
result.add(new LocalReplacementUsageInfo(expression, replacement));
}
}
}
}
});
if (!myCreateLocal) {
for (PsiReference ref : ReferencesSearch.search(myParameter).findAll()) {
result.add(new UsageInfo(ref));
}
}
final UsageInfo[] usageInfos = result.toArray(new UsageInfo[result.size()]);
return UsageViewUtil.removeDuplicatedUsages(usageInfos);
}
private static PsiElement replaceArgs(final Map<PsiVariable, PsiElement> elementsToReplace, PsiElement expression) {
final Map<PsiElement, PsiElement> replacements = new HashMap<PsiElement, PsiElement>();
expression.accept(new JavaRecursiveElementVisitor() {
@Override
public void visitReferenceExpression(PsiReferenceExpression referenceExpression) {
super.visitReferenceExpression(referenceExpression);
final PsiElement resolved = referenceExpression.resolve();
if (resolved instanceof PsiVariable) {
final PsiVariable variable = (PsiVariable)resolved;
final PsiElement replacement = elementsToReplace.get(variable);
if (replacement != null) {
replacements.put(referenceExpression, replacement);
}
}
}
});
return RefactoringUtil.replaceElementsWithMap(expression, replacements);
}
@Override
protected boolean preprocessUsages(Ref<UsageInfo[]> refUsages) {
final MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>();
final UsageInfo[] usages = refUsages.get();
final InaccessibleExpressionsDetector detector = new InaccessibleExpressionsDetector(conflicts);
myInitializer.accept(detector);
for (UsageInfo usage : usages) {
if (usage instanceof LocalReplacementUsageInfo) {
final PsiElement replacement = ((LocalReplacementUsageInfo)usage).getReplacement();
if (replacement != null) {
replacement.accept(detector);
}
}
}
final Set<PsiVariable> vars = new HashSet<PsiVariable>();
for (UsageInfo usageInfo : usages) {
if (usageInfo instanceof LocalReplacementUsageInfo) {
final PsiVariable var = ((LocalReplacementUsageInfo)usageInfo).getVariable();
if (var != null) {
vars.add(var);
}
}
}
for (PsiVariable var : vars) {
for (PsiReference ref : ReferencesSearch.search(var)) {
final PsiElement element = ref.getElement();
if (element instanceof PsiExpression && isAccessedForWriting((PsiExpression)element)) {
conflicts.putValue(element, "Parameter initializer depends on value which is not available inside method and cannot be inlined");
break;
}
}
}
return showConflicts(conflicts, usages);
}
private static boolean isAccessedForWriting (PsiExpression expr) {
while (expr.getParent() instanceof PsiArrayAccessExpression) {
expr = (PsiExpression)expr.getParent();
}
return PsiUtil.isAccessedForWriting(expr);
}
@Override
protected void performRefactoring(UsageInfo[] usages) {
final List<PsiClassType> thrownExceptions = ExceptionUtil.getThrownCheckedExceptions(new PsiElement[]{myInitializer});
final Set<PsiVariable> varsUsedInInitializer = new HashSet<PsiVariable>();
final Set<PsiJavaCodeReferenceElement> paramRefsToInline = new HashSet<PsiJavaCodeReferenceElement>();
final Map<PsiElement, PsiElement> replacements = new HashMap<PsiElement, PsiElement>();
for (UsageInfo usage : usages) {
if (usage instanceof LocalReplacementUsageInfo) {
final LocalReplacementUsageInfo replacementUsageInfo = (LocalReplacementUsageInfo)usage;
final PsiElement element = replacementUsageInfo.getElement();
final PsiElement replacement = replacementUsageInfo.getReplacement();
if (element != null && replacement != null) {
replacements.put(element, replacement);
}
varsUsedInInitializer.add(replacementUsageInfo.getVariable());
}
else {
LOG.assertTrue(!myCreateLocal);
paramRefsToInline.add((PsiJavaCodeReferenceElement)usage.getElement());
}
}
myInitializer = (PsiExpression)RefactoringUtil.replaceElementsWithMap(myInitializer, replacements);
if (myCreateLocal) {
final PsiElementFactory factory = JavaPsiFacade.getInstance(myMethod.getProject()).getElementFactory();
PsiDeclarationStatement localDeclaration =
factory.createVariableDeclarationStatement(myParameter.getName(), myParameter.getType(), myInitializer);
final PsiLocalVariable declaredVar = (PsiLocalVariable)localDeclaration.getDeclaredElements()[0];
PsiUtil.setModifierProperty(declaredVar, PsiModifier.FINAL, myParameter.hasModifierProperty(PsiModifier.FINAL));
final PsiExpression localVarInitializer =
InlineUtil.inlineVariable(myParameter, myInitializer, (PsiReferenceExpression)factory.createExpressionFromText(myParameter.getName(), myMethod));
final PsiExpression initializer = declaredVar.getInitializer();
LOG.assertTrue(initializer != null);
initializer.replace(localVarInitializer);
final PsiCodeBlock body = myMethod.getBody();
if (body != null) {
PsiElement anchor = findAnchorForLocalVariableDeclaration(body);
body.addAfter(localDeclaration, anchor);
}
} else {
for (PsiJavaCodeReferenceElement paramRef : paramRefsToInline) {
InlineUtil.inlineVariable(myParameter, myInitializer, paramRef);
}
}
//delete var if it becomes unused
for (PsiVariable variable : varsUsedInInitializer) {
if (variable != null && variable.isValid()) {
if (ReferencesSearch.search(variable).findFirst() == null) {
variable.delete();
}
}
}
SameParameterValueInspection.InlineParameterValueFix.removeParameter(myMethod, myParameter);
if (!thrownExceptions.isEmpty()) {
for (PsiClassType exception : thrownExceptions) {
PsiClass exceptionClass = exception.resolve();
if (exceptionClass != null) {
PsiUtil.addException(myMethod, exceptionClass);
}
}
}
}
@Nullable
private PsiElement findAnchorForLocalVariableDeclaration(PsiCodeBlock body) {
PsiElement anchor = body.getLBrace();
if (myMethod.isConstructor()) {
final PsiStatement[] statements = body.getStatements();
if (statements.length > 0 && statements[0] instanceof PsiExpressionStatement) {
final PsiExpression expression = ((PsiExpressionStatement)statements[0]).getExpression();
if (expression instanceof PsiMethodCallExpression) {
final String referenceName = ((PsiMethodCallExpression)expression).getMethodExpression().getReferenceName();
if (PsiKeyword.SUPER.equals(referenceName) || PsiKeyword.THIS.equals(referenceName)) {
anchor = statements[0];
}
}
}
}
return anchor;
}
private static class LocalReplacementUsageInfo extends UsageInfo {
private final PsiElement myReplacement;
private final PsiVariable myVariable;
public LocalReplacementUsageInfo(@NotNull PsiReference element, @NotNull PsiElement replacement) {
super(element);
final PsiElement resolved = element.resolve();
myVariable = resolved instanceof PsiVariable ? (PsiVariable)resolved : null;
myReplacement = replacement;
}
@Nullable
public PsiElement getReplacement() {
return myReplacement.isValid() ? myReplacement : null;
}
@Nullable
public PsiVariable getVariable() {
return myVariable != null && myVariable.isValid() ? myVariable : null;
}
}
private class InaccessibleExpressionsDetector extends JavaRecursiveElementWalkingVisitor {
private final MultiMap<PsiElement, String> myConflicts;
public InaccessibleExpressionsDetector(MultiMap<PsiElement, String> conflicts) {
myConflicts = conflicts;
}
@Override
public void visitReferenceExpression(final PsiReferenceExpression expression) {
super.visitReferenceExpression(expression);
final PsiElement element = expression.resolve();
if (element instanceof PsiMember && !((PsiModifierListOwner)element).hasModifierProperty(PsiModifier.STATIC)) {
if (myMethod.hasModifierProperty(PsiModifier.STATIC)) {
myConflicts.putValue(expression, "Parameter initializer depends on " + RefactoringUIUtil.getDescription(element, false) + " which is not available inside the static method");
}
}
if (element instanceof PsiMethod || element instanceof PsiField) {
if (!mySameClass && !((PsiModifierListOwner)element).hasModifierProperty(PsiModifier.STATIC)) {
myConflicts.putValue(expression, "Parameter initializer depends on non static member from some other class");
} else if (!PsiUtil.isAccessible((PsiMember)element, myMethod, null)) {
myConflicts.putValue(expression, "Parameter initializer depends on value which is not available inside method");
}
} else if (element instanceof PsiParameter) {
boolean bound = false;
for (PsiParameter parameter : myMethod.getParameterList().getParameters()) {
if (parameter.getType().equals(((PsiParameter)element).getType()) && parameter.getName().equals(((PsiParameter)element).getName())) {
bound = true;
}
}
if (!bound) {
myConflicts.putValue(expression, "Parameter initializer depends on callers parameter");
}
}
}
@Override
public void visitThisExpression(PsiThisExpression thisExpression) {
super.visitThisExpression(thisExpression);
final PsiJavaCodeReferenceElement qualifier = thisExpression.getQualifier();
PsiElement containingClass;
if (qualifier != null) {
containingClass = qualifier.resolve();
}
else {
containingClass = PsiTreeUtil.getParentOfType(myMethodCall, PsiClass.class);
}
final PsiClass methodContainingClass = myMethod.getContainingClass();
LOG.assertTrue(methodContainingClass != null);
if (!PsiTreeUtil.isAncestor(containingClass, methodContainingClass, false)) {
myConflicts.putValue(thisExpression,
"Parameter initializer depends on this which is not available inside the method and cannot be inlined");
} else if (myMethod.hasModifierProperty(PsiModifier.STATIC)) {
myConflicts.putValue(thisExpression, "Parameter initializer depends on this which is not available inside the static method");
}
}
@Override
public void visitReferenceElement(PsiJavaCodeReferenceElement reference) {
super.visitReferenceElement(reference);
if (myMethod.hasModifierProperty(PsiModifier.STATIC)) {
final PsiElement resolved = reference.resolve();
if (resolved instanceof PsiClass && !((PsiClass)resolved).hasModifierProperty(PsiModifier.STATIC)) {
myConflicts.putValue(reference, "Parameter initializer depends on non static class which is not available inside static method");
}
}
}
@Override
public void visitNewExpression(PsiNewExpression expression) {
super.visitNewExpression(expression);
final PsiJavaCodeReferenceElement reference = expression.getClassOrAnonymousClassReference();
if (reference != null) {
final PsiElement resolved = reference.resolve();
if (resolved instanceof PsiClass) {
final PsiClass refClass = (PsiClass)resolved;
final String classUnavailableMessage = "Parameter initializer depends on " +
RefactoringUIUtil.getDescription(refClass, true) +
" which is not available inside method and cannot be inlined";
if (!PsiUtil.isAccessible(refClass, myMethod, null)) {
myConflicts.putValue(expression, classUnavailableMessage);
}
else {
final PsiClass methodContainingClass = myMethod.getContainingClass();
LOG.assertTrue(methodContainingClass != null);
if (!PsiTreeUtil.isAncestor(myMethod, refClass, false)) {
PsiElement parent = refClass;
while ((parent = parent.getParent()) instanceof PsiClass) {
if (!PsiUtil.isAccessible((PsiClass)parent, myMethod, null)) {
break;
}
}
if (!(parent instanceof PsiFile)) {
myConflicts.putValue(expression, classUnavailableMessage);
}
}
}
}
}
}
}
}