001 /**
002 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003 */
004 package net.sourceforge.pmd.lang.java.rule.strings;
005
006 import java.util.Iterator;
007 import java.util.List;
008
009 import net.sourceforge.pmd.lang.ast.Node;
010 import net.sourceforge.pmd.lang.java.ast.ASTAdditiveExpression;
011 import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression;
012 import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
013 import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement;
014 import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
015 import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
016 import net.sourceforge.pmd.lang.java.ast.ASTName;
017 import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression;
018 import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
019 import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
020 import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper;
021
022 /*
023 * How this rule works:
024 * find additive expressions: +
025 * check that the addition is between anything other than two literals
026 * if true and also the parent is StringBuffer constructor or append,
027 * report a violation.
028 *
029 * @author mgriffa
030 */
031 public class InefficientStringBufferingRule extends AbstractJavaRule {
032
033 @Override
034 public Object visit(ASTAdditiveExpression node, Object data) {
035 ASTBlockStatement bs = node.getFirstParentOfType(ASTBlockStatement.class);
036 if (bs == null) {
037 return data;
038 }
039
040 int immediateLiterals = 0;
041 List<ASTLiteral> nodes = node.findDescendantsOfType(ASTLiteral.class);
042 for (ASTLiteral literal: nodes) {
043 if (literal.getNthParent(3) instanceof ASTAdditiveExpression) {
044 immediateLiterals++;
045 }
046 if (literal.isIntLiteral() || literal.isFloatLiteral()) {
047 return data;
048 }
049 }
050
051 if (immediateLiterals > 1) {
052 return data;
053 }
054
055 // if literal + public static final, return
056 List<ASTName> nameNodes = node.findDescendantsOfType(ASTName.class);
057 for (ASTName name: nameNodes) {
058 if (name.getNameDeclaration() instanceof VariableNameDeclaration) {
059 VariableNameDeclaration vnd = (VariableNameDeclaration)name.getNameDeclaration();
060 if (vnd.getAccessNodeParent().isFinal() && vnd.getAccessNodeParent().isStatic()) {
061 return data;
062 }
063 }
064 }
065
066 if (bs.isAllocation()) {
067 for (Iterator<ASTName> iterator = nameNodes.iterator(); iterator.hasNext();) {
068 ASTName name = iterator.next();
069 if (!name.getImage().endsWith("length")) {
070 break;
071 } else if (!iterator.hasNext()) {
072 return data; //All names end with length
073 }
074 }
075
076 if (isAllocatedStringBuffer(node)) {
077 addViolation(data, node);
078 }
079 } else if (isInStringBufferOperation(node, 6, "append")) {
080 addViolation(data, node);
081 }
082 return data;
083 }
084
085 protected static boolean isInStringBufferOperation(Node node, int length, String methodName) {
086 if (!(node.getNthParent(length) instanceof ASTStatementExpression)) {
087 return false;
088 }
089 ASTStatementExpression s = node.getFirstParentOfType(ASTStatementExpression.class);
090 if (s == null) {
091 return false;
092 }
093 ASTName n = s.getFirstDescendantOfType(ASTName.class);
094 if (n == null || n.getImage().indexOf(methodName) == -1 || !(n.getNameDeclaration() instanceof VariableNameDeclaration)) {
095 return false;
096 }
097
098 // TODO having to hand-code this kind of dredging around is ridiculous
099 // we need something to support this in the framework
100 // but, "for now" (tm):
101 // if more than one arg to append(), skip it
102 ASTArgumentList argList = s.getFirstDescendantOfType(ASTArgumentList.class);
103 if (argList == null || argList.jjtGetNumChildren() > 1) {
104 return false;
105 }
106 return TypeHelper.isA((VariableNameDeclaration)n.getNameDeclaration(), StringBuffer.class);
107 }
108
109 private boolean isAllocatedStringBuffer(ASTAdditiveExpression node) {
110 ASTAllocationExpression ao = node.getFirstParentOfType(ASTAllocationExpression.class);
111 if (ao == null) {
112 return false;
113 }
114 // note that the child can be an ArrayDimsAndInits, for example, from java.lang.FloatingDecimal: t = new int[ nWords+wordcount+1 ];
115 ASTClassOrInterfaceType an = ao.getFirstChildOfType(ASTClassOrInterfaceType.class);
116 return an != null && (TypeHelper.isA(an, StringBuffer.class) || TypeHelper.isA(an, StringBuilder.class));
117 }
118 }
|