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