/** * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.lang.java.rule.strings; import java.util.List; import java.util.Map; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement; import net.sourceforge.pmd.lang.java.ast.ASTExpression; import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTName; import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression; import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix; import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix; import net.sourceforge.pmd.lang.java.ast.ASTStatement; import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.java.ast.AbstractJavaNode; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper; import net.sourceforge.pmd.lang.symboltable.NameOccurrence; /** * Original rule was written with XPath, but didn't verify whether the two calls * to append would have been done on the same variable. * * <pre> //BlockStatement[./Statement/StatementExpression//PrimaryPrefix/Name[ends-with(@Image,'.append')] [substring-before(@Image, '.') = ancestor::Block//LocalVariableDeclaration[./Type//ClassOrInterfaceType[@Image='StringBuffer']]//VariableDeclaratorId/@Image ] ]/following-sibling::*[1][./Statement/StatementExpression//PrimaryPrefix/Name[ends-with(@Image,'.append')] [substring-before(@Image, '.') = ancestor::Block//LocalVariableDeclaration[./Type//ClassOrInterfaceType[@Image='StringBuffer']]//VariableDeclaratorId/@Image ] ] | //BlockStatement[./Statement/StatementExpression//PrimaryPrefix/Name[ends-with(@Image,'.append')] [substring-before(@Image, '.') = ancestor::Block//LocalVariableDeclaration[./Type//ClassOrInterfaceType[@Image='StringBuilder']]//VariableDeclaratorId/@Image ] ]/following-sibling::*[1][./Statement/StatementExpression//PrimaryPrefix/Name[ends-with(@Image,'.append')] [substring-before(@Image, '.') = ancestor::Block//LocalVariableDeclaration[./Type//ClassOrInterfaceType[@Image='StringBuilder']]//VariableDeclaratorId/@Image ] ] * * </pre> * */ public class ConsecutiveAppendsShouldReuseRule extends AbstractJavaRule { @Override public Object visit(ASTBlockStatement node, Object data) { String variable = getVariableAppended(node); if (variable != null) { ASTBlockStatement nextSibling = getNextBlockStatementSibling(node); if (nextSibling != null) { String nextVariable = getVariableAppended(nextSibling); if (nextVariable != null && nextVariable.equals(variable)) { addViolation(data, node); } } } return super.visit(node, data); } private ASTBlockStatement getNextBlockStatementSibling(Node node) { Node parent = node.jjtGetParent(); int childIndex = -1; for (int i = 0; i < parent.jjtGetNumChildren(); i++) { if (parent.jjtGetChild(i) == node) { childIndex = i; break; } } if (childIndex + 1 < parent.jjtGetNumChildren()) { Node nextSibling = parent.jjtGetChild(childIndex + 1); if (nextSibling instanceof ASTBlockStatement) { return (ASTBlockStatement) nextSibling; } } return null; } private String getVariableAppended(ASTBlockStatement node) { if (isFirstChild(node, ASTStatement.class)) { ASTStatement statement = (ASTStatement) node.jjtGetChild(0); if (isFirstChild(statement, ASTStatementExpression.class)) { ASTStatementExpression stmtExp = (ASTStatementExpression) statement.jjtGetChild(0); if (stmtExp.jjtGetNumChildren() == 1) { ASTPrimaryPrefix primaryPrefix = stmtExp.getFirstDescendantOfType(ASTPrimaryPrefix.class); if (primaryPrefix != null) { ASTName name = primaryPrefix.getFirstChildOfType(ASTName.class); if (name != null) { String image = name.getImage(); if (image.endsWith(".append")) { String variable = image.substring(0, image.indexOf('.')); if (isAStringBuilderBuffer(primaryPrefix, variable)) { return variable; } } } } } else { final ASTExpression exp = stmtExp.getFirstDescendantOfType(ASTExpression.class); if (isFirstChild(exp, ASTPrimaryExpression.class)) { final ASTPrimarySuffix primarySuffix = ((ASTPrimaryExpression) exp.jjtGetChild(0)) .getFirstDescendantOfType(ASTPrimarySuffix.class); if (primarySuffix != null) { final String name = primarySuffix.getImage(); if ("append".equals(name)) { final ASTPrimaryExpression pExp = stmtExp .getFirstDescendantOfType(ASTPrimaryExpression.class); if (pExp != null) { final ASTName astName = stmtExp.getFirstDescendantOfType(ASTName.class); if (astName != null) { final String variable = astName.getImage(); if (isAStringBuilderBuffer(primarySuffix, variable)) { return variable; } } } } } } } } } else if (isFirstChild(node, ASTLocalVariableDeclaration.class)) { ASTLocalVariableDeclaration lvd = (ASTLocalVariableDeclaration) node.jjtGetChild(0); ASTVariableDeclaratorId vdId = lvd.getFirstDescendantOfType(ASTVariableDeclaratorId.class); ASTExpression exp = lvd.getFirstDescendantOfType(ASTExpression.class); if (exp != null) { ASTPrimarySuffix primarySuffix = exp.getFirstDescendantOfType(ASTPrimarySuffix.class); if (primarySuffix != null) { final String name = primarySuffix.getImage(); if ("append".equals(name)) { String variable = vdId.getImage(); if (isAStringBuilderBuffer(primarySuffix, variable)) { return variable; } } } } } return null; } private boolean isAStringBuilderBuffer(AbstractJavaNode node, String name) { Map<VariableNameDeclaration, List<NameOccurrence>> declarations = node.getScope() .getDeclarations(VariableNameDeclaration.class); for (VariableNameDeclaration decl : declarations.keySet()) { if (decl.getName().equals(name) && TypeHelper.isEither(decl, StringBuilder.class, StringBuffer.class)) { return true; } } return false; } private boolean isFirstChild(Node node, Class<?> clazz) { if (node.jjtGetNumChildren() == 1 && clazz.isAssignableFrom(node.jjtGetChild(0).getClass())) { return true; } return false; } }