Clover coverage report - PMD - 3.9
Coverage timestamp: Tue Dec 19 2006 09:38:44 EST
file stats: LOC: 322   Methods: 10
NCLOC: 222   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
ConsecutiveLiteralAppends.java 95.5% 99.1% 90% 97.4%
coverage coverage
 1    /**
 2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
 3    */
 4    package net.sourceforge.pmd.rules.strings;
 5   
 6    import net.sourceforge.pmd.AbstractRule;
 7    import net.sourceforge.pmd.PropertyDescriptor;
 8    import net.sourceforge.pmd.ast.ASTAdditiveExpression;
 9    import net.sourceforge.pmd.ast.ASTArgumentList;
 10    import net.sourceforge.pmd.ast.ASTDoStatement;
 11    import net.sourceforge.pmd.ast.ASTForStatement;
 12    import net.sourceforge.pmd.ast.ASTIfStatement;
 13    import net.sourceforge.pmd.ast.ASTLiteral;
 14    import net.sourceforge.pmd.ast.ASTMethodDeclaration;
 15    import net.sourceforge.pmd.ast.ASTName;
 16    import net.sourceforge.pmd.ast.ASTPrimaryExpression;
 17    import net.sourceforge.pmd.ast.ASTPrimarySuffix;
 18    import net.sourceforge.pmd.ast.ASTSwitchLabel;
 19    import net.sourceforge.pmd.ast.ASTSwitchStatement;
 20    import net.sourceforge.pmd.ast.ASTVariableDeclaratorId;
 21    import net.sourceforge.pmd.ast.ASTWhileStatement;
 22    import net.sourceforge.pmd.ast.Node;
 23    import net.sourceforge.pmd.ast.SimpleNode;
 24    import net.sourceforge.pmd.properties.IntegerProperty;
 25    import net.sourceforge.pmd.symboltable.NameOccurrence;
 26   
 27    import java.util.HashSet;
 28    import java.util.Iterator;
 29    import java.util.List;
 30    import java.util.Map;
 31    import java.util.Set;
 32   
 33    /**
 34    * This rule finds concurrent calls to StringBuffer.append where String literals
 35    * are used It would be much better to make these calls using one call to
 36    * .append
 37    * <p/>
 38    * example:
 39    * <p/>
 40    * <pre>
 41    * StringBuffer buf = new StringBuffer();
 42    * buf.append(&quot;Hello&quot;);
 43    * buf.append(&quot; &quot;).append(&quot;World&quot;);
 44    * </pre>
 45    * <p/>
 46    * This would be more eloquently put as:
 47    * <p/>
 48    * <pre>
 49    * StringBuffer buf = new StringBuffer();
 50    * buf.append(&quot;Hello World&quot;);
 51    * </pre>
 52    * <p/>
 53    * The rule takes one parameter, threshold, which defines the lower limit of
 54    * consecutive appends before a violation is created. The default is 1.
 55    */
 56    public class ConsecutiveLiteralAppends extends AbstractRule {
 57   
 58    private final static Set blockParents;
 59   
 60    static {
 61  14 blockParents = new HashSet();
 62  14 blockParents.add(ASTForStatement.class);
 63  14 blockParents.add(ASTWhileStatement.class);
 64  14 blockParents.add(ASTDoStatement.class);
 65  14 blockParents.add(ASTIfStatement.class);
 66  14 blockParents.add(ASTSwitchStatement.class);
 67  14 blockParents.add(ASTMethodDeclaration.class);
 68    }
 69   
 70    private static final PropertyDescriptor thresholdDescriptor = new IntegerProperty(
 71    "threshold",
 72    "?",
 73    1,
 74    1.0f
 75    );
 76   
 77    private static final Map propertyDescriptorsByName = asFixedMap(thresholdDescriptor);
 78   
 79   
 80    private int threshold = 1;
 81   
 82  78 public Object visit(ASTVariableDeclaratorId node, Object data) {
 83   
 84  78 if (!isStringBuffer(node)) {
 85  35 return data;
 86    }
 87  43 threshold = getIntProperty(thresholdDescriptor);
 88   
 89  43 int concurrentCount = checkConstructor(node, data);
 90  43 Node lastBlock = getFirstParentBlock(node);
 91  43 Node currentBlock = lastBlock;
 92  43 Map decls = node.getScope().getVariableDeclarations();
 93  43 SimpleNode rootNode = null;
 94    // only want the constructor flagged if it's really containing strings
 95  43 if (concurrentCount == 1) {
 96  2 rootNode = node;
 97    }
 98  43 for (Iterator iter = decls.entrySet().iterator(); iter.hasNext();) {
 99  58 Map.Entry entry = (Map.Entry) iter.next();
 100  58 List decl = (List) entry.getValue();
 101  58 for (int ix = 0; ix < decl.size(); ix++) {
 102  141 NameOccurrence no = (NameOccurrence) decl.get(ix);
 103  141 SimpleNode n = no.getLocation();
 104   
 105  141 currentBlock = getFirstParentBlock(n);
 106   
 107  141 if (!InefficientStringBuffering.isInStringBufferOperation(n, 3,"append")) {
 108  26 if (!no.isPartOfQualifiedName()) {
 109  14 checkForViolation(rootNode, data, concurrentCount);
 110  14 concurrentCount = 0;
 111    }
 112  26 continue;
 113    }
 114  115 ASTPrimaryExpression s = (ASTPrimaryExpression) n
 115    .getFirstParentOfType(ASTPrimaryExpression.class);
 116  115 int numChildren = s.jjtGetNumChildren();
 117  115 for (int jx = 0; jx < numChildren; jx++) {
 118  246 SimpleNode sn = (SimpleNode) s.jjtGetChild(jx);
 119  246 if (!(sn instanceof ASTPrimarySuffix)
 120    || sn.getImage() != null) {
 121  123 continue;
 122    }
 123   
 124    // see if it changed blocks
 125  123 if ((currentBlock != null && lastBlock != null && !currentBlock
 126    .equals(lastBlock))
 127    || (currentBlock == null ^ lastBlock == null)) {
 128  46 checkForViolation(rootNode, data, concurrentCount);
 129  46 concurrentCount = 0;
 130    }
 131   
 132    // if concurrent is 0 then we reset the root to report from
 133    // here
 134  123 if (concurrentCount == 0) {
 135  93 rootNode = sn;
 136    }
 137  123 if (isAdditive(sn)) {
 138  7 concurrentCount = processAdditive(data,
 139    concurrentCount, sn, rootNode);
 140  7 if (concurrentCount != 0) {
 141  4 rootNode = sn;
 142    }
 143  116 } else if (!isAppendingStringLiteral(sn)) {
 144  13 checkForViolation(rootNode, data, concurrentCount);
 145  13 concurrentCount = 0;
 146    } else {
 147  103 concurrentCount++;
 148    }
 149  123 lastBlock = currentBlock;
 150    }
 151    }
 152    }
 153  43 checkForViolation(rootNode, data, concurrentCount);
 154  43 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  43 private int checkConstructor(ASTVariableDeclaratorId node, Object data) {
 164  43 Node parent = node.jjtGetParent();
 165  43 if (parent.jjtGetNumChildren() >= 2) {
 166  43 ASTArgumentList list = (ASTArgumentList) ((SimpleNode) parent
 167    .jjtGetChild(1)).getFirstChildOfType(ASTArgumentList.class);
 168  43 if (list != null) {
 169  4 ASTLiteral literal = (ASTLiteral) list
 170    .getFirstChildOfType(ASTLiteral.class);
 171  4 if (!isAdditive(list) && literal != null
 172    && literal.isStringLiteral()) {
 173  1 return 1;
 174    }
 175  3 return processAdditive(data, 0, list, node);
 176    }
 177    }
 178  39 return 0;
 179    }
 180   
 181  10 private int processAdditive(Object data, int concurrentCount,
 182    SimpleNode sn, SimpleNode rootNode) {
 183  10 ASTAdditiveExpression additive = (ASTAdditiveExpression) sn
 184    .getFirstChildOfType(ASTAdditiveExpression.class);
 185  10 if (additive == null) {
 186  1 return 0;
 187    }
 188  9 int count = concurrentCount;
 189  9 boolean found = false;
 190  9 for (int ix = 0; ix < additive.jjtGetNumChildren(); ix++) {
 191  20 SimpleNode childNode = (SimpleNode) additive.jjtGetChild(ix);
 192  20 if (childNode.jjtGetNumChildren() != 1
 193    || childNode.findChildrenOfType(ASTName.class).size() != 0) {
 194  7 if (!found) {
 195  7 checkForViolation(rootNode, data, count);
 196  7 found = true;
 197    }
 198  7 count = 0;
 199    } else {
 200  13 count++;
 201    }
 202    }
 203   
 204    // no variables appended, compiler will take care of merging all the
 205    // string concats, we really only have 1 then
 206  9 if (!found) {
 207  2 count = 1;
 208    }
 209   
 210  9 return count;
 211    }
 212   
 213    /**
 214    * Checks to see if there is string concatenation in the node.
 215    *
 216    * This method checks if it's additive with respect to the append method
 217    * only.
 218    *
 219    * @param n
 220    * Node to check
 221    * @return true if the node has an additive expression (i.e. "Hello " +
 222    * Const.WORLD)
 223    */
 224  127 private boolean isAdditive(SimpleNode n) {
 225  127 List lstAdditive = n.findChildrenOfType(ASTAdditiveExpression.class);
 226  127 if (lstAdditive.isEmpty()) {
 227  116 return false;
 228    }
 229    // if there are more than 1 set of arguments above us we're not in the
 230    // append
 231    // but a sub-method call
 232  11 for (int ix = 0; ix < lstAdditive.size(); ix++) {
 233  11 ASTAdditiveExpression expr = (ASTAdditiveExpression) lstAdditive.get(ix);
 234  11 if (expr.getParentsOfType(ASTArgumentList.class).size() != 1) {
 235  2 return false;
 236    }
 237    }
 238  9 return true;
 239    }
 240   
 241    /**
 242    * Get the first parent. Keep track of the last node though. For If
 243    * statements it's the only way we can differentiate between if's and else's
 244    * For switches it's the only way we can differentiate between switches
 245    *
 246    * @param node The node to check
 247    * @return The first parent block
 248    */
 249  184 private Node getFirstParentBlock(Node node) {
 250  184 Node parentNode = node.jjtGetParent();
 251   
 252  184 Node lastNode = node;
 253  184 while (parentNode != null
 254    && !blockParents.contains(parentNode.getClass())) {
 255  1139 lastNode = parentNode;
 256  1139 parentNode = parentNode.jjtGetParent();
 257    }
 258  184 if (parentNode != null
 259    && parentNode.getClass().equals(ASTIfStatement.class)) {
 260  17 parentNode = lastNode;
 261  167 } else if (parentNode != null
 262    && parentNode.getClass().equals(ASTSwitchStatement.class)) {
 263  10 parentNode = getSwitchParent(parentNode, lastNode);
 264    }
 265  184 return parentNode;
 266    }
 267   
 268    /**
 269    * Determine which SwitchLabel we belong to inside a switch
 270    *
 271    * @param parentNode The parent node we're looking at
 272    * @param lastNode The last node processed
 273    * @return The parent node for the switch statement
 274    */
 275  10 private Node getSwitchParent(Node parentNode, Node lastNode) {
 276  10 int allChildren = parentNode.jjtGetNumChildren();
 277  10 ASTSwitchLabel label = null;
 278  103 for (int ix = 0; ix < allChildren; ix++) {
 279  103 Node n = parentNode.jjtGetChild(ix);
 280  103 if (n.getClass().equals(ASTSwitchLabel.class)) {
 281  36 label = (ASTSwitchLabel) n;
 282  67 } else if (n.equals(lastNode)) {
 283  10 parentNode = label;
 284  10 break;
 285    }
 286    }
 287  10 return parentNode;
 288    }
 289   
 290    /**
 291    * Helper method checks to see if a violation occured, and adds a
 292    * RuleViolation if it did
 293    */
 294  123 private void checkForViolation(SimpleNode node, Object data,
 295    int concurrentCount) {
 296  123 if (concurrentCount > threshold) {
 297  20 String[] param = {String.valueOf(concurrentCount)};
 298  20 addViolation(data, node, param);
 299    }
 300    }
 301   
 302  116 private boolean isAppendingStringLiteral(SimpleNode node) {
 303  116 SimpleNode n = node;
 304  116 while (n.jjtGetNumChildren() != 0
 305    && !n.getClass().equals(ASTLiteral.class)) {
 306  696 n = (SimpleNode) n.jjtGetChild(0);
 307    }
 308  116 return n.getClass().equals(ASTLiteral.class);
 309    }
 310   
 311  78 private static boolean isStringBuffer(ASTVariableDeclaratorId node) {
 312  78 SimpleNode nn = node.getTypeNameNode();
 313  78 if (nn.jjtGetNumChildren() == 0) {
 314  9 return false;
 315    }
 316  69 return "StringBuffer".equals(((SimpleNode) nn.jjtGetChild(0)).getImage());
 317    }
 318   
 319  0 protected Map propertiesByName() {
 320  0 return propertyDescriptorsByName;
 321    }
 322    }