inline parameter: conflict if replacement would be inaccessible
[fedora-idea.git] / java / java-impl / src / com / intellij / refactoring / inline / InlineParameterExpressionProcessor.java
bloba360bedfcbf57bd1c2236d34ce91e36b0e52b535
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.refactoring.inline;
18 import com.intellij.codeInsight.ExceptionUtil;
19 import com.intellij.codeInspection.sameParameterValue.SameParameterValueInspection;
20 import com.intellij.openapi.diagnostic.Logger;
21 import com.intellij.openapi.util.Key;
22 import com.intellij.openapi.util.Ref;
23 import com.intellij.psi.*;
24 import com.intellij.psi.controlFlow.DefUseUtil;
25 import com.intellij.psi.search.searches.ReferencesSearch;
26 import com.intellij.psi.util.PsiTreeUtil;
27 import com.intellij.psi.util.PsiUtil;
28 import com.intellij.refactoring.BaseRefactoringProcessor;
29 import com.intellij.refactoring.util.InlineUtil;
30 import com.intellij.refactoring.util.RefactoringUIUtil;
31 import com.intellij.refactoring.util.RefactoringUtil;
32 import com.intellij.usageView.UsageInfo;
33 import com.intellij.usageView.UsageViewDescriptor;
34 import com.intellij.usageView.UsageViewUtil;
35 import com.intellij.util.containers.MultiMap;
36 import org.jetbrains.annotations.NotNull;
37 import org.jetbrains.annotations.Nullable;
39 import java.util.*;
41 /**
42 * @author yole
44 public class InlineParameterExpressionProcessor extends BaseRefactoringProcessor {
45 private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.inline.InlineParameterExpressionProcessor");
46 public static final Key<Boolean> CREATE_LOCAL_FOR_TESTS = Key.create("CREATE_INLINE_PARAMETER_LOCAL_FOR_TESTS");
48 private final PsiCallExpression myMethodCall;
49 private final PsiMethod myMethod;
50 private final PsiParameter myParameter;
51 private PsiExpression myInitializer;
52 private final boolean mySameClass;
53 private final PsiMethod myCallingMethod;
54 private boolean myCreateLocal;
56 public InlineParameterExpressionProcessor(final PsiCallExpression methodCall,
57 final PsiMethod method,
58 final PsiParameter parameter,
59 final PsiExpression initializer,
60 boolean createLocal) {
61 super(method.getProject());
62 myMethodCall = methodCall;
63 myMethod = method;
64 myParameter = parameter;
65 myInitializer = initializer;
66 myCreateLocal = createLocal;
68 PsiClass callingClass = PsiTreeUtil.getParentOfType(methodCall, PsiClass.class);
69 mySameClass = (callingClass == myMethod.getContainingClass());
70 myCallingMethod = PsiTreeUtil.getParentOfType(myMethodCall, PsiMethod.class);
73 @Override
74 protected String getCommandName() {
75 return InlineParameterHandler.REFACTORING_NAME;
78 @Override
79 protected void refreshElements(PsiElement[] elements) {
82 @Override
83 protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) {
84 return new InlineViewDescriptor(myParameter);
87 @NotNull
88 @Override
89 protected UsageInfo[] findUsages() {
91 int parameterIndex = myMethod.getParameterList().getParameterIndex(myParameter);
92 final Map<PsiVariable, PsiElement> localToParamRef = new HashMap<PsiVariable, PsiElement>();
93 final PsiExpression[] arguments = myMethodCall.getArgumentList().getExpressions();
94 for (int i = 0; i < arguments.length; i++) {
95 if (i != parameterIndex && arguments[i] instanceof PsiReferenceExpression) {
96 final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)arguments[i];
97 final PsiElement element = referenceExpression.resolve();
98 if (element instanceof PsiLocalVariable || element instanceof PsiParameter) {
99 final PsiParameter param = myMethod.getParameterList().getParameters()[i];
100 final PsiExpression paramRef =
101 JavaPsiFacade.getInstance(myMethod.getProject()).getElementFactory().createExpressionFromText(param.getName(), myMethod);
102 localToParamRef.put((PsiVariable)element, paramRef);
107 final List<UsageInfo> result = new ArrayList<UsageInfo>();
108 myInitializer.accept(new JavaRecursiveElementVisitor() {
109 @Override
110 public void visitReferenceExpression(final PsiReferenceExpression expression) {
111 super.visitReferenceExpression(expression);
112 final PsiElement element = expression.resolve();
113 if (element instanceof PsiLocalVariable) {
114 final PsiLocalVariable localVariable = (PsiLocalVariable)element;
115 final PsiElement[] elements = DefUseUtil.getDefs(myCallingMethod.getBody(), localVariable, expression);
116 if (elements.length == 1) {
117 PsiExpression localInitializer = null;
118 if (elements[0] instanceof PsiLocalVariable) {
119 localInitializer = ((PsiLocalVariable)elements[0]).getInitializer();
121 else if (elements[0] instanceof PsiAssignmentExpression) {
122 localInitializer = ((PsiAssignmentExpression)elements[0]).getRExpression();
124 else if (elements[0] instanceof PsiReferenceExpression) {
125 final PsiReferenceExpression refElement = (PsiReferenceExpression)elements[0];
126 final PsiElement parent = refElement.getParent();
127 if (parent instanceof PsiAssignmentExpression && ((PsiAssignmentExpression)parent).getLExpression() == refElement) {
128 localInitializer = ((PsiAssignmentExpression)parent).getRExpression();
131 if (localInitializer != null) {
132 final PsiElement replacement;
133 if (localToParamRef.containsKey(localVariable)) {
134 replacement = localToParamRef.get(localVariable);
136 else {
137 replacement = replaceArgs(localToParamRef, localInitializer.copy());
139 result.add(new LocalReplacementUsageInfo(expression, replacement));
146 if (!myCreateLocal) {
147 for (PsiReference ref : ReferencesSearch.search(myParameter).findAll()) {
148 result.add(new UsageInfo(ref));
152 final UsageInfo[] usageInfos = result.toArray(new UsageInfo[result.size()]);
153 return UsageViewUtil.removeDuplicatedUsages(usageInfos);
156 private static PsiElement replaceArgs(final Map<PsiVariable, PsiElement> elementsToReplace, PsiElement expression) {
157 final Map<PsiElement, PsiElement> replacements = new HashMap<PsiElement, PsiElement>();
158 expression.accept(new JavaRecursiveElementVisitor() {
159 @Override
160 public void visitReferenceExpression(PsiReferenceExpression referenceExpression) {
161 super.visitReferenceExpression(referenceExpression);
162 final PsiElement resolved = referenceExpression.resolve();
163 if (resolved instanceof PsiVariable) {
164 final PsiVariable variable = (PsiVariable)resolved;
165 final PsiElement replacement = elementsToReplace.get(variable);
166 if (replacement != null) {
167 replacements.put(referenceExpression, replacement);
172 return RefactoringUtil.replaceElementsWithMap(expression, replacements);
175 @Override
176 protected boolean preprocessUsages(Ref<UsageInfo[]> refUsages) {
177 final MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>();
178 final UsageInfo[] usages = refUsages.get();
179 final InaccessibleExpressionsDetector detector = new InaccessibleExpressionsDetector(conflicts);
180 myInitializer.accept(detector);
181 for (UsageInfo usage : usages) {
182 if (usage instanceof LocalReplacementUsageInfo) {
183 final PsiElement replacement = ((LocalReplacementUsageInfo)usage).getReplacement();
184 if (replacement != null) {
185 replacement.accept(detector);
190 final Set<PsiVariable> vars = new HashSet<PsiVariable>();
191 for (UsageInfo usageInfo : usages) {
192 if (usageInfo instanceof LocalReplacementUsageInfo) {
193 final PsiVariable var = ((LocalReplacementUsageInfo)usageInfo).getVariable();
194 if (var != null) {
195 vars.add(var);
199 for (PsiVariable var : vars) {
200 for (PsiReference ref : ReferencesSearch.search(var)) {
201 final PsiElement element = ref.getElement();
202 if (element instanceof PsiExpression && isAccessedForWriting((PsiExpression)element)) {
203 conflicts.putValue(element, "Parameter initializer depends on value which is not available inside method and cannot be inlined");
204 break;
208 return showConflicts(conflicts);
211 private static boolean isAccessedForWriting (PsiExpression expr) {
212 while (expr.getParent() instanceof PsiArrayAccessExpression) {
213 expr = (PsiExpression)expr.getParent();
215 return PsiUtil.isAccessedForWriting(expr);
218 @Override
219 protected void performRefactoring(UsageInfo[] usages) {
220 final List<PsiClassType> thrownExceptions = ExceptionUtil.getThrownCheckedExceptions(new PsiElement[]{myInitializer});
221 final Set<PsiVariable> varsUsedInInitializer = new HashSet<PsiVariable>();
222 final Set<PsiJavaCodeReferenceElement> paramRefsToInline = new HashSet<PsiJavaCodeReferenceElement>();
223 final Map<PsiElement, PsiElement> replacements = new HashMap<PsiElement, PsiElement>();
224 for (UsageInfo usage : usages) {
225 if (usage instanceof LocalReplacementUsageInfo) {
226 final LocalReplacementUsageInfo replacementUsageInfo = (LocalReplacementUsageInfo)usage;
227 final PsiElement element = replacementUsageInfo.getElement();
228 final PsiElement replacement = replacementUsageInfo.getReplacement();
229 if (element != null && replacement != null) {
230 replacements.put(element, replacement);
232 varsUsedInInitializer.add(replacementUsageInfo.getVariable());
234 else {
235 LOG.assertTrue(!myCreateLocal);
236 paramRefsToInline.add((PsiJavaCodeReferenceElement)usage.getElement());
239 myInitializer = (PsiExpression)RefactoringUtil.replaceElementsWithMap(myInitializer, replacements);
241 if (myCreateLocal) {
242 final PsiElementFactory factory = JavaPsiFacade.getInstance(myMethod.getProject()).getElementFactory();
243 PsiDeclarationStatement localDeclaration =
244 factory.createVariableDeclarationStatement(myParameter.getName(), myParameter.getType(), myInitializer);
245 final PsiLocalVariable declaredVar = (PsiLocalVariable)localDeclaration.getDeclaredElements()[0];
246 PsiUtil.setModifierProperty(declaredVar, PsiModifier.FINAL, myParameter.hasModifierProperty(PsiModifier.FINAL));
247 final PsiExpression localVarInitializer =
248 InlineUtil.inlineVariable(myParameter, myInitializer, (PsiReferenceExpression)factory.createExpressionFromText(myParameter.getName(), myMethod));
249 final PsiExpression initializer = declaredVar.getInitializer();
250 LOG.assertTrue(initializer != null);
251 initializer.replace(localVarInitializer);
252 final PsiCodeBlock body = myMethod.getBody();
253 if (body != null) {
254 body.addAfter(localDeclaration, body.getLBrace());
256 } else {
257 for (PsiJavaCodeReferenceElement paramRef : paramRefsToInline) {
258 InlineUtil.inlineVariable(myParameter, myInitializer, paramRef);
262 //delete var if it becomes unused
263 for (PsiVariable variable : varsUsedInInitializer) {
264 if (variable != null && variable.isValid()) {
265 if (ReferencesSearch.search(variable).findFirst() == null) {
266 variable.delete();
271 SameParameterValueInspection.InlineParameterValueFix.removeParameter(myMethod, myParameter);
273 if (!thrownExceptions.isEmpty()) {
274 for (PsiClassType exception : thrownExceptions) {
275 PsiClass exceptionClass = exception.resolve();
276 if (exceptionClass != null) {
277 PsiUtil.addException(myMethod, exceptionClass);
283 private static class LocalReplacementUsageInfo extends UsageInfo {
284 private final PsiElement myReplacement;
285 private final PsiVariable myVariable;
287 public LocalReplacementUsageInfo(@NotNull PsiReference element, @NotNull PsiElement replacement) {
288 super(element);
289 final PsiElement resolved = element.resolve();
290 myVariable = resolved instanceof PsiVariable ? (PsiVariable)resolved : null;
291 myReplacement = replacement;
294 @Nullable
295 public PsiElement getReplacement() {
296 return myReplacement.isValid() ? myReplacement : null;
299 @Nullable
300 public PsiVariable getVariable() {
301 return myVariable != null && myVariable.isValid() ? myVariable : null;
305 private class InaccessibleExpressionsDetector extends JavaRecursiveElementWalkingVisitor {
306 private final MultiMap<PsiElement, String> myConflicts;
308 public InaccessibleExpressionsDetector(MultiMap<PsiElement, String> conflicts) {
309 myConflicts = conflicts;
312 @Override
313 public void visitReferenceExpression(final PsiReferenceExpression expression) {
314 super.visitReferenceExpression(expression);
315 final PsiElement element = expression.resolve();
316 if (element instanceof PsiMember && !((PsiModifierListOwner)element).hasModifierProperty(PsiModifier.STATIC)) {
317 if (myMethod.hasModifierProperty(PsiModifier.STATIC)) {
318 myConflicts.putValue(expression, "Parameter initializer depends on " + RefactoringUIUtil.getDescription(element, false) + " which is not available inside the static method");
321 if (element instanceof PsiMethod || element instanceof PsiField) {
322 if (!mySameClass && !((PsiModifierListOwner)element).hasModifierProperty(PsiModifier.STATIC)) {
323 myConflicts.putValue(expression, "Parameter initializer depend on non static member from some other class");
324 } else if (!PsiUtil.isAccessible((PsiMember)element, myMethod, null)) {
325 myConflicts.putValue(expression, "Parameter initializer depends on value which is not available inside method");
327 } else if (element instanceof PsiParameter) {
328 myConflicts.putValue(expression, "Parameter initializer depends on callers parameter");
332 @Override
333 public void visitThisExpression(PsiThisExpression thisExpression) {
334 super.visitThisExpression(thisExpression);
335 final PsiJavaCodeReferenceElement qualifier = thisExpression.getQualifier();
336 PsiElement containingClass;
337 if (qualifier != null) {
338 containingClass = qualifier.resolve();
340 else {
341 containingClass = PsiTreeUtil.getParentOfType(myMethodCall, PsiClass.class);
343 final PsiClass methodContainingClass = myMethod.getContainingClass();
344 LOG.assertTrue(methodContainingClass != null);
345 if (!PsiTreeUtil.isAncestor(containingClass, methodContainingClass, false)) {
346 myConflicts.putValue(thisExpression,
347 "Parameter initializer depends on this which is not available inside the method and cannot be inlined");
348 } else if (myMethod.hasModifierProperty(PsiModifier.STATIC)) {
349 myConflicts.putValue(thisExpression, "Parameter initializer depends on this which is not available inside the static method");
353 @Override
354 public void visitReferenceElement(PsiJavaCodeReferenceElement reference) {
355 super.visitReferenceElement(reference);
356 if (myMethod.hasModifierProperty(PsiModifier.STATIC)) {
357 final PsiElement resolved = reference.resolve();
358 if (resolved instanceof PsiClass && !((PsiClass)resolved).hasModifierProperty(PsiModifier.STATIC)) {
359 myConflicts.putValue(reference, "Parameter initializer depends on non static class which is not available inside static method");
364 @Override
365 public void visitNewExpression(PsiNewExpression expression) {
366 super.visitNewExpression(expression);
367 final PsiJavaCodeReferenceElement reference = expression.getClassOrAnonymousClassReference();
368 if (reference != null) {
369 final PsiElement resolved = reference.resolve();
370 if (resolved instanceof PsiClass) {
371 final PsiClass refClass = (PsiClass)resolved;
372 final String classUnavailableMessage = "Parameter initializer depends on " +
373 RefactoringUIUtil.getDescription(refClass, true) +
374 " which is not available inside method and cannot be inlined";
375 if (!PsiUtil.isAccessible(refClass, myMethod, null)) {
376 myConflicts.putValue(expression, classUnavailableMessage);
378 else {
379 final PsiClass methodContainingClass = myMethod.getContainingClass();
380 LOG.assertTrue(methodContainingClass != null);
381 if (!PsiTreeUtil.isAncestor(myMethod, refClass, false)) {
382 PsiElement parent = refClass;
383 while ((parent = parent.getParent()) instanceof PsiClass) {
384 if (!PsiUtil.isAccessible((PsiClass)parent, myMethod, null)) {
385 break;
388 if (!(parent instanceof PsiFile)) {
389 myConflicts.putValue(expression, classUnavailableMessage);