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.PropertyDescriptor;
007: import net.sourceforge.pmd.ast.ASTAdditiveExpression;
008: import net.sourceforge.pmd.ast.ASTArgumentList;
009: import net.sourceforge.pmd.ast.ASTDoStatement;
010: import net.sourceforge.pmd.ast.ASTForStatement;
011: import net.sourceforge.pmd.ast.ASTIfStatement;
012: import net.sourceforge.pmd.ast.ASTLiteral;
013: import net.sourceforge.pmd.ast.ASTMethodDeclaration;
014: import net.sourceforge.pmd.ast.ASTName;
015: import net.sourceforge.pmd.ast.ASTPrimaryExpression;
016: import net.sourceforge.pmd.ast.ASTPrimarySuffix;
017: import net.sourceforge.pmd.ast.ASTSwitchLabel;
018: import net.sourceforge.pmd.ast.ASTSwitchStatement;
019: import net.sourceforge.pmd.ast.ASTVariableDeclaratorId;
020: import net.sourceforge.pmd.ast.ASTWhileStatement;
021: import net.sourceforge.pmd.ast.Node;
022: import net.sourceforge.pmd.ast.SimpleNode;
023: import net.sourceforge.pmd.ast.TypeNode;
024: import net.sourceforge.pmd.properties.IntegerProperty;
025: import net.sourceforge.pmd.symboltable.NameOccurrence;
026: import net.sourceforge.pmd.symboltable.VariableNameDeclaration;
027: import net.sourceforge.pmd.typeresolution.TypeHelper;
028:
029: import java.util.HashSet;
030: import java.util.List;
031: import java.util.Map;
032: import java.util.Set;
033:
034: /**
035: * This rule finds concurrent calls to StringBuffer.append where String literals
036: * are used It would be much better to make these calls using one call to
037: * .append
038: * <p/>
039: * example:
040: * <p/>
041: * <pre>
042: * StringBuffer buf = new StringBuffer();
043: * buf.append("Hello");
044: * buf.append(" ").append("World");
045: * </pre>
046: * <p/>
047: * This would be more eloquently put as:
048: * <p/>
049: * <pre>
050: * StringBuffer buf = new StringBuffer();
051: * buf.append("Hello World");
052: * </pre>
053: * <p/>
054: * The rule takes one parameter, threshold, which defines the lower limit of
055: * consecutive appends before a violation is created. The default is 1.
056: */
057: public class ConsecutiveLiteralAppends extends AbstractRule {
058:
059: private final static Set<Class> blockParents;
060:
061: static {
062: blockParents = new HashSet<Class>();
063: blockParents.add(ASTForStatement.class);
064: blockParents.add(ASTWhileStatement.class);
065: blockParents.add(ASTDoStatement.class);
066: blockParents.add(ASTIfStatement.class);
067: blockParents.add(ASTSwitchStatement.class);
068: blockParents.add(ASTMethodDeclaration.class);
069: }
070:
071: private static final PropertyDescriptor thresholdDescriptor = new IntegerProperty(
072: "threshold", "?", 1, 1.0f);
073:
074: private static final Map<String, PropertyDescriptor> propertyDescriptorsByName = asFixedMap(thresholdDescriptor);
075:
076: private int threshold = 1;
077:
078: public Object visit(ASTVariableDeclaratorId node, Object data) {
079:
080: if (!isStringBuffer(node)) {
081: return data;
082: }
083: threshold = getIntProperty(thresholdDescriptor);
084:
085: int concurrentCount = checkConstructor(node, data);
086: Node lastBlock = getFirstParentBlock(node);
087: Node currentBlock = lastBlock;
088: Map<VariableNameDeclaration, List<NameOccurrence>> decls = node
089: .getScope().getVariableDeclarations();
090: SimpleNode rootNode = null;
091: // only want the constructor flagged if it's really containing strings
092: if (concurrentCount == 1) {
093: rootNode = node;
094: }
095: for (Map.Entry<VariableNameDeclaration, List<NameOccurrence>> entry : decls
096: .entrySet()) {
097: List<NameOccurrence> decl = entry.getValue();
098: for (NameOccurrence no : decl) {
099: SimpleNode n = no.getLocation();
100:
101: currentBlock = getFirstParentBlock(n);
102:
103: if (!InefficientStringBuffering
104: .isInStringBufferOperation(n, 3, "append")) {
105: if (!no.isPartOfQualifiedName()) {
106: checkForViolation(rootNode, data,
107: concurrentCount);
108: concurrentCount = 0;
109: }
110: continue;
111: }
112: ASTPrimaryExpression s = n
113: .getFirstParentOfType(ASTPrimaryExpression.class);
114: int numChildren = s.jjtGetNumChildren();
115: for (int jx = 0; jx < numChildren; jx++) {
116: SimpleNode sn = (SimpleNode) s.jjtGetChild(jx);
117: if (!(sn instanceof ASTPrimarySuffix)
118: || sn.getImage() != null) {
119: continue;
120: }
121:
122: // see if it changed blocks
123: if ((currentBlock != null && lastBlock != null && !currentBlock
124: .equals(lastBlock))
125: || (currentBlock == null ^ lastBlock == null)) {
126: checkForViolation(rootNode, data,
127: concurrentCount);
128: concurrentCount = 0;
129: }
130:
131: // if concurrent is 0 then we reset the root to report from
132: // here
133: if (concurrentCount == 0) {
134: rootNode = sn;
135: }
136: if (isAdditive(sn)) {
137: concurrentCount = processAdditive(data,
138: concurrentCount, sn, rootNode);
139: if (concurrentCount != 0) {
140: rootNode = sn;
141: }
142: } else if (!isAppendingStringLiteral(sn)) {
143: checkForViolation(rootNode, data,
144: concurrentCount);
145: concurrentCount = 0;
146: } else {
147: concurrentCount++;
148: }
149: lastBlock = currentBlock;
150: }
151: }
152: }
153: checkForViolation(rootNode, data, concurrentCount);
154: return data;
155: }
156:
157: /**
158: * Determie if the constructor contains (or ends with) a String Literal
159: *
160: * @param node
161: * @return 1 if the constructor contains string argument, else 0
162: */
163: private int checkConstructor(ASTVariableDeclaratorId node,
164: Object data) {
165: Node parent = node.jjtGetParent();
166: if (parent.jjtGetNumChildren() >= 2) {
167: ASTArgumentList list = ((SimpleNode) parent.jjtGetChild(1))
168: .getFirstChildOfType(ASTArgumentList.class);
169: if (list != null) {
170: ASTLiteral literal = list
171: .getFirstChildOfType(ASTLiteral.class);
172: if (!isAdditive(list) && literal != null
173: && literal.isStringLiteral()) {
174: return 1;
175: }
176: return processAdditive(data, 0, list, node);
177: }
178: }
179: return 0;
180: }
181:
182: private int processAdditive(Object data, int concurrentCount,
183: SimpleNode sn, SimpleNode rootNode) {
184: ASTAdditiveExpression additive = sn
185: .getFirstChildOfType(ASTAdditiveExpression.class);
186: if (additive == null) {
187: return 0;
188: }
189: int count = concurrentCount;
190: boolean found = false;
191: for (int ix = 0; ix < additive.jjtGetNumChildren(); ix++) {
192: SimpleNode childNode = (SimpleNode) additive
193: .jjtGetChild(ix);
194: if (childNode.jjtGetNumChildren() != 1
195: || childNode.findChildrenOfType(ASTName.class)
196: .size() != 0) {
197: if (!found) {
198: checkForViolation(rootNode, data, count);
199: found = true;
200: }
201: count = 0;
202: } else {
203: count++;
204: }
205: }
206:
207: // no variables appended, compiler will take care of merging all the
208: // string concats, we really only have 1 then
209: if (!found) {
210: count = 1;
211: }
212:
213: return count;
214: }
215:
216: /**
217: * Checks to see if there is string concatenation in the node.
218: *
219: * This method checks if it's additive with respect to the append method
220: * only.
221: *
222: * @param n
223: * Node to check
224: * @return true if the node has an additive expression (i.e. "Hello " +
225: * Const.WORLD)
226: */
227: private boolean isAdditive(SimpleNode n) {
228: List lstAdditive = n
229: .findChildrenOfType(ASTAdditiveExpression.class);
230: if (lstAdditive.isEmpty()) {
231: return false;
232: }
233: // if there are more than 1 set of arguments above us we're not in the
234: // append
235: // but a sub-method call
236: for (int ix = 0; ix < lstAdditive.size(); ix++) {
237: ASTAdditiveExpression expr = (ASTAdditiveExpression) lstAdditive
238: .get(ix);
239: if (expr.getParentsOfType(ASTArgumentList.class).size() != 1) {
240: return false;
241: }
242: }
243: return true;
244: }
245:
246: /**
247: * Get the first parent. Keep track of the last node though. For If
248: * statements it's the only way we can differentiate between if's and else's
249: * For switches it's the only way we can differentiate between switches
250: *
251: * @param node The node to check
252: * @return The first parent block
253: */
254: private Node getFirstParentBlock(Node node) {
255: Node parentNode = node.jjtGetParent();
256:
257: Node lastNode = node;
258: while (parentNode != null
259: && !blockParents.contains(parentNode.getClass())) {
260: lastNode = parentNode;
261: parentNode = parentNode.jjtGetParent();
262: }
263: if (parentNode != null
264: && parentNode.getClass().equals(ASTIfStatement.class)) {
265: parentNode = lastNode;
266: } else if (parentNode != null
267: && parentNode.getClass().equals(
268: ASTSwitchStatement.class)) {
269: parentNode = getSwitchParent(parentNode, lastNode);
270: }
271: return parentNode;
272: }
273:
274: /**
275: * Determine which SwitchLabel we belong to inside a switch
276: *
277: * @param parentNode The parent node we're looking at
278: * @param lastNode The last node processed
279: * @return The parent node for the switch statement
280: */
281: private Node getSwitchParent(Node parentNode, Node lastNode) {
282: int allChildren = parentNode.jjtGetNumChildren();
283: ASTSwitchLabel label = null;
284: for (int ix = 0; ix < allChildren; ix++) {
285: Node n = parentNode.jjtGetChild(ix);
286: if (n.getClass().equals(ASTSwitchLabel.class)) {
287: label = (ASTSwitchLabel) n;
288: } else if (n.equals(lastNode)) {
289: parentNode = label;
290: break;
291: }
292: }
293: return parentNode;
294: }
295:
296: /**
297: * Helper method checks to see if a violation occured, and adds a
298: * RuleViolation if it did
299: */
300: private void checkForViolation(SimpleNode node, Object data,
301: int concurrentCount) {
302: if (concurrentCount > threshold) {
303: String[] param = { String.valueOf(concurrentCount) };
304: addViolation(data, node, param);
305: }
306: }
307:
308: private boolean isAppendingStringLiteral(SimpleNode node) {
309: SimpleNode n = node;
310: while (n.jjtGetNumChildren() != 0
311: && !n.getClass().equals(ASTLiteral.class)) {
312: n = (SimpleNode) n.jjtGetChild(0);
313: }
314: return n.getClass().equals(ASTLiteral.class);
315: }
316:
317: private static boolean isStringBuffer(ASTVariableDeclaratorId node) {
318:
319: if (node.getType() != null) {
320: return node.getType().equals(StringBuffer.class);
321: }
322: SimpleNode nn = node.getTypeNameNode();
323: if (nn.jjtGetNumChildren() == 0) {
324: return false;
325: }
326: return TypeHelper.isA((TypeNode) nn.jjtGetChild(0),
327: StringBuffer.class);
328: }
329:
330: protected Map<String, PropertyDescriptor> propertiesByName() {
331: return propertyDescriptorsByName;
332: }
333: }
|