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 "&&" 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
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
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
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
84 return
85 node instanceof ASTUnaryExpressionNotPlusMinus &&
86 "!".equals(node.getImage());
87 }
88
89 private static boolean isNotEquals(SimpleNode node) {
90
91 return
92 node instanceof ASTEqualityExpression &&
93 "!=".equals(node.getImage());
94 }
95
96 private static boolean isConditionalWithAllMatches(SimpleNode node) {
97
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
109 if (!isMatch(inode)) {
110 return false;
111 }
112 }
113
114 return true;
115 }
116
117 private static boolean isParenthesisAroundMatch(SimpleNode node) {
118
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
135 return isMatch(knode);
136 }
137 }