InefficientStringBufferingRule.java
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(3instanceof 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(lengthinstanceof 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== -|| !(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 }