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.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          blockParents = new HashSet();
    62          blockParents.add(ASTForStatement.class);
    63          blockParents.add(ASTWhileStatement.class);
    64          blockParents.add(ASTDoStatement.class);
    65          blockParents.add(ASTIfStatement.class);
    66          blockParents.add(ASTSwitchStatement.class);
    67          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      public Object visit(ASTVariableDeclaratorId node, Object data) {
    83
    84          if (!isStringBuffer(node)) {
    85              return data;
    86          }
    87          threshold = getIntProperty(thresholdDescriptor);
    88
    89          int concurrentCount = checkConstructor(node, data);
    90          Node lastBlock = getFirstParentBlock(node);
    91          Node currentBlock = lastBlock;
    92          Map decls = node.getScope().getVariableDeclarations();
    93          SimpleNode rootNode = null;
    94          // only want the constructor flagged if it's really containing strings
    95          if (concurrentCount == 1) {
    96              rootNode = node;
    97          }
    98          for (Iterator iter = decls.entrySet().iterator(); iter.hasNext();) {
    99              Map.Entry entry = (Map.Entry) iter.next();
    100             List decl = (List) entry.getValue();
    101             for (int ix = 0; ix < decl.size(); ix++) {
    102                 NameOccurrence no = (NameOccurrence) decl.get(ix);
    103                 SimpleNode n = no.getLocation();
    104
    105                 currentBlock = getFirstParentBlock(n);
    106
    107                 if (!InefficientStringBuffering.isInStringBufferOperation(n, 3,"append")) {
    108                     if (!no.isPartOfQualifiedName()) {
    109                         checkForViolation(rootNode, data, concurrentCount);
    110                         concurrentCount = 0;
    111                     }
    112                     continue;
    113                 }
    114                 ASTPrimaryExpression s = (ASTPrimaryExpression) n
    115                         .getFirstParentOfType(ASTPrimaryExpression.class);
    116                 int numChildren = s.jjtGetNumChildren();
    117                 for (int jx = 0; jx < numChildren; jx++) {
    118                     SimpleNode sn = (SimpleNode) s.jjtGetChild(jx);
    119                     if (!(sn instanceof ASTPrimarySuffix)
    120                             || sn.getImage() != null) {
    121                         continue;
    122                     }
    123
    124                     // see if it changed blocks
    125                     if ((currentBlock != null && lastBlock != null && !currentBlock
    126                             .equals(lastBlock))
    127                             || (currentBlock == null ^ lastBlock == null)) {
    128                         checkForViolation(rootNode, data, concurrentCount);
    129                         concurrentCount = 0;
    130                     }
    131
    132                     // if concurrent is 0 then we reset the root to report from
    133                     // here
    134                     if (concurrentCount == 0) {
    135                         rootNode = sn;
    136                     }
    137                     if (isAdditive(sn)) {
    138                         concurrentCount = processAdditive(data,
    139                                 concurrentCount, sn, rootNode);
    140                         if (concurrentCount != 0) {
    141                             rootNode = sn;
    142                         }
    143                     } else if (!isAppendingStringLiteral(sn)) {
    144                         checkForViolation(rootNode, data, concurrentCount);
    145                         concurrentCount = 0;
    146                     } else {
    147                         concurrentCount++;
    148                     }
    149                     lastBlock = currentBlock;
    150                 }
    151             }
    152         }
    153         checkForViolation(rootNode, data, concurrentCount);
    154         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     private int checkConstructor(ASTVariableDeclaratorId node, Object data) {
    164         Node parent = node.jjtGetParent();
    165         if (parent.jjtGetNumChildren() >= 2) {
    166             ASTArgumentList list = (ASTArgumentList) ((SimpleNode) parent
    167                     .jjtGetChild(1)).getFirstChildOfType(ASTArgumentList.class);
    168             if (list != null) {
    169                 ASTLiteral literal = (ASTLiteral) list
    170                         .getFirstChildOfType(ASTLiteral.class);
    171                 if (!isAdditive(list) && literal != null
    172                         && literal.isStringLiteral()) {
    173                     return 1;
    174                 }
    175                 return processAdditive(data, 0, list, node);
    176             }
    177         }
    178         return 0;
    179     }
    180
    181     private int processAdditive(Object data, int concurrentCount,
    182                                 SimpleNode sn, SimpleNode rootNode) {
    183         ASTAdditiveExpression additive = (ASTAdditiveExpression) sn
    184                 .getFirstChildOfType(ASTAdditiveExpression.class);
    185         if (additive == null) {
    186             return 0;
    187         }
    188         int count = concurrentCount;
    189         boolean found = false;
    190         for (int ix = 0; ix < additive.jjtGetNumChildren(); ix++) {
    191             SimpleNode childNode = (SimpleNode) additive.jjtGetChild(ix);
    192             if (childNode.jjtGetNumChildren() != 1
    193                     || childNode.findChildrenOfType(ASTName.class).size() != 0) {
    194                 if (!found) {
    195                     checkForViolation(rootNode, data, count);
    196                     found = true;
    197                 }
    198                 count = 0;
    199             } else {
    200                 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         if (!found) {
    207             count = 1;
    208         }
    209
    210         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     private boolean isAdditive(SimpleNode n) {
    225         List lstAdditive = n.findChildrenOfType(ASTAdditiveExpression.class);
    226         if (lstAdditive.isEmpty()) {
    227             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         for (int ix = 0; ix < lstAdditive.size(); ix++) {
    233             ASTAdditiveExpression expr = (ASTAdditiveExpression) lstAdditive.get(ix);
    234             if (expr.getParentsOfType(ASTArgumentList.class).size() != 1) {
    235                 return false;
    236             }
    237         }
    238         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     private Node getFirstParentBlock(Node node) {
    250         Node parentNode = node.jjtGetParent();
    251
    252         Node lastNode = node;
    253         while (parentNode != null
    254                 && !blockParents.contains(parentNode.getClass())) {
    255             lastNode = parentNode;
    256             parentNode = parentNode.jjtGetParent();
    257         }
    258         if (parentNode != null
    259                 && parentNode.getClass().equals(ASTIfStatement.class)) {
    260             parentNode = lastNode;
    261         } else if (parentNode != null
    262                 && 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 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     private Node getSwitchParent(Node parentNode, Node lastNode) {
    276         int allChildren = parentNode.jjtGetNumChildren();
    277         ASTSwitchLabel label = null;
    278         for (int ix = 0; ix < allChildren; ix++) {
    279             Node n = parentNode.jjtGetChild(ix);
    280             if (n.getClass().equals(ASTSwitchLabel.class)) {
    281                 label = (ASTSwitchLabel) n;
    282             } else if (n.equals(lastNode)) {
    283                 parentNode = label;
    284                 break;
    285             }
    286         }
    287         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     private void checkForViolation(SimpleNode node, Object data,
    295                                    int concurrentCount) {
    296         if (concurrentCount > threshold) {
    297             String[] param = {String.valueOf(concurrentCount)};
    298             addViolation(data, node, param);
    299         }
    300     }
    301
    302     private boolean isAppendingStringLiteral(SimpleNode node) {
    303         SimpleNode n = node;
    304         while (n.jjtGetNumChildren() != 0
    305                 && !n.getClass().equals(ASTLiteral.class)) {
    306             n = (SimpleNode) n.jjtGetChild(0);
    307         }
    308         return n.getClass().equals(ASTLiteral.class);
    309     }
    310
    311     private static boolean isStringBuffer(ASTVariableDeclaratorId node) {
    312         SimpleNode nn = node.getTypeNameNode();
    313         if (nn.jjtGetNumChildren() == 0) {
    314             return false;
    315         }
    316         return "StringBuffer".equals(((SimpleNode) nn.jjtGetChild(0)).getImage());
    317     }
    318
    319     protected Map propertiesByName() {
    320     	return propertyDescriptorsByName;
    321     }
    322 }