Java: Don't cause endless recursive calls of the equals() method in the inspection...
[idea/community.git] / plugins / InspectionGadgets / src / com / siyeh / ig / equality / EqualityOperatorComparesObjectsInspection.java
1 /*
2  * Copyright 2003-2014 Dave Griffith, Bas Leijdekkers
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.equality;
17
18 import com.intellij.codeInspection.ProblemDescriptor;
19 import com.intellij.openapi.project.Project;
20 import com.intellij.psi.*;
21 import com.intellij.psi.tree.IElementType;
22 import com.intellij.psi.util.PsiTreeUtil;
23 import com.intellij.psi.util.PsiUtil;
24 import com.intellij.psi.util.TypeConversionUtil;
25 import com.siyeh.InspectionGadgetsBundle;
26 import com.siyeh.ig.BaseInspection;
27 import com.siyeh.ig.BaseInspectionVisitor;
28 import com.siyeh.ig.InspectionGadgetsFix;
29 import com.siyeh.ig.PsiReplacementUtil;
30 import com.siyeh.ig.psiutils.ClassUtils;
31 import com.siyeh.ig.psiutils.ParenthesesUtils;
32 import org.jetbrains.annotations.Nls;
33 import org.jetbrains.annotations.NonNls;
34 import org.jetbrains.annotations.NotNull;
35
36 public class EqualityOperatorComparesObjectsInspection extends BaseInspection {
37
38   @Nls
39   @NotNull
40   @Override
41   public String getDisplayName() {
42     return InspectionGadgetsBundle.message("equality.operator.compares.objects.name");
43   }
44
45   @NotNull
46   @Override
47   protected String buildErrorString(Object... infos) {
48     return InspectionGadgetsBundle.message("equality.operator.compares.objects.descriptor", infos);
49   }
50
51   @Override
52   public BaseInspectionVisitor buildVisitor() {
53     return new ObjectEqualityVisitor();
54   }
55
56   @NotNull
57   @Override
58   protected InspectionGadgetsFix[] buildFixes(Object... infos) {
59     return new InspectionGadgetsFix[]{new EqualsFix(infos), new SafeEqualsFix(infos)};
60   }
61
62   private static void doFixImpl(@NotNull PsiElement element) {
63     final PsiBinaryExpression exp = (PsiBinaryExpression)element;
64     final PsiExpression lhs = exp.getLOperand();
65     final PsiExpression rhs = exp.getROperand();
66     if (rhs == null) {
67       return;
68     }
69     final PsiExpression strippedLhs = ParenthesesUtils.stripParentheses(lhs);
70     if (strippedLhs == null) {
71       return;
72     }
73     final PsiExpression strippedRhs = ParenthesesUtils.stripParentheses(rhs);
74     if (strippedRhs == null) {
75       return;
76     }
77     final String lhText = strippedLhs.getText();
78     final String rhText = strippedRhs.getText();
79
80     final String prefix = exp.getOperationTokenType().equals(JavaTokenType.EQEQ) ? "" : "!";
81     @NonNls final String expString;
82     if (ParenthesesUtils.getPrecedence(strippedLhs) > ParenthesesUtils.METHOD_CALL_PRECEDENCE) {
83       expString = prefix + '(' + lhText + ").equals(" + rhText + ')';
84     }
85     else {
86       expString = prefix + lhText + ".equals(" + rhText + ')';
87     }
88     PsiReplacementUtil.replaceExpression(exp, expString);
89   }
90
91   private static void doSafeFixImpl(PsiElement element) {
92     final PsiBinaryExpression exp = (PsiBinaryExpression)element;
93     final PsiExpression lhs = exp.getLOperand();
94     final PsiExpression rhs = exp.getROperand();
95     if (rhs == null) {
96       return;
97     }
98     final PsiExpression strippedLhs =
99       ParenthesesUtils.stripParentheses(lhs);
100     if (strippedLhs == null) {
101       return;
102     }
103     final PsiExpression strippedRhs =
104       ParenthesesUtils.stripParentheses(rhs);
105     if (strippedRhs == null) {
106       return;
107     }
108     final String lhsText = strippedLhs.getText();
109     final String rhsText = strippedRhs.getText();
110     final PsiJavaToken operationSign = exp.getOperationSign();
111     final IElementType tokenType = operationSign.getTokenType();
112     final String signText = operationSign.getText();
113     @NonNls final StringBuilder newExpression = new StringBuilder();
114     if (PsiUtil.isLanguageLevel7OrHigher(element) && ClassUtils.findClass("java.util.Objects", element) != null) {
115       if (tokenType.equals(JavaTokenType.NE)) {
116         newExpression.append('!');
117       }
118       newExpression.append("java.util.Objects.equals(").append(lhsText).append(',').append(rhsText).append(')');
119     }
120     else {
121       newExpression.append(lhsText).append("==null?").append(rhsText).append(signText).append(" null:");
122       if (tokenType.equals(JavaTokenType.NE)) {
123         newExpression.append('!');
124       }
125       if (ParenthesesUtils.getPrecedence(strippedLhs) > ParenthesesUtils.METHOD_CALL_PRECEDENCE) {
126         newExpression.append('(').append(lhsText).append(')');
127       }
128       else {
129         newExpression.append(lhsText);
130       }
131       newExpression.append(".equals(").append(rhsText).append(')');
132     }
133     PsiReplacementUtil.replaceExpressionAndShorten(exp, newExpression.toString());
134   }
135
136   private static class SafeEqualsFix extends InspectionGadgetsFix {
137     private final Object[] myInfos;
138
139     public SafeEqualsFix(Object... infos) {
140       myInfos = infos;
141     }
142
143     @Nls
144     @NotNull
145     @Override
146     public String getName() {
147       return InspectionGadgetsBundle.message("equality.operator.compares.objects.safe.quickfix", myInfos);
148     }
149
150     @Nls
151     @NotNull
152     @Override
153     public String getFamilyName() {
154       return InspectionGadgetsBundle.message("equality.operator.compares.objects.safe.family.quickfix");
155     }
156
157     @Override
158     protected void doFix(Project project, ProblemDescriptor descriptor) {
159       doSafeFixImpl(descriptor.getPsiElement());
160     }
161   }
162
163   private static class EqualsFix extends InspectionGadgetsFix {
164     private final Object[] myInfos;
165
166     public EqualsFix(Object... infos) {
167       myInfos = infos;
168     }
169
170     @Nls
171     @NotNull
172     @Override
173     public String getName() {
174       return InspectionGadgetsBundle.message("equality.operator.compares.objects.quickfix", myInfos);
175     }
176
177     @Nls
178     @NotNull
179     @Override
180     public String getFamilyName() {
181       return InspectionGadgetsBundle.message("equality.operator.compares.objects.family.quickfix");
182     }
183
184     @Override
185     protected void doFix(Project project, ProblemDescriptor descriptor) {
186       doFixImpl(descriptor.getPsiElement());
187     }
188   }
189
190   private static class ObjectEqualityVisitor extends BaseInspectionVisitor {
191     @Override
192     public void visitBinaryExpression(PsiBinaryExpression expression) {
193       super.visitBinaryExpression(expression);
194       final IElementType tokenType = expression.getOperationTokenType();
195       if (!tokenType.equals(JavaTokenType.NE) &&
196           !tokenType.equals(JavaTokenType.EQEQ)) {
197         return;
198       }
199       final PsiExpression lhs = expression.getLOperand();
200       final PsiType lhsType = lhs.getType();
201       if (lhsType == null || lhsType instanceof PsiPrimitiveType || TypeConversionUtil.isEnumType(lhsType)) {
202         return;
203       }
204       final PsiExpression rhs = expression.getROperand();
205       if (rhs == null) {
206         return;
207       }
208       final PsiType rhsType = rhs.getType();
209       if (rhsType == null || rhsType instanceof PsiPrimitiveType || TypeConversionUtil.isEnumType(rhsType)) {
210         return;
211       }
212       if (lhs instanceof PsiThisExpression || rhs instanceof PsiThisExpression) {
213         final PsiMethod method = PsiTreeUtil.getParentOfType(expression, PsiMethod.class);
214         if (method != null && "equals".equals(method.getName())) {
215           return;
216         }
217       }
218       final String operationText = expression.getOperationSign().getText();
219       final String prefix = tokenType.equals(JavaTokenType.NE) ? "!" : "";
220       registerError(expression, operationText, prefix);
221     }
222   }
223 }