ConfusingTernaryRule.java
001 /**
002  * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003  */
004 package net.sourceforge.pmd.lang.java.rule.design;
005 
006 import net.sourceforge.pmd.lang.ast.Node;
007 import net.sourceforge.pmd.lang.java.ast.ASTConditionalAndExpression;
008 import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression;
009 import net.sourceforge.pmd.lang.java.ast.ASTConditionalOrExpression;
010 import net.sourceforge.pmd.lang.java.ast.ASTEqualityExpression;
011 import net.sourceforge.pmd.lang.java.ast.ASTExpression;
012 import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
013 import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
014 import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
015 import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpressionNotPlusMinus;
016 import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
017 
018 /**
019  * if (x != y) { diff(); } else { same(); } and<br>
020  * (!x ? diff() : same());.
021  <p/>
022  * XPath can handle the easy cases, e.g.:<pre>
023  *    //IfStatement[
024  *      Statement[2]
025  *      and Expression[
026  *        EqualityExpression[@Image="!="] or
027  *        UnaryExpressionNotPlusMinus[@Image="!"]]]
028  </pre>
029  * but "&amp;&amp;" and "||" are difficult, since we need a match
030  * for <i>all</i> children instead of just one.  This can be done by
031  * using a double-negative, e.g.:<pre>
032  *    not(*[not(<i>matchme</i>)])
033  </pre>
034  * Still, XPath is unable to handle arbitrarily nested cases, since it
035  * lacks recursion, e.g.:<pre>
036  *   if (((x != !y)) || !(x)) { diff(); } else { same(); }
037  </pre>
038  */
039 public class ConfusingTernaryRule extends AbstractJavaRule {
040 
041     public Object visit(ASTIfStatement node, Object data) {
042         // look for "if (match) ..; else .."
043         if (node.jjtGetNumChildren() == 3) {
044             Node inode = node.jjtGetChild(0);
045             if (inode instanceof ASTExpression &&
046                     inode.jjtGetNumChildren() == 1) {
047           Node jnode = inode.jjtGetChild(0);
048                 if (isMatch(jnode)) {
049                     addViolation(data, node);
050                 }
051             }
052         }
053         return super.visit(node, data);
054     }
055 
056     public Object visit(ASTConditionalExpression node, Object data) {
057         // look for "match ? .. : .."
058         if (node.jjtGetNumChildren() 0) {
059             Node inode = node.jjtGetChild(0);
060             if (isMatch(inode)) {
061                 addViolation(data, node);
062             }
063         }
064         return super.visit(node, data);
065     }
066 
067     // recursive!
068     private static boolean isMatch(Node node) {
069         return
070                 isUnaryNot(node||
071                 isNotEquals(node||
072                 isConditionalWithAllMatches(node||
073                 isParenthesisAroundMatch(node);
074     }
075 
076     private static boolean isUnaryNot(Node node) {
077         // look for "!x"
078         return
079                 node instanceof ASTUnaryExpressionNotPlusMinus &&
080                 "!".equals(node.getImage());
081     }
082 
083     private static boolean isNotEquals(Node node) {
084         // look for "x != y"
085         return
086                 node instanceof ASTEqualityExpression &&
087                 "!=".equals(node.getImage());
088     }
089 
090     private static boolean isConditionalWithAllMatches(Node node) {
091         // look for "match && match" or "match || match"
092         if (!(node instanceof ASTConditionalAndExpression&&
093                 !(node instanceof ASTConditionalOrExpression)) {
094             return false;
095         }
096         int n = node.jjtGetNumChildren();
097         if (n <= 0) {
098             return false;
099         }
100         for (int i = 0; i < n; i++) {
101             Node inode = 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(Node node) {
112         // look for "(match)"
113         if (!(node instanceof ASTPrimaryExpression||
114                 (node.jjtGetNumChildren() != 1)) {
115             return false;
116         }
117         Node inode = node.jjtGetChild(0);
118         if (!(inode instanceof ASTPrimaryPrefix||
119                 (inode.jjtGetNumChildren() != 1)) {
120             return false;
121         }
122         Node jnode = inode.jjtGetChild(0);
123         if (!(jnode instanceof ASTExpression||
124                 (jnode.jjtGetNumChildren() != 1)) {
125             return false;
126         }
127         Node knode = jnode.jjtGetChild(0);
128         // recurse!
129         return isMatch(knode);
130     }
131 }