/** * 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.ASTAllocationExpression; import net.sourceforge.pmd.lang.java.ast.ASTEqualityExpression; import net.sourceforge.pmd.lang.java.ast.ASTInitializer; import net.sourceforge.pmd.lang.java.ast.ASTName; import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix; import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix; import net.sourceforge.pmd.lang.java.ast.ASTReferenceType; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; public class CompareObjectsWithEqualsRule extends AbstractJavaRule { private boolean hasName(Node n) { return n.jjtGetNumChildren() > 0 && n.jjtGetChild(0) instanceof ASTName; } /** * Indicate whether this node is allocating a new object. * * @param n * node that might be allocating a new object * @return true if child 0 is an AllocationExpression */ private boolean isAllocation(Node n) { return n.jjtGetNumChildren() > 0 && n.jjtGetChild(0) instanceof ASTAllocationExpression && n.jjtGetParent().jjtGetNumChildren() == 1; } public Object visit(ASTEqualityExpression node, Object data) { Node c0 = node.jjtGetChild(0).jjtGetChild(0); Node c1 = node.jjtGetChild(1).jjtGetChild(0); // If either side is allocating a new object, there's no way an // equals expression is correct if (isAllocation(c0) || isAllocation(c1)) { addViolation(data, node); return data; } // skip if either child is not a simple name if (!hasName(c0) || !hasName(c1)) { return data; } // skip if either is a qualified name if (isQualifiedName(c0.jjtGetChild(0)) || isQualifiedName(c1.jjtGetChild(0))) { return data; } // skip if either is part of a qualified name if (isPartOfQualifiedName(node.jjtGetChild(0)) || isPartOfQualifiedName(node.jjtGetChild(1))) { return data; } // skip static initializers... missing some cases here if (!node.getParentsOfType(ASTInitializer.class).isEmpty()) { return data; } ASTName n0 = (ASTName) c0.jjtGetChild(0); ASTName n1 = (ASTName) c1.jjtGetChild(0); if (n0.getNameDeclaration() instanceof VariableNameDeclaration && n1.getNameDeclaration() instanceof VariableNameDeclaration) { VariableNameDeclaration nd0 = (VariableNameDeclaration) n0.getNameDeclaration(); VariableNameDeclaration nd1 = (VariableNameDeclaration) n1.getNameDeclaration(); // skip array dereferences... this misses some cases // FIXME catch comparisons btwn array elements of reference types if (nd0.isArray() || nd1.isArray()) { return data; } if (nd0.isReferenceType() && nd1.isReferenceType()) { ASTReferenceType type0 = ((Node) nd0.getAccessNodeParent()) .getFirstDescendantOfType(ASTReferenceType.class); ASTReferenceType type1 = ((Node) nd1.getAccessNodeParent()) .getFirstDescendantOfType(ASTReferenceType.class); // skip, if it is an enum if (type0.getType() != null && type0.getType().equals(type1.getType()) && type0.getType().isEnum()) { return data; } addViolation(data, node); } } return data; } /** * Checks whether the given node contains a qualified name, consisting of * one ASTPrimaryPrefix and one or more ASTPrimarySuffix nodes. * * @param node * the node * @return <code>true</code> if it is a qualified name */ private boolean isPartOfQualifiedName(Node node) { return node.jjtGetChild(0) instanceof ASTPrimaryPrefix && !node.findChildrenOfType(ASTPrimarySuffix.class).isEmpty(); } }