blob: 807700bc52aec0bd86dee41bc77ce47fcc010c25 [file] [log] [blame]
/*
* Copyright 2000-2014 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.safeDelete;
import com.intellij.codeInsight.daemon.impl.quickfix.RemoveUnusedVariableUtil;
import com.intellij.codeInsight.generation.GetterSetterPrototypeProvider;
import com.intellij.find.findUsages.PsiElement2UsageTargetAdapter;
import com.intellij.ide.util.SuperMethodWarningUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.javadoc.PsiDocTag;
import com.intellij.psi.search.searches.FunctionalExpressionSearch;
import com.intellij.psi.search.searches.OverridingMethodsSearch;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.MethodSignatureUtil;
import com.intellij.psi.util.PropertyUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.JavaRefactoringSettings;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.safeDelete.usageInfo.*;
import com.intellij.refactoring.util.RefactoringMessageUtil;
import com.intellij.refactoring.util.RefactoringUIUtil;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewUtil;
import com.intellij.usages.*;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.Processor;
import com.intellij.util.containers.HashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class JavaSafeDeleteProcessor extends SafeDeleteProcessorDelegateBase {
private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.safeDelete.JavaSafeDeleteProcessor");
public boolean handlesElement(final PsiElement element) {
return element instanceof PsiClass || element instanceof PsiMethod ||
element instanceof PsiField || element instanceof PsiParameter || element instanceof PsiLocalVariable || element instanceof PsiPackage;
}
@Nullable
public NonCodeUsageSearchInfo findUsages(final PsiElement element, final PsiElement[] allElementsToDelete, final List<UsageInfo> usages) {
Condition<PsiElement> insideDeletedCondition = getUsageInsideDeletedFilter(allElementsToDelete);
if (element instanceof PsiClass) {
findClassUsages((PsiClass) element, allElementsToDelete, usages);
if (element instanceof PsiTypeParameter) {
findTypeParameterExternalUsages((PsiTypeParameter)element, usages);
}
}
else if (element instanceof PsiMethod) {
insideDeletedCondition = findMethodUsages((PsiMethod) element, allElementsToDelete, usages);
}
else if (element instanceof PsiField) {
insideDeletedCondition = findFieldUsages((PsiField)element, usages, allElementsToDelete);
}
else if (element instanceof PsiParameter) {
LOG.assertTrue(((PsiParameter) element).getDeclarationScope() instanceof PsiMethod);
findParameterUsages((PsiParameter)element, usages);
}
else if (element instanceof PsiLocalVariable) {
for (PsiReference reference : ReferencesSearch.search(element)) {
PsiReferenceExpression referencedElement = (PsiReferenceExpression)reference.getElement();
final PsiStatement statement = PsiTreeUtil.getParentOfType(referencedElement, PsiStatement.class);
boolean isSafeToDelete = PsiUtil.isAccessedForWriting(referencedElement);
boolean hasSideEffects = false;
if (PsiUtil.isOnAssignmentLeftHand(referencedElement)) {
hasSideEffects =
RemoveUnusedVariableUtil
.checkSideEffects(((PsiAssignmentExpression)referencedElement.getParent()).getRExpression(), ((PsiLocalVariable)element),
new ArrayList<PsiElement>());
}
usages.add(new SafeDeleteReferenceJavaDeleteUsageInfo(statement, element, isSafeToDelete && !hasSideEffects));
}
}
return new NonCodeUsageSearchInfo(insideDeletedCondition, element);
}
@Nullable
@Override
public Collection<? extends PsiElement> getElementsToSearch(PsiElement element,
@Nullable Module module,
Collection<PsiElement> allElementsToDelete) {
Project project = element.getProject();
if (element instanceof PsiPackage && module != null) {
final PsiDirectory[] directories = ((PsiPackage)element).getDirectories(module.getModuleScope());
if (directories.length == 0) return null;
return Arrays.asList(directories);
} else if (element instanceof PsiMethod) {
final PsiMethod[] methods =
SuperMethodWarningUtil.checkSuperMethods((PsiMethod)element, RefactoringBundle.message("to.delete.with.usage.search"),
allElementsToDelete);
if (methods.length == 0) return null;
final ArrayList<PsiMethod> psiMethods = new ArrayList<PsiMethod>(Arrays.asList(methods));
psiMethods.add((PsiMethod)element);
return psiMethods;
}
else if (element instanceof PsiParameter && ((PsiParameter) element).getDeclarationScope() instanceof PsiMethod) {
PsiMethod method = (PsiMethod) ((PsiParameter) element).getDeclarationScope();
final Set<PsiParameter> parametersToDelete = new HashSet<PsiParameter>();
parametersToDelete.add((PsiParameter) element);
final int parameterIndex = method.getParameterList().getParameterIndex((PsiParameter) element);
final List<PsiMethod> superMethods = new ArrayList<PsiMethod>(Arrays.asList(method.findDeepestSuperMethods()));
if (superMethods.isEmpty()) {
superMethods.add(method);
}
for (PsiMethod superMethod : superMethods) {
parametersToDelete.add(superMethod.getParameterList().getParameters()[parameterIndex]);
OverridingMethodsSearch.search(superMethod).forEach(new Processor<PsiMethod>() {
public boolean process(PsiMethod overrider) {
parametersToDelete.add(overrider.getParameterList().getParameters()[parameterIndex]);
return true;
}
});
}
if (parametersToDelete.size() > 1 && !ApplicationManager.getApplication().isUnitTestMode()) {
String message = RefactoringBundle.message("0.is.a.part.of.method.hierarchy.do.you.want.to.delete.multiple.parameters", UsageViewUtil.getLongName(method));
if (Messages.showYesNoDialog(project, message, SafeDeleteHandler.REFACTORING_NAME,
Messages.getQuestionIcon()) != Messages.YES) return null;
}
return parametersToDelete;
}
else {
return Collections.singletonList(element);
}
}
@Override
public UsageView showUsages(UsageInfo[] usages, UsageViewPresentation presentation, UsageViewManager manager, PsiElement[] elements) {
final List<PsiElement> overridingMethods = new ArrayList<PsiElement>();
final List<UsageInfo> others = new ArrayList<UsageInfo>();
for (UsageInfo usage : usages) {
if (usage instanceof SafeDeleteOverridingMethodUsageInfo) {
overridingMethods.add(((SafeDeleteOverridingMethodUsageInfo)usage).getOverridingMethod());
} else {
others.add(usage);
}
}
UsageTarget[] targets = new UsageTarget[elements.length + overridingMethods.size()];
for (int i = 0; i < targets.length; i++) {
if (i < elements.length) {
targets[i] = new PsiElement2UsageTargetAdapter(elements[i]);
} else {
targets[i] = new PsiElement2UsageTargetAdapter(overridingMethods.get(i - elements.length));
}
}
return manager.showUsages(targets,
UsageInfoToUsageConverter.convert(elements,
others.toArray(new UsageInfo[others.size()])),
presentation
);
}
public Collection<PsiElement> getAdditionalElementsToDelete(final PsiElement element,
final Collection<PsiElement> allElementsToDelete,
final boolean askUser) {
if (element instanceof PsiField) {
PsiField field = (PsiField)element;
final Project project = element.getProject();
String propertyName = JavaCodeStyleManager.getInstance(project).variableNameToPropertyName(field.getName(), VariableKind.FIELD);
PsiClass aClass = field.getContainingClass();
if (aClass != null) {
boolean isStatic = field.hasModifierProperty(PsiModifier.STATIC);
PsiMethod[] getters = GetterSetterPrototypeProvider.findGetters(aClass, propertyName, isStatic);
if (getters != null) {
final List<PsiMethod> validGetters = new ArrayList<PsiMethod>(1);
for (PsiMethod getter : getters) {
if (!allElementsToDelete.contains(getter) && (getter != null && getter.isPhysical())) {
validGetters.add(getter);
}
}
getters = validGetters.isEmpty() ? null : validGetters.toArray(new PsiMethod[validGetters.size()]);
}
PsiMethod setter = PropertyUtil.findPropertySetter(aClass, propertyName, isStatic, false);
if (allElementsToDelete.contains(setter) || setter != null && !setter.isPhysical()) setter = null;
if (askUser && (getters != null || setter != null)) {
final String message =
RefactoringMessageUtil.getGetterSetterMessage(field.getName(), RefactoringBundle.message("delete.title"), getters != null ? getters[0] : null, setter);
if (!ApplicationManager.getApplication().isUnitTestMode() && Messages.showYesNoDialog(project, message, RefactoringBundle.message("safe.delete.title"), Messages.getQuestionIcon()) != Messages.YES) {
getters = null;
setter = null;
}
}
List<PsiElement> elements = new ArrayList<PsiElement>();
if (setter != null) elements.add(setter);
if (getters != null) Collections.addAll(elements, getters);
return elements;
}
}
return null;
}
public Collection<String> findConflicts(final PsiElement element, final PsiElement[] allElementsToDelete) {
if (element instanceof PsiMethod) {
final PsiClass containingClass = ((PsiMethod)element).getContainingClass();
if (!containingClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
final PsiMethod[] superMethods = ((PsiMethod) element).findSuperMethods();
for (PsiMethod superMethod : superMethods) {
if (isInside(superMethod, allElementsToDelete)) continue;
if (superMethod.hasModifierProperty(PsiModifier.ABSTRACT)) {
String message = RefactoringBundle.message("0.implements.1", RefactoringUIUtil.getDescription(element, true),
RefactoringUIUtil.getDescription(superMethod, true));
return Collections.singletonList(message);
}
}
}
}
return null;
}
@Nullable
public UsageInfo[] preprocessUsages(final Project project, final UsageInfo[] usages) {
ArrayList<UsageInfo> result = new ArrayList<UsageInfo>();
ArrayList<UsageInfo> overridingMethods = new ArrayList<UsageInfo>();
for (UsageInfo usage : usages) {
if (usage.isNonCodeUsage) {
result.add(usage);
}
else if (usage instanceof SafeDeleteOverridingMethodUsageInfo) {
overridingMethods.add(usage);
}
else {
result.add(usage);
}
}
if(!overridingMethods.isEmpty()) {
if (ApplicationManager.getApplication().isUnitTestMode()) {
result.addAll(overridingMethods);
}
else {
OverridingMethodsDialog dialog = new OverridingMethodsDialog(project, overridingMethods);
dialog.show();
if(!dialog.isOK()) return null;
result.addAll(dialog.getSelected());
}
}
return result.toArray(new UsageInfo[result.size()]);
}
public void prepareForDeletion(final PsiElement element) throws IncorrectOperationException {
if (element instanceof PsiVariable) {
((PsiVariable)element).normalizeDeclaration();
}
}
@Override
public boolean isToSearchInComments(PsiElement element) {
if (element instanceof PsiClass) {
return JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_CLASS;
}
else if (element instanceof PsiMethod) {
return JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_METHOD;
}
else if (element instanceof PsiVariable) {
return JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_VARIABLE;
}
else if (element instanceof PsiPackage) {
return JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_PACKAGE;
}
return false;
}
@Override
public void setToSearchInComments(PsiElement element, boolean enabled) {
if (element instanceof PsiClass) {
JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_CLASS = enabled;
}
else if (element instanceof PsiMethod) {
JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_METHOD = enabled;
}
else if (element instanceof PsiVariable) {
JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_VARIABLE = enabled;
}
else if (element instanceof PsiPackage) {
JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_PACKAGE = enabled;
}
}
@Override
public boolean isToSearchForTextOccurrences(PsiElement element) {
if (element instanceof PsiClass) {
return JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_CLASS;
}
else if (element instanceof PsiMethod) {
return JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_METHOD;
}
else if (element instanceof PsiVariable) {
return JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_VARIABLE;
}
else if (element instanceof PsiPackage) {
return JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_PACKAGE;
}
return false;
}
@Override
public void setToSearchForTextOccurrences(PsiElement element, boolean enabled) {
if (element instanceof PsiClass) {
JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_CLASS = enabled;
}
else if (element instanceof PsiMethod) {
JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_METHOD = enabled;
}
else if (element instanceof PsiVariable) {
JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_VARIABLE = enabled;
}
else if (element instanceof PsiPackage) {
JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_PACKAGE = enabled;
}
}
public static Condition<PsiElement> getUsageInsideDeletedFilter(final PsiElement[] allElementsToDelete) {
return new Condition<PsiElement>() {
public boolean value(final PsiElement usage) {
return !(usage instanceof PsiFile) && isInside(usage, allElementsToDelete);
}
};
}
private static void findClassUsages(final PsiClass psiClass, final PsiElement[] allElementsToDelete, final List<UsageInfo> usages) {
final boolean justPrivates = containsOnlyPrivates(psiClass);
ReferencesSearch.search(psiClass).forEach(new Processor<PsiReference>() {
public boolean process(final PsiReference reference) {
final PsiElement element = reference.getElement();
if (!isInside(element, allElementsToDelete)) {
PsiElement parent = element.getParent();
if (parent instanceof PsiReferenceList) {
final PsiElement pparent = parent.getParent();
if (pparent instanceof PsiClass) {
final PsiClass inheritor = (PsiClass) pparent;
//If psiClass contains only private members, then it is safe to remove it and change inheritor's extends/implements accordingly
if (justPrivates) {
if (parent.equals(inheritor.getExtendsList()) || parent.equals(inheritor.getImplementsList())) {
usages.add(new SafeDeleteExtendsClassUsageInfo((PsiJavaCodeReferenceElement)element, psiClass, inheritor));
return true;
}
}
}
}
LOG.assertTrue(element.getTextRange() != null);
usages.add(new SafeDeleteReferenceJavaDeleteUsageInfo(element, psiClass, isInNonStaticImport(element)));
}
return true;
}
});
}
private static boolean isInNonStaticImport(PsiElement element) {
return ImportSearcher.getImport(element, true) != null;
}
private static boolean containsOnlyPrivates(final PsiClass aClass) {
final PsiField[] fields = aClass.getFields();
for (PsiField field : fields) {
if (!field.hasModifierProperty(PsiModifier.PRIVATE)) return false;
}
final PsiMethod[] methods = aClass.getMethods();
for (PsiMethod method : methods) {
if (!method.hasModifierProperty(PsiModifier.PRIVATE)) {
if (method.isConstructor()) { //skip non-private constructors with call to super only
final PsiCodeBlock body = method.getBody();
if (body != null) {
final PsiStatement[] statements = body.getStatements();
if (statements.length == 0) continue;
if (statements.length == 1 && statements[0] instanceof PsiExpressionStatement) {
final PsiExpression expression = ((PsiExpressionStatement)statements[0]).getExpression();
if (expression instanceof PsiMethodCallExpression) {
PsiReferenceExpression methodExpression = ((PsiMethodCallExpression)expression).getMethodExpression();
if (methodExpression.getText().equals(PsiKeyword.SUPER)) {
continue;
}
}
}
}
}
return false;
}
}
final PsiClass[] inners = aClass.getInnerClasses();
for (PsiClass inner : inners) {
if (!inner.hasModifierProperty(PsiModifier.PRIVATE)) return false;
}
return true;
}
private static void findTypeParameterExternalUsages(final PsiTypeParameter typeParameter, final Collection<UsageInfo> usages) {
PsiTypeParameterListOwner owner = typeParameter.getOwner();
if (owner != null) {
final PsiTypeParameterList parameterList = owner.getTypeParameterList();
if (parameterList != null) {
final int paramsCount = parameterList.getTypeParameters().length;
final int index = parameterList.getTypeParameterIndex(typeParameter);
ReferencesSearch.search(owner).forEach(new Processor<PsiReference>() {
public boolean process(final PsiReference reference) {
if (reference instanceof PsiJavaCodeReferenceElement) {
final PsiReferenceParameterList parameterList = ((PsiJavaCodeReferenceElement)reference).getParameterList();
if (parameterList != null) {
PsiTypeElement[] typeArgs = parameterList.getTypeParameterElements();
if (typeArgs.length > index) {
if (typeArgs.length == 1 && paramsCount > 1 && typeArgs[0].getType() instanceof PsiDiamondType) return true;
usages.add(new SafeDeleteReferenceJavaDeleteUsageInfo(typeArgs[index], typeParameter, true));
}
}
}
return true;
}
});
}
}
}
@Nullable
private static Condition<PsiElement> findMethodUsages(PsiMethod psiMethod, final PsiElement[] allElementsToDelete, List<UsageInfo> usages) {
final Collection<PsiReference> references = ReferencesSearch.search(psiMethod).findAll();
if(psiMethod.isConstructor()) {
return findConstructorUsages(psiMethod, references, usages, allElementsToDelete);
}
final PsiMethod[] overridingMethods =
removeDeletedMethods(OverridingMethodsSearch.search(psiMethod, true).toArray(PsiMethod.EMPTY_ARRAY),
allElementsToDelete);
findFunctionalExpressions(usages, ArrayUtil.prepend(psiMethod, overridingMethods));
final HashMap<PsiMethod, Collection<PsiReference>> methodToReferences = new HashMap<PsiMethod, Collection<PsiReference>>();
for (PsiMethod overridingMethod : overridingMethods) {
final Collection<PsiReference> overridingReferences = ReferencesSearch.search(overridingMethod).findAll();
methodToReferences.put(overridingMethod, overridingReferences);
}
final Set<PsiMethod> validOverriding =
validateOverridingMethods(psiMethod, references, Arrays.asList(overridingMethods), methodToReferences, usages,
allElementsToDelete);
for (PsiReference reference : references) {
final PsiElement element = reference.getElement();
if (!isInside(element, allElementsToDelete) && !isInside(element, validOverriding)) {
usages.add(new SafeDeleteReferenceJavaDeleteUsageInfo(element, psiMethod, PsiTreeUtil.getParentOfType(element, PsiImportStaticStatement.class) != null));
}
}
return new Condition<PsiElement>() {
public boolean value(PsiElement usage) {
if(usage instanceof PsiFile) return false;
return isInside(usage, allElementsToDelete) || isInside(usage, validOverriding);
}
};
}
private static void findFunctionalExpressions(final List<UsageInfo> usages, PsiMethod... methods) {
for (PsiMethod method : methods) {
final PsiClass containingClass = method.getContainingClass();
FunctionalExpressionSearch.search(method).forEach(new Processor<PsiFunctionalExpression>() {
@Override
public boolean process(PsiFunctionalExpression expression) {
usages.add(new SafeDeleteFunctionalExpressionUsageInfo(expression, containingClass));
return true;
}
});
}
}
private static PsiMethod[] removeDeletedMethods(PsiMethod[] methods, final PsiElement[] allElementsToDelete) {
ArrayList<PsiMethod> list = new ArrayList<PsiMethod>();
for (PsiMethod method : methods) {
boolean isDeleted = false;
for (PsiElement element : allElementsToDelete) {
if (element == method) {
isDeleted = true;
break;
}
}
if (!isDeleted) {
list.add(method);
}
}
return list.toArray(new PsiMethod[list.size()]);
}
@Nullable
private static Condition<PsiElement> findConstructorUsages(PsiMethod constructor, Collection<PsiReference> originalReferences, List<UsageInfo> usages,
final PsiElement[] allElementsToDelete) {
HashMap<PsiMethod, Collection<PsiReference>> constructorsToRefs = new HashMap<PsiMethod, Collection<PsiReference>>();
HashSet<PsiMethod> newConstructors = new HashSet<PsiMethod>();
if (isTheOnlyEmptyDefaultConstructor(constructor)) return null;
newConstructors.add(constructor);
constructorsToRefs.put(constructor, originalReferences);
HashSet<PsiMethod> passConstructors = new HashSet<PsiMethod>();
do {
passConstructors.clear();
for (PsiMethod method : newConstructors) {
final Collection<PsiReference> references = constructorsToRefs.get(method);
for (PsiReference reference : references) {
PsiMethod overridingConstructor = getOverridingConstructorOfSuperCall(reference.getElement());
if (overridingConstructor != null && !constructorsToRefs.containsKey(overridingConstructor)) {
Collection<PsiReference> overridingConstructorReferences = ReferencesSearch.search(overridingConstructor).findAll();
constructorsToRefs.put(overridingConstructor, overridingConstructorReferences);
passConstructors.add(overridingConstructor);
}
}
}
newConstructors.clear();
newConstructors.addAll(passConstructors);
}
while(!newConstructors.isEmpty());
final Set<PsiMethod> validOverriding =
validateOverridingMethods(constructor, originalReferences, constructorsToRefs.keySet(), constructorsToRefs, usages,
allElementsToDelete);
return new Condition<PsiElement>() {
public boolean value(PsiElement usage) {
if(usage instanceof PsiFile) return false;
return isInside(usage, allElementsToDelete) || isInside(usage, validOverriding);
}
};
}
private static boolean isTheOnlyEmptyDefaultConstructor(final PsiMethod constructor) {
if (constructor.getParameterList().getParameters().length > 0) return false;
final PsiCodeBlock body = constructor.getBody();
if (body != null && body.getStatements().length > 0) return false;
return constructor.getContainingClass().getConstructors().length == 1;
}
private static Set<PsiMethod> validateOverridingMethods(PsiMethod originalMethod, final Collection<PsiReference> originalReferences,
Collection<PsiMethod> overridingMethods, HashMap<PsiMethod, Collection<PsiReference>> methodToReferences,
List<UsageInfo> usages,
final PsiElement[] allElementsToDelete) {
Set<PsiMethod> validOverriding = new LinkedHashSet<PsiMethod>(overridingMethods);
Set<PsiMethod> multipleInterfaceImplementations = new HashSet<PsiMethod>();
boolean anyNewBadRefs;
do {
anyNewBadRefs = false;
for (PsiMethod overridingMethod : overridingMethods) {
if (validOverriding.contains(overridingMethod)) {
final Collection<PsiReference> overridingReferences = methodToReferences.get(overridingMethod);
boolean anyOverridingRefs = false;
for (final PsiReference overridingReference : overridingReferences) {
final PsiElement element = overridingReference.getElement();
if (!isInside(element, allElementsToDelete) && !isInside(element, validOverriding)) {
anyOverridingRefs = true;
break;
}
}
if (!anyOverridingRefs && isMultipleInterfacesImplementation(overridingMethod, originalMethod, allElementsToDelete)) {
anyOverridingRefs = true;
multipleInterfaceImplementations.add(overridingMethod);
}
if (anyOverridingRefs) {
validOverriding.remove(overridingMethod);
anyNewBadRefs = true;
for (PsiReference reference : originalReferences) {
final PsiElement element = reference.getElement();
if (!isInside(element, allElementsToDelete) && !isInside(element, overridingMethods)) {
usages.add(new SafeDeleteReferenceJavaDeleteUsageInfo(element, originalMethod, false));
validOverriding.clear();
}
}
}
}
}
}
while(anyNewBadRefs && !validOverriding.isEmpty());
for (PsiMethod method : validOverriding) {
if (method != originalMethod) {
usages.add(new SafeDeleteOverridingMethodUsageInfo(method, originalMethod));
}
}
for (PsiMethod method : overridingMethods) {
if (!validOverriding.contains(method) &&
!multipleInterfaceImplementations.contains(method) &&
canBePrivate(method, methodToReferences.get(method), validOverriding, allElementsToDelete)) {
usages.add(new SafeDeletePrivatizeMethod(method, originalMethod));
} else {
usages.add(new SafeDeleteOverrideAnnotation(method, originalMethod));
}
}
return validOverriding;
}
private static boolean isMultipleInterfacesImplementation(final PsiMethod method,
PsiMethod originalMethod,
final PsiElement[] allElementsToDelete) {
final PsiMethod[] methods = method.findSuperMethods();
for(PsiMethod superMethod: methods) {
if (ArrayUtil.find(allElementsToDelete, superMethod) < 0 && !MethodSignatureUtil.isSuperMethod(originalMethod, superMethod)) {
return true;
}
}
return false;
}
@Nullable
private static PsiMethod getOverridingConstructorOfSuperCall(final PsiElement element) {
if(element instanceof PsiReferenceExpression && "super".equals(element.getText())) {
PsiElement parent = element.getParent();
if(parent instanceof PsiMethodCallExpression) {
parent = parent.getParent();
if(parent instanceof PsiExpressionStatement) {
parent = parent.getParent();
if(parent instanceof PsiCodeBlock) {
parent = parent.getParent();
if(parent instanceof PsiMethod && ((PsiMethod) parent).isConstructor()) {
return (PsiMethod) parent;
}
}
}
}
}
return null;
}
private static boolean canBePrivate(PsiMethod method, Collection<PsiReference> references, Collection<? extends PsiElement> deleted,
final PsiElement[] allElementsToDelete) {
final PsiClass containingClass = method.getContainingClass();
if(containingClass == null) {
return false;
}
PsiManager manager = method.getManager();
final JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject());
final PsiElementFactory factory = facade.getElementFactory();
final PsiModifierList privateModifierList;
try {
final PsiMethod newMethod = factory.createMethod("x3", PsiType.VOID);
privateModifierList = newMethod.getModifierList();
privateModifierList.setModifierProperty(PsiModifier.PRIVATE, true);
} catch (IncorrectOperationException e) {
LOG.assertTrue(false);
return false;
}
for (PsiReference reference : references) {
final PsiElement element = reference.getElement();
if (!isInside(element, allElementsToDelete) && !isInside(element, deleted)
&& !facade.getResolveHelper().isAccessible(method, privateModifierList, element, null, null)) {
return false;
}
}
return true;
}
private static Condition<PsiElement> findFieldUsages(final PsiField psiField, final List<UsageInfo> usages, final PsiElement[] allElementsToDelete) {
final Condition<PsiElement> isInsideDeleted = getUsageInsideDeletedFilter(allElementsToDelete);
ReferencesSearch.search(psiField).forEach(new Processor<PsiReference>() {
public boolean process(final PsiReference reference) {
if (!isInsideDeleted.value(reference.getElement())) {
final PsiElement element = reference.getElement();
final PsiElement parent = element.getParent();
if (parent instanceof PsiAssignmentExpression && element == ((PsiAssignmentExpression)parent).getLExpression()) {
usages.add(new SafeDeleteFieldWriteReference((PsiAssignmentExpression)parent, psiField));
}
else {
TextRange range = reference.getRangeInElement();
usages.add(new SafeDeleteReferenceJavaDeleteUsageInfo(reference.getElement(), psiField, range.getStartOffset(),
range.getEndOffset(), false, PsiTreeUtil.getParentOfType(element, PsiImportStaticStatement.class) != null));
}
}
return true;
}
});
return isInsideDeleted;
}
private static void findParameterUsages(final PsiParameter parameter, final List<UsageInfo> usages) {
final PsiMethod method = (PsiMethod)parameter.getDeclarationScope();
//search for refs to current method only, do not search for refs to overriding methods, they'll be searched separately
ReferencesSearch.search(method).forEach(new Processor<PsiReference>() {
public boolean process(final PsiReference reference) {
PsiElement element = reference.getElement();
if (element != null) {
JavaSafeDeleteDelegate.EP.forLanguage(element.getLanguage()).createUsageInfoForParameter(reference, usages, parameter, method);
}
return true;
}
});
ReferencesSearch.search(parameter).forEach(new Processor<PsiReference>() {
public boolean process(final PsiReference reference) {
PsiElement element = reference.getElement();
final PsiDocTag docTag = PsiTreeUtil.getParentOfType(element, PsiDocTag.class);
if (docTag != null) {
usages.add(new SafeDeleteReferenceJavaDeleteUsageInfo(docTag, parameter, true));
return true;
}
boolean isSafeDelete = false;
if (element.getParent().getParent() instanceof PsiMethodCallExpression) {
PsiMethodCallExpression call = (PsiMethodCallExpression)element.getParent().getParent();
PsiReferenceExpression methodExpression = call.getMethodExpression();
if (methodExpression.getText().equals(PsiKeyword.SUPER)) {
isSafeDelete = true;
}
else if (methodExpression.getQualifierExpression() instanceof PsiSuperExpression) {
final PsiMethod superMethod = call.resolveMethod();
if (superMethod != null && MethodSignatureUtil.isSuperMethod(superMethod, method)) {
isSafeDelete = true;
}
}
}
usages.add(new SafeDeleteReferenceJavaDeleteUsageInfo(element, parameter, isSafeDelete));
return true;
}
});
findFunctionalExpressions(usages, method);
}
private static boolean isInside(PsiElement place, PsiElement[] ancestors) {
return isInside(place, Arrays.asList(ancestors));
}
private static boolean isInside(PsiElement place, Collection<? extends PsiElement> ancestors) {
for (PsiElement element : ancestors) {
if (isInside(place, element)) return true;
}
return false;
}
public static boolean isInside (PsiElement place, PsiElement ancestor) {
if (SafeDeleteProcessor.isInside(place, ancestor)) return true;
if (PsiTreeUtil.getParentOfType(place, PsiComment.class, false) != null && ancestor instanceof PsiClass) {
final PsiClass aClass = (PsiClass)ancestor;
if (aClass.getParent() instanceof PsiJavaFile) {
final PsiJavaFile file = (PsiJavaFile)aClass.getParent();
if (PsiTreeUtil.isAncestor(file, place, false)) {
if (file.getClasses().length == 1) { // file will be deleted on class deletion
return true;
}
}
}
}
return false;
}
private static class SafeDeleteFunctionalExpressionUsageInfo extends SafeDeleteReferenceUsageInfo {
public SafeDeleteFunctionalExpressionUsageInfo(@NotNull PsiElement element, PsiElement referencedElement) {
super(element, referencedElement, false);
}
@Override
public void deleteElement() throws IncorrectOperationException {}
}
}