View Javadoc

    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.ASTBlockStatement;
    9   import net.sourceforge.pmd.ast.ASTFieldDeclaration;
    10  import net.sourceforge.pmd.ast.ASTFormalParameter;
    11  import net.sourceforge.pmd.ast.ASTIfStatement;
    12  import net.sourceforge.pmd.ast.ASTLiteral;
    13  import net.sourceforge.pmd.ast.ASTName;
    14  import net.sourceforge.pmd.ast.ASTPrimaryExpression;
    15  import net.sourceforge.pmd.ast.ASTPrimaryPrefix;
    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.Node;
    21  import net.sourceforge.pmd.ast.SimpleNode;
    22  import net.sourceforge.pmd.symboltable.NameOccurrence;
    23
    24  import java.util.HashMap;
    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 StringBuffers which may have been pre-sized incorrectly
    33   *
    34   * See http://sourceforge.net/forum/forum.php?thread_id=1438119&forum_id=188194
    35   * @author Allan Caplan
    36   */
    37  public class InsufficientStringBufferDeclaration extends AbstractRule {
    38
    39      private final static Set blockParents;
    40
    41      static {
    42          blockParents = new HashSet();
    43          blockParents.add(ASTIfStatement.class);
    44          blockParents.add(ASTSwitchStatement.class);
    45      }
    46
    47      public Object visit(ASTVariableDeclaratorId node, Object data) {
    48
    49          if (!"StringBuffer".equals(node.getNameDeclaration().getTypeImage())) {
    50              return data;
    51          }
    52          Node rootNode = node;
    53          int anticipatedLength = 0;
    54          int constructorLength = 16;
    55
    56          constructorLength = getConstructorLength(node, constructorLength);
    57          anticipatedLength = getInitialLength(node);
    58          List usage = node.getUsages();
    59          Map blocks = new HashMap();
    60          for (int ix = 0; ix < usage.size(); ix++) {
    61              NameOccurrence no = (NameOccurrence) usage.get(ix);
    62              SimpleNode n = no.getLocation();
    63              if (!InefficientStringBuffering.isInStringBufferOperation(n, 3, "append")) {
    64
    65                  if (!no.isOnLeftHandSide() && !InefficientStringBuffering.isInStringBufferOperation(n, 3, "setLength")) {
    66                      continue;
    67                  }
    68                  if (constructorLength != -1 && anticipatedLength > constructorLength) {
    69                      anticipatedLength += processBlocks(blocks);
    70                      String[] param = { String.valueOf(constructorLength), String.valueOf(anticipatedLength) };
    71                      addViolation(data, rootNode, param);
    72                  }
    73                  constructorLength = getConstructorLength(n, constructorLength);
    74                  rootNode = n;
    75                  anticipatedLength = getInitialLength(node);
    76              }
    77              ASTPrimaryExpression s = (ASTPrimaryExpression) n.getFirstParentOfType(ASTPrimaryExpression.class);
    78              int numChildren = s.jjtGetNumChildren();
    79              for (int jx = 0; jx < numChildren; jx++) {
    80                  SimpleNode sn = (SimpleNode) s.jjtGetChild(jx);
    81                  if (!(sn instanceof ASTPrimarySuffix) || sn.getImage() != null) {
    82                      continue;
    83                  }
    84                  int thisSize = 0;
    85                  Node block = getFirstParentBlock(sn);
    86                  if (isAdditive(sn)) {
    87                      thisSize = processAdditive(sn);
    88                  } else {
    89                      thisSize = processNode(sn);
    90                  }
    91                  if (block != null) {
    92                      storeBlockStatistics(blocks, thisSize, block);
    93                  } else {
    94                      anticipatedLength += thisSize;
    95                  }
    96              }
    97          }
    98          anticipatedLength += processBlocks(blocks);
    99          if (constructorLength != -1 && anticipatedLength > constructorLength) {
    100             String[] param = { String.valueOf(constructorLength), String.valueOf(anticipatedLength) };
    101             addViolation(data, rootNode, param);
    102         }
    103         return data;
    104     }
    105
    106     /***
    107      * This rule is concerned with IF and Switch blocks. Process the block into
    108      * a local Map, from which we can later determine which is the longest block
    109      * inside
    110      *
    111      * @param blocks
    112      *            The map of blocks in the method being investigated
    113      * @param thisSize
    114      *            The size of the current block
    115      * @param block
    116      *            The block in question
    117      */
    118     private void storeBlockStatistics(Map blocks, int thisSize, Node block) {
    119         Node statement = block.jjtGetParent();
    120         if (ASTIfStatement.class.equals(block.jjtGetParent().getClass())) {
    121             // Else Ifs are their own subnode in AST. So we have to
    122             // look a little farther up the tree to find the IF statement
    123             Node possibleStatement = ((SimpleNode) statement).getFirstParentOfType(ASTIfStatement.class);
    124             while(possibleStatement != null && possibleStatement.getClass().equals(ASTIfStatement.class)) {
    125                 statement = possibleStatement;
    126                 possibleStatement = ((SimpleNode) possibleStatement).getFirstParentOfType(ASTIfStatement.class);
    127             }
    128         }
    129         Map thisBranch = (Map) blocks.get(statement);
    130         if (thisBranch == null) {
    131             thisBranch = new HashMap();
    132             blocks.put(statement, thisBranch);
    133         }
    134         Integer x = (Integer) thisBranch.get(block);
    135         if (x != null) {
    136             thisSize += x.intValue();
    137         }
    138         thisBranch.put(statement, new Integer(thisSize));
    139     }
    140
    141     private int processBlocks(Map blocks) {
    142         int anticipatedLength = 0;
    143         int ifLength = 0;
    144         for (Iterator iter = blocks.entrySet().iterator(); iter.hasNext();) {
    145             Map.Entry entry = (Map.Entry) iter.next();
    146             ifLength = 0;
    147             for (Iterator iter2 = ((Map) entry.getValue()).entrySet().iterator(); iter2.hasNext();) {
    148                 Map.Entry entry2 = (Map.Entry) iter2.next();
    149                 Integer value = (Integer) entry2.getValue();
    150                 ifLength = Math.max(ifLength, value.intValue());
    151             }
    152             anticipatedLength += ifLength;
    153         }
    154         return anticipatedLength;
    155     }
    156
    157     private int processAdditive(SimpleNode sn) {
    158         ASTAdditiveExpression additive = (ASTAdditiveExpression) sn.getFirstChildOfType(ASTAdditiveExpression.class);
    159         if (additive == null) {
    160             return 0;
    161         }
    162         int anticipatedLength = 0;
    163         for (int ix = 0; ix < additive.jjtGetNumChildren(); ix++) {
    164             SimpleNode childNode = (SimpleNode) additive.jjtGetChild(ix);
    165             ASTLiteral literal = (ASTLiteral) childNode.getFirstChildOfType(ASTLiteral.class);
    166             if (literal != null && literal.getImage() != null) {
    167                 anticipatedLength += literal.getImage().length() - 2;
    168             }
    169         }
    170
    171         return anticipatedLength;
    172     }
    173
    174     private static final boolean isLiteral(String str) {
    175         if (str.length() == 0) {
    176             return false;
    177         }
    178         char c = str.charAt(0);
    179         return (c == '"' || c == '\'');
    180     }
    181
    182     private int processNode(SimpleNode sn) {
    183         int anticipatedLength = 0;
    184         ASTPrimaryPrefix xn = (ASTPrimaryPrefix) sn.getFirstChildOfType(ASTPrimaryPrefix.class);
    185         if (xn.jjtGetNumChildren() != 0 && xn.jjtGetChild(0).getClass().equals(ASTLiteral.class)) {
    186             String str = ((SimpleNode) xn.jjtGetChild(0)).getImage();
    187             if(isLiteral(str)){
    188                 anticipatedLength += str.length() - 2;
    189             } else if(str.startsWith("0x")){
    190                 anticipatedLength += 1;
    191             } else {
    192                 anticipatedLength += str.length();
    193             }
    194         }
    195         return anticipatedLength;
    196     }
    197
    198     private int getConstructorLength(SimpleNode node, int constructorLength) {
    199         int iConstructorLength = constructorLength;
    200         SimpleNode block = (SimpleNode) node.getFirstParentOfType(ASTBlockStatement.class);
    201         List literal;
    202
    203         if (block == null) {
    204             block = (ASTFieldDeclaration) node.getFirstParentOfType(ASTFieldDeclaration.class);
    205         }
    206         if (block == null) {
    207             block = (ASTFormalParameter) node.getFirstParentOfType(ASTFormalParameter.class);
    208             if (block != null) {
    209                 iConstructorLength = -1;
    210             }
    211         }
    212         literal = (block.findChildrenOfType(ASTLiteral.class));
    213         if (literal.isEmpty()) {
    214             List name = (block.findChildrenOfType(ASTName.class));
    215             if (!name.isEmpty()) {
    216                 iConstructorLength = -1;
    217             }
    218         } else if (literal.size() == 1) {
    219             String str = ((SimpleNode) literal.get(0)).getImage();
    220             if (str == null) {
    221                 iConstructorLength = 0;
    222             } else if (isLiteral(str)) {
    223                 // since it's not taken into account
    224                 // anywhere. only count the extra 16
    225                 // characters
    226                 iConstructorLength = 14 + str.length(); // don't add the constructor's length,
    227             } else {
    228                 iConstructorLength = Integer.parseInt(str);
    229             }
    230         } else {
    231             iConstructorLength = -1;
    232         }
    233
    234         if(iConstructorLength == 0){
    235             iConstructorLength = 16;
    236         }
    237
    238         return iConstructorLength;
    239     }
    240
    241
    242     private int getInitialLength(SimpleNode node) {
    243         SimpleNode block = (SimpleNode) node.getFirstParentOfType(ASTBlockStatement.class);
    244         List literal;
    245
    246         if (block == null) {
    247             block = (ASTFieldDeclaration) node.getFirstParentOfType(ASTFieldDeclaration.class);
    248             if (block == null) {
    249                 block = (ASTFormalParameter) node.getFirstParentOfType(ASTFormalParameter.class);
    250             }
    251         }
    252         literal = (block.findChildrenOfType(ASTLiteral.class));
    253         if (literal.size() == 1) {
    254             String str = ((SimpleNode) literal.get(0)).getImage();
    255             if (str != null && isLiteral(str)) {
    256                 return str.length() - 2; // take off the quotes
    257             }
    258         }
    259
    260         return 0;
    261     }
    262
    263     private boolean isAdditive(SimpleNode n) {
    264         return n.findChildrenOfType(ASTAdditiveExpression.class).size() >= 1;
    265     }
    266
    267     /***
    268      * Locate the block that the given node is in, if any
    269      *
    270      * @param node
    271      *            The node we're looking for a parent of
    272      * @return Node - The node that corresponds to any block that may be a
    273      *         parent of this object
    274      */
    275     private Node getFirstParentBlock(Node node) {
    276         Node parentNode = node.jjtGetParent();
    277
    278         Node lastNode = node;
    279         while (parentNode != null && !blockParents.contains(parentNode.getClass())) {
    280             lastNode = parentNode;
    281             parentNode = parentNode.jjtGetParent();
    282         }
    283         if (parentNode != null && ASTIfStatement.class.equals(parentNode.getClass())) {
    284             parentNode = lastNode;
    285         } else if (parentNode != null && parentNode.getClass().equals(ASTSwitchStatement.class)) {
    286             parentNode = getSwitchParent(parentNode, lastNode);
    287         }
    288         return parentNode;
    289     }
    290
    291     /***
    292      * Determine which SwitchLabel we belong to inside a switch
    293      *
    294      * @param parentNode
    295      *            The parent node we're looking at
    296      * @param lastNode
    297      *            The last node processed
    298      * @return The parent node for the switch statement
    299      */
    300     private static Node getSwitchParent(Node parentNode, Node lastNode) {
    301         int allChildren = parentNode.jjtGetNumChildren();
    302         ASTSwitchLabel label = null;
    303         for (int ix = 0; ix < allChildren; ix++) {
    304             Node n = parentNode.jjtGetChild(ix);
    305             if (n.getClass().equals(ASTSwitchLabel.class)) {
    306                 label = (ASTSwitchLabel) n;
    307             } else if (n.equals(lastNode)) {
    308                 parentNode = label;
    309                 break;
    310             }
    311         }
    312         return parentNode;
    313     }
    314
    315 }