Updated version of the "comparingReference" sample plugin.
[idea/community.git] / samples / comparingReferences / source / com / intellij / codeInspection / ComparingReferencesInspection.java
1 package com.intellij.codeInspection;
2
3 import com.intellij.codeInsight.daemon.GroupNames;
4 import com.intellij.openapi.diagnostic.Logger;
5 import com.intellij.openapi.project.Project;
6 import com.intellij.openapi.util.Ref;
7 import com.intellij.psi.*;
8 import com.intellij.psi.tree.IElementType;
9 import com.intellij.ui.DocumentAdapter;
10 import com.intellij.util.IncorrectOperationException;
11 import org.jetbrains.annotations.NonNls;
12 import org.jetbrains.annotations.NotNull;
13
14 import javax.swing.*;
15 import javax.swing.event.DocumentEvent;
16 import java.awt.*;
17 import java.util.ArrayList;
18 import java.util.Arrays;
19 import java.util.StringTokenizer;
20
21 /**
22  * @author max
23  */
24 public class ComparingReferencesInspection extends BaseJavaLocalInspectionTool {
25   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.ComparingReferencesInspection");
26
27   private final LocalQuickFix myQuickFix = new MyQuickFix();
28
29   @SuppressWarnings({"WeakerAccess"}) @NonNls public String CHECKED_CLASSES = "java.lang.String;java.util.Date";
30   @NonNls private static final String DESCRIPTION_TEMPLATE = InspectionsBundle.message("inspection.comparing.references.problem.descriptor");
31
32   @NotNull
33   public String getDisplayName() {
34
35       return "'==' or '!=' instead of 'equals()'";
36   }
37
38   @NotNull
39   public String getGroupDisplayName() {
40     return GroupNames.BUGS_GROUP_NAME;
41   }
42
43   @NotNull
44   public String getShortName() {
45     return "ComparingReferences";
46   }
47
48   private boolean isCheckedType(PsiType type) {
49     if (!(type instanceof PsiClassType)) return false;
50
51     StringTokenizer tokenizer = new StringTokenizer(CHECKED_CLASSES, ";");
52     while (tokenizer.hasMoreTokens()) {
53       String className = tokenizer.nextToken();
54       if (type.equalsToText(className)) return true;
55     }
56
57     return false;
58   }
59
60     @NotNull
61     @Override
62     public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
63      return new JavaElementVisitor() {
64
65          @Override
66          public void visitReferenceExpression(PsiReferenceExpression psiReferenceExpression) {
67          }
68
69
70       @Override public void visitBinaryExpression(PsiBinaryExpression expression) {
71           super.visitBinaryExpression(expression);
72           IElementType opSign = expression.getOperationSign().getTokenType();
73           if (opSign == JavaTokenType.EQEQ || opSign == JavaTokenType.NE) {
74               PsiExpression lOperand = expression.getLOperand();
75               PsiExpression rOperand = expression.getROperand();
76               if (rOperand == null || isNullLiteral(lOperand) || isNullLiteral(rOperand)) return;
77
78               PsiType lType = lOperand.getType();
79               PsiType rType = rOperand.getType();
80
81               if (isCheckedType(lType) || isCheckedType(rType)) {
82                   holder.registerProblem(expression,
83                           DESCRIPTION_TEMPLATE, myQuickFix);
84               }
85           }
86       }
87     };
88   }
89
90   private static boolean isNullLiteral(PsiExpression expr) {
91     return expr instanceof PsiLiteralExpression && "null".equals(expr.getText());
92   }
93
94   private static class MyQuickFix implements LocalQuickFix {
95     @NotNull
96     public String getName() {
97         // The test (see the TestThisPlugin class) uses this string to identify the quick fix action.
98       return InspectionsBundle.message("inspection.comparing.references.use.quickfix");
99     }
100
101
102     public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
103       try {
104         PsiBinaryExpression binaryExpression = (PsiBinaryExpression)descriptor.getPsiElement();
105         IElementType opSign = binaryExpression.getOperationSign().getTokenType();
106         PsiExpression lExpr = binaryExpression.getLOperand();
107         PsiExpression rExpr = binaryExpression.getROperand();
108         if (rExpr == null)
109           return;
110
111         PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory();
112         PsiMethodCallExpression equalsCall = (PsiMethodCallExpression)factory.createExpressionFromText("a.equals(b)", null);
113
114         equalsCall.getMethodExpression().getQualifierExpression().replace(lExpr);
115         equalsCall.getArgumentList().getExpressions()[0].replace(rExpr);
116
117         PsiExpression result = (PsiExpression)binaryExpression.replace(equalsCall);
118
119         if (opSign == JavaTokenType.NE) {
120           PsiPrefixExpression negation = (PsiPrefixExpression)factory.createExpressionFromText("!a", null);
121           negation.getOperand().replace(result);
122           result.replace(negation);
123         }
124       }
125       catch (IncorrectOperationException e) {
126         LOG.error(e);
127       }
128     }
129
130     @NotNull
131     public String getFamilyName() {
132       return getName();
133     }
134   }
135
136   public JComponent createOptionsPanel() {
137     JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT));
138     final JTextField checkedClasses = new JTextField(CHECKED_CLASSES);
139     checkedClasses.getDocument().addDocumentListener(new DocumentAdapter() {
140       public void textChanged(DocumentEvent event) {
141         CHECKED_CLASSES = checkedClasses.getText();
142       }
143     });
144
145     panel.add(checkedClasses);
146     return panel;
147   }
148
149   public boolean isEnabledByDefault() {
150     return true;
151   }
152 }