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.ASTExpression;
12  import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration;
13  import net.sourceforge.pmd.lang.java.ast.ASTName;
14  import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
15  import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
16  import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
17  import net.sourceforge.pmd.lang.java.ast.ASTStatement;
18  import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression;
19  import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
20  import net.sourceforge.pmd.lang.java.ast.AbstractJavaNode;
21  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
22  import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
23  import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper;
24  import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
25  
26  /**
27   * Original rule was written with XPath, but didn't verify whether the two calls to append
28   * would have been done on the same variable.
29   * 
30   * <pre>
31  //BlockStatement[./Statement/StatementExpression//PrimaryPrefix/Name[ends-with(@Image,'.append')]
32                                        [substring-before(@Image, '.') =
33                                           ancestor::Block//LocalVariableDeclaration[./Type//ClassOrInterfaceType[@Image='StringBuffer']]//VariableDeclaratorId/@Image
34                                        ]
35                  ]/following-sibling::*[1][./Statement/StatementExpression//PrimaryPrefix/Name[ends-with(@Image,'.append')]
36                                           [substring-before(@Image, '.') = 
37                                               ancestor::Block//LocalVariableDeclaration[./Type//ClassOrInterfaceType[@Image='StringBuffer']]//VariableDeclaratorId/@Image
38                                           ]
39                                        ] 
40  |
41  //BlockStatement[./Statement/StatementExpression//PrimaryPrefix/Name[ends-with(@Image,'.append')]
42                                        [substring-before(@Image, '.') = 
43                                           ancestor::Block//LocalVariableDeclaration[./Type//ClassOrInterfaceType[@Image='StringBuilder']]//VariableDeclaratorId/@Image
44                                        ]
45                  ]/following-sibling::*[1][./Statement/StatementExpression//PrimaryPrefix/Name[ends-with(@Image,'.append')]
46                                           [substring-before(@Image, '.') = 
47                                               ancestor::Block//LocalVariableDeclaration[./Type//ClassOrInterfaceType[@Image='StringBuilder']]//VariableDeclaratorId/@Image
48                                           ]
49                                        ]
50  
51   * </pre>
52   *
53   */
54  public class ConsecutiveAppendsShouldReuseRule  extends AbstractJavaRule {
55  
56      @Override
57      public Object visit(ASTBlockStatement node, Object data) {
58          String variable = getVariableAppended(node);
59          if (variable != null) {
60              ASTBlockStatement nextSibling = getNextBlockStatementSibling(node);
61              if (nextSibling != null) {
62                  String nextVariable = getVariableAppended(nextSibling);
63                  if (nextVariable != null && nextVariable.equals(variable)) {
64                      addViolation(data, node);
65                  }
66              }
67          }
68          return super.visit(node, data);
69      }
70      private ASTBlockStatement getNextBlockStatementSibling(Node node) {
71          Node parent = node.jjtGetParent();
72          int childIndex = -1;
73          for (int i = 0; i < parent.jjtGetNumChildren(); i++) {
74              if (parent.jjtGetChild(i) == node) {
75                  childIndex = i;
76                  break;
77              }
78          }
79          if (childIndex + 1 < parent.jjtGetNumChildren()) {
80              Node nextSibling = parent.jjtGetChild(childIndex + 1);
81              if (nextSibling instanceof ASTBlockStatement) {
82                  return (ASTBlockStatement)nextSibling;
83              }
84          }
85          return null;
86      }
87      private String getVariableAppended(ASTBlockStatement node) {
88          if (isFirstChild(node, ASTStatement.class)) {
89              ASTStatement statement = (ASTStatement) node.jjtGetChild(0);
90              if (isFirstChild(statement, ASTStatementExpression.class)) {
91                  ASTStatementExpression stmtExp = (ASTStatementExpression) statement.jjtGetChild(0);
92                 if (stmtExp.jjtGetNumChildren() == 1) {
93                     ASTPrimaryPrefix primaryPrefix = stmtExp.getFirstDescendantOfType(ASTPrimaryPrefix.class);
94                     if (primaryPrefix != null) {
95                         ASTName name = primaryPrefix.getFirstChildOfType(ASTName.class);
96                         if (name != null) {
97                             String image = name.getImage();
98                             if (image.endsWith(".append")) {
99                             String variable = image.substring(0, image.indexOf('.'));
100                                if (isAStringBuilderBuffer(primaryPrefix, variable)) {
101                                    return variable;
102                                }
103                            }
104                        }
105                    }
106                } else {
107                    final ASTExpression exp = stmtExp.getFirstDescendantOfType(ASTExpression.class);
108                    if (isFirstChild(exp, ASTPrimaryExpression.class)) {
109                        final ASTPrimarySuffix primarySuffix = ((ASTPrimaryExpression) exp.jjtGetChild(0)).getFirstDescendantOfType(ASTPrimarySuffix.class);
110                        if (primarySuffix != null) {
111                            final String name = primarySuffix.getImage();
112                            if (name != null && name.equals("append")) {
113                                final ASTPrimaryExpression pExp = stmtExp.getFirstDescendantOfType(ASTPrimaryExpression.class);
114                                if (pExp != null) {
115                                    final ASTName astName = stmtExp.getFirstDescendantOfType(ASTName.class);
116                                    if (astName != null) {
117                                        final String variable = astName.getImage();
118                                        if (isAStringBuilderBuffer(primarySuffix, variable)) {
119                                            return variable;
120                                        }
121                                    }
122                                }
123                            }
124                        }
125                    }
126                }
127             }
128         } else if (isFirstChild(node, ASTLocalVariableDeclaration.class)) {
129             ASTLocalVariableDeclaration lvd = (ASTLocalVariableDeclaration) node.jjtGetChild(0);
130 
131             ASTVariableDeclaratorId vdId =  lvd.getFirstDescendantOfType(ASTVariableDeclaratorId.class);
132             ASTExpression exp =  lvd.getFirstDescendantOfType(ASTExpression.class);
133 
134             if (exp != null) {
135                 ASTPrimarySuffix primarySuffix = exp.getFirstDescendantOfType(ASTPrimarySuffix.class);
136                 if (primarySuffix != null) {
137                     final String name = primarySuffix.getImage();
138                     if (name != null && name.equals("append")) {
139                         String variable = vdId.getImage();
140                         if (isAStringBuilderBuffer(primarySuffix, variable)) {
141                             return variable;
142                         }
143                     }
144                 }
145             }
146         }
147 
148         return null;
149     }
150 
151     private boolean isAStringBuilderBuffer(AbstractJavaNode node, String name) {
152         Map<VariableNameDeclaration, List<NameOccurrence>> declarations = node.getScope().getDeclarations(VariableNameDeclaration.class);
153         for (VariableNameDeclaration decl : declarations.keySet()) {
154             if (decl.getName().equals(name) && TypeHelper.isEither(decl, StringBuilder.class, StringBuffer.class)) {
155                 return true;
156             }
157         }
158         return false;
159     }
160 
161     private boolean isFirstChild(Node node, Class<?> clazz) {
162         if (node.jjtGetNumChildren() == 1 && clazz.isAssignableFrom(node.jjtGetChild(0).getClass())) {
163             return true;
164         }
165         return false;
166     }
167 }