View Javadoc

1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.java.rule.strings;
5   
6   import java.util.Iterator;
7   import java.util.List;
8   
9   import net.sourceforge.pmd.lang.ast.Node;
10  import net.sourceforge.pmd.lang.java.ast.ASTAdditiveExpression;
11  import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression;
12  import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
13  import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement;
14  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
15  import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter;
16  import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
17  import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration;
18  import net.sourceforge.pmd.lang.java.ast.ASTName;
19  import net.sourceforge.pmd.lang.java.ast.ASTPrimitiveType;
20  import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression;
21  import net.sourceforge.pmd.lang.java.ast.ASTType;
22  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
23  import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
24  import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper;
25  
26  /**
27   * How this rule works:
28   * find additive expressions: +
29   * check that the addition is between anything other than two literals
30   * if true and also the parent is StringBuffer constructor or append,
31   * report a violation.
32   *
33   * @author mgriffa
34   */
35  public class InefficientStringBufferingRule extends AbstractJavaRule {
36  
37      @Override
38      public Object visit(ASTAdditiveExpression node, Object data) {
39          ASTBlockStatement bs = node.getFirstParentOfType(ASTBlockStatement.class);
40          if (bs == null) {
41              return data;
42          }
43  
44          int immediateLiterals = 0;
45          int immediateStringLiterals = 0;
46          List<ASTLiteral> nodes = node.findDescendantsOfType(ASTLiteral.class);
47          for (ASTLiteral literal: nodes) {
48              if (literal.getNthParent(3) instanceof ASTAdditiveExpression) {
49                  immediateLiterals++;
50                  if (literal.isStringLiteral()) {
51                  	immediateStringLiterals++;
52                  }
53              }
54              if (literal.isIntLiteral() || literal.isFloatLiteral()) {
55          	return data;
56              }
57          }
58  
59          if (immediateLiterals > 1) {
60              return data;
61          }
62  
63          // if literal + public static final, return
64          List<ASTName> nameNodes = node.findDescendantsOfType(ASTName.class);
65          for (ASTName name: nameNodes) {
66              if (name.getNameDeclaration() instanceof VariableNameDeclaration) {
67                  VariableNameDeclaration vnd = (VariableNameDeclaration)name.getNameDeclaration();
68                  if (vnd.getAccessNodeParent().isFinal() && vnd.getAccessNodeParent().isStatic()) {
69                      return data;
70                  }
71              }
72          }
73          
74          // if literal primitive type and not strings variables, then return
75          boolean stringFound = false;
76          for (ASTName name: nameNodes) {
77          	if (!isPrimitiveType(name) && isStringType(name)) {
78          		stringFound = true;
79          		break;
80          	}
81          }
82          if (!stringFound && immediateStringLiterals == 0) {
83          	return data;
84          }
85  
86          if (bs.isAllocation()) {
87              for (Iterator<ASTName> iterator = nameNodes.iterator(); iterator.hasNext();) {
88              	ASTName name = iterator.next();
89      			if (!name.getImage().endsWith("length")) {
90      				break;
91      			} else if (!iterator.hasNext()) {
92      				return data;	//All names end with length
93      			}
94      		}
95  
96              if (isAllocatedStringBuffer(node)) {
97                  addViolation(data, node);
98              }
99          } else if (isInStringBufferOperation(node, 6, "append")) {
100             addViolation(data, node);
101         }
102         return data;
103     }
104 
105     private boolean isStringType(ASTName name) {
106     	ASTType type = getTypeNode(name);
107     	if (type != null) {
108     		List<ASTClassOrInterfaceType> types = type.findDescendantsOfType(ASTClassOrInterfaceType.class);
109     		if (!types.isEmpty()) {
110     			ASTClassOrInterfaceType typeDeclaration = types.get(0);
111     			if (String.class == typeDeclaration.getType() || "String".equals(typeDeclaration.getImage())) {
112     				return true;
113     			}
114     		}
115     	}
116 		return false;
117 	}
118 
119 	private boolean isPrimitiveType(ASTName name) {
120 		ASTType type = getTypeNode(name);
121 		if (type != null && !type.findChildrenOfType(ASTPrimitiveType.class).isEmpty()) {
122 			return true;
123 		}
124 		return false;
125 	}
126 	
127 	private ASTType getTypeNode(ASTName name) {
128     	if (name.getNameDeclaration() instanceof VariableNameDeclaration) {
129     		VariableNameDeclaration vnd = (VariableNameDeclaration) name.getNameDeclaration();
130     		if (vnd.getAccessNodeParent() instanceof ASTLocalVariableDeclaration) {
131     			ASTLocalVariableDeclaration l = (ASTLocalVariableDeclaration)vnd.getAccessNodeParent();
132     			return l.getTypeNode();
133     		} else if (vnd.getAccessNodeParent() instanceof ASTFormalParameter) {
134     			ASTFormalParameter p = (ASTFormalParameter) vnd.getAccessNodeParent();
135     			return p.getTypeNode();
136     		}
137     	}
138 		return null;
139 	}
140 
141 	protected static boolean isInStringBufferOperation(Node node, int length, String methodName) {
142         if (!(node.getNthParent(length) instanceof ASTStatementExpression)) {
143             return false;
144         }
145         ASTStatementExpression s = node.getFirstParentOfType(ASTStatementExpression.class);
146         if (s == null) {
147             return false;
148         }
149         ASTName n = s.getFirstDescendantOfType(ASTName.class);
150         if (n == null || n.getImage().indexOf(methodName) == -1 || !(n.getNameDeclaration() instanceof VariableNameDeclaration)) {
151             return false;
152         }
153 
154         // TODO having to hand-code this kind of dredging around is ridiculous
155         // we need something to support this in the framework
156         // but, "for now" (tm):
157         // if more than one arg to append(), skip it
158         ASTArgumentList argList = s.getFirstDescendantOfType(ASTArgumentList.class);
159         if (argList == null || argList.jjtGetNumChildren() > 1) {
160             return false;
161         }
162         return TypeHelper.isEither((VariableNameDeclaration)n.getNameDeclaration(), StringBuffer.class, StringBuilder.class);
163     }
164 
165     private boolean isAllocatedStringBuffer(ASTAdditiveExpression node) {
166         ASTAllocationExpression ao = node.getFirstParentOfType(ASTAllocationExpression.class);
167         if (ao == null) {
168             return false;
169         }
170         // note that the child can be an ArrayDimsAndInits, for example, from java.lang.FloatingDecimal:  t = new int[ nWords+wordcount+1 ];
171         ASTClassOrInterfaceType an = ao.getFirstChildOfType(ASTClassOrInterfaceType.class);
172         return an != null && TypeHelper.isEither(an, StringBuffer.class, StringBuilder.class);
173     }
174 }
175