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.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          blockParents = new HashSet();
    60          blockParents.add(ASTForStatement.class);
    61          blockParents.add(ASTWhileStatement.class);
    62          blockParents.add(ASTDoStatement.class);
    63          blockParents.add(ASTIfStatement.class);
    64          blockParents.add(ASTSwitchStatement.class);
    65          blockParents.add(ASTMethodDeclaration.class);
    66      }
    67
    68      private int threshold = 1;
    69
    70      public Object visit(ASTVariableDeclaratorId node, Object data) {
    71
    72          if (!isStringBuffer(node)) {
    73              return data;
    74          }
    75          threshold = getIntProperty("threshold");
    76
    77          int concurrentCount = checkConstructor(node, data);
    78          Node lastBlock = getFirstParentBlock(node);
    79          Node currentBlock = lastBlock;
    80          Map decls = node.getScope().getVariableDeclarations();
    81          SimpleNode rootNode = null;
    82          // only want the constructor flagged if it's really containing strings
    83          if (concurrentCount == 1) {
    84              rootNode = node;
    85          }
    86          for (Iterator iter = decls.entrySet().iterator(); iter.hasNext();) {
    87              Map.Entry entry = (Map.Entry) iter.next();
    88              List decl = (List) entry.getValue();
    89              for (int ix = 0; ix < decl.size(); ix++) {
    90                  NameOccurrence no = (NameOccurrence) decl.get(ix);
    91                  SimpleNode n = no.getLocation();
    92
    93                  currentBlock = getFirstParentBlock(n);
    94
    95                  if (!InefficientStringBuffering.isInStringBufferAppend(n, 3)) {
    96                      if (!no.isPartOfQualifiedName()) {
    97                          checkForViolation(rootNode, data, concurrentCount);
    98                          concurrentCount = 0;
    99                      }
    100                     continue;
    101                 }
    102                 ASTPrimaryExpression s = (ASTPrimaryExpression) n
    103                         .getFirstParentOfType(ASTPrimaryExpression.class);
    104                 int numChildren = s.jjtGetNumChildren();
    105                 for (int jx = 0; jx < numChildren; jx++) {
    106                     SimpleNode sn = (SimpleNode) s.jjtGetChild(jx);
    107                     if (!(sn instanceof ASTPrimarySuffix)
    108                             || sn.getImage() != null) {
    109                         continue;
    110                     }
    111
    112                     // see if it changed blocks
    113                     if ((currentBlock != null && lastBlock != null && !currentBlock
    114                             .equals(lastBlock))
    115                             || (currentBlock == null ^ lastBlock == null)) {
    116                         checkForViolation(rootNode, data, concurrentCount);
    117                         concurrentCount = 0;
    118                     }
    119
    120                     // if concurrent is 0 then we reset the root to report from
    121                     // here
    122                     if (concurrentCount == 0) {
    123                         rootNode = sn;
    124                     }
    125                     if (isAdditive(sn)) {
    126                         concurrentCount = processAdditive(data,
    127                                 concurrentCount, sn, rootNode);
    128                         if (concurrentCount != 0) {
    129                             rootNode = sn;
    130                         }
    131                     } else if (!isAppendingStringLiteral(sn)) {
    132                         checkForViolation(rootNode, data, concurrentCount);
    133                         concurrentCount = 0;
    134                     } else {
    135                         concurrentCount++;
    136                     }
    137                     lastBlock = currentBlock;
    138                 }
    139             }
    140         }
    141         checkForViolation(rootNode, data, concurrentCount);
    142         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     private int checkConstructor(ASTVariableDeclaratorId node, Object data) {
    152         Node parent = node.jjtGetParent();
    153         if (parent.jjtGetNumChildren() >= 2) {
    154             ASTArgumentList list = (ASTArgumentList) ((SimpleNode) parent
    155                     .jjtGetChild(1)).getFirstChildOfType(ASTArgumentList.class);
    156             if (list != null) {
    157                 ASTLiteral literal = (ASTLiteral) list
    158                         .getFirstChildOfType(ASTLiteral.class);
    159                 if (!isAdditive(list) && literal != null
    160                         && literal.isStringLiteral()) {
    161                     return 1;
    162                 }
    163                 return processAdditive(data, 0, list, node);
    164             }
    165         }
    166         return 0;
    167     }
    168
    169     private int processAdditive(Object data, int concurrentCount,
    170                                 SimpleNode sn, SimpleNode rootNode) {
    171         ASTAdditiveExpression additive = (ASTAdditiveExpression) sn
    172                 .getFirstChildOfType(ASTAdditiveExpression.class);
    173         if (additive == null) {
    174             return 0;
    175         }
    176         int count = concurrentCount;
    177         boolean found = false;
    178         for (int ix = 0; ix < additive.jjtGetNumChildren(); ix++) {
    179             SimpleNode childNode = (SimpleNode) additive.jjtGetChild(ix);
    180             if (childNode.jjtGetNumChildren() != 1
    181                     || childNode.findChildrenOfType(ASTName.class).size() != 0) {
    182                 if (!found) {
    183                     checkForViolation(rootNode, data, count);
    184                     found = true;
    185                 }
    186                 count = 0;
    187             } else {
    188                 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         if (!found) {
    195             count = 1;
    196         }
    197
    198         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     private boolean isAdditive(SimpleNode n) {
    213         List lstAdditive = n.findChildrenOfType(ASTAdditiveExpression.class);
    214         if (lstAdditive.size() == 0) {
    215             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         for (int ix = 0; ix < lstAdditive.size(); ix++) {
    221             ASTAdditiveExpression expr = (ASTAdditiveExpression) lstAdditive.get(ix);
    222             if (expr.getParentsOfType(ASTArgumentList.class).size() != 1) {
    223                 return false;
    224             }
    225         }
    226         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     private Node getFirstParentBlock(Node node) {
    238         Node parentNode = node.jjtGetParent();
    239
    240         Node lastNode = node;
    241         while (parentNode != null
    242                 && !blockParents.contains(parentNode.getClass())) {
    243             lastNode = parentNode;
    244             parentNode = parentNode.jjtGetParent();
    245         }
    246         if (parentNode != null
    247                 && parentNode.getClass().equals(ASTIfStatement.class)) {
    248             parentNode = lastNode;
    249         } else if (parentNode != null
    250                 && parentNode.getClass().equals(ASTSwitchStatement.class)) {
    251             parentNode = getSwitchParent(parentNode, lastNode);
    252         }
    253         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     private Node getSwitchParent(Node parentNode, Node lastNode) {
    264         int allChildren = parentNode.jjtGetNumChildren();
    265         ASTSwitchLabel label = null;
    266         for (int ix = 0; ix < allChildren; ix++) {
    267             Node n = parentNode.jjtGetChild(ix);
    268             if (n.getClass().equals(ASTSwitchLabel.class)) {
    269                 label = (ASTSwitchLabel) n;
    270             } else if (n.equals(lastNode)) {
    271                 parentNode = label;
    272                 break;
    273             }
    274         }
    275         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     private void checkForViolation(SimpleNode node, Object data,
    283                                    int concurrentCount) {
    284         if (concurrentCount > threshold) {
    285             String[] param = {String.valueOf(concurrentCount)};
    286             addViolation(data, node, param);
    287         }
    288     }
    289
    290     private boolean isAppendingStringLiteral(SimpleNode node) {
    291         SimpleNode n = node;
    292         while (n.jjtGetNumChildren() != 0
    293                 && !n.getClass().equals(ASTLiteral.class)) {
    294             n = (SimpleNode) n.jjtGetChild(0);
    295         }
    296         return n.getClass().equals(ASTLiteral.class);
    297     }
    298
    299     private static boolean isStringBuffer(ASTVariableDeclaratorId node) {
    300         SimpleNode nn = (SimpleNode) node.getTypeNameNode();
    301         if (nn.jjtGetNumChildren() == 0) {
    302             return false;
    303         }
    304         return "StringBuffer".equals(((SimpleNode) nn.jjtGetChild(0))
    305                 .getImage());
    306     }
    307
    308 }