3e67dbc119e4d06dce5bd4b802c2c803e8a22cbf
[idea/community.git] / java / java-impl / src / com / intellij / refactoring / rename / RenameJavaVariableProcessor.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.refactoring.rename;
17
18 import com.intellij.lang.StdLanguages;
19 import com.intellij.openapi.application.ApplicationManager;
20 import com.intellij.openapi.diagnostic.Logger;
21 import com.intellij.openapi.project.Project;
22 import com.intellij.openapi.ui.Messages;
23 import com.intellij.psi.*;
24 import com.intellij.psi.codeStyle.JavaCodeStyleManager;
25 import com.intellij.psi.codeStyle.VariableKind;
26 import com.intellij.psi.search.searches.ClassInheritorsSearch;
27 import com.intellij.psi.search.searches.OverridingMethodsSearch;
28 import com.intellij.psi.util.PropertyUtil;
29 import com.intellij.psi.util.PsiUtil;
30 import com.intellij.refactoring.HelpID;
31 import com.intellij.refactoring.JavaRefactoringSettings;
32 import com.intellij.refactoring.RefactoringBundle;
33 import com.intellij.refactoring.listeners.RefactoringElementListener;
34 import com.intellij.refactoring.util.ConflictsUtil;
35 import com.intellij.refactoring.util.MoveRenameUsageInfo;
36 import com.intellij.refactoring.util.RefactoringMessageUtil;
37 import com.intellij.refactoring.util.RefactoringUtil;
38 import com.intellij.usageView.UsageInfo;
39 import com.intellij.util.IncorrectOperationException;
40 import com.intellij.util.Processor;
41 import com.intellij.util.containers.MultiMap;
42 import org.jetbrains.annotations.NonNls;
43 import org.jetbrains.annotations.NotNull;
44 import org.jetbrains.annotations.Nullable;
45
46 import java.util.ArrayList;
47 import java.util.Collection;
48 import java.util.List;
49 import java.util.Map;
50
51 public class RenameJavaVariableProcessor extends RenameJavaMemberProcessor {
52   private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.rename.RenameJavaVariableProcessor");
53
54   public boolean canProcessElement(@NotNull final PsiElement element) {
55     return element instanceof PsiVariable;
56   }
57
58   public void renameElement(final PsiElement psiElement,
59                             final String newName,
60                             final UsageInfo[] usages, final RefactoringElementListener listener) throws IncorrectOperationException {
61     PsiVariable variable = (PsiVariable) psiElement;
62     List<MemberHidesOuterMemberUsageInfo> outerHides = new ArrayList<MemberHidesOuterMemberUsageInfo>();
63     List<MemberHidesStaticImportUsageInfo> staticImportHides = new ArrayList<MemberHidesStaticImportUsageInfo>();
64
65     List<PsiElement> occurrencesToCheckForConflict = new ArrayList<PsiElement>();
66     // rename all references
67     for (UsageInfo usage : usages) {
68       final PsiElement element = usage.getElement();
69       if (element == null) continue;
70
71       if (usage instanceof MemberHidesStaticImportUsageInfo) {
72         staticImportHides.add((MemberHidesStaticImportUsageInfo)usage);
73       } else if (usage instanceof LocalHidesFieldUsageInfo) {
74         PsiJavaCodeReferenceElement collidingRef = (PsiJavaCodeReferenceElement)element;
75         PsiElement resolved = collidingRef.resolve();
76
77         if (resolved instanceof PsiField) {
78           qualifyMember((PsiField)resolved, collidingRef, newName);
79         }
80         else {
81           // do nothing
82         }
83       }
84       else if (usage instanceof MemberHidesOuterMemberUsageInfo) {
85         PsiJavaCodeReferenceElement collidingRef = (PsiJavaCodeReferenceElement)element;
86         PsiField resolved = (PsiField)collidingRef.resolve();
87         outerHides.add(new MemberHidesOuterMemberUsageInfo(element, resolved));
88       }
89       else {
90         final PsiReference ref;
91         if (usage instanceof MoveRenameUsageInfo) {
92           ref = usage.getReference();
93         }
94         else {
95           ref = element.getReference();
96         }
97         if (ref != null) {
98           PsiElement newElem = ref.handleElementRename(newName);
99           if (variable instanceof PsiField) {
100             occurrencesToCheckForConflict.add(newElem);
101           }
102         }
103       }
104       }
105     // do actual rename
106     variable.setName(newName);
107     listener.elementRenamed(variable);
108
109     if (variable instanceof PsiField) {
110       for (PsiElement occurrence : occurrencesToCheckForConflict) {
111         fixPossibleNameCollisionsForFieldRenaming((PsiField) variable, newName, occurrence);
112       }
113     }
114
115     qualifyOuterMemberReferences(outerHides);
116     qualifyStaticImportReferences(staticImportHides);
117   }
118
119   private static void fixPossibleNameCollisionsForFieldRenaming(PsiField field, String newName, PsiElement replacedOccurence) throws IncorrectOperationException {
120     if (!(replacedOccurence instanceof PsiReferenceExpression)) return;
121     PsiElement elem = ((PsiReferenceExpression)replacedOccurence).resolve();
122
123     if (elem == null || elem == field) {
124       // If reference is unresolved, then field is not hidden by anyone...
125       return;
126     }
127
128     if (elem instanceof PsiLocalVariable || elem instanceof PsiParameter || (elem instanceof PsiField && elem != replacedOccurence))  {
129       qualifyMember(field, replacedOccurence, newName);
130     }
131   }
132
133   public void prepareRenaming(final PsiElement element, final String newName, final Map<PsiElement, String> allRenames) {
134     if (element instanceof PsiField && StdLanguages.JAVA.equals(element.getLanguage())) {
135       prepareFieldRenaming((PsiField)element, newName, allRenames);
136     }
137   }
138
139   private static void prepareFieldRenaming(PsiField field, String newName, final Map<PsiElement, String> allRenames) {
140     // search for getters/setters
141     PsiClass aClass = field.getContainingClass();
142
143     Project project = field.getProject();
144     final JavaCodeStyleManager manager = JavaCodeStyleManager.getInstance(project);
145
146     final String propertyName = manager.variableNameToPropertyName(field.getName(), VariableKind.FIELD);
147     String newPropertyName = manager.variableNameToPropertyName(newName, VariableKind.FIELD);
148
149     boolean isStatic = field.hasModifierProperty(PsiModifier.STATIC);
150     PsiMethod getter = PropertyUtil.findPropertyGetter(aClass, propertyName, isStatic, false);
151     PsiMethod setter = PropertyUtil.findPropertySetter(aClass, propertyName, isStatic, false);
152
153     boolean shouldRenameSetterParameter = false;
154
155     if (setter != null) {
156       String parameterName = manager.propertyNameToVariableName(propertyName, VariableKind.PARAMETER);
157       PsiParameter setterParameter = setter.getParameterList().getParameters()[0];
158       shouldRenameSetterParameter = parameterName.equals(setterParameter.getName());
159     }
160
161     String newGetterName = "";
162
163     if (getter != null) {
164       String getterId = getter.getName();
165       newGetterName = PropertyUtil.suggestGetterName(newPropertyName, field.getType(), getterId);
166       if (newGetterName.equals(getterId)) {
167         getter = null;
168         newGetterName = null;
169       } else {
170         for (PsiMethod method : getter.findDeepestSuperMethods()) {
171           if (method instanceof PsiCompiledElement) {
172             getter = null;
173             break;
174           }
175         }
176       }
177     }
178
179     String newSetterName = "";
180     if (setter != null) {
181       newSetterName = PropertyUtil.suggestSetterName(newPropertyName);
182       final String newSetterParameterName = manager.propertyNameToVariableName(newPropertyName, VariableKind.PARAMETER);
183       if (newSetterName.equals(setter.getName())) {
184         setter = null;
185         newSetterName = null;
186         shouldRenameSetterParameter = false;
187       }
188       else if (newSetterParameterName.equals(setter.getParameterList().getParameters()[0].getName())) {
189         shouldRenameSetterParameter = false;
190       } else {
191         for (PsiMethod method : setter.findDeepestSuperMethods()) {
192           if (method instanceof PsiCompiledElement) {
193             setter = null;
194             break;
195           }
196         }
197       }
198     }
199
200     if ((getter != null || setter != null) && askToRenameAccesors(getter, setter, newName, project)) {
201       getter = null;
202       setter = null;
203       shouldRenameSetterParameter = false;
204     }
205
206     if (getter != null) {
207       addOverriddenAndImplemented(getter, newGetterName, allRenames);
208     }
209
210     if (setter != null) {
211       addOverriddenAndImplemented(setter, newSetterName, allRenames);
212     }
213
214     if (shouldRenameSetterParameter) {
215       PsiParameter parameter = setter.getParameterList().getParameters()[0];
216       allRenames.put(parameter, manager.propertyNameToVariableName(newPropertyName, VariableKind.PARAMETER));
217     }
218   }
219
220   private static boolean askToRenameAccesors(PsiMethod getter, PsiMethod setter, String newName, final Project project) {
221     if (ApplicationManager.getApplication().isUnitTestMode()) return false;
222     String text = RefactoringMessageUtil.getGetterSetterMessage(newName, RefactoringBundle.message("rename.title"), getter, setter);
223     return Messages.showYesNoDialog(project, text, RefactoringBundle.message("rename.title"), Messages.getQuestionIcon()) != 0;
224   }
225
226   private static void addOverriddenAndImplemented(PsiMethod methodPrototype, final String newName, final Map<PsiElement, String> allRenames) {
227     allRenames.put(methodPrototype, newName);
228     for (PsiMethod method : methodPrototype.findDeepestSuperMethods()) {
229       OverridingMethodsSearch.search(method).forEach(new Processor<PsiMethod>() {
230         public boolean process(PsiMethod psiMethod) {
231           RenameProcessor.assertNonCompileElement(psiMethod);
232           allRenames.put(psiMethod, newName);
233           return true;
234         }
235       });
236       allRenames.put(method, newName);
237     }
238   }
239
240   public void findCollisions(final PsiElement element, final String newName, final Map<? extends PsiElement, String> allRenames,
241                              final List<UsageInfo> result) {
242     if (element instanceof PsiField) {
243       PsiField field = (PsiField) element;
244       findMemberHidesOuterMemberCollisions(field, newName, result);
245       findSubmemberHidesFieldCollisions(field, newName, result);
246       findCollisionsAgainstNewName(field, newName, result);
247     }
248     else if (element instanceof PsiLocalVariable || element instanceof PsiParameter) {
249       JavaUnresolvableLocalCollisionDetector.findCollisions(element, newName, result);
250       findLocalHidesFieldCollisions(element, newName, allRenames, result);
251     }
252   }
253
254   @Override
255   public void findExistingNameConflicts(PsiElement element, String newName, MultiMap<PsiElement, String> conflicts) {
256     if (element instanceof PsiCompiledElement) return;
257     if (element instanceof PsiField) {
258       PsiField refactoredField = (PsiField)element;
259       if (newName.equals(refactoredField.getName())) return;
260       ConflictsUtil.checkFieldConflicts(
261         refactoredField.getContainingClass(),
262         newName,
263         conflicts
264       );
265     }
266   }
267
268   @Nullable
269   @NonNls
270   public String getHelpID(final PsiElement element) {
271     if (element instanceof PsiField){
272       return HelpID.RENAME_FIELD;
273     }
274     else if (element instanceof PsiLocalVariable){
275       return HelpID.RENAME_VARIABLE;
276     }
277     else if (element instanceof PsiParameter){
278       return HelpID.RENAME_PARAMETER;
279     }
280     return null;
281   }
282
283   public boolean isToSearchInComments(final PsiElement element) {
284     if (element instanceof PsiField){
285       return JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_FIELD;
286     }
287     return JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_VARIABLE;
288   }
289
290   public void setToSearchInComments(final PsiElement element, final boolean enabled) {
291     if (element instanceof PsiField){
292       JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_FIELD = enabled;
293     }
294     JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_VARIABLE = enabled;
295   }
296
297   public boolean isToSearchForTextOccurrences(final PsiElement element) {
298     if (element instanceof PsiField) {
299       return JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_FIELD;
300     }
301     return JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_VARIABLE;
302   }
303
304   public void setToSearchForTextOccurrences(final PsiElement element, final boolean enabled) {
305     if (element instanceof PsiField) {
306       JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_FIELD = enabled;
307     }
308     JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_VARIABLE = enabled;
309   }
310
311   private static void findSubmemberHidesFieldCollisions(final PsiField field, final String newName, final List<UsageInfo> result) {
312     if (field.getContainingClass() == null) return;
313     if (field.hasModifierProperty(PsiModifier.PRIVATE)) return;
314     final PsiClass containingClass = field.getContainingClass();
315     Collection<PsiClass> inheritors = ClassInheritorsSearch.search(containingClass, true).findAll();
316     for (PsiClass inheritor : inheritors) {
317       PsiField conflictingField = inheritor.findFieldByName(newName, false);
318       if (conflictingField != null) {
319         result.add(new SubmemberHidesMemberUsageInfo(conflictingField, field));
320       }
321     }
322   }
323
324   private static void findLocalHidesFieldCollisions(final PsiElement element, final String newName, final Map<? extends PsiElement, String> allRenames, final List<UsageInfo> result) {
325     if (!(element instanceof PsiLocalVariable) && !(element instanceof PsiParameter)) return;
326
327     PsiClass toplevel = PsiUtil.getTopLevelClass(element);
328     if (toplevel == null) return;
329
330     PsiElement scopeElement;
331     if (element instanceof PsiLocalVariable) {
332       scopeElement = RefactoringUtil.getVariableScope((PsiLocalVariable)element);
333     }
334     else { // Parameter
335       scopeElement = ((PsiParameter) element).getDeclarationScope();
336     }
337
338     LOG.assertTrue(scopeElement != null);
339     scopeElement.accept(new JavaRecursiveElementWalkingVisitor() {
340       @Override public void visitReferenceExpression(PsiReferenceExpression expression) {
341         super.visitReferenceExpression(expression);
342         if (!expression.isQualified()) {
343           PsiElement resolved = expression.resolve();
344           if (resolved instanceof PsiField) {
345             final PsiField field = (PsiField)resolved;
346             String fieldNewName = allRenames.containsKey(field) ? allRenames.get(field) : field.getName();
347             if (newName.equals(fieldNewName)) {
348               result.add(new LocalHidesFieldUsageInfo(expression, element));
349             }
350           }
351         }
352       }
353     });
354   }
355 }