inline parameter: check inner/local classes (IDEA-40703)
[fedora-idea.git] / java / java-impl / src / com / intellij / refactoring / inline / InlineParameterExpressionProcessor.java
blob8f1751d448bbc0a1f7f1e5a1fecc98f01b025550
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 if (localInitializer != null) {
125 final PsiElement replacement;
126 if (localToParamRef.containsKey(localVariable)) {
127 replacement = localToParamRef.get(localVariable);
129 else {
130 replacement = replaceArgs(localToParamRef, localInitializer.copy());
132 result.add(new LocalReplacementUsageInfo(expression, replacement));
139 if (!myCreateLocal) {
140 for (PsiReference ref : ReferencesSearch.search(myParameter).findAll()) {
141 result.add(new UsageInfo(ref));
145 final UsageInfo[] usageInfos = result.toArray(new UsageInfo[result.size()]);
146 return UsageViewUtil.removeDuplicatedUsages(usageInfos);
149 private static PsiElement replaceArgs(final Map<PsiVariable, PsiElement> elementsToReplace, PsiElement expression) {
150 final Map<PsiElement, PsiElement> replacements = new HashMap<PsiElement, PsiElement>();
151 expression.accept(new JavaRecursiveElementVisitor() {
152 @Override
153 public void visitReferenceExpression(PsiReferenceExpression referenceExpression) {
154 super.visitReferenceExpression(referenceExpression);
155 final PsiElement resolved = referenceExpression.resolve();
156 if (resolved instanceof PsiVariable) {
157 final PsiVariable variable = (PsiVariable)resolved;
158 final PsiElement replacement = elementsToReplace.get(variable);
159 if (replacement != null) {
160 replacements.put(referenceExpression, replacement);
165 return RefactoringUtil.replaceElementsWithMap(expression, replacements);
168 @Override
169 protected boolean preprocessUsages(Ref<UsageInfo[]> refUsages) {
170 final MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>();
171 myInitializer.accept(new JavaRecursiveElementWalkingVisitor() {
172 @Override
173 public void visitReferenceExpression(final PsiReferenceExpression expression) {
174 super.visitReferenceExpression(expression);
175 final PsiElement element = expression.resolve();
176 if (element instanceof PsiMethod || element instanceof PsiField) {
177 if (!mySameClass && !((PsiModifierListOwner)element).hasModifierProperty(PsiModifier.STATIC)) {
178 conflicts.putValue(expression, "Parameter initializer depend on non static member from some other class");
180 } else if (element instanceof PsiParameter) {
181 conflicts.putValue(expression, "Parameter initializer depends on callers parameter");
185 @Override
186 public void visitThisExpression(PsiThisExpression thisExpression) {
187 super.visitThisExpression(thisExpression);
188 final PsiJavaCodeReferenceElement qualifier = thisExpression.getQualifier();
189 PsiElement containingClass;
190 if (qualifier != null) {
191 containingClass = qualifier.resolve();
193 else {
194 containingClass = PsiTreeUtil.getParentOfType(myMethodCall, PsiClass.class);
196 final PsiClass methodContainingClass = myMethod.getContainingClass();
197 LOG.assertTrue(methodContainingClass != null);
198 if (!PsiTreeUtil.isAncestor(containingClass, methodContainingClass, false)) {
199 conflicts.putValue(thisExpression,
200 "Parameter initializer depends on this which is not available inside the method and cannot be inlined");
204 @Override
205 public void visitNewExpression(PsiNewExpression expression) {
206 super.visitNewExpression(expression);
207 final PsiJavaCodeReferenceElement reference = expression.getClassOrAnonymousClassReference();
208 if (reference != null) {
209 final PsiElement resolved = reference.resolve();
210 if (resolved instanceof PsiClass) {
211 final PsiClass refClass = (PsiClass)resolved;
212 final String classUnavailableMessage = "Parameter initializer depends on " +
213 RefactoringUIUtil.getDescription(refClass, true) +
214 " which is not available inside method and cannot be inlined";
215 if (!PsiUtil.isAccessible(refClass, myMethod, null)) {
216 conflicts.putValue(expression, classUnavailableMessage);
218 else {
219 final PsiClass methodContainingClass = myMethod.getContainingClass();
220 LOG.assertTrue(methodContainingClass != null);
221 if (!PsiTreeUtil.isAncestor(myMethod, refClass, false)) {
222 PsiElement parent = refClass;
223 while ((parent = parent.getParent()) instanceof PsiClass) {
224 if (!PsiUtil.isAccessible((PsiClass)parent, myMethod, null)) {
225 break;
228 if (!(parent instanceof PsiFile)) {
229 conflicts.putValue(expression, classUnavailableMessage);
238 final UsageInfo[] usages = refUsages.get();
239 final Set<PsiVariable> vars = new HashSet<PsiVariable>();
240 for (UsageInfo usageInfo : usages) {
241 if (usageInfo instanceof LocalReplacementUsageInfo) {
242 final PsiVariable var = ((LocalReplacementUsageInfo)usageInfo).getVariable();
243 if (var != null) {
244 vars.add(var);
248 for (PsiVariable var : vars) {
249 for (PsiReference ref : ReferencesSearch.search(var)) {
250 final PsiElement element = ref.getElement();
251 if (element instanceof PsiExpression && isAccessedForWriting((PsiExpression)element)) {
252 conflicts.putValue(element, "Parameter initializer depends on value which is not available inside method and cannot be inlined");
253 break;
257 return showConflicts(conflicts);
260 private static boolean isAccessedForWriting (PsiExpression expr) {
261 while (expr.getParent() instanceof PsiArrayAccessExpression) {
262 expr = (PsiExpression)expr.getParent();
264 return PsiUtil.isAccessedForWriting(expr);
267 @Override
268 protected void performRefactoring(UsageInfo[] usages) {
269 final List<PsiClassType> thrownExceptions = ExceptionUtil.getThrownCheckedExceptions(new PsiElement[]{myInitializer});
270 final Set<PsiVariable> varsUsedInInitializer = new HashSet<PsiVariable>();
271 final Set<PsiJavaCodeReferenceElement> paramRefsToInline = new HashSet<PsiJavaCodeReferenceElement>();
272 final Map<PsiElement, PsiElement> replacements = new HashMap<PsiElement, PsiElement>();
273 for (UsageInfo usage : usages) {
274 if (usage instanceof LocalReplacementUsageInfo) {
275 final LocalReplacementUsageInfo replacementUsageInfo = (LocalReplacementUsageInfo)usage;
276 final PsiElement element = replacementUsageInfo.getElement();
277 final PsiElement replacement = replacementUsageInfo.getReplacement();
278 if (element != null && replacement != null) {
279 replacements.put(element, replacement);
281 varsUsedInInitializer.add(replacementUsageInfo.getVariable());
283 else {
284 LOG.assertTrue(!myCreateLocal);
285 paramRefsToInline.add((PsiJavaCodeReferenceElement)usage.getElement());
288 myInitializer = (PsiExpression)RefactoringUtil.replaceElementsWithMap(myInitializer, replacements);
290 if (myCreateLocal) {
291 final PsiElementFactory factory = JavaPsiFacade.getInstance(myMethod.getProject()).getElementFactory();
292 PsiDeclarationStatement localDeclaration =
293 factory.createVariableDeclarationStatement(myParameter.getName(), myParameter.getType(), myInitializer);
294 final PsiLocalVariable declaredVar = (PsiLocalVariable)localDeclaration.getDeclaredElements()[0];
295 PsiUtil.setModifierProperty(declaredVar, PsiModifier.FINAL, myParameter.hasModifierProperty(PsiModifier.FINAL));
296 final PsiCodeBlock body = myMethod.getBody();
297 if (body != null) {
298 body.addAfter(localDeclaration, body.getLBrace());
300 } else {
301 for (PsiJavaCodeReferenceElement paramRef : paramRefsToInline) {
302 InlineUtil.inlineVariable(myParameter, myInitializer, paramRef);
306 //delete var if it becomes unused
307 for (PsiVariable variable : varsUsedInInitializer) {
308 if (variable != null && variable.isValid()) {
309 if (ReferencesSearch.search(variable).findFirst() == null) {
310 variable.delete();
315 SameParameterValueInspection.InlineParameterValueFix.removeParameter(myMethod, myParameter);
317 if (!thrownExceptions.isEmpty()) {
318 for (PsiClassType exception : thrownExceptions) {
319 PsiClass exceptionClass = exception.resolve();
320 if (exceptionClass != null) {
321 PsiUtil.addException(myMethod, exceptionClass);
327 private static class LocalReplacementUsageInfo extends UsageInfo {
328 private final PsiElement myReplacement;
329 private final PsiVariable myVariable;
331 public LocalReplacementUsageInfo(@NotNull PsiReference element, @NotNull PsiElement replacement) {
332 super(element);
333 final PsiElement resolved = element.resolve();
334 myVariable = resolved instanceof PsiVariable ? (PsiVariable)resolved : null;
335 myReplacement = replacement;
338 @Nullable
339 public PsiElement getReplacement() {
340 return myReplacement.isValid() ? myReplacement : null;
343 @Nullable
344 public PsiVariable getVariable() {
345 return myVariable != null && myVariable.isValid() ? myVariable : null;