EA-33059 - NPE: RenameJavaVariableProcessor.prepareFieldRenaming
[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             shouldRenameSetterParameter = false;
195             break;
196           }
197         }
198       }
199     }
200
201     if ((getter != null || setter != null) && askToRenameAccesors(getter, setter, newName, project)) {
202       getter = null;
203       setter = null;
204       shouldRenameSetterParameter = false;
205     }
206
207     if (getter != null) {
208       addOverriddenAndImplemented(getter, newGetterName, allRenames);
209     }
210
211     if (setter != null) {
212       addOverriddenAndImplemented(setter, newSetterName, allRenames);
213     }
214
215     if (shouldRenameSetterParameter) {
216       PsiParameter parameter = setter.getParameterList().getParameters()[0];
217       allRenames.put(parameter, manager.propertyNameToVariableName(newPropertyName, VariableKind.PARAMETER));
218     }
219   }
220
221   private static boolean askToRenameAccesors(PsiMethod getter, PsiMethod setter, String newName, final Project project) {
222     if (ApplicationManager.getApplication().isUnitTestMode()) return false;
223     String text = RefactoringMessageUtil.getGetterSetterMessage(newName, RefactoringBundle.message("rename.title"), getter, setter);
224     return Messages.showYesNoDialog(project, text, RefactoringBundle.message("rename.title"), Messages.getQuestionIcon()) != 0;
225   }
226
227   private static void addOverriddenAndImplemented(PsiMethod methodPrototype, final String newName, final Map<PsiElement, String> allRenames) {
228     allRenames.put(methodPrototype, newName);
229     for (PsiMethod method : methodPrototype.findDeepestSuperMethods()) {
230       OverridingMethodsSearch.search(method).forEach(new Processor<PsiMethod>() {
231         public boolean process(PsiMethod psiMethod) {
232           RenameProcessor.assertNonCompileElement(psiMethod);
233           allRenames.put(psiMethod, newName);
234           return true;
235         }
236       });
237       allRenames.put(method, newName);
238     }
239   }
240
241   public void findCollisions(final PsiElement element, final String newName, final Map<? extends PsiElement, String> allRenames,
242                              final List<UsageInfo> result) {
243     if (element instanceof PsiField) {
244       PsiField field = (PsiField) element;
245       findMemberHidesOuterMemberCollisions(field, newName, result);
246       findSubmemberHidesFieldCollisions(field, newName, result);
247       findCollisionsAgainstNewName(field, newName, result);
248     }
249     else if (element instanceof PsiLocalVariable || element instanceof PsiParameter) {
250       JavaUnresolvableLocalCollisionDetector.findCollisions(element, newName, result);
251       findLocalHidesFieldCollisions(element, newName, allRenames, result);
252     }
253   }
254
255   @Override
256   public void findExistingNameConflicts(PsiElement element, String newName, MultiMap<PsiElement, String> conflicts) {
257     if (element instanceof PsiCompiledElement) return;
258     if (element instanceof PsiField) {
259       PsiField refactoredField = (PsiField)element;
260       if (newName.equals(refactoredField.getName())) return;
261       ConflictsUtil.checkFieldConflicts(
262         refactoredField.getContainingClass(),
263         newName,
264         conflicts
265       );
266     }
267   }
268
269   @Nullable
270   @NonNls
271   public String getHelpID(final PsiElement element) {
272     if (element instanceof PsiField){
273       return HelpID.RENAME_FIELD;
274     }
275     else if (element instanceof PsiLocalVariable){
276       return HelpID.RENAME_VARIABLE;
277     }
278     else if (element instanceof PsiParameter){
279       return HelpID.RENAME_PARAMETER;
280     }
281     return null;
282   }
283
284   public boolean isToSearchInComments(final PsiElement element) {
285     if (element instanceof PsiField){
286       return JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_FIELD;
287     }
288     return JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_VARIABLE;
289   }
290
291   public void setToSearchInComments(final PsiElement element, final boolean enabled) {
292     if (element instanceof PsiField){
293       JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_FIELD = enabled;
294     }
295     JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_VARIABLE = enabled;
296   }
297
298   public boolean isToSearchForTextOccurrences(final PsiElement element) {
299     if (element instanceof PsiField) {
300       return JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_FIELD;
301     }
302     return JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_VARIABLE;
303   }
304
305   public void setToSearchForTextOccurrences(final PsiElement element, final boolean enabled) {
306     if (element instanceof PsiField) {
307       JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_FIELD = enabled;
308     }
309     JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_VARIABLE = enabled;
310   }
311
312   private static void findSubmemberHidesFieldCollisions(final PsiField field, final String newName, final List<UsageInfo> result) {
313     if (field.getContainingClass() == null) return;
314     if (field.hasModifierProperty(PsiModifier.PRIVATE)) return;
315     final PsiClass containingClass = field.getContainingClass();
316     Collection<PsiClass> inheritors = ClassInheritorsSearch.search(containingClass, true).findAll();
317     for (PsiClass inheritor : inheritors) {
318       PsiField conflictingField = inheritor.findFieldByName(newName, false);
319       if (conflictingField != null) {
320         result.add(new SubmemberHidesMemberUsageInfo(conflictingField, field));
321       }
322     }
323   }
324
325   private static void findLocalHidesFieldCollisions(final PsiElement element, final String newName, final Map<? extends PsiElement, String> allRenames, final List<UsageInfo> result) {
326     if (!(element instanceof PsiLocalVariable) && !(element instanceof PsiParameter)) return;
327
328     PsiClass toplevel = PsiUtil.getTopLevelClass(element);
329     if (toplevel == null) return;
330
331     PsiElement scopeElement;
332     if (element instanceof PsiLocalVariable) {
333       scopeElement = RefactoringUtil.getVariableScope((PsiLocalVariable)element);
334     }
335     else { // Parameter
336       scopeElement = ((PsiParameter) element).getDeclarationScope();
337     }
338
339     LOG.assertTrue(scopeElement != null);
340     scopeElement.accept(new JavaRecursiveElementWalkingVisitor() {
341       @Override public void visitReferenceExpression(PsiReferenceExpression expression) {
342         super.visitReferenceExpression(expression);
343         if (!expression.isQualified()) {
344           PsiElement resolved = expression.resolve();
345           if (resolved instanceof PsiField) {
346             final PsiField field = (PsiField)resolved;
347             String fieldNewName = allRenames.containsKey(field) ? allRenames.get(field) : field.getName();
348             if (newName.equals(fieldNewName)) {
349               result.add(new LocalHidesFieldUsageInfo(expression, element));
350             }
351           }
352         }
353       }
354     });
355   }
356 }