/**
* 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;
}
}