View Javadoc

1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.java.rule.strings;
5   
6   import java.util.List;
7   import java.util.Map;
8   
9   import net.sourceforge.pmd.lang.ast.Node;
10  import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement;
11  import net.sourceforge.pmd.lang.java.ast.ASTName;
12  import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
13  import net.sourceforge.pmd.lang.java.ast.ASTStatement;
14  import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression;
15  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
16  import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
17  import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper;
18  import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
19  
20  /**
21   * Original rule was written with XPath, but didn't verify whether the two calls to append
22   * would have been done on the same variable.
23   * 
24   * <pre>
25  //BlockStatement[./Statement/StatementExpression//PrimaryPrefix/Name[ends-with(@Image,'.append')]
26                                        [substring-before(@Image, '.') =
27                                           ancestor::Block//LocalVariableDeclaration[./Type//ClassOrInterfaceType[@Image='StringBuffer']]//VariableDeclaratorId/@Image
28                                        ]
29                  ]/following-sibling::*[1][./Statement/StatementExpression//PrimaryPrefix/Name[ends-with(@Image,'.append')]
30                                           [substring-before(@Image, '.') = 
31                                               ancestor::Block//LocalVariableDeclaration[./Type//ClassOrInterfaceType[@Image='StringBuffer']]//VariableDeclaratorId/@Image
32                                           ]
33                                        ] 
34  |
35  //BlockStatement[./Statement/StatementExpression//PrimaryPrefix/Name[ends-with(@Image,'.append')]
36                                        [substring-before(@Image, '.') = 
37                                           ancestor::Block//LocalVariableDeclaration[./Type//ClassOrInterfaceType[@Image='StringBuilder']]//VariableDeclaratorId/@Image
38                                        ]
39                  ]/following-sibling::*[1][./Statement/StatementExpression//PrimaryPrefix/Name[ends-with(@Image,'.append')]
40                                           [substring-before(@Image, '.') = 
41                                               ancestor::Block//LocalVariableDeclaration[./Type//ClassOrInterfaceType[@Image='StringBuilder']]//VariableDeclaratorId/@Image
42                                           ]
43                                        ]
44  
45   * </pre>
46   *
47   */
48  public class ConsecutiveAppendsShouldReuseRule  extends AbstractJavaRule {
49  
50      @Override
51      public Object visit(ASTBlockStatement node, Object data) {
52          String variable = getVariableAppended(node);
53          if (variable != null) {
54              ASTBlockStatement nextSibling = getNextBlockStatementSibling(node);
55              if (nextSibling != null) {
56                  String nextVariable = getVariableAppended(nextSibling);
57                  if (nextVariable != null && nextVariable.equals(variable)) {
58                      addViolation(data, node);
59                  }
60              }
61          }
62          return super.visit(node, data);
63      }
64      private ASTBlockStatement getNextBlockStatementSibling(Node node) {
65          Node parent = node.jjtGetParent();
66          int childIndex = -1;
67          for (int i = 0; i < parent.jjtGetNumChildren(); i++) {
68              if (parent.jjtGetChild(i) == node) {
69                  childIndex = i;
70                  break;
71              }
72          }
73          if (childIndex + 1 < parent.jjtGetNumChildren()) {
74              Node nextSibling = parent.jjtGetChild(childIndex + 1);
75              if (nextSibling instanceof ASTBlockStatement) {
76                  return (ASTBlockStatement)nextSibling;
77              }
78          }
79          return null;
80      }
81      private String getVariableAppended(ASTBlockStatement node) {
82          if (isFirstChild(node, ASTStatement.class)) {
83              ASTStatement statement = (ASTStatement) node.jjtGetChild(0);
84              if (isFirstChild(statement, ASTStatementExpression.class)) {
85                  ASTStatementExpression stmtExp = (ASTStatementExpression) statement.jjtGetChild(0);
86                  ASTPrimaryPrefix primaryPrefix = stmtExp.getFirstDescendantOfType(ASTPrimaryPrefix.class);
87                  if (primaryPrefix != null) {
88                      ASTName name = primaryPrefix.getFirstChildOfType(ASTName.class);
89                      if (name != null) {
90                          String image = name.getImage();
91                          if (image.endsWith(".append")) {
92                              String variable = image.substring(0, image.indexOf('.'));
93                              if (isAStringBuilderBuffer(primaryPrefix, variable)) {
94                                  return variable;
95                              }
96                          }
97                      }
98                  }
99              }
100         }
101         return null;
102     }
103     private boolean isAStringBuilderBuffer(ASTPrimaryPrefix prefix, String name) {
104         Map<VariableNameDeclaration, List<NameOccurrence>> declarations = prefix.getScope().getDeclarations(VariableNameDeclaration.class);
105         for (VariableNameDeclaration decl : declarations.keySet()) {
106             if (decl.getName().equals(name) && TypeHelper.isEither(decl, StringBuilder.class, StringBuffer.class)) {
107                 return true;
108             }
109         }
110         return false;
111     }
112     
113     private boolean isFirstChild(Node node, Class<?> clazz) {
114         if (node.jjtGetNumChildren() == 1 && clazz.isAssignableFrom(node.jjtGetChild(0).getClass())) {
115             return true;
116         }
117         return false;
118     }
119 }