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 java.util.HashMap;
006: import java.util.HashSet;
007: import java.util.List;
008: import java.util.Map;
009: import java.util.Set;
010:
011: import net.sourceforge.pmd.AbstractRule;
012: import net.sourceforge.pmd.ast.ASTAdditiveExpression;
013: import net.sourceforge.pmd.ast.ASTBlockStatement;
014: import net.sourceforge.pmd.ast.ASTFieldDeclaration;
015: import net.sourceforge.pmd.ast.ASTFormalParameter;
016: import net.sourceforge.pmd.ast.ASTIfStatement;
017: import net.sourceforge.pmd.ast.ASTLiteral;
018: import net.sourceforge.pmd.ast.ASTMultiplicativeExpression;
019: import net.sourceforge.pmd.ast.ASTName;
020: import net.sourceforge.pmd.ast.ASTPrimaryExpression;
021: import net.sourceforge.pmd.ast.ASTPrimaryPrefix;
022: import net.sourceforge.pmd.ast.ASTPrimarySuffix;
023: import net.sourceforge.pmd.ast.ASTSwitchLabel;
024: import net.sourceforge.pmd.ast.ASTSwitchStatement;
025: import net.sourceforge.pmd.ast.ASTVariableDeclaratorId;
026: import net.sourceforge.pmd.ast.Node;
027: import net.sourceforge.pmd.ast.SimpleNode;
028: import net.sourceforge.pmd.symboltable.NameOccurrence;
029: import net.sourceforge.pmd.typeresolution.TypeHelper;
030:
031: /**
032: * This rule finds StringBuffers which may have been pre-sized incorrectly
033: *
034: * See http://sourceforge.net/forum/forum.php?thread_id=1438119&forum_id=188194
035: * @author Allan Caplan
036: */
037: public class InsufficientStringBufferDeclaration extends AbstractRule {
038:
039: private final static Set<Class<? extends SimpleNode>> blockParents;
040:
041: static {
042: blockParents = new HashSet<Class<? extends SimpleNode>>();
043: blockParents.add(ASTIfStatement.class);
044: blockParents.add(ASTSwitchStatement.class);
045: }
046:
047: public Object visit(ASTVariableDeclaratorId node, Object data) {
048: if (!TypeHelper.isA(node.getNameDeclaration(),
049: StringBuffer.class)) {
050: return data;
051: }
052: Node rootNode = node;
053: int anticipatedLength = 0;
054: int constructorLength = 16;
055:
056: constructorLength = getConstructorLength(node,
057: constructorLength);
058: anticipatedLength = getInitialLength(node);
059: List<NameOccurrence> usage = node.getUsages();
060: Map<Node, Map<Node, Integer>> blocks = new HashMap<Node, Map<Node, Integer>>();
061: for (int ix = 0; ix < usage.size(); ix++) {
062: NameOccurrence no = usage.get(ix);
063: SimpleNode n = no.getLocation();
064: if (!InefficientStringBuffering.isInStringBufferOperation(
065: n, 3, "append")) {
066:
067: if (!no.isOnLeftHandSide()
068: && !InefficientStringBuffering
069: .isInStringBufferOperation(n, 3,
070: "setLength")) {
071: continue;
072: }
073: if (constructorLength != -1
074: && anticipatedLength > constructorLength) {
075: anticipatedLength += processBlocks(blocks);
076: String[] param = {
077: String.valueOf(constructorLength),
078: String.valueOf(anticipatedLength) };
079: addViolation(data, rootNode, param);
080: }
081: constructorLength = getConstructorLength(n,
082: constructorLength);
083: rootNode = n;
084: anticipatedLength = getInitialLength(node);
085: }
086: ASTPrimaryExpression s = n
087: .getFirstParentOfType(ASTPrimaryExpression.class);
088: int numChildren = s.jjtGetNumChildren();
089: for (int jx = 0; jx < numChildren; jx++) {
090: SimpleNode sn = (SimpleNode) s.jjtGetChild(jx);
091: if (!(sn instanceof ASTPrimarySuffix)
092: || sn.getImage() != null) {
093: continue;
094: }
095: int this Size = 0;
096: Node block = getFirstParentBlock(sn);
097: if (isAdditive(sn)) {
098: this Size = processAdditive(sn);
099: } else {
100: this Size = processNode(sn);
101: }
102: if (block != null) {
103: storeBlockStatistics(blocks, this Size, block);
104: } else {
105: anticipatedLength += this Size;
106: }
107: }
108: }
109: anticipatedLength += processBlocks(blocks);
110: if (constructorLength != -1
111: && anticipatedLength > constructorLength) {
112: String[] param = { String.valueOf(constructorLength),
113: String.valueOf(anticipatedLength) };
114: addViolation(data, rootNode, param);
115: }
116: return data;
117: }
118:
119: /**
120: * This rule is concerned with IF and Switch blocks. Process the block into
121: * a local Map, from which we can later determine which is the longest block
122: * inside
123: *
124: * @param blocks
125: * The map of blocks in the method being investigated
126: * @param thisSize
127: * The size of the current block
128: * @param block
129: * The block in question
130: */
131: private void storeBlockStatistics(
132: Map<Node, Map<Node, Integer>> blocks, int this Size,
133: Node block) {
134: Node statement = block.jjtGetParent();
135: if (ASTIfStatement.class
136: .equals(block.jjtGetParent().getClass())) {
137: // Else Ifs are their own subnode in AST. So we have to
138: // look a little farther up the tree to find the IF statement
139: Node possibleStatement = ((SimpleNode) statement)
140: .getFirstParentOfType(ASTIfStatement.class);
141: while (possibleStatement != null
142: && possibleStatement.getClass().equals(
143: ASTIfStatement.class)) {
144: statement = possibleStatement;
145: possibleStatement = ((SimpleNode) possibleStatement)
146: .getFirstParentOfType(ASTIfStatement.class);
147: }
148: }
149: Map<Node, Integer> this Branch = blocks.get(statement);
150: if (this Branch == null) {
151: this Branch = new HashMap<Node, Integer>();
152: blocks.put(statement, this Branch);
153: }
154: Integer x = this Branch.get(block);
155: if (x != null) {
156: this Size += x;
157: }
158: this Branch.put(statement, this Size);
159: }
160:
161: private int processBlocks(Map<Node, Map<Node, Integer>> blocks) {
162: int anticipatedLength = 0;
163: int ifLength = 0;
164: for (Map.Entry<Node, Map<Node, Integer>> entry : blocks
165: .entrySet()) {
166: ifLength = 0;
167: for (Map.Entry<Node, Integer> entry2 : entry.getValue()
168: .entrySet()) {
169: Integer value = entry2.getValue();
170: ifLength = Math.max(ifLength, value.intValue());
171: }
172: anticipatedLength += ifLength;
173: }
174: return anticipatedLength;
175: }
176:
177: private int processAdditive(SimpleNode sn) {
178: ASTAdditiveExpression additive = sn
179: .getFirstChildOfType(ASTAdditiveExpression.class);
180: if (additive == null) {
181: return 0;
182: }
183: int anticipatedLength = 0;
184: for (int ix = 0; ix < additive.jjtGetNumChildren(); ix++) {
185: SimpleNode childNode = (SimpleNode) additive
186: .jjtGetChild(ix);
187: ASTLiteral literal = childNode
188: .getFirstChildOfType(ASTLiteral.class);
189: if (literal != null && literal.getImage() != null) {
190: anticipatedLength += literal.getImage().length() - 2;
191: }
192: }
193:
194: return anticipatedLength;
195: }
196:
197: private static final boolean isLiteral(String str) {
198: if (str.length() == 0) {
199: return false;
200: }
201: char c = str.charAt(0);
202: return (c == '"' || c == '\'');
203: }
204:
205: private int processNode(SimpleNode sn) {
206: int anticipatedLength = 0;
207: ASTPrimaryPrefix xn = sn
208: .getFirstChildOfType(ASTPrimaryPrefix.class);
209: if (xn.jjtGetNumChildren() != 0
210: && xn.jjtGetChild(0).getClass()
211: .equals(ASTLiteral.class)) {
212: String str = ((SimpleNode) xn.jjtGetChild(0)).getImage();
213: if (str != null) {
214: if (isLiteral(str)) {
215: anticipatedLength += str.length() - 2;
216: } else if (str.startsWith("0x")) {
217: anticipatedLength += 1;
218: } else {
219: anticipatedLength += str.length();
220: }
221: }
222: }
223: return anticipatedLength;
224: }
225:
226: private int getConstructorLength(SimpleNode node,
227: int constructorLength) {
228: int iConstructorLength = constructorLength;
229: SimpleNode block = node
230: .getFirstParentOfType(ASTBlockStatement.class);
231: List<ASTLiteral> literal;
232:
233: if (block == null) {
234: block = node
235: .getFirstParentOfType(ASTFieldDeclaration.class);
236: }
237: if (block == null) {
238: block = node.getFirstParentOfType(ASTFormalParameter.class);
239: if (block != null) {
240: iConstructorLength = -1;
241: }
242: }
243:
244: //if there is any addition/subtraction going on then just use the default.
245: ASTAdditiveExpression exp = block
246: .getFirstChildOfType(ASTAdditiveExpression.class);
247: if (exp != null) {
248: return 16;
249: }
250: ASTMultiplicativeExpression mult = block
251: .getFirstChildOfType(ASTMultiplicativeExpression.class);
252: if (mult != null) {
253: return 16;
254: }
255:
256: literal = block.findChildrenOfType(ASTLiteral.class);
257: if (literal.isEmpty()) {
258: List<ASTName> name = block
259: .findChildrenOfType(ASTName.class);
260: if (!name.isEmpty()) {
261: iConstructorLength = -1;
262: }
263: } else if (literal.size() == 1) {
264: String str = literal.get(0).getImage();
265: if (str == null) {
266: iConstructorLength = 0;
267: } else if (isLiteral(str)) {
268: // since it's not taken into account
269: // anywhere. only count the extra 16
270: // characters
271: iConstructorLength = 14 + str.length(); // don't add the constructor's length,
272: } else {
273: iConstructorLength = Integer.parseInt(str);
274: }
275: } else {
276: iConstructorLength = -1;
277: }
278:
279: if (iConstructorLength == 0) {
280: iConstructorLength = 16;
281: }
282:
283: return iConstructorLength;
284: }
285:
286: private int getInitialLength(SimpleNode node) {
287: SimpleNode block = node
288: .getFirstParentOfType(ASTBlockStatement.class);
289:
290: if (block == null) {
291: block = node
292: .getFirstParentOfType(ASTFieldDeclaration.class);
293: if (block == null) {
294: block = node
295: .getFirstParentOfType(ASTFormalParameter.class);
296: }
297: }
298: List<ASTLiteral> literal = block
299: .findChildrenOfType(ASTLiteral.class);
300: if (literal.size() == 1) {
301: String str = literal.get(0).getImage();
302: if (str != null && isLiteral(str)) {
303: return str.length() - 2; // take off the quotes
304: }
305: }
306:
307: return 0;
308: }
309:
310: private boolean isAdditive(SimpleNode n) {
311: return n.findChildrenOfType(ASTAdditiveExpression.class).size() >= 1;
312: }
313:
314: /**
315: * Locate the block that the given node is in, if any
316: *
317: * @param node
318: * The node we're looking for a parent of
319: * @return Node - The node that corresponds to any block that may be a
320: * parent of this object
321: */
322: private Node getFirstParentBlock(Node node) {
323: Node parentNode = node.jjtGetParent();
324:
325: Node lastNode = node;
326: while (parentNode != null
327: && !blockParents.contains(parentNode.getClass())) {
328: lastNode = parentNode;
329: parentNode = parentNode.jjtGetParent();
330: }
331: if (parentNode != null
332: && ASTIfStatement.class.equals(parentNode.getClass())) {
333: parentNode = lastNode;
334: } else if (parentNode != null
335: && parentNode.getClass().equals(
336: ASTSwitchStatement.class)) {
337: parentNode = getSwitchParent(parentNode, lastNode);
338: }
339: return parentNode;
340: }
341:
342: /**
343: * Determine which SwitchLabel we belong to inside a switch
344: *
345: * @param parentNode
346: * The parent node we're looking at
347: * @param lastNode
348: * The last node processed
349: * @return The parent node for the switch statement
350: */
351: private static Node getSwitchParent(Node parentNode, Node lastNode) {
352: int allChildren = parentNode.jjtGetNumChildren();
353: ASTSwitchLabel label = null;
354: for (int ix = 0; ix < allChildren; ix++) {
355: Node n = parentNode.jjtGetChild(ix);
356: if (n.getClass().equals(ASTSwitchLabel.class)) {
357: label = (ASTSwitchLabel) n;
358: } else if (n.equals(lastNode)) {
359: parentNode = label;
360: break;
361: }
362: }
363: return parentNode;
364: }
365:
366: }
|