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