/** * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.lang.java.rule.optimizations; import java.math.BigInteger; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTBooleanLiteral; import net.sourceforge.pmd.lang.java.ast.ASTCastExpression; import net.sourceforge.pmd.lang.java.ast.ASTExpression; import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTLiteral; import net.sourceforge.pmd.lang.java.ast.ASTNullLiteral; import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression; import net.sourceforge.pmd.lang.java.ast.ASTReferenceType; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; /** * Detects redundant field initializers, i.e. the field initializer expressions * the JVM would assign by default. * * @author lucian.ciufudean@gmail.com * @since Apr 10, 2009 */ public class RedundantFieldInitializerRule extends AbstractJavaRule { public RedundantFieldInitializerRule() { addRuleChainVisit(ASTFieldDeclaration.class); } public Object visit(ASTFieldDeclaration fieldDeclaration, Object data) { // Finals can only be initialized once. if (fieldDeclaration.isFinal()) { return data; } // Look for a match to the following XPath: // VariableDeclarator/VariableInitializer/Expression/PrimaryExpression/PrimaryPrefix/Literal for (ASTVariableDeclarator variableDeclarator : fieldDeclaration .findChildrenOfType(ASTVariableDeclarator.class)) { if (variableDeclarator.jjtGetNumChildren() > 1) { final Node variableInitializer = variableDeclarator.jjtGetChild(1); if (variableInitializer.jjtGetChild(0) instanceof ASTExpression) { final Node expression = variableInitializer.jjtGetChild(0); final Node primaryExpression; if (expression.jjtGetNumChildren() == 1) { if (expression.jjtGetChild(0) instanceof ASTPrimaryExpression) { primaryExpression = expression.jjtGetChild(0); } else if (expression.jjtGetChild(0) instanceof ASTCastExpression && expression.jjtGetChild(0).jjtGetChild(1) instanceof ASTPrimaryExpression) { primaryExpression = expression.jjtGetChild(0).jjtGetChild(1); } else { continue; } } else { continue; } final Node primaryPrefix = primaryExpression.jjtGetChild(0); if (primaryPrefix.jjtGetNumChildren() == 1 && primaryPrefix.jjtGetChild(0) instanceof ASTLiteral) { final ASTLiteral literal = (ASTLiteral) primaryPrefix.jjtGetChild(0); if (isRef(fieldDeclaration, variableDeclarator)) { // Reference type if (literal.jjtGetNumChildren() == 1 && literal.jjtGetChild(0) instanceof ASTNullLiteral) { addViolation(data, variableDeclarator); } } else { // Primitive type if (literal.jjtGetNumChildren() == 1 && literal.jjtGetChild(0) instanceof ASTBooleanLiteral) { // boolean type ASTBooleanLiteral booleanLiteral = (ASTBooleanLiteral) literal.jjtGetChild(0); if (!booleanLiteral.isTrue()) { addViolation(data, variableDeclarator); } } else if (literal.jjtGetNumChildren() == 0) { // numeric type // Note: Not catching NumberFormatException, as // it shouldn't be happening on valid source // code. Number value = -1; if (literal.isIntLiteral()) { value = parseInteger(literal.getImage()); } else if (literal.isLongLiteral()) { String s = literal.getImage(); // remove the ending "l" or "L" for long // values s = s.substring(0, s.length() - 1); value = parseInteger(s); } else if (literal.isFloatLiteral()) { String s = literal.getImage(); // remove the ending "f" or "F" for float // values s = s.substring(0, s.length() - 1); value = Float.valueOf(s); } else if (literal.isDoubleLiteral()) { value = Double.valueOf(literal.getImage()); } else if (literal.isCharLiteral()) { value = (int) literal.getImage().charAt(1); } if (value.doubleValue() == 0) { addViolation(data, variableDeclarator); } } } } } } } return data; } /** * Checks if a FieldDeclaration is a reference type (includes arrays). The * reference information is in the FieldDeclaration for this example: * * <pre> * int[] ia1 * </pre> * * and in the VariableDeclarator for this example: * * <pre> * int ia2[]; * </pre> * * . * * @param fieldDeclaration * the field to check. * @param variableDeclarator * the variable declarator to check. * @return <code>true</code> if the field is a reference. <code>false</code> * otherwise. */ private boolean isRef(ASTFieldDeclaration fieldDeclaration, ASTVariableDeclarator variableDeclarator) { Node type = fieldDeclaration.jjtGetChild(0).jjtGetChild(0); if (type instanceof ASTReferenceType) { // Reference type, array or otherwise return true; } else { // Primitive array? return ((ASTVariableDeclaratorId) variableDeclarator.jjtGetChild(0)).isArray(); } } private void addViolation(Object data, ASTVariableDeclarator variableDeclarator) { super.addViolation(data, variableDeclarator, variableDeclarator.jjtGetChild(0).getImage()); } private Number parseInteger(String s) { boolean negative = false; String number = s; if (number.charAt(0) == '-') { negative = true; number = number.substring(1); } BigInteger result; if (number.startsWith("0x") || number.startsWith("0X")) { result = new BigInteger(number.substring(2).replaceAll("_", ""), 16); } else if (number.startsWith("0b") || number.startsWith("0B")) { result = new BigInteger(number.substring(2).replaceAll("_", ""), 8); } else if (number.charAt(0) == '0' && number.length() > 1) { result = new BigInteger(number.substring(1).replaceAll("_", ""), 8); } else { result = new BigInteger(number.replaceAll("_", "")); } if (negative) { result = result.negate(); } return result; } }