IG: try to make inspection non-quadratic
[idea/community.git] / plugins / InspectionGadgets / InspectionGadgetsAnalysis / src / com / siyeh / ig / naming / LambdaUnfriendlyMethodOverloadInspectionBase.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.siyeh.ig.naming;
17
18 import com.intellij.openapi.util.Key;
19 import com.intellij.psi.*;
20 import com.intellij.psi.util.PsiSuperMethodUtil;
21 import com.siyeh.InspectionGadgetsBundle;
22 import com.siyeh.ig.BaseInspection;
23 import com.siyeh.ig.BaseInspectionVisitor;
24 import org.jetbrains.annotations.Nls;
25 import org.jetbrains.annotations.NotNull;
26
27 import java.util.Objects;
28
29 /**
30  * @author Bas Leijdekkers
31  */
32 public class LambdaUnfriendlyMethodOverloadInspectionBase extends BaseInspection {
33   @Nls
34   @NotNull
35   @Override
36   public String getDisplayName() {
37     return InspectionGadgetsBundle.message("lambda.unfriendly.method.overload.display.name");
38   }
39
40   @NotNull
41   @Override
42   protected String buildErrorString(Object... infos) {
43     final PsiMethod method = (PsiMethod)infos[0];
44     return InspectionGadgetsBundle.message(method.isConstructor()
45                                            ? "lambda.unfriendly.constructor.overload.problem.descriptor"
46                                            : "lambda.unfriendly.method.overload.problem.descriptor");
47   }
48
49   @Override
50   public BaseInspectionVisitor buildVisitor() {
51     return new LambdaUnfriendlyMethodOverloadVisitor();
52   }
53
54   private static class LambdaUnfriendlyMethodOverloadVisitor extends BaseInspectionVisitor {
55
56     private static final Key<Boolean> SKIP_MARKER = new Key<>("skip marker for lambda unfriendly method overload inspection");
57
58     @Override
59     public void visitMethod(PsiMethod method) {
60       super.visitMethod(method);
61       if (method.getUserData(SKIP_MARKER) == Boolean.TRUE) {
62         return;
63       }
64       final PsiParameterList parameterList = method.getParameterList();
65       final int parametersCount = parameterList.getParametersCount();
66       if (parametersCount == 0) {
67         return;
68       }
69       final PsiParameter[] parameters = parameterList.getParameters();
70       int functionalIndex = -1;
71       for (int i = 0; i < parameters.length; i++) {
72         final PsiParameter parameter = parameters[i];
73         if (LambdaUtil.isFunctionalType(parameter.getType())) {
74           functionalIndex = i;
75           break;
76         }
77       }
78        if (functionalIndex < 0) {
79          return;
80        }
81       final PsiClass containingClass = method.getContainingClass();
82       if (containingClass == null) {
83         return;
84       }
85       final String name = method.getName();
86       boolean problemFound = false;
87       for (PsiMethod sameNameMethod : containingClass.findMethodsByName(name, true)) {
88         if (method.equals(sameNameMethod) || PsiSuperMethodUtil.isSuperMethod(method, sameNameMethod)) {
89           continue;
90         }
91         final PsiParameterList otherParameterList = sameNameMethod.getParameterList();
92         if (parametersCount != otherParameterList.getParametersCount()) {
93           continue;
94         }
95         final PsiParameter[] otherParameters = otherParameterList.getParameters();
96         final PsiType otherFunctionalType = otherParameters[functionalIndex].getType();
97         final PsiType functionalType = parameters[functionalIndex].getType();
98         if (!areOtherParameterTypesConvertible(parameters, otherParameters, functionalIndex) ||
99             !LambdaUtil.isFunctionalType(otherFunctionalType) ||
100             Objects.equals(functionalType, otherFunctionalType)) {
101           continue;
102         }
103
104         if (areSameShapeFunctionalTypes(functionalType, otherFunctionalType)) {
105           problemFound = true;
106           if (containingClass.equals(sameNameMethod.getContainingClass())) {
107             registerMethodError(sameNameMethod, sameNameMethod);
108             sameNameMethod.putUserData(SKIP_MARKER, Boolean.TRUE);
109           }
110         }
111       }
112       if (problemFound) {
113         registerMethodError(method, method);
114       }
115     }
116
117     private static boolean areSameShapeFunctionalTypes(PsiType one, PsiType two) {
118       final PsiMethod method1 = LambdaUtil.getFunctionalInterfaceMethod(one);
119       final PsiMethod method2 = LambdaUtil.getFunctionalInterfaceMethod(two);
120       if (method1 == null || method2 == null) {
121         return false;
122       }
123       final PsiType returnType1 = method1.getReturnType();
124       final PsiType returnType2 = method2.getReturnType();
125       if (PsiType.VOID.equals(returnType1) ^ PsiType.VOID.equals(returnType2)) {
126         return false;
127       }
128       return method1.getParameterList().getParametersCount() == method2.getParameterList().getParametersCount();
129     }
130
131     private static boolean areOtherParameterTypesConvertible(PsiParameter[] parameters, PsiParameter[] otherParameters, int notThisOne) {
132       for (int i = 0; i < parameters.length; i++) {
133         if (i == notThisOne) {
134           continue;
135         }
136         final PsiType type = parameters[i].getType();
137         final PsiType otherType = otherParameters[i].getType();
138         if (!type.isAssignableFrom(otherType) && !otherType.isAssignableFrom(type)) {
139           return false;
140         }
141       }
142       return true;
143     }
144   }
145 }