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