/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.rule.strings;
import java.util.Iterator;
import java.util.List;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.ast.ASTAdditiveExpression;
import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression;
import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter;
import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTName;
import net.sourceforge.pmd.lang.java.ast.ASTPrimitiveType;
import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression;
import net.sourceforge.pmd.lang.java.ast.ASTType;
import net.sourceforge.pmd.lang.java.ast.AccessNode;
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
import net.sourceforge.pmd.lang.java.symboltable.TypedNameDeclaration;
import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper;
/**
* How this rule works: find additive expressions: + check that the addition is
* between anything other than two literals if true and also the parent is
* StringBuffer constructor or append, report a violation.
*
* @author mgriffa
*/
public class InefficientStringBufferingRule extends AbstractJavaRule {
@Override
public Object visit(ASTAdditiveExpression node, Object data) {
ASTBlockStatement bs = node.getFirstParentOfType(ASTBlockStatement.class);
if (bs == null) {
return data;
}
int immediateLiterals = 0;
int immediateStringLiterals = 0;
List<ASTLiteral> nodes = node.findDescendantsOfType(ASTLiteral.class);
for (ASTLiteral literal : nodes) {
if (literal.getNthParent(3) instanceof ASTAdditiveExpression) {
immediateLiterals++;
if (literal.isStringLiteral()) {
immediateStringLiterals++;
}
}
if (literal.isIntLiteral() || literal.isFloatLiteral() || literal.isDoubleLiteral()
|| literal.isLongLiteral()) {
return data;
}
}
if (immediateLiterals > 1) {
return data;
}
// if literal + public static final, return
List<ASTName> nameNodes = node.findDescendantsOfType(ASTName.class);
for (ASTName name : nameNodes) {
if (name.getNameDeclaration() != null && name.getNameDeclaration() instanceof VariableNameDeclaration) {
VariableNameDeclaration vnd = (VariableNameDeclaration) name.getNameDeclaration();
AccessNode accessNodeParent = vnd.getAccessNodeParent();
if (accessNodeParent.isFinal() && accessNodeParent.isStatic()) {
return data;
}
}
}
// if literal primitive type and not strings variables, then return
boolean stringFound = false;
for (ASTName name : nameNodes) {
if (!isPrimitiveType(name) && isStringType(name)) {
stringFound = true;
break;
}
}
if (!stringFound && immediateStringLiterals == 0) {
return data;
}
if (bs.isAllocation()) {
for (Iterator<ASTName> iterator = nameNodes.iterator(); iterator.hasNext();) {
ASTName name = iterator.next();
if (!name.getImage().endsWith("length")) {
break;
} else if (!iterator.hasNext()) {
return data; // All names end with length
}
}
if (isAllocatedStringBuffer(node)) {
addViolation(data, node);
}
} else if (isInStringBufferOperation(node, 6, "append")) {
addViolation(data, node);
}
return data;
}
private boolean isStringType(ASTName name) {
ASTType type = getTypeNode(name);
if (type != null) {
List<ASTClassOrInterfaceType> types = type.findDescendantsOfType(ASTClassOrInterfaceType.class);
if (!types.isEmpty()) {
ASTClassOrInterfaceType typeDeclaration = types.get(0);
if (String.class == typeDeclaration.getType() || "String".equals(typeDeclaration.getImage())) {
return true;
}
}
}
return false;
}
private boolean isPrimitiveType(ASTName name) {
ASTType type = getTypeNode(name);
if (type != null && !type.findChildrenOfType(ASTPrimitiveType.class).isEmpty()) {
return true;
}
return false;
}
private ASTType getTypeNode(ASTName name) {
if (name.getNameDeclaration() instanceof VariableNameDeclaration) {
VariableNameDeclaration vnd = (VariableNameDeclaration) name.getNameDeclaration();
if (vnd.getAccessNodeParent() instanceof ASTLocalVariableDeclaration) {
ASTLocalVariableDeclaration l = (ASTLocalVariableDeclaration) vnd.getAccessNodeParent();
return l.getTypeNode();
} else if (vnd.getAccessNodeParent() instanceof ASTFormalParameter) {
ASTFormalParameter p = (ASTFormalParameter) vnd.getAccessNodeParent();
return p.getTypeNode();
}
}
return null;
}
protected static boolean isInStringBufferOperation(Node node, int length, String methodName) {
if (!(node.getNthParent(length) instanceof ASTStatementExpression)) {
return false;
}
ASTStatementExpression s = node.getFirstParentOfType(ASTStatementExpression.class);
if (s == null) {
return false;
}
ASTName n = s.getFirstDescendantOfType(ASTName.class);
if (n == null || n.getImage().indexOf(methodName) == -1
|| !(n.getNameDeclaration() instanceof TypedNameDeclaration)) {
return false;
}
// TODO having to hand-code this kind of dredging around is ridiculous
// we need something to support this in the framework
// but, "for now" (tm):
// if more than one arg to append(), skip it
ASTArgumentList argList = s.getFirstDescendantOfType(ASTArgumentList.class);
if (argList == null || argList.jjtGetNumChildren() > 1) {
return false;
}
return TypeHelper.isEither((TypedNameDeclaration) n.getNameDeclaration(), StringBuffer.class,
StringBuilder.class);
}
private boolean isAllocatedStringBuffer(ASTAdditiveExpression node) {
ASTAllocationExpression ao = node.getFirstParentOfType(ASTAllocationExpression.class);
if (ao == null) {
return false;
}
// note that the child can be an ArrayDimsAndInits, for example, from
// java.lang.FloatingDecimal: t = new int[ nWords+wordcount+1 ];
ASTClassOrInterfaceType an = ao.getFirstChildOfType(ASTClassOrInterfaceType.class);
return an != null && TypeHelper.isEither(an, StringBuffer.class, StringBuilder.class);
}
}