View Javadoc
1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.java.rule.design;
5   
6   import net.sourceforge.pmd.lang.ast.Node;
7   import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression;
8   import net.sourceforge.pmd.lang.java.ast.ASTEqualityExpression;
9   import net.sourceforge.pmd.lang.java.ast.ASTInitializer;
10  import net.sourceforge.pmd.lang.java.ast.ASTName;
11  import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
12  import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
13  import net.sourceforge.pmd.lang.java.ast.ASTReferenceType;
14  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
15  import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
16  
17  public class CompareObjectsWithEqualsRule extends AbstractJavaRule {
18  
19      private boolean hasName(Node n) {
20          return n.jjtGetNumChildren() > 0 && n.jjtGetChild(0) instanceof ASTName;
21      }
22  
23      /**
24       * Indicate whether this node is allocating a new object.
25       * 
26       * @param n
27       *            node that might be allocating a new object
28       * @return true if child 0 is an AllocationExpression
29       */
30      private boolean isAllocation(Node n) {
31          return n.jjtGetNumChildren() > 0 && n.jjtGetChild(0) instanceof ASTAllocationExpression
32                  && n.jjtGetParent().jjtGetNumChildren() == 1;
33      }
34  
35      public Object visit(ASTEqualityExpression node, Object data) {
36          Node c0 = node.jjtGetChild(0).jjtGetChild(0);
37          Node c1 = node.jjtGetChild(1).jjtGetChild(0);
38  
39          // If either side is allocating a new object, there's no way an
40          // equals expression is correct
41          if (isAllocation(c0) || isAllocation(c1)) {
42              addViolation(data, node);
43              return data;
44          }
45  
46          // skip if either child is not a simple name
47          if (!hasName(c0) || !hasName(c1)) {
48              return data;
49          }
50  
51          // skip if either is a qualified name
52          if (isQualifiedName(c0.jjtGetChild(0)) || isQualifiedName(c1.jjtGetChild(0))) {
53              return data;
54          }
55  
56          // skip if either is part of a qualified name
57          if (isPartOfQualifiedName(node.jjtGetChild(0)) || isPartOfQualifiedName(node.jjtGetChild(1))) {
58              return data;
59          }
60  
61          // skip static initializers... missing some cases here
62          if (!node.getParentsOfType(ASTInitializer.class).isEmpty()) {
63              return data;
64          }
65  
66          ASTName n0 = (ASTName) c0.jjtGetChild(0);
67          ASTName n1 = (ASTName) c1.jjtGetChild(0);
68  
69          if (n0.getNameDeclaration() instanceof VariableNameDeclaration
70                  && n1.getNameDeclaration() instanceof VariableNameDeclaration) {
71              VariableNameDeclaration nd0 = (VariableNameDeclaration) n0.getNameDeclaration();
72              VariableNameDeclaration nd1 = (VariableNameDeclaration) n1.getNameDeclaration();
73  
74              // skip array dereferences... this misses some cases
75              // FIXME catch comparisons btwn array elements of reference types
76              if (nd0.isArray() || nd1.isArray()) {
77                  return data;
78              }
79  
80              if (nd0.isReferenceType() && nd1.isReferenceType()) {
81                  ASTReferenceType type0 = ((Node) nd0.getAccessNodeParent()).getFirstDescendantOfType(ASTReferenceType.class);
82                  ASTReferenceType type1 = ((Node) nd1.getAccessNodeParent()).getFirstDescendantOfType(ASTReferenceType.class);
83                  // skip, if it is an enum
84                  if (type0.getType() != null && type0.getType().equals(type1.getType()) && type0.getType().isEnum()) {
85                      return data;
86                  }
87  
88                  addViolation(data, node);
89              }
90          }
91  
92          return data;
93      }
94  
95      /**
96       * Checks whether the given node contains a qualified name, consisting of
97       * one ASTPrimaryPrefix and one or more ASTPrimarySuffix nodes.
98       * 
99       * @param node
100      *            the node
101      * @return <code>true</code> if it is a qualified name
102      */
103     private boolean isPartOfQualifiedName(Node node) {
104         return node.jjtGetChild(0) instanceof ASTPrimaryPrefix
105                 && !node.findChildrenOfType(ASTPrimarySuffix.class).isEmpty();
106     }
107 }