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