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