IDEA-79962 (Inspection "equals() and hashCode() not paired " does not provide miss...
[idea/community.git] / java / java-analysis-impl / src / com / intellij / codeInspection / equalsAndHashcode / EqualsAndHashcodeBase.java
1 /*
2  * Copyright 2000-2014 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.intellij.codeInspection.equalsAndHashcode;
17
18 import com.intellij.codeInspection.*;
19 import com.intellij.openapi.application.ApplicationManager;
20 import com.intellij.openapi.project.Project;
21 import com.intellij.openapi.roots.ProjectRootManager;
22 import com.intellij.openapi.util.Computable;
23 import com.intellij.openapi.util.Pair;
24 import com.intellij.psi.*;
25 import com.intellij.psi.search.GlobalSearchScope;
26 import com.intellij.psi.util.CachedValueProvider;
27 import com.intellij.psi.util.CachedValuesManager;
28 import com.intellij.psi.util.MethodSignatureUtil;
29 import org.jetbrains.annotations.NonNls;
30 import org.jetbrains.annotations.NotNull;
31 import org.jetbrains.annotations.Nullable;
32
33 /**
34  * @author max
35  */
36 public class EqualsAndHashcodeBase extends BaseJavaBatchLocalInspectionTool {
37   @Override
38   @NotNull
39   public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, final boolean isOnTheFly) {
40     final Project project = holder.getProject();
41     Pair<PsiMethod, PsiMethod> pair = CachedValuesManager.getManager(project).getCachedValue(project, new CachedValueProvider<Pair<PsiMethod, PsiMethod>>() {
42       @Override
43       public Result<Pair<PsiMethod, PsiMethod>> compute() {
44         final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
45         final PsiClass psiObjectClass = ApplicationManager.getApplication().runReadAction(
46             new Computable<PsiClass>() {
47               @Override
48               @Nullable
49               public PsiClass compute() {
50                 return psiFacade.findClass(CommonClassNames.JAVA_LANG_OBJECT, GlobalSearchScope.allScope(project));
51               }
52             }
53         );
54         if (psiObjectClass == null) {
55           return Result.create(null, ProjectRootManager.getInstance(project));
56         }
57         PsiMethod[] methods = psiObjectClass.getMethods();
58         PsiMethod myEquals = null;
59         PsiMethod myHashCode = null;
60         for (PsiMethod method : methods) {
61           @NonNls final String name = method.getName();
62           if ("equals".equals(name)) {
63             myEquals = method;
64           }
65           else if ("hashCode".equals(name)) {
66             myHashCode = method;
67           }
68         }
69         return Result.create(Pair.create(myEquals, myHashCode), psiObjectClass);
70       }
71     });
72
73     if (pair == null) return new PsiElementVisitor() {};
74
75     //jdk wasn't configured for the project
76     final PsiMethod myEquals = pair.first;
77     final PsiMethod myHashCode = pair.second;
78     if (myEquals == null || myHashCode == null || !myEquals.isValid() || !myHashCode.isValid()) return new PsiElementVisitor() {};
79
80     return new JavaElementVisitor() {
81       @Override public void visitClass(PsiClass aClass) {
82         super.visitClass(aClass);
83         boolean [] hasEquals = {false};
84         boolean [] hasHashCode = {false};
85         processClass(aClass, hasEquals, hasHashCode, myEquals, myHashCode);
86         if (hasEquals[0] != hasHashCode[0]) {
87           PsiIdentifier identifier = aClass.getNameIdentifier();
88           holder.registerProblem(identifier != null ? identifier : aClass,
89                                  hasEquals[0]
90                                   ? InspectionsBundle.message("inspection.equals.hashcode.only.one.defined.problem.descriptor", "<code>equals()</code>", "<code>hashCode()</code>")
91                                   : InspectionsBundle.message("inspection.equals.hashcode.only.one.defined.problem.descriptor","<code>hashCode()</code>", "<code>equals()</code>"),
92                                   buildFixes(isOnTheFly, hasEquals[0]));
93         }
94       }
95     };
96   }
97
98   private static void processClass(final PsiClass aClass,
99                                    final boolean[] hasEquals,
100                                    final boolean[] hasHashCode,
101                                    PsiMethod equals, PsiMethod hashcode) {
102     final PsiMethod[] methods = aClass.getMethods();
103     for (PsiMethod method : methods) {
104       if (MethodSignatureUtil.areSignaturesEqual(method, equals)) {
105         hasEquals[0] = true;
106       }
107       else if (MethodSignatureUtil.areSignaturesEqual(method, hashcode)) {
108         hasHashCode[0] = true;
109       }
110     }
111   }
112
113   @Override
114   @NotNull
115   public String getDisplayName() {
116     return InspectionsBundle.message("inspection.equals.hashcode.display.name");
117   }
118
119   @Override
120   @NotNull
121   public String getGroupDisplayName() {
122     return "";
123   }
124
125   @Override
126   @NotNull
127   public String getShortName() {
128     return "EqualsAndHashcode";
129   }
130
131   protected LocalQuickFix[] buildFixes(boolean isOnTheFly, boolean hasEquals) {
132     return LocalQuickFix.EMPTY_ARRAY;
133   }
134 }