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 }