org.jetbrains.jet.plugin.refactoring.safeDelete.KotlinSafeDeleteProcessor.java Source code

Java tutorial

Introduction

Here is the source code for org.jetbrains.jet.plugin.refactoring.safeDelete.KotlinSafeDeleteProcessor.java

Source

/*
 * Copyright 2010-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 org.jetbrains.jet.plugin.refactoring.safeDelete;

import com.intellij.ide.IdeBundle;
import com.intellij.openapi.application.ApplicationManager;
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.psi.*;
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.PsiTreeUtil;
import com.intellij.refactoring.safeDelete.JavaSafeDeleteProcessor;
import com.intellij.refactoring.safeDelete.NonCodeUsageSearchInfo;
import com.intellij.refactoring.safeDelete.usageInfo.SafeDeleteOverrideAnnotation;
import com.intellij.refactoring.safeDelete.usageInfo.SafeDeleteOverridingMethodUsageInfo;
import com.intellij.refactoring.safeDelete.usageInfo.SafeDeleteReferenceJavaDeleteUsageInfo;
import com.intellij.refactoring.safeDelete.usageInfo.SafeDeleteReferenceSimpleDeleteUsageInfo;
import com.intellij.usageView.UsageInfo;
import com.intellij.util.ArrayUtilRt;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.asJava.LightClassUtil;
import org.jetbrains.jet.lang.descriptors.CallableMemberDescriptor;
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
import org.jetbrains.jet.lang.descriptors.Modality;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.BindingContextUtils;
import org.jetbrains.jet.lang.resolve.java.JetClsMethod;
import org.jetbrains.jet.lexer.JetTokens;
import org.jetbrains.jet.plugin.JetBundle;
import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache;
import org.jetbrains.jet.plugin.refactoring.JetRefactoringUtil;

import java.util.*;

public class KotlinSafeDeleteProcessor extends JavaSafeDeleteProcessor {
    public static boolean canDeleteElement(@NotNull PsiElement element) {
        return element instanceof JetClassOrObject || element instanceof JetObjectDeclarationName
                || element instanceof JetNamedFunction || element instanceof PsiMethod
                || element instanceof JetProperty || element instanceof JetTypeParameter
                || element instanceof JetParameter;
    }

    @Override
    public boolean handlesElement(@NotNull PsiElement element) {
        return canDeleteElement(element);
    }

    @NotNull
    protected static NonCodeUsageSearchInfo getSearchInfo(@NotNull PsiElement element,
            @NotNull final Collection<? extends PsiElement> ignoredElements) {

        return new NonCodeUsageSearchInfo(new Condition<PsiElement>() {
            @Override
            public boolean value(PsiElement usage) {
                if (usage instanceof JetFile)
                    return false;
                return isInside(usage, ignoredElements);
            }
        }, element);
    }

    @NotNull
    protected static NonCodeUsageSearchInfo getSearchInfo(@NotNull PsiElement element,
            @NotNull PsiElement[] ignoredElements) {
        return getSearchInfo(element, Arrays.asList(ignoredElements));
    }

    @Nullable
    @Override
    public NonCodeUsageSearchInfo findUsages(@NotNull PsiElement element, @NotNull PsiElement[] allElementsToDelete,
            @NotNull List<UsageInfo> result) {
        if (element instanceof JetClassOrObject) {
            return findClassOrObjectUsages(element, (JetClassOrObject) element, allElementsToDelete, result);
        }
        if (element instanceof JetObjectDeclarationName) {
            PsiElement parent = getObjectDeclarationOrFail(element);
            return findClassOrObjectUsages(element, (JetObjectDeclaration) parent, allElementsToDelete, result);
        }
        if (element instanceof JetNamedFunction) {
            return findFunctionUsages((JetNamedFunction) element, allElementsToDelete, result);
        }
        if (element instanceof PsiMethod) {
            return findPsiMethodUsages((PsiMethod) element, allElementsToDelete, result);
        }
        if (element instanceof JetProperty) {
            JetProperty property = (JetProperty) element;

            if (property.isLocal()) {
                return findLocalVariableUsages(property, allElementsToDelete, result);
            }
            return findPropertyUsages(property, allElementsToDelete, result);
        }
        if (element instanceof JetTypeParameter) {
            return findTypeParameterUsages((JetTypeParameter) element, allElementsToDelete, result);
        }
        if (element instanceof JetParameter) {
            JetParameter jetParameter = (JetParameter) element;

            PsiParameter psiParameter = getPsiParameter(jetParameter);
            if (psiParameter != null) {
                super.findUsages(psiParameter, allElementsToDelete, result);
            }

            return findParameterUsages(jetParameter, allElementsToDelete, result);
        }

        return getSearchInfo(element, allElementsToDelete);
    }

    @NotNull
    private static PsiElement getObjectDeclarationOrFail(@NotNull PsiElement element) {
        PsiElement parent = element.getParent();
        assert parent instanceof JetObjectDeclaration;
        return parent;
    }

    @SuppressWarnings("MethodOverridesPrivateMethodOfSuperclass")
    protected static boolean isInside(@NotNull PsiElement place, @NotNull PsiElement[] ancestors) {
        return isInside(place, Arrays.asList(ancestors));
    }

    @SuppressWarnings("MethodOverridesPrivateMethodOfSuperclass")
    protected static boolean isInside(@NotNull PsiElement place,
            @NotNull Collection<? extends PsiElement> ancestors) {
        for (PsiElement element : ancestors) {
            if (isInside(place, element))
                return true;
        }
        return false;
    }

    @SuppressWarnings("MethodOverridesStaticMethodOfSuperclass")
    public static boolean isInside(@NotNull PsiElement place, @NotNull PsiElement ancestor) {
        if (ancestor instanceof JetClsMethod) {
            ancestor = ((JetClsMethod) ancestor).getOrigin();
        }
        return JavaSafeDeleteProcessor.isInside(place, ancestor);
    }

    @NotNull
    protected static NonCodeUsageSearchInfo findClassOrObjectUsages(@NotNull PsiElement referencedElement,
            @NotNull final JetClassOrObject classOrObject, @NotNull final PsiElement[] allElementsToDelete,
            @NotNull final List<UsageInfo> result) {
        ReferencesSearch.search(referencedElement).forEach(new Processor<PsiReference>() {
            @Override
            public boolean process(PsiReference reference) {
                PsiElement element = reference.getElement();

                if (!isInside(element, allElementsToDelete)) {
                    JetImportDirective importDirective = PsiTreeUtil.getParentOfType(element,
                            JetImportDirective.class, false);
                    if (importDirective != null) {
                        result.add(new SafeDeleteImportDirectiveUsageInfo(importDirective, classOrObject));
                        return true;
                    }

                    result.add(new SafeDeleteReferenceSimpleDeleteUsageInfo(element, classOrObject, false));
                }
                return true;
            }
        });

        return getSearchInfo(referencedElement, allElementsToDelete);
    }

    @Nullable
    protected NonCodeUsageSearchInfo findPsiMethodUsages(@NotNull PsiMethod method,
            @NotNull PsiElement[] allElementsToDelete, @NotNull List<UsageInfo> result) {
        List<UsageInfo> javaUsages = new ArrayList<UsageInfo>();
        NonCodeUsageSearchInfo searchInfo = super.findUsages(method, allElementsToDelete, javaUsages);

        for (UsageInfo usageInfo : javaUsages) {
            if (usageInfo instanceof SafeDeleteOverridingMethodUsageInfo) {
                SafeDeleteOverridingMethodUsageInfo overrideUsageInfo = (SafeDeleteOverridingMethodUsageInfo) usageInfo;
                usageInfo = new KotlinSafeDeleteOverridingUsageInfo(
                        overrideUsageInfo.getSmartPointer().getElement(), overrideUsageInfo.getReferencedElement());
            } else if (usageInfo instanceof SafeDeleteOverrideAnnotation) {
                SafeDeleteOverrideAnnotation overrideAnnotationUsageInfo = (SafeDeleteOverrideAnnotation) usageInfo;
                usageInfo = new KotlinSafeDeleteOverrideAnnotation(
                        overrideAnnotationUsageInfo.getSmartPointer().getElement(),
                        overrideAnnotationUsageInfo.getReferencedElement());
            }
            result.add(usageInfo);
        }

        return searchInfo;
    }

    @NotNull
    private static <T, C extends T> List<C> difference(@NotNull Collection<C> from, @NotNull T[] a) {
        List<C> list = new ArrayList<C>(from);
        list.removeAll(Arrays.asList(a));
        return list;
    }

    private static void processDeclarationUsages(@NotNull JetDeclaration declaration,
            @NotNull PsiElement[] allElementsToDelete, @NotNull List<UsageInfo> result,
            @NotNull Collection<PsiReference> references,
            @NotNull List<? extends PsiElement> overridingDeclarations) {
        for (PsiReference reference : references) {
            PsiElement element = reference.getElement();
            if (!isInside(element, allElementsToDelete) && !isInside(element, overridingDeclarations)) {
                JetImportDirective importDirective = PsiTreeUtil.getParentOfType(element, JetImportDirective.class,
                        false);
                if (importDirective != null) {
                    result.add(new SafeDeleteImportDirectiveUsageInfo(importDirective, declaration));
                } else {
                    result.add(new SafeDeleteReferenceSimpleDeleteUsageInfo(element, declaration, false));
                }
            }
        }
    }

    @NotNull
    protected static NonCodeUsageSearchInfo findFunctionUsages(@NotNull JetNamedFunction function,
            @NotNull PsiElement[] allElementsToDelete, @NotNull List<UsageInfo> result) {
        PsiMethod lightMethod = LightClassUtil.getLightClassMethod(function);
        if (lightMethod == null) {
            return getSearchInfo(function, allElementsToDelete);
        }

        Collection<PsiReference> references = ReferencesSearch.search(function).findAll();
        List<PsiMethod> overridingMethods = difference(OverridingMethodsSearch.search(lightMethod, true).findAll(),
                allElementsToDelete);

        processDeclarationUsages(function, allElementsToDelete, result, references, overridingMethods);

        Map<PsiMethod, Collection<PsiReference>> methodToReferences = getOverridingUsagesMap(overridingMethods);
        Set<PsiMethod> safeOverriding = filterSafeOverridingMethods(lightMethod, references, overridingMethods,
                methodToReferences, result, allElementsToDelete);

        List<PsiElement> ignoredElements = new ArrayList<PsiElement>(safeOverriding);
        ContainerUtil.addAll(ignoredElements, allElementsToDelete);
        return getSearchInfo(function, ignoredElements);
    }

    @NotNull
    protected static NonCodeUsageSearchInfo findPropertyUsages(@NotNull JetProperty property,
            @NotNull PsiElement[] allElementsToDelete, @NotNull List<UsageInfo> result) {
        LightClassUtil.PropertyAccessorsPsiMethods propertyMethods = LightClassUtil
                .getLightClassPropertyMethods(property);
        PsiMethod getter = propertyMethods.getGetter();
        PsiMethod setter = propertyMethods.getSetter();

        Collection<PsiReference> references = ReferencesSearch.search(property).findAll();

        Collection<PsiMethod> getterOverriding = (getter != null)
                ? OverridingMethodsSearch.search(getter, true).findAll()
                : Collections.<PsiMethod>emptyList();
        Collection<PsiMethod> setterOverriding = (setter != null)
                ? OverridingMethodsSearch.search(setter, true).findAll()
                : Collections.<PsiMethod>emptyList();

        List<PsiMethod> overridingMethods = new ArrayList<PsiMethod>();
        overridingMethods.addAll(getterOverriding);
        overridingMethods.addAll(setterOverriding);
        overridingMethods = difference(overridingMethods, allElementsToDelete);

        processDeclarationUsages(property, allElementsToDelete, result, references, overridingMethods);

        Map<PsiMethod, Collection<PsiReference>> methodToReferences = getOverridingUsagesMap(overridingMethods);
        Set<PsiMethod> safeGetterOverriding = getter != null
                ? filterSafeOverridingMethods(getter, references, getterOverriding, methodToReferences, result,
                        allElementsToDelete)
                : Collections.<PsiMethod>emptySet();
        Set<PsiMethod> safeSetterOverriding = setter != null
                ? filterSafeOverridingMethods(setter, references, setterOverriding, methodToReferences, result,
                        allElementsToDelete)
                : Collections.<PsiMethod>emptySet();

        List<PsiElement> ignoredElements = new ArrayList<PsiElement>(safeGetterOverriding);
        ignoredElements.addAll(safeSetterOverriding);
        ContainerUtil.addAll(ignoredElements, allElementsToDelete);
        return getSearchInfo(property, ignoredElements);
    }

    @NotNull
    private static Map<PsiMethod, Collection<PsiReference>> getOverridingUsagesMap(
            @NotNull List<PsiMethod> overridingMethods) {
        Map<PsiMethod, Collection<PsiReference>> methodToReferences = new HashMap<PsiMethod, Collection<PsiReference>>();
        for (PsiMethod overridingMethod : overridingMethods) {
            Collection<PsiReference> overridingReferences = ReferencesSearch
                    .search(overridingMethod instanceof JetClsMethod ? ((JetClsMethod) overridingMethod).getOrigin()
                            : overridingMethod)
                    .findAll();
            methodToReferences.put(overridingMethod, overridingReferences);
        }
        return methodToReferences;
    }

    @NotNull
    protected static NonCodeUsageSearchInfo findLocalVariableUsages(@NotNull final JetProperty property,
            @NotNull final PsiElement[] allElementsToDelete, @NotNull final List<UsageInfo> result) {
        ReferencesSearch.search(property, property.getUseScope()).forEach(new Processor<PsiReference>() {
            @Override
            public boolean process(PsiReference reference) {
                PsiElement element = reference.getElement();
                if (!isInside(element, allElementsToDelete)) {
                    result.add(new SafeDeleteReferenceSimpleDeleteUsageInfo(element, property, false));
                }
                return true;
            }
        });

        return getSearchInfo(property, allElementsToDelete);
    }

    @NotNull
    protected static NonCodeUsageSearchInfo findParameterUsages(@NotNull final JetParameter parameter,
            @NotNull final PsiElement[] allElementsToDelete, @NotNull final List<UsageInfo> result) {
        NonCodeUsageSearchInfo searchInfo = getSearchInfo(parameter, allElementsToDelete);

        final JetNamedFunction function = PsiTreeUtil.getParentOfType(parameter, JetNamedFunction.class);
        if (function == null || parameter.getParent() != function.getValueParameterList())
            return searchInfo;

        final int parameterIndex = function.getValueParameters().indexOf(parameter);

        ReferencesSearch.search(parameter, parameter.getUseScope()).forEach(new Processor<PsiReference>() {
            @Override
            public boolean process(PsiReference reference) {
                PsiElement element = reference.getElement();
                if (!isInside(element, allElementsToDelete)) {
                    result.add(new SafeDeleteReferenceSimpleDeleteUsageInfo(element, parameter, false));
                }
                return true;
            }
        });

        ReferencesSearch.search(function).forEach(new Processor<PsiReference>() {
            @Override
            public boolean process(PsiReference reference) {
                processParameterUsageInCall(reference, function, parameterIndex, result, parameter);
                return true;
            }
        });

        return searchInfo;
    }

    static void processParameterUsageInCall(@NotNull PsiReference reference,
            @NotNull PsiElement originalDeclaration, int parameterIndex, @NotNull List<UsageInfo> result,
            @NotNull PsiElement parameter) {
        PsiElement element = reference.getElement();

        JetCallExpression callExpression = PsiTreeUtil.getParentOfType(reference.getElement(),
                JetCallExpression.class, false);
        if (callExpression == null)
            return;

        JetExpression calleeExpression = callExpression.getCalleeExpression();
        if (!(calleeExpression instanceof JetReferenceExpression
                && PsiTreeUtil.isAncestor(calleeExpression, element, false)))
            return;

        BindingContext bindingContext = AnalyzerFacadeWithCache
                .analyzeFileWithCache((JetFile) element.getContainingFile()).getBindingContext();
        DeclarationDescriptor descriptor = bindingContext.get(BindingContext.REFERENCE_TARGET,
                (JetReferenceExpression) calleeExpression);
        if (descriptor == null)
            return;

        PsiElement declaration = BindingContextUtils.descriptorToDeclaration(bindingContext, descriptor);
        if (originalDeclaration.equals(declaration)) {
            List<? extends ValueArgument> args = callExpression.getValueArguments();
            int argCount = args.size();
            if (parameterIndex < argCount) {
                result.add(new SafeDeleteValueArgumentListUsageInfo((JetValueArgument) args.get(parameterIndex),
                        parameter));
            } else {
                List<JetExpression> lambdaArgs = callExpression.getFunctionLiteralArguments();
                int lambdaIndex = parameterIndex - argCount;
                if (lambdaIndex < lambdaArgs.size()) {
                    result.add(new SafeDeleteReferenceSimpleDeleteUsageInfo(lambdaArgs.get(lambdaIndex), parameter,
                            true));
                }
            }
        }
    }

    @NotNull
    protected static NonCodeUsageSearchInfo findTypeParameterUsages(@NotNull final JetTypeParameter parameter,
            @NotNull final PsiElement[] allElementsToDelete, @NotNull final List<UsageInfo> result) {
        NonCodeUsageSearchInfo searchInfo = getSearchInfo(parameter, allElementsToDelete);

        ReferencesSearch.search(parameter).forEach(new Processor<PsiReference>() {
            @Override
            public boolean process(PsiReference reference) {
                PsiElement element = reference.getElement();

                if (!isInside(element, allElementsToDelete)) {
                    result.add(new SafeDeleteReferenceSimpleDeleteUsageInfo(element, parameter, false));
                }
                return true;
            }
        });

        JetTypeParameterListOwner owner = PsiTreeUtil.getParentOfType(parameter, JetTypeParameterListOwner.class);
        if (owner == null)
            return searchInfo;

        List<JetTypeParameter> parameterList = owner.getTypeParameters();
        final int parameterCount = parameterList.size();
        final int parameterIndex = parameterList.indexOf(parameter);

        ReferencesSearch.search(owner).forEach(new Processor<PsiReference>() {
            @Override
            public boolean process(PsiReference reference) {
                if (reference instanceof PsiJavaCodeReferenceElement) {
                    processJavaTypeArgumentListCandidate((PsiJavaCodeReferenceElement) reference, parameterIndex,
                            parameterCount, result, parameter);
                } else {
                    processKotlinTypeArgumentListCandidate(reference, parameterIndex, result, parameter);
                }
                return true;
            }
        });

        return searchInfo;
    }

    private static void processJavaTypeArgumentListCandidate(@NotNull PsiJavaCodeReferenceElement reference,
            int parameterIndex, int parameterCount, @NotNull List<UsageInfo> result,
            @NotNull PsiElement parameter) {
        PsiReferenceParameterList parameterList = reference.getParameterList();
        if (parameterList != null) {
            PsiTypeElement[] typeArgs = parameterList.getTypeParameterElements();
            if (typeArgs.length > parameterIndex) {
                if (typeArgs.length == 1 && parameterCount > 1 && typeArgs[0].getType() instanceof PsiDiamondType) {
                    return;
                }
                result.add(new SafeDeleteReferenceJavaDeleteUsageInfo(typeArgs[parameterIndex], parameter, true));
            }
        }
    }

    private static void processKotlinTypeArgumentListCandidate(@NotNull PsiReference reference, int parameterIndex,
            @NotNull List<UsageInfo> result, @NotNull JetTypeParameter parameter) {
        PsiElement referencedElement = reference.getElement();

        JetTypeArgumentList argList = null;

        JetUserType type = PsiTreeUtil.getParentOfType(referencedElement, JetUserType.class);
        if (type != null) {
            argList = type.getTypeArgumentList();
        } else {
            JetCallExpression callExpression = PsiTreeUtil.getParentOfType(referencedElement,
                    JetCallExpression.class);
            if (callExpression != null) {
                argList = callExpression.getTypeArgumentList();
            }
        }

        if (argList != null) {
            List<JetTypeProjection> projections = argList.getArguments();
            if (parameterIndex < projections.size()) {
                result.add(new SafeDeleteTypeArgumentListUsageInfo(projections.get(parameterIndex), parameter));
            }
        }
    }

    /*
     * Mostly copied from JavaSafeDeleteProcessor.validateOverridingMethods
     * Revision: d4fc033
     * (simplified and implemented proper treatment of light methods)
     */
    private static Set<PsiMethod> filterSafeOverridingMethods(@NotNull PsiMethod originalMethod,
            @NotNull Collection<PsiReference> originalReferences, @NotNull Collection<PsiMethod> overridingMethods,
            @NotNull Map<PsiMethod, Collection<PsiReference>> methodToReferences, @NotNull List<UsageInfo> usages,
            @NotNull PsiElement[] allElementsToDelete) {
        Set<PsiMethod> validOverriding = new LinkedHashSet<PsiMethod>(overridingMethods);
        boolean anyNewBadRefs;
        do {
            anyNewBadRefs = false;
            for (PsiMethod overridingMethod : overridingMethods) {
                if (validOverriding.contains(overridingMethod)) {
                    Collection<PsiReference> overridingReferences = methodToReferences.get(overridingMethod);
                    boolean anyOverridingRefs = false;
                    for (PsiReference overridingReference : overridingReferences) {
                        PsiElement element = overridingReference.getElement();
                        if (!isInside(element, allElementsToDelete) && !isInside(element, validOverriding)) {
                            anyOverridingRefs = true;
                            break;
                        }
                    }

                    if (!anyOverridingRefs && isMultipleInterfacesImplementation(overridingMethod, originalMethod,
                            allElementsToDelete)) {
                        anyOverridingRefs = true;
                    }

                    if (anyOverridingRefs) {
                        validOverriding.remove(overridingMethod);
                        anyNewBadRefs = true;

                        for (PsiReference reference : originalReferences) {
                            PsiElement element = reference.getElement();
                            if (!isInside(element, allElementsToDelete) && !isInside(element, overridingMethods)) {
                                validOverriding.clear();
                            }
                        }
                    }
                }
            }
        } while (anyNewBadRefs && !validOverriding.isEmpty());

        for (PsiMethod method : validOverriding) {
            if (method != originalMethod) {
                usages.add(new KotlinSafeDeleteOverridingUsageInfo(method, originalMethod));
            }
        }

        return validOverriding;
    }

    @SuppressWarnings("MethodOverridesPrivateMethodOfSuperclass")
    private static boolean isMultipleInterfacesImplementation(@NotNull PsiMethod method,
            @NotNull PsiMethod originalMethod, @NotNull PsiElement[] ignore) {
        PsiMethod[] methods = method.findSuperMethods();
        for (PsiMethod superMethod : methods) {
            PsiElement relevantElement = superMethod instanceof JetClsMethod
                    ? ((JetClsMethod) superMethod).getOrigin()
                    : superMethod;
            relevantElement = JetPsiUtil.ascendIfPropertyAccessor(relevantElement);
            if (ArrayUtilRt.find(ignore, relevantElement) < 0
                    && !MethodSignatureUtil.isSuperMethod(originalMethod, superMethod)) {
                return true;
            }
        }
        return false;
    }

    @Override
    @Nullable
    public Collection<String> findConflicts(@NotNull PsiElement element,
            @NotNull PsiElement[] allElementsToDelete) {
        if (element instanceof JetNamedFunction || element instanceof JetProperty) {
            JetClass jetClass = PsiTreeUtil.getParentOfType(element, JetClass.class);
            if (jetClass == null || jetClass.getBody() != element.getParent())
                return null;

            JetModifierList modifierList = jetClass.getModifierList();
            if (modifierList != null && modifierList.hasModifier(JetTokens.ABSTRACT_KEYWORD))
                return null;

            BindingContext bindingContext = AnalyzerFacadeWithCache
                    .analyzeFileWithCache((JetFile) element.getContainingFile()).getBindingContext();

            DeclarationDescriptor declarationDescriptor = bindingContext
                    .get(BindingContext.DECLARATION_TO_DESCRIPTOR, element);
            if (!(declarationDescriptor instanceof CallableMemberDescriptor))
                return null;

            List<String> messages = new ArrayList<String>();
            CallableMemberDescriptor callableDescriptor = (CallableMemberDescriptor) declarationDescriptor;
            for (CallableMemberDescriptor overridenDescriptor : callableDescriptor.getOverriddenDescriptors()) {
                if (overridenDescriptor.getModality() == Modality.ABSTRACT) {
                    String message = JetBundle.message("x.implements.y",
                            JetRefactoringUtil.formatFunction(callableDescriptor, bindingContext, true),
                            JetRefactoringUtil.formatClass(callableDescriptor.getContainingDeclaration(),
                                    bindingContext, true),
                            JetRefactoringUtil.formatFunction(overridenDescriptor, bindingContext, true),
                            JetRefactoringUtil.formatClass(overridenDescriptor.getContainingDeclaration(),
                                    bindingContext, true));
                    messages.add(message);
                }
            }

            if (!messages.isEmpty())
                return messages;
        }
        return super.findConflicts(element, allElementsToDelete);
    }

    /*
     * Mostly copied from JavaSafeDeleteProcessor.preprocessUsages
     * Revision: d4fc033
     * (replaced original dialog)
     */
    @Nullable
    @Override
    public UsageInfo[] preprocessUsages(@NotNull Project project, @NotNull UsageInfo[] usages) {
        ArrayList<UsageInfo> result = new ArrayList<UsageInfo>();
        ArrayList<UsageInfo> overridingMethodUsages = new ArrayList<UsageInfo>();

        for (UsageInfo usage : usages) {
            if (usage instanceof KotlinSafeDeleteOverridingUsageInfo) {
                overridingMethodUsages.add(usage);
            } else {
                result.add(usage);
            }
        }

        if (!overridingMethodUsages.isEmpty()) {
            if (ApplicationManager.getApplication().isUnitTestMode()) {
                result.addAll(overridingMethodUsages);
            } else {
                KotlinOverridingDialog dialog = new KotlinOverridingDialog(project, overridingMethodUsages);
                dialog.show();
                if (!dialog.isOK())
                    return null;
                result.addAll(dialog.getSelected());
            }
        }

        return result.toArray(new UsageInfo[result.size()]);
    }

    static void removeOverrideModifier(@NotNull PsiElement element) {
        if (element instanceof JetNamedFunction || element instanceof JetProperty) {
            JetModifierList modifierList = ((JetModifierListOwner) element).getModifierList();
            if (modifierList == null)
                return;

            PsiElement overrideModifier = modifierList.getModifier(JetTokens.OVERRIDE_KEYWORD);
            if (overrideModifier != null) {
                overrideModifier.delete();
            }
        } else if (element instanceof PsiMethod) {
            PsiMethod method = (PsiMethod) element;

            PsiAnnotation overrideAnnotation = null;
            for (PsiAnnotation annotation : method.getModifierList().getAnnotations()) {
                if ("java.lang.Override".equals(annotation.getQualifiedName())) {
                    overrideAnnotation = annotation;
                    break;
                }
            }

            if (overrideAnnotation != null) {
                overrideAnnotation.delete();
            }
        }
    }

    @Nullable
    private static PsiParameter getPsiParameter(@NotNull JetParameter parameter) {
        JetNamedFunction function = PsiTreeUtil.getParentOfType(parameter, JetNamedFunction.class);
        if (function == null || parameter.getParent() != function.getValueParameterList())
            return null;

        PsiMethod lightMethod = LightClassUtil.getLightClassMethod(function);
        if (lightMethod == null)
            return null;

        int parameterIndex = function.getValueParameters().indexOf(parameter);
        return lightMethod.getParameterList().getParameters()[parameterIndex];
    }

    @Override
    public void prepareForDeletion(@NotNull PsiElement element) throws IncorrectOperationException {
        if (element instanceof PsiMethod) {
            cleanUpOverrides((PsiMethod) element);
        } else if (element instanceof JetNamedFunction) {
            PsiMethod lightMethod = LightClassUtil.getLightClassMethod((JetNamedFunction) element);
            if (lightMethod == null) {
                return;
            }

            cleanUpOverrides(lightMethod);
        } else if (element instanceof JetProperty) {
            LightClassUtil.PropertyAccessorsPsiMethods propertyMethods = LightClassUtil
                    .getLightClassPropertyMethods((JetProperty) element);
            PsiMethod getter = propertyMethods.getGetter();
            PsiMethod setter = propertyMethods.getSetter();

            if (getter != null) {
                cleanUpOverrides(getter);
            }
            if (setter != null) {
                cleanUpOverrides(setter);
            }
        } else if (element instanceof JetTypeParameter) {
            deleteElementAndCleanParent(element);
        } else if (element instanceof JetParameter) {
            JetPsiUtil.deleteElementWithDelimiters(element);
        }
    }

    public static void deleteElementAndCleanParent(@NotNull PsiElement element) {
        PsiElement parent = element.getParent();
        JetPsiUtil.deleteElementWithDelimiters(element);
        JetPsiUtil.deleteChildlessElement(parent, element.getClass());
    }

    private static boolean checkPsiMethodEquality(@NotNull PsiMethod method1, @NotNull PsiMethod method2) {
        if (method1 instanceof JetClsMethod && method2 instanceof JetClsMethod) {
            return ((JetClsMethod) method1).getOrigin().equals(((JetClsMethod) method2).getOrigin());
        }
        return method1.equals(method2);
    }

    public static void cleanUpOverrides(@NotNull PsiMethod method) {
        Collection<PsiMethod> superMethods = Arrays.asList(method.findSuperMethods(true));
        Collection<PsiMethod> overridingMethods = OverridingMethodsSearch.search(method, true).findAll();
        overrideLoop: for (PsiMethod overridingMethod : overridingMethods) {
            PsiElement overridingElement = overridingMethod instanceof JetClsMethod
                    ? ((JetClsMethod) overridingMethod).getOrigin()
                    : overridingMethod;

            Collection<PsiMethod> currentSuperMethods = new ArrayList<PsiMethod>();
            ContainerUtil.addAll(currentSuperMethods, overridingMethod.findSuperMethods(true));
            currentSuperMethods.addAll(superMethods);
            for (PsiMethod superMethod : currentSuperMethods) {
                if (!checkPsiMethodEquality(superMethod, method))
                    continue overrideLoop;
            }

            removeOverrideModifier(overridingElement);
        }
    }

    @Nullable
    @Override
    public Collection<? extends PsiElement> getElementsToSearch(@NotNull PsiElement element,
            @Nullable Module module, @NotNull Collection<PsiElement> allElementsToDelete) {
        if (element instanceof JetParameter) {
            PsiParameter psiParameter = getPsiParameter((JetParameter) element);
            if (psiParameter != null)
                return checkParametersInMethodHierarchy(psiParameter);
        }

        if (element instanceof PsiParameter) {
            return checkParametersInMethodHierarchy((PsiParameter) element);
        }

        if (ApplicationManager.getApplication().isUnitTestMode()) {
            return Collections.singletonList(element);
        }

        if (element instanceof JetNamedFunction || element instanceof JetProperty) {
            return JetRefactoringUtil.checkSuperMethods((JetDeclaration) element, allElementsToDelete,
                    "super.methods.delete.with.usage.search");
        }

        return super.getElementsToSearch(element, module, allElementsToDelete);
    }

    @Nullable
    private static Collection<? extends PsiElement> checkParametersInMethodHierarchy(
            @NotNull PsiParameter parameter) {
        PsiMethod method = (PsiMethod) parameter.getDeclarationScope();
        int parameterIndex = method.getParameterList().getParameterIndex(parameter);

        Set<PsiElement> parametersToDelete = collectParametersToDelete(method, parameterIndex);
        if (parametersToDelete.size() > 1) {
            if (ApplicationManager.getApplication().isUnitTestMode()) {
                return parametersToDelete;
            }

            String message = JetBundle.message("delete.param.in.method.hierarchy",
                    JetRefactoringUtil.formatJavaOrLightMethod(method));
            int exitCode = Messages.showOkCancelDialog(parameter.getProject(), message,
                    IdeBundle.message("title.warning"), Messages.getQuestionIcon());
            if (exitCode == Messages.OK) {
                return parametersToDelete;
            } else {
                return null;
            }
        }

        return parametersToDelete;
    }

    // TODO: generalize breadth-first search
    @NotNull
    private static Set<PsiElement> collectParametersToDelete(@NotNull PsiMethod method, int parameterIndex) {
        Deque<PsiMethod> queue = new ArrayDeque<PsiMethod>();
        Set<PsiMethod> visited = new HashSet<PsiMethod>();
        Set<PsiElement> parametersToDelete = new HashSet<PsiElement>();

        queue.add(method);
        while (!queue.isEmpty()) {
            PsiMethod currentMethod = queue.poll();

            visited.add(currentMethod);
            addParameter(currentMethod, parametersToDelete, parameterIndex);

            for (PsiMethod superMethod : currentMethod.findSuperMethods(true)) {
                if (!visited.contains(superMethod)) {
                    queue.offer(superMethod);
                }
            }
            for (PsiMethod overrider : OverridingMethodsSearch.search(currentMethod)) {
                if (!visited.contains(overrider)) {
                    queue.offer(overrider);
                }
            }
        }
        return parametersToDelete;
    }

    private static void addParameter(@NotNull PsiMethod method, @NotNull Set<PsiElement> result,
            int parameterIndex) {
        if (method instanceof JetClsMethod) {
            JetDeclaration declaration = ((JetClsMethod) method).getOrigin();
            if (declaration instanceof JetNamedFunction) {
                result.add(((JetNamedFunction) declaration).getValueParameters().get(parameterIndex));
            }
        } else {
            result.add(method.getParameterList().getParameters()[parameterIndex]);
        }
    }

    @Nullable
    @Override
    public Collection<PsiElement> getAdditionalElementsToDelete(@NotNull PsiElement element,
            @NotNull Collection<PsiElement> allElementsToDelete, boolean askUser) {
        if (element instanceof JetObjectDeclarationName) {
            return Arrays.asList(getObjectDeclarationOrFail(element));
        }
        return super.getAdditionalElementsToDelete(element, allElementsToDelete, askUser);
    }
}