/*
* Copyright 2005-2017 Bas Leijdekkers
*
* 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.siyeh.ig.inheritance;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.search.PackageScope;
import com.intellij.psi.search.PsiSearchHelper;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.Query;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.BaseInspection;
import com.siyeh.ig.BaseInspectionVisitor;
import com.siyeh.ig.InspectionGadgetsFix;
import com.siyeh.ig.psiutils.EquivalenceChecker;
import com.siyeh.ig.psiutils.MethodCallUtils;
import com.siyeh.ig.psiutils.MethodUtils;
import com.siyeh.ig.psiutils.ParenthesesUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class RedundantMethodOverrideInspection extends BaseInspection {
@Override
@NotNull
public String getDisplayName() {
return InspectionGadgetsBundle.message(
"redundant.method.override.display.name");
}
@Override
@NotNull
protected String buildErrorString(Object... infos) {
return InspectionGadgetsBundle.message(
"redundant.method.override.problem.descriptor");
}
@Override
@Nullable
protected InspectionGadgetsFix buildFix(Object... infos) {
return new RedundantMethodOverrideFix();
}
private static class RedundantMethodOverrideFix
extends InspectionGadgetsFix {
@Override
@NotNull
public String getFamilyName() {
return InspectionGadgetsBundle.message(
"redundant.method.override.quickfix");
}
@Override
public void doFix(Project project, ProblemDescriptor descriptor) {
final PsiElement methodNameIdentifier = descriptor.getPsiElement();
final PsiElement method = methodNameIdentifier.getParent();
assert method != null;
deleteElement(method);
}
}
@Override
public BaseInspectionVisitor buildVisitor() {
return new RedundantMethodOverrideVisitor();
}
private static class RedundantMethodOverrideVisitor extends BaseInspectionVisitor {
@Override
public void visitMethod(PsiMethod method) {
super.visitMethod(method);
final PsiCodeBlock body = method.getBody();
if (body == null) {
return;
}
if (method.getNameIdentifier() == null) {
return;
}
final PsiMethod superMethod = MethodUtils.getSuper(method);
if (superMethod == null) {
return;
}
if (!modifierListsAreEquivalent(method.getModifierList(), superMethod.getModifierList())) {
return;
}
final PsiType superReturnType = superMethod.getReturnType();
if (superReturnType == null || !superReturnType.equals(method.getReturnType())) {
return;
}
if (method.hasModifierProperty(PsiModifier.FINAL)) {
return; // method overridden and made final - not redundant
}
final PsiCodeBlock superBody = superMethod.getBody();
EquivalenceChecker checker = new ParameterEquivalenceChecker(method, superMethod);
if (checker.codeBlocksAreEquivalent(body, superBody) || isSuperCallWithSameArguments(body, method, superMethod)) {
registerMethodError(method);
}
}
private static class ParameterEquivalenceChecker extends EquivalenceChecker {
private final PsiMethod myMethod;
private final PsiMethod mySuperMethod;
ParameterEquivalenceChecker(@NotNull PsiMethod method, @NotNull PsiMethod superMethod) {
myMethod = method;
mySuperMethod = superMethod;
}
@Override
protected Match referenceExpressionsMatch(PsiReferenceExpression referenceExpression1,
PsiReferenceExpression referenceExpression2) {
if (areSameParameters(referenceExpression1, referenceExpression2)) {
return EXACT_MATCH;
}
return super.referenceExpressionsMatch(referenceExpression1, referenceExpression2);
}
private boolean areSameParameters(PsiReferenceExpression referenceExpression1, PsiReferenceExpression referenceExpression2) {
// parameters of super method and overridden method should be considered the same
PsiElement resolved1 = referenceExpression1.resolve();
PsiElement resolved2 = referenceExpression2.resolve();
if (!(resolved1 instanceof PsiParameter) || !(resolved2 instanceof PsiParameter)) return false;
PsiElement scope1 = ((PsiParameter)resolved1).getDeclarationScope();
PsiElement scope2 = ((PsiParameter)resolved2).getDeclarationScope();
if (scope1 == scope2 || scope1 != myMethod && scope1 != mySuperMethod || scope2 != myMethod && scope2 != mySuperMethod) {
return false;
}
PsiElement parent1 = resolved1.getParent();
PsiElement parent2 = resolved2.getParent();
if (!(parent1 instanceof PsiParameterList) || !(parent2 instanceof PsiParameterList)) {
return false;
}
int index1 = ((PsiParameterList)parent1).getParameterIndex((PsiParameter)resolved1);
int index2 = ((PsiParameterList)parent2).getParameterIndex((PsiParameter)resolved2);
return index1 == index2;
}
}
private boolean isSuperCallWithSameArguments(PsiCodeBlock body, PsiMethod method, PsiMethod superMethod) {
final PsiStatement[] statements = body.getStatements();
if (statements.length != 1) {
return false;
}
final PsiStatement statement = statements[0];
final PsiExpression expression;
if (PsiType.VOID.equals(method.getReturnType())) {
if (statement instanceof PsiExpressionStatement) {
final PsiExpressionStatement expressionStatement = (PsiExpressionStatement)statement;
expression = expressionStatement.getExpression();
}
else {
return false;
}
}
else {
if (statement instanceof PsiReturnStatement) {
final PsiReturnStatement returnStatement = (PsiReturnStatement)statement;
expression = ParenthesesUtils.stripParentheses(returnStatement.getReturnValue());
}
else {
return false;
}
}
if (!(expression instanceof PsiMethodCallExpression)) {
return false;
}
final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)expression;
if (!MethodCallUtils.isSuperMethodCall(methodCallExpression, method)) return false;
if (superMethod.hasModifierProperty(PsiModifier.PROTECTED)) {
final PsiJavaFile file = (PsiJavaFile)method.getContainingFile();
// implementing a protected method in another package makes it available to that package.
PsiPackage aPackage = JavaPsiFacade.getInstance(method.getProject()).findPackage(file.getPackageName());
if (aPackage == null) {
return false; // when package statement is incorrect
}
final PackageScope scope = new PackageScope(aPackage, false, false);
if (isOnTheFly()) {
final PsiSearchHelper searchHelper = PsiSearchHelper.SERVICE.getInstance(method.getProject());
final PsiSearchHelper.SearchCostResult cost =
searchHelper.isCheapEnoughToSearch(method.getName(), scope, null, null);
if (cost == PsiSearchHelper.SearchCostResult.ZERO_OCCURRENCES) {
return true;
}
if (cost == PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES) {
return false;
}
}
final Query<PsiReference> search = ReferencesSearch.search(method, scope);
final PsiClass containingClass = method.getContainingClass();
for (PsiReference reference : search) {
if (!PsiTreeUtil.isAncestor(containingClass, reference.getElement(), true)) {
return false;
}
}
}
return areSameArguments(methodCallExpression, method);
}
private static boolean areSameArguments(PsiMethodCallExpression methodCallExpression, PsiMethod method) {
// void foo(int param) { super.foo(42); } is not redundant
PsiExpression[] arguments = methodCallExpression.getArgumentList().getExpressions();
PsiParameter[] parameters = method.getParameterList().getParameters();
if (arguments.length != parameters.length) return false;
for (int i = 0; i < arguments.length; i++) {
PsiExpression argument = arguments[i];
PsiExpression exp = PsiUtil.deparenthesizeExpression(argument);
if (!(exp instanceof PsiReferenceExpression)) return false;
PsiElement resolved = ((PsiReferenceExpression)exp).resolve();
if (!method.getManager().areElementsEquivalent(parameters[i], resolved)) {
return false;
}
}
return true;
}
private static boolean modifierListsAreEquivalent(@Nullable PsiModifierList list1, @Nullable PsiModifierList list2) {
if (list1 == null) {
return list2 == null;
}
else if (list2 == null) {
return false;
}
if (list1.hasModifierProperty(PsiModifier.STRICTFP) != list2.hasModifierProperty(PsiModifier.STRICTFP) ||
list1.hasModifierProperty(PsiModifier.SYNCHRONIZED) != list2.hasModifierProperty(PsiModifier.SYNCHRONIZED) ||
list1.hasModifierProperty(PsiModifier.PUBLIC) != list2.hasModifierProperty(PsiModifier.PUBLIC) ||
list1.hasModifierProperty(PsiModifier.PROTECTED) != list2.hasModifierProperty(PsiModifier.PROTECTED)) {
return false;
}
return AnnotationUtil.equal(list1.getAnnotations(), list2.getAnnotations());
}
}
}