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