View Javadoc

1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.rules.design;
5   
6   import net.sourceforge.pmd.AbstractRule;
7   import net.sourceforge.pmd.ast.ASTConditionalAndExpression;
8   import net.sourceforge.pmd.ast.ASTConditionalExpression;
9   import net.sourceforge.pmd.ast.ASTConditionalOrExpression;
10  import net.sourceforge.pmd.ast.ASTEqualityExpression;
11  import net.sourceforge.pmd.ast.ASTExpression;
12  import net.sourceforge.pmd.ast.ASTIfStatement;
13  import net.sourceforge.pmd.ast.ASTPrimaryExpression;
14  import net.sourceforge.pmd.ast.ASTPrimaryPrefix;
15  import net.sourceforge.pmd.ast.ASTUnaryExpressionNotPlusMinus;
16  import net.sourceforge.pmd.ast.SimpleNode;
17  
18  /**
19   * if (x != y) { diff(); } else { same(); } and<br>
20   * (!x ? diff() : same());.
21   * <p/>
22   * XPath can handle the easy cases, e.g.:<pre>
23   *    //IfStatement[
24   *      Statement[2]
25   *      and Expression[
26   *        EqualityExpression[@Image="!="] or
27   *        UnaryExpressionNotPlusMinus[@Image="!"]]]
28   * </pre>
29   * but "&amp;&amp;" and "||" are difficult, since we need a match
30   * for <i>all</i> children instead of just one.  This can be done by
31   * using a double-negative, e.g.:<pre>
32   *    not(*[not(<i>matchme</i>)])
33   * </pre>
34   * Still, XPath is unable to handle arbitrarily nested cases, since it
35   * lacks recursion, e.g.:<pre>
36   *   if (((x != !y)) || !(x)) { diff(); } else { same(); }
37   * </pre>
38   */
39  public class ConfusingTernary extends AbstractRule {
40  
41      public Object visit(ASTIfStatement node, Object data) {
42          // look for "if (match) ..; else .."
43          if (node.jjtGetNumChildren() == 3) {
44              SimpleNode inode = (SimpleNode) node.jjtGetChild(0);
45              if (inode instanceof ASTExpression &&
46                      inode.jjtGetNumChildren() == 1) {
47                  SimpleNode jnode = (SimpleNode) inode.jjtGetChild(0);
48                  if (isMatch(jnode)) {
49                      addViolation(data, node);
50                  }
51              }
52          }
53          return super.visit(node, data);
54      }
55  
56      public Object visit(ASTConditionalExpression node, Object data) {
57          // look for "match ? .. : .."
58          if (node.jjtGetNumChildren() > 0) {
59              SimpleNode inode = (SimpleNode) node.jjtGetChild(0);
60              if (isMatch(inode)) {
61                  addViolation(data, node);
62              }
63          }
64          return super.visit(node, data);
65      }
66  
67      // recursive!
68      private static boolean isMatch(SimpleNode node) {
69          return
70                  isUnaryNot(node) ||
71                  isNotEquals(node) ||
72                  isConditionalWithAllMatches(node) ||
73                  isParenthesisAroundMatch(node);
74      }
75  
76      private static boolean isUnaryNot(SimpleNode node) {
77          // look for "!x"
78          return
79                  node instanceof ASTUnaryExpressionNotPlusMinus &&
80                  "!".equals(node.getImage());
81      }
82  
83      private static boolean isNotEquals(SimpleNode node) {
84          // look for "x != y"
85          return
86                  node instanceof ASTEqualityExpression &&
87                  "!=".equals(node.getImage());
88      }
89  
90      private static boolean isConditionalWithAllMatches(SimpleNode node) {
91          // look for "match && match" or "match || match"
92          if (!(node instanceof ASTConditionalAndExpression) &&
93                  !(node instanceof ASTConditionalOrExpression)) {
94              return false;
95          }
96          int i_max = node.jjtGetNumChildren();
97          if (i_max <= 0) {
98              return false;
99          }
100         for (int i = 0; i < i_max; i++) {
101             SimpleNode inode = (SimpleNode) node.jjtGetChild(i);
102             // recurse!
103             if (!isMatch(inode)) {
104                 return false;
105             }
106         }
107         // all match
108         return true;
109     }
110 
111     private static boolean isParenthesisAroundMatch(SimpleNode node) {
112         // look for "(match)"
113         if (!(node instanceof ASTPrimaryExpression) ||
114                 (node.jjtGetNumChildren() != 1)) {
115             return false;
116         }
117         SimpleNode inode = (SimpleNode) node.jjtGetChild(0);
118         if (!(inode instanceof ASTPrimaryPrefix) ||
119                 (inode.jjtGetNumChildren() != 1)) {
120             return false;
121         }
122         SimpleNode jnode = (SimpleNode) inode.jjtGetChild(0);
123         if (!(jnode instanceof ASTExpression) ||
124                 (jnode.jjtGetNumChildren() != 1)) {
125             return false;
126         }
127         SimpleNode knode = (SimpleNode) jnode.jjtGetChild(0);
128         // recurse!
129         return isMatch(knode);
130     }
131 }