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 "&&" 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
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
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
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
78 return
79 node instanceof ASTUnaryExpressionNotPlusMinus &&
80 "!".equals(node.getImage());
81 }
82
83 private static boolean isNotEquals(SimpleNode node) {
84
85 return
86 node instanceof ASTEqualityExpression &&
87 "!=".equals(node.getImage());
88 }
89
90 private static boolean isConditionalWithAllMatches(SimpleNode node) {
91
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
103 if (!isMatch(inode)) {
104 return false;
105 }
106 }
107
108 return true;
109 }
110
111 private static boolean isParenthesisAroundMatch(SimpleNode node) {
112
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
129 return isMatch(knode);
130 }
131 }