/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.rule.design;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.ast.ASTConditionalAndExpression;
import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression;
import net.sourceforge.pmd.lang.java.ast.ASTConditionalOrExpression;
import net.sourceforge.pmd.lang.java.ast.ASTEqualityExpression;
import net.sourceforge.pmd.lang.java.ast.ASTExpression;
import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpressionNotPlusMinus;
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
import net.sourceforge.pmd.lang.rule.properties.BooleanProperty;
/**
* <code>if (x != y) { diff(); } else { same(); }</code> and<br>
* <code>(!x ? diff() : same());</code>
*
* <p>XPath can handle the easy cases, e.g.:</p>
*
* <pre>
* //IfStatement[
* Statement[2]
* and Expression[
* EqualityExpression[@Image="!="] or
* UnaryExpressionNotPlusMinus[@Image="!"]]]
* </pre>
*
* <p>But "&&" and "||" are difficult, since we need a match for <i>all</i>
* children instead of just one. This can be done by using a double-negative,
* e.g.:</p>
*
* <pre>
* not(*[not(<i>matchme</i>)])
* </pre>
*
* <p>Still, XPath is unable to handle arbitrarily nested cases, since it lacks
* recursion, e.g.:</p>
*
* <pre>
* if (((x != !y)) || !(x)) {
* diff();
* } else {
* same();
* }
* </pre>
*/
public class ConfusingTernaryRule extends AbstractJavaRule {
private static BooleanProperty ignoreElseIfProperty = new BooleanProperty("ignoreElseIf",
"Ignore conditions with an else-if case", Boolean.FALSE, 0);
public ConfusingTernaryRule() {
super();
definePropertyDescriptor(ignoreElseIfProperty);
}
public Object visit(ASTIfStatement node, Object data) {
// look for "if (match) ..; else .."
if (node.jjtGetNumChildren() == 3) {
Node inode = node.jjtGetChild(0);
if (inode instanceof ASTExpression && inode.jjtGetNumChildren() == 1) {
Node jnode = inode.jjtGetChild(0);
if (isMatch(jnode)) {
if (!getProperty(ignoreElseIfProperty)
|| !(node.jjtGetChild(2).jjtGetChild(0) instanceof ASTIfStatement)
&& !(node.jjtGetParent().jjtGetParent() instanceof ASTIfStatement)) {
addViolation(data, node);
}
}
}
}
return super.visit(node, data);
}
public Object visit(ASTConditionalExpression node, Object data) {
// look for "match ? .. : .."
if (node.jjtGetNumChildren() > 0) {
Node inode = node.jjtGetChild(0);
if (isMatch(inode)) {
addViolation(data, node);
}
}
return super.visit(node, data);
}
// recursive!
private static boolean isMatch(Node node) {
return isUnaryNot(node) || isNotEquals(node) || isConditionalWithAllMatches(node)
|| isParenthesisAroundMatch(node);
}
private static boolean isUnaryNot(Node node) {
// look for "!x"
return node instanceof ASTUnaryExpressionNotPlusMinus && "!".equals(node.getImage());
}
private static boolean isNotEquals(Node node) {
// look for "x != y"
return node instanceof ASTEqualityExpression && "!=".equals(node.getImage());
}
private static boolean isConditionalWithAllMatches(Node node) {
// look for "match && match" or "match || match"
if (!(node instanceof ASTConditionalAndExpression) && !(node instanceof ASTConditionalOrExpression)) {
return false;
}
int n = node.jjtGetNumChildren();
if (n <= 0) {
return false;
}
for (int i = 0; i < n; i++) {
Node inode = node.jjtGetChild(i);
// recurse!
if (!isMatch(inode)) {
return false;
}
}
// all match
return true;
}
private static boolean isParenthesisAroundMatch(Node node) {
// look for "(match)"
if (!(node instanceof ASTPrimaryExpression) || node.jjtGetNumChildren() != 1) {
return false;
}
Node inode = node.jjtGetChild(0);
if (!(inode instanceof ASTPrimaryPrefix) || inode.jjtGetNumChildren() != 1) {
return false;
}
Node jnode = inode.jjtGetChild(0);
if (!(jnode instanceof ASTExpression) || jnode.jjtGetNumChildren() != 1) {
return false;
}
Node knode = jnode.jjtGetChild(0);
// recurse!
return isMatch(knode);
}
}