blob: 73623205b438bc9640d917ac1c2259e15e760871 [file] [log] [blame]
/*
* Copyright 2000-2013 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.rename;
import com.intellij.codeInsight.generation.GetterSetterPrototypeProvider;
import com.intellij.lang.StdLanguages;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.controlFlow.ControlFlowUtil;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.psi.search.searches.OverridingMethodsSearch;
import com.intellij.psi.util.PropertyUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.HelpID;
import com.intellij.refactoring.JavaRefactoringSettings;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.listeners.RefactoringElementListener;
import com.intellij.refactoring.util.ConflictsUtil;
import com.intellij.refactoring.util.MoveRenameUsageInfo;
import com.intellij.refactoring.util.RefactoringMessageUtil;
import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.usageView.UsageInfo;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.Processor;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class RenameJavaVariableProcessor extends RenameJavaMemberProcessor {
private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.rename.RenameJavaVariableProcessor");
public boolean canProcessElement(@NotNull final PsiElement element) {
return element instanceof PsiVariable;
}
public void renameElement(final PsiElement psiElement,
final String newName,
final UsageInfo[] usages,
@Nullable RefactoringElementListener listener) throws IncorrectOperationException {
PsiVariable variable = (PsiVariable) psiElement;
List<MemberHidesOuterMemberUsageInfo> outerHides = new ArrayList<MemberHidesOuterMemberUsageInfo>();
List<MemberHidesStaticImportUsageInfo> staticImportHides = new ArrayList<MemberHidesStaticImportUsageInfo>();
List<PsiElement> occurrencesToCheckForConflict = new ArrayList<PsiElement>();
// rename all references
for (UsageInfo usage : usages) {
final PsiElement element = usage.getElement();
if (element == null) continue;
if (usage instanceof MemberHidesStaticImportUsageInfo) {
staticImportHides.add((MemberHidesStaticImportUsageInfo)usage);
} else if (usage instanceof LocalHidesFieldUsageInfo) {
PsiJavaCodeReferenceElement collidingRef = (PsiJavaCodeReferenceElement)element;
PsiElement resolved = collidingRef.resolve();
if (resolved instanceof PsiField) {
qualifyMember((PsiField)resolved, collidingRef, newName);
}
else {
// do nothing
}
}
else if (usage instanceof MemberHidesOuterMemberUsageInfo) {
PsiJavaCodeReferenceElement collidingRef = (PsiJavaCodeReferenceElement)element;
PsiField resolved = (PsiField)collidingRef.resolve();
outerHides.add(new MemberHidesOuterMemberUsageInfo(element, resolved));
}
else {
final PsiReference ref;
if (usage instanceof MoveRenameUsageInfo) {
ref = usage.getReference();
}
else {
ref = element.getReference();
}
if (ref != null) {
PsiElement newElem = ref.handleElementRename(newName);
if (variable instanceof PsiField) {
occurrencesToCheckForConflict.add(newElem);
}
}
}
}
// do actual rename
variable.setName(newName);
if (listener != null) {
listener.elementRenamed(variable);
}
if (variable instanceof PsiField) {
for (PsiElement occurrence : occurrencesToCheckForConflict) {
fixPossibleNameCollisionsForFieldRenaming((PsiField) variable, newName, occurrence);
}
}
qualifyOuterMemberReferences(outerHides);
qualifyStaticImportReferences(staticImportHides);
}
private static void fixPossibleNameCollisionsForFieldRenaming(PsiField field, String newName, PsiElement replacedOccurence) throws IncorrectOperationException {
if (!(replacedOccurence instanceof PsiReferenceExpression)) return;
PsiElement elem = ((PsiReferenceExpression)replacedOccurence).resolve();
if (elem == null || elem == field || elem.isEquivalentTo(field)) {
// If reference is unresolved, then field is not hidden by anyone...
return;
}
if (elem instanceof PsiLocalVariable || elem instanceof PsiParameter || (elem instanceof PsiField && elem != replacedOccurence)) {
qualifyMember(field, replacedOccurence, newName);
}
}
public void prepareRenaming(final PsiElement element, final String newName, final Map<PsiElement, String> allRenames) {
if (element instanceof PsiField && StdLanguages.JAVA.equals(element.getLanguage())) {
prepareFieldRenaming((PsiField)element, newName, allRenames);
}
}
private static void prepareFieldRenaming(PsiField field, String newName, final Map<PsiElement, String> allRenames) {
// search for getters/setters
PsiClass aClass = field.getContainingClass();
Project project = field.getProject();
final JavaCodeStyleManager manager = JavaCodeStyleManager.getInstance(project);
final String propertyName = PropertyUtil.suggestPropertyName(field, field.getName());
String newPropertyName = PropertyUtil.suggestPropertyName(field, newName);
boolean isStatic = field.hasModifierProperty(PsiModifier.STATIC);
PsiMethod[] getters = GetterSetterPrototypeProvider.findGetters(aClass, propertyName, isStatic);
PsiMethod setter = PropertyUtil.findPropertySetter(aClass, propertyName, isStatic, false);
boolean shouldRenameSetterParameter = false;
if (setter != null) {
String parameterName = manager.propertyNameToVariableName(propertyName, VariableKind.PARAMETER);
PsiParameter setterParameter = setter.getParameterList().getParameters()[0];
shouldRenameSetterParameter = parameterName.equals(setterParameter.getName());
}
if (getters != null) {
List<PsiMethod> validGetters = new ArrayList<PsiMethod>();
for (PsiMethod getter : getters) {
String newGetterName = GetterSetterPrototypeProvider.suggestNewGetterName(propertyName, newPropertyName, getter);
String getterId = null;
if (newGetterName == null) {
getterId = getter.getName();
newGetterName = PropertyUtil.suggestGetterName(newPropertyName, field.getType(), getterId);
}
if (newGetterName.equals(getterId)) {
continue;
}
else {
boolean valid = true;
for (PsiMethod method : getter.findDeepestSuperMethods()) {
if (method instanceof PsiCompiledElement) {
valid = false;
break;
}
}
if (!valid) continue;
}
validGetters.add(getter);
}
getters = validGetters.isEmpty() ? null : validGetters.toArray(new PsiMethod[validGetters.size()]);
}
String newSetterName = "";
if (setter != null) {
newSetterName = PropertyUtil.suggestSetterName(newPropertyName);
final String newSetterParameterName = manager.propertyNameToVariableName(newPropertyName, VariableKind.PARAMETER);
if (newSetterName.equals(setter.getName())) {
setter = null;
newSetterName = null;
shouldRenameSetterParameter = false;
}
else if (newSetterParameterName.equals(setter.getParameterList().getParameters()[0].getName())) {
shouldRenameSetterParameter = false;
} else {
for (PsiMethod method : setter.findDeepestSuperMethods()) {
if (method instanceof PsiCompiledElement) {
setter = null;
shouldRenameSetterParameter = false;
break;
}
}
}
}
if ((getters != null || setter != null) && askToRenameAccesors(getters != null ? getters[0] : null, setter, newName, project)) {
getters = null;
setter = null;
shouldRenameSetterParameter = false;
}
if (getters != null) {
for (PsiMethod getter : getters) {
String newGetterName = GetterSetterPrototypeProvider.suggestNewGetterName(propertyName, newPropertyName, getter);
if (newGetterName == null) {
newGetterName = PropertyUtil.suggestGetterName(newPropertyName, field.getType(), getter.getName());
}
addOverriddenAndImplemented(getter, newGetterName, allRenames);
}
}
if (setter != null) {
addOverriddenAndImplemented(setter, newSetterName, allRenames);
}
if (shouldRenameSetterParameter) {
PsiParameter parameter = setter.getParameterList().getParameters()[0];
allRenames.put(parameter, manager.propertyNameToVariableName(newPropertyName, VariableKind.PARAMETER));
}
}
private static boolean askToRenameAccesors(PsiMethod getter, PsiMethod setter, String newName, final Project project) {
if (ApplicationManager.getApplication().isUnitTestMode()) return false;
String text = RefactoringMessageUtil.getGetterSetterMessage(newName, RefactoringBundle.message("rename.title"), getter, setter);
return Messages.showYesNoDialog(project, text, RefactoringBundle.message("rename.title"), Messages.getQuestionIcon()) != Messages.YES;
}
private static void addOverriddenAndImplemented(PsiMethod methodPrototype, final String newName, final Map<PsiElement, String> allRenames) {
allRenames.put(methodPrototype, newName);
PsiMethod[] methods = methodPrototype.findDeepestSuperMethods();
if (methods.length == 0) {
methods = new PsiMethod[] {methodPrototype};
}
for (PsiMethod method : methods) {
OverridingMethodsSearch.search(method).forEach(new Processor<PsiMethod>() {
public boolean process(PsiMethod psiMethod) {
RenameProcessor.assertNonCompileElement(psiMethod);
allRenames.put(psiMethod, newName);
return true;
}
});
allRenames.put(method, newName);
}
}
public void findCollisions(final PsiElement element, final String newName, final Map<? extends PsiElement, String> allRenames,
final List<UsageInfo> result) {
if (element instanceof PsiField) {
PsiField field = (PsiField) element;
findMemberHidesOuterMemberCollisions(field, newName, result);
findSubmemberHidesFieldCollisions(field, newName, result);
findCollisionsAgainstNewName(field, newName, result);
}
else if (element instanceof PsiLocalVariable || element instanceof PsiParameter) {
JavaUnresolvableLocalCollisionDetector.findCollisions(element, newName, result);
findLocalHidesFieldCollisions(element, newName, allRenames, result);
}
}
@Override
public void findExistingNameConflicts(PsiElement element,
String newName,
MultiMap<PsiElement, String> conflicts,
Map<PsiElement, String> allRenames) {
for (PsiElement psiElement : allRenames.keySet()) {
RenamePsiElementProcessor.forElement(psiElement).findExistingNameConflicts(psiElement, allRenames.get(psiElement), conflicts);
}
}
@Override
public void findExistingNameConflicts(PsiElement element, String newName, MultiMap<PsiElement, String> conflicts) {
if (element instanceof PsiCompiledElement) return;
if (element instanceof PsiField) {
PsiField refactoredField = (PsiField)element;
if (newName.equals(refactoredField.getName())) return;
ConflictsUtil.checkFieldConflicts(
refactoredField.getContainingClass(),
newName,
conflicts
);
}
}
@Nullable
@NonNls
public String getHelpID(final PsiElement element) {
if (element instanceof PsiField){
return HelpID.RENAME_FIELD;
}
else if (element instanceof PsiLocalVariable){
return HelpID.RENAME_VARIABLE;
}
else if (element instanceof PsiParameter){
return HelpID.RENAME_PARAMETER;
}
return null;
}
public boolean isToSearchInComments(final PsiElement element) {
if (element instanceof PsiField){
return JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_FIELD;
}
return JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_VARIABLE;
}
public void setToSearchInComments(final PsiElement element, final boolean enabled) {
if (element instanceof PsiField){
JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_FIELD = enabled;
}
JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_VARIABLE = enabled;
}
public boolean isToSearchForTextOccurrences(final PsiElement element) {
if (element instanceof PsiField) {
return JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_FIELD;
}
return JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_VARIABLE;
}
public void setToSearchForTextOccurrences(final PsiElement element, final boolean enabled) {
if (element instanceof PsiField) {
JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_FIELD = enabled;
}
JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_VARIABLE = enabled;
}
private static void findSubmemberHidesFieldCollisions(final PsiField field, final String newName, final List<UsageInfo> result) {
if (field.getContainingClass() == null) return;
if (field.hasModifierProperty(PsiModifier.PRIVATE)) return;
final PsiClass containingClass = field.getContainingClass();
Collection<PsiClass> inheritors = ClassInheritorsSearch.search(containingClass, true).findAll();
for (PsiClass inheritor : inheritors) {
PsiField conflictingField = inheritor.findFieldByName(newName, false);
if (conflictingField != null) {
result.add(new SubmemberHidesMemberUsageInfo(conflictingField, field));
}
else { //local class
final PsiMember member = PsiTreeUtil.getParentOfType(inheritor, PsiMember.class);
if (member != null) {
final ArrayList<PsiVariable> variables = new ArrayList<PsiVariable>();
ControlFlowUtil.collectOuterLocals(variables, inheritor, inheritor, member);
for (PsiVariable variable : variables) {
if (newName.equals(variable.getName())) {
result.add(new FieldHidesLocalUsageInfo(variable, field));
}
}
}
}
}
}
private static void findLocalHidesFieldCollisions(final PsiElement element, final String newName, final Map<? extends PsiElement, String> allRenames, final List<UsageInfo> result) {
if (!(element instanceof PsiLocalVariable) && !(element instanceof PsiParameter)) return;
PsiClass toplevel = PsiUtil.getTopLevelClass(element);
if (toplevel == null) return;
PsiElement scopeElement;
if (element instanceof PsiLocalVariable) {
scopeElement = RefactoringUtil.getVariableScope((PsiLocalVariable)element);
}
else { // Parameter
scopeElement = ((PsiParameter) element).getDeclarationScope();
}
LOG.assertTrue(scopeElement != null);
scopeElement.accept(new JavaRecursiveElementWalkingVisitor() {
@Override public void visitReferenceExpression(PsiReferenceExpression expression) {
super.visitReferenceExpression(expression);
if (!expression.isQualified()) {
PsiElement resolved = expression.resolve();
if (resolved instanceof PsiField) {
final PsiField field = (PsiField)resolved;
String fieldNewName = allRenames.containsKey(field) ? allRenames.get(field) : field.getName();
if (newName.equals(fieldNewName)) {
result.add(new LocalHidesFieldUsageInfo(expression, element));
}
}
}
}
});
}
}