blob: f09da834c3483cc580ffaceef1b7b03cc204d4f1 [file] [log] [blame]
/*
* Copyright 2000-2012 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.ide.util.SuperMethodWarningUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Pass;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.PsiElementProcessor;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.psi.search.searches.MethodReferencesSearch;
import com.intellij.psi.search.searches.OverridingMethodsSearch;
import com.intellij.psi.util.MethodSignature;
import com.intellij.psi.util.MethodSignatureUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.TypeConversionUtil;
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.RefactoringUIUtil;
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.HashSet;
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 RenameJavaMethodProcessor extends RenameJavaMemberProcessor {
private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.rename.RenameJavaMethodProcessor");
public boolean canProcessElement(@NotNull final PsiElement element) {
return element instanceof PsiMethod;
}
public void renameElement(final PsiElement psiElement,
final String newName,
final UsageInfo[] usages,
@Nullable RefactoringElementListener listener) throws IncorrectOperationException {
PsiMethod method = (PsiMethod) psiElement;
Set<PsiMethod> methodAndOverriders = new HashSet<PsiMethod>();
Set<PsiClass> containingClasses = new HashSet<PsiClass>();
LinkedHashSet<PsiElement> renamedReferences = new LinkedHashSet<PsiElement>();
List<MemberHidesOuterMemberUsageInfo> outerHides = new ArrayList<MemberHidesOuterMemberUsageInfo>();
List<MemberHidesStaticImportUsageInfo> staticImportHides = new ArrayList<MemberHidesStaticImportUsageInfo>();
methodAndOverriders.add(method);
containingClasses.add(method.getContainingClass());
// do actual rename of overriding/implementing methods and of references to all them
for (UsageInfo usage : usages) {
PsiElement element = usage.getElement();
if (element == null) continue;
if (usage instanceof MemberHidesStaticImportUsageInfo) {
staticImportHides.add((MemberHidesStaticImportUsageInfo)usage);
} else if (usage instanceof MemberHidesOuterMemberUsageInfo) {
PsiJavaCodeReferenceElement collidingRef = (PsiJavaCodeReferenceElement)element;
PsiMethod resolved = (PsiMethod)collidingRef.resolve();
outerHides.add(new MemberHidesOuterMemberUsageInfo(element, resolved));
}
else if (!(element instanceof PsiMethod)) {
final PsiReference ref;
if (usage instanceof MoveRenameUsageInfo) {
ref = usage.getReference();
}
else {
ref = element.getReference();
}
if (ref instanceof PsiImportStaticReferenceElement && ((PsiImportStaticReferenceElement)ref).multiResolve(false).length > 1) {
continue;
}
if (ref != null) {
PsiElement e = processRef(ref, newName);
if (e != null) {
renamedReferences.add(e);
}
}
}
else {
PsiMethod overrider = (PsiMethod)element;
methodAndOverriders.add(overrider);
containingClasses.add(overrider.getContainingClass());
}
}
// do actual rename of method
method.setName(newName);
for (UsageInfo usage : usages) {
PsiElement element = usage.getElement();
if (element instanceof PsiMethod) {
((PsiMethod)element).setName(newName);
}
}
if (listener != null) {
listener.elementRenamed(method);
}
for (PsiElement element: renamedReferences) {
fixNameCollisionsWithInnerClassMethod(element, newName, methodAndOverriders, containingClasses,
method.hasModifierProperty(PsiModifier.STATIC));
}
qualifyOuterMemberReferences(outerHides);
qualifyStaticImportReferences(staticImportHides);
}
/**
* handles rename of refs
* @param ref
* @param newName
* @return
*/
@Nullable
protected PsiElement processRef(PsiReference ref, String newName) {
return ref.handleElementRename(newName);
}
private static void fixNameCollisionsWithInnerClassMethod(final PsiElement element, final String newName,
final Set<PsiMethod> methodAndOverriders, final Set<PsiClass> containingClasses,
final boolean isStatic) throws IncorrectOperationException {
if (!(element instanceof PsiReferenceExpression)) return;
PsiElement elem = ((PsiReferenceExpression)element).resolve();
if (elem instanceof PsiMethod) {
PsiMethod actualMethod = (PsiMethod) elem;
if (!methodAndOverriders.contains(actualMethod)) {
PsiClass outerClass = PsiTreeUtil.getParentOfType(element, PsiClass.class);
while (outerClass != null) {
if (containingClasses.contains(outerClass)) {
qualifyMember(element, newName, outerClass, isStatic);
break;
}
outerClass = PsiTreeUtil.getParentOfType(outerClass, PsiClass.class);
}
}
}
}
@NotNull
public Collection<PsiReference> findReferences(final PsiElement element) {
GlobalSearchScope projectScope = GlobalSearchScope.projectScope(element.getProject());
return MethodReferencesSearch.search((PsiMethod)element, projectScope, true).findAll();
}
public void findCollisions(final PsiElement element, final String newName, final Map<? extends PsiElement, String> allRenames,
final List<UsageInfo> result) {
final PsiMethod methodToRename = (PsiMethod)element;
findSubmemberHidesMemberCollisions(methodToRename, newName, result);
findMemberHidesOuterMemberCollisions((PsiMethod) element, newName, result);
findCollisionsAgainstNewName(methodToRename, newName, result);
findHidingMethodWithOtherSignature(methodToRename, newName, result);
final PsiClass containingClass = methodToRename.getContainingClass();
if (containingClass != null) {
final PsiMethod patternMethod = (PsiMethod)methodToRename.copy();
try {
patternMethod.setName(newName);
final PsiMethod methodInBaseClass = containingClass.findMethodBySignature(patternMethod, true);
if (methodInBaseClass != null && methodInBaseClass.getContainingClass() != containingClass) {
if (methodInBaseClass.hasModifierProperty(PsiModifier.FINAL)) {
result.add(new UnresolvableCollisionUsageInfo(methodInBaseClass, methodToRename) {
@Override
public String getDescription() {
return "Renaming method will override final \"" + RefactoringUIUtil.getDescription(methodInBaseClass, true) + "\"";
}
});
}
}
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
}
private void findHidingMethodWithOtherSignature(final PsiMethod methodToRename, final String newName, final List<UsageInfo> result) {
final PsiClass containingClass = methodToRename.getContainingClass();
if (containingClass != null) {
final PsiMethod prototype = getPrototypeWithNewName(methodToRename, newName);
if (prototype == null || containingClass.findMethodBySignature(prototype, true) != null) return;
final PsiMethod[] methodsByName = containingClass.findMethodsByName(newName, true);
if (methodsByName.length > 0) {
for (UsageInfo info : result) {
final PsiElement element = info.getElement();
if (element instanceof PsiReferenceExpression) {
if (((PsiReferenceExpression)element).resolve() == methodToRename) {
final PsiElement parent = element.getParent();
final PsiReferenceExpression copyRef;
if (parent instanceof PsiMethodCallExpression) {
final PsiMethodCallExpression copy = (PsiMethodCallExpression)JavaPsiFacade.getElementFactory(element.getProject())
.createExpressionFromText(parent.getText(), element);
copyRef = copy.getMethodExpression();
} else {
LOG.assertTrue(element instanceof PsiMethodReferenceExpression, element.getText());
copyRef = (PsiReferenceExpression)element.copy();
}
final PsiReferenceExpression expression = (PsiReferenceExpression)processRef(copyRef, newName);
if (expression == null) continue;
final JavaResolveResult resolveResult = expression.advancedResolve(true);
final PsiMember resolveResultElement = (PsiMember)resolveResult.getElement();
if (resolveResult.isValidResult() && resolveResultElement != null) {
result.add(new UnresolvableCollisionUsageInfo(element, methodToRename) {
@Override
public String getDescription() {
return "Method call would be linked to \"" + RefactoringUIUtil.getDescription(resolveResultElement, true) +
"\" after rename";
}
});
break;
}
}
}
}
}
}
}
private static PsiMethod getPrototypeWithNewName(PsiMethod methodToRename, String newName) {
final PsiMethod prototype = (PsiMethod)methodToRename.copy();
try {
prototype.setName(newName);
}
catch (IncorrectOperationException e) {
LOG.error(e);
return null;
}
return prototype;
}
public void findExistingNameConflicts(final PsiElement element, final String newName, final MultiMap<PsiElement, String> conflicts) {
if (element instanceof PsiCompiledElement) return;
final PsiMethod refactoredMethod = (PsiMethod)element;
if (newName.equals(refactoredMethod.getName())) return;
final PsiMethod prototype = getPrototypeWithNewName(refactoredMethod, newName);
if (prototype == null) return;
ConflictsUtil.checkMethodConflicts(
refactoredMethod.getContainingClass(),
refactoredMethod,
prototype,
conflicts);
}
@Override
public void prepareRenaming(PsiElement element, final String newName, final Map<PsiElement, String> allRenames, SearchScope scope) {
final PsiMethod method = (PsiMethod) element;
OverridingMethodsSearch.search(method, scope, true).forEach(new Processor<PsiMethod>() {
public boolean process(PsiMethod overrider) {
if (overrider instanceof PsiMirrorElement) {
final PsiElement prototype = ((PsiMirrorElement)overrider).getPrototype();
if (prototype instanceof PsiMethod) {
overrider = (PsiMethod)prototype;
}
}
if (overrider instanceof SyntheticElement) return true;
final String overriderName = overrider.getName();
final String baseName = method.getName();
final String newOverriderName = RefactoringUtil.suggestNewOverriderName(overriderName, baseName, newName);
if (newOverriderName != null) {
RenameProcessor.assertNonCompileElement(overrider);
allRenames.put(overrider, newOverriderName);
}
return true;
}
});
}
@NonNls
public String getHelpID(final PsiElement element) {
return HelpID.RENAME_METHOD;
}
public boolean isToSearchInComments(final PsiElement psiElement) {
return JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_METHOD;
}
public void setToSearchInComments(final PsiElement element, final boolean enabled) {
JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_METHOD = enabled;
}
@Nullable
public PsiElement substituteElementToRename(PsiElement element, Editor editor) {
PsiMethod psiMethod = (PsiMethod)element;
if (psiMethod.isConstructor()) {
PsiClass containingClass = psiMethod.getContainingClass();
if (containingClass == null) return null;
if (Comparing.strEqual(psiMethod.getName(), containingClass.getName())) {
element = containingClass;
if (!PsiElementRenameHandler.canRename(element.getProject(), editor, element)) {
return null;
}
return element;
}
}
return SuperMethodWarningUtil.checkSuperMethod(psiMethod, RefactoringBundle.message("to.rename"));
}
@Override
public void substituteElementToRename(@NotNull PsiElement element,
@NotNull final Editor editor,
@NotNull final Pass<PsiElement> renameCallback) {
final PsiMethod psiMethod = (PsiMethod)element;
if (psiMethod.isConstructor()) {
final PsiClass containingClass = psiMethod.getContainingClass();
if (containingClass == null) return;
if (!Comparing.strEqual(psiMethod.getName(), containingClass.getName())) {
renameCallback.pass(psiMethod);
return;
}
super.substituteElementToRename(element, editor, renameCallback);
}
else {
SuperMethodWarningUtil.checkSuperMethod(psiMethod, "Rename", new PsiElementProcessor<PsiMethod>() {
@Override
public boolean execute(@NotNull PsiMethod method) {
if (!PsiElementRenameHandler.canRename(method.getProject(), editor, method)) return false;
renameCallback.pass(method);
return false;
}
}, editor);
}
}
private static void findSubmemberHidesMemberCollisions(final PsiMethod method, final String newName, final List<UsageInfo> result) {
final PsiClass containingClass = method.getContainingClass();
if (containingClass == null) return;
if (method.hasModifierProperty(PsiModifier.PRIVATE)) return;
Collection<PsiClass> inheritors = ClassInheritorsSearch.search(containingClass, true).findAll();
MethodSignature oldSignature = method.getSignature(PsiSubstitutor.EMPTY);
MethodSignature newSignature = MethodSignatureUtil.createMethodSignature(newName, oldSignature.getParameterTypes(),
oldSignature.getTypeParameters(),
oldSignature.getSubstitutor(),
method.isConstructor());
for (PsiClass inheritor : inheritors) {
PsiSubstitutor superSubstitutor = TypeConversionUtil.getSuperClassSubstitutor(containingClass, inheritor, PsiSubstitutor.EMPTY);
final PsiMethod[] methodsByName = inheritor.findMethodsByName(newName, false);
for (PsiMethod conflictingMethod : methodsByName) {
if (newSignature.equals(conflictingMethod.getSignature(superSubstitutor))) {
result.add(new SubmemberHidesMemberUsageInfo(conflictingMethod, method));
break;
}
}
}
}
public boolean isToSearchForTextOccurrences(final PsiElement element) {
return JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_METHOD;
}
public void setToSearchForTextOccurrences(final PsiElement element, final boolean enabled) {
JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_METHOD = enabled;
}
}