001: /**
002: * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003: */package net.sourceforge.pmd.rules.strings;
004:
005: import net.sourceforge.pmd.AbstractRule;
006: import net.sourceforge.pmd.ast.ASTAdditiveExpression;
007: import net.sourceforge.pmd.ast.ASTAllocationExpression;
008: import net.sourceforge.pmd.ast.ASTArgumentList;
009: import net.sourceforge.pmd.ast.ASTBlockStatement;
010: import net.sourceforge.pmd.ast.ASTClassOrInterfaceType;
011: import net.sourceforge.pmd.ast.ASTLiteral;
012: import net.sourceforge.pmd.ast.ASTName;
013: import net.sourceforge.pmd.ast.ASTStatementExpression;
014: import net.sourceforge.pmd.ast.Node;
015: import net.sourceforge.pmd.ast.SimpleNode;
016: import net.sourceforge.pmd.symboltable.VariableNameDeclaration;
017: import net.sourceforge.pmd.typeresolution.TypeHelper;
018:
019: import java.util.Iterator;
020: import java.util.List;
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 InefficientStringBuffering extends AbstractRule {
032:
033: public Object visit(ASTAdditiveExpression node, Object data) {
034: ASTBlockStatement bs = node
035: .getFirstParentOfType(ASTBlockStatement.class);
036: if (bs == null) {
037: return data;
038: }
039:
040: int immediateLiterals = 0;
041: List<ASTLiteral> nodes = node
042: .findChildrenOfType(ASTLiteral.class);
043: for (ASTLiteral literal : nodes) {
044: if (literal.jjtGetParent().jjtGetParent().jjtGetParent() instanceof ASTAdditiveExpression) {
045: immediateLiterals++;
046: }
047: try {
048: Integer.parseInt(literal.getImage());
049: return data;
050: } catch (NumberFormatException nfe) {
051: // NFE means new StringBuffer("a" + "b"), want to flag those
052: }
053: }
054:
055: if (immediateLiterals > 1) {
056: return data;
057: }
058:
059: // if literal + public static final, return
060: List<ASTName> nameNodes = node
061: .findChildrenOfType(ASTName.class);
062: for (ASTName name : nameNodes) {
063: if (name.getNameDeclaration() instanceof VariableNameDeclaration) {
064: VariableNameDeclaration vnd = (VariableNameDeclaration) name
065: .getNameDeclaration();
066: if (vnd.getAccessNodeParent().isFinal()
067: && vnd.getAccessNodeParent().isStatic()) {
068: return data;
069: }
070: }
071: }
072:
073: if (bs.isAllocation()) {
074: for (Iterator<ASTName> iterator = nameNodes.iterator(); iterator
075: .hasNext();) {
076: ASTName name = iterator.next();
077: if (!name.getImage().endsWith("length")) {
078: break;
079: } else if (!iterator.hasNext()) {
080: return data; //All names end with length
081: }
082: }
083:
084: if (isAllocatedStringBuffer(node)) {
085: addViolation(data, node);
086: }
087: } else if (isInStringBufferOperation(node, 6, "append")) {
088: addViolation(data, node);
089: }
090: return data;
091: }
092:
093: protected static boolean isInStringBufferOperation(SimpleNode node,
094: int length, String methodName) {
095: if (!xParentIsStatementExpression(node, length)) {
096: return false;
097: }
098: ASTStatementExpression s = node
099: .getFirstParentOfType(ASTStatementExpression.class);
100: if (s == null) {
101: return false;
102: }
103: ASTName n = s.getFirstChildOfType(ASTName.class);
104: if (n == null
105: || n.getImage().indexOf(methodName) == -1
106: || !(n.getNameDeclaration() instanceof VariableNameDeclaration)) {
107: return false;
108: }
109:
110: // TODO having to hand-code this kind of dredging around is ridiculous
111: // we need something to support this in the framework
112: // but, "for now" (tm):
113: // if more than one arg to append(), skip it
114: ASTArgumentList argList = s
115: .getFirstChildOfType(ASTArgumentList.class);
116: if (argList == null || argList.jjtGetNumChildren() > 1) {
117: return false;
118: }
119: return TypeHelper.isA((VariableNameDeclaration) n
120: .getNameDeclaration(), StringBuffer.class);
121: }
122:
123: // TODO move this method to SimpleNode
124: private static boolean xParentIsStatementExpression(
125: SimpleNode node, int length) {
126: Node curr = node;
127: for (int i = 0; i < length; i++) {
128: if (node.jjtGetParent() == null) {
129: return false;
130: }
131: curr = curr.jjtGetParent();
132: }
133: return curr instanceof ASTStatementExpression;
134: }
135:
136: private boolean isAllocatedStringBuffer(ASTAdditiveExpression node) {
137: ASTAllocationExpression ao = node
138: .getFirstParentOfType(ASTAllocationExpression.class);
139: if (ao == null) {
140: return false;
141: }
142: // note that the child can be an ArrayDimsAndInits, for example, from java.lang.FloatingDecimal: t = new int[ nWords+wordcount+1 ];
143: ASTClassOrInterfaceType an = ao
144: .getFirstChildOfType(ASTClassOrInterfaceType.class);
145: return an != null
146: && (TypeHelper.isA(an, StringBuffer.class) || TypeHelper
147: .isA(an, StringBuilder.class));
148: }
149: }
|