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.optimizations;
5   
6   import java.math.BigInteger;
7   
8   import net.sourceforge.pmd.lang.ast.Node;
9   import net.sourceforge.pmd.lang.java.ast.ASTBooleanLiteral;
10  import net.sourceforge.pmd.lang.java.ast.ASTCastExpression;
11  import net.sourceforge.pmd.lang.java.ast.ASTExpression;
12  import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
13  import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
14  import net.sourceforge.pmd.lang.java.ast.ASTNullLiteral;
15  import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
16  import net.sourceforge.pmd.lang.java.ast.ASTReferenceType;
17  import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator;
18  import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
19  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
20  
21  /**
22   * Detects redundant field initializers, i.e. the field initializer expressions
23   * the JVM would assign by default.
24   *
25   * @author lucian.ciufudean@gmail.com
26   * @since Apr 10, 2009
27   */
28  public class RedundantFieldInitializerRule extends AbstractJavaRule {
29  
30      public RedundantFieldInitializerRule() {
31          addRuleChainVisit(ASTFieldDeclaration.class);
32      }
33  
34      public Object visit(ASTFieldDeclaration fieldDeclaration, Object data) {
35          // Finals can only be initialized once.
36          if (fieldDeclaration.isFinal()) {
37              return data;
38          }
39  
40          // Look for a match to the following XPath:
41          // VariableDeclarator/VariableInitializer/Expression/PrimaryExpression/PrimaryPrefix/Literal
42          for (ASTVariableDeclarator variableDeclarator : fieldDeclaration
43                  .findChildrenOfType(ASTVariableDeclarator.class)) {
44              if (variableDeclarator.jjtGetNumChildren() > 1) {
45                  final Node variableInitializer = variableDeclarator.jjtGetChild(1);
46                  if (variableInitializer.jjtGetChild(0) instanceof ASTExpression) {
47                      final Node expression = variableInitializer.jjtGetChild(0);
48                      final Node primaryExpression;
49                      if (expression.jjtGetNumChildren() == 1) {
50                          if (expression.jjtGetChild(0) instanceof ASTPrimaryExpression) {
51                              primaryExpression = expression.jjtGetChild(0);
52                          } else if (expression.jjtGetChild(0) instanceof ASTCastExpression
53                                  && expression.jjtGetChild(0).jjtGetChild(1) instanceof ASTPrimaryExpression) {
54                              primaryExpression = expression.jjtGetChild(0).jjtGetChild(1);
55                          } else {
56                              continue;
57                          }
58                      } else {
59                          continue;
60                      }
61                      final Node primaryPrefix = primaryExpression.jjtGetChild(0);
62                      if (primaryPrefix.jjtGetNumChildren() == 1 && primaryPrefix.jjtGetChild(0) instanceof ASTLiteral) {
63                          final ASTLiteral literal = (ASTLiteral) primaryPrefix.jjtGetChild(0);
64                          if (isRef(fieldDeclaration, variableDeclarator)) {
65                              // Reference type
66                              if (literal.jjtGetNumChildren() == 1 && literal.jjtGetChild(0) instanceof ASTNullLiteral) {
67                                  addViolation(data, variableDeclarator);
68                              }
69                          } else {
70                              // Primitive type
71                              if (literal.jjtGetNumChildren() == 1 && literal.jjtGetChild(0) instanceof ASTBooleanLiteral) {
72                                  // boolean type
73                                  ASTBooleanLiteral booleanLiteral = (ASTBooleanLiteral) literal.jjtGetChild(0);
74                                  if (!booleanLiteral.isTrue()) {
75                                      addViolation(data, variableDeclarator);
76                                  }
77                              } else if (literal.jjtGetNumChildren() == 0) {
78                                  // numeric type
79                                  // Note: Not catching NumberFormatException, as
80                                  // it shouldn't be happening on valid source
81                                  // code.
82                                  Number value = -1;
83                                  if (literal.isIntLiteral()) {
84                                      value = parseInteger(literal.getImage());
85                                  } else if (literal.isLongLiteral()) {
86                                      String s = literal.getImage();
87                                      // remove the ending "l" or "L" for long
88                                      // values
89                                      s = s.substring(0, s.length() - 1);
90                                      value = parseInteger(s);
91                                  } else if (literal.isFloatLiteral()) {
92                                      String s = literal.getImage();
93                                      // remove the ending "f" or "F" for float
94                                      // values
95                                      s = s.substring(0, s.length() - 1);
96                                      value = Float.valueOf(s);
97                                  } else if (literal.isDoubleLiteral()) {
98                                      value = Double.valueOf(literal.getImage());
99                                  } else if (literal.isCharLiteral()) {
100                                     value = (int) literal.getImage().charAt(1);
101                                 }
102 
103                                 if (value.doubleValue() == 0) {
104                                     addViolation(data, variableDeclarator);
105                                 }
106                             }
107                         }
108                     }
109                 }
110             }
111         }
112 
113         return data;
114     }
115 
116     /**
117      * Checks if a FieldDeclaration is a reference type (includes arrays). The
118      * reference information is in the FieldDeclaration for this example:
119      * 
120      * <pre>
121      * int[] ia1
122      * </pre>
123      * 
124      * and in the VariableDeclarator for this example:
125      * 
126      * <pre>
127      * int ia2[];
128      * </pre>
129      * 
130      * .
131      *
132      * @param fieldDeclaration
133      *            the field to check.
134      * @param variableDeclarator
135      *            the variable declarator to check.
136      * @return <code>true</code> if the field is a reference. <code>false</code>
137      *         otherwise.
138      */
139     private boolean isRef(ASTFieldDeclaration fieldDeclaration, ASTVariableDeclarator variableDeclarator) {
140         Node type = fieldDeclaration.jjtGetChild(0).jjtGetChild(0);
141         if (type instanceof ASTReferenceType) {
142             // Reference type, array or otherwise
143             return true;
144         } else {
145             // Primitive array?
146             return ((ASTVariableDeclaratorId) variableDeclarator.jjtGetChild(0)).isArray();
147         }
148     }
149 
150     private void addViolation(Object data, ASTVariableDeclarator variableDeclarator) {
151         super.addViolation(data, variableDeclarator, variableDeclarator.jjtGetChild(0).getImage());
152     }
153 
154     private Number parseInteger(String s) {
155         boolean negative = false;
156         String number = s;
157         if (number.charAt(0) == '-') {
158             negative = true;
159             number = number.substring(1);
160         }
161         BigInteger result;
162         if (number.startsWith("0x") || number.startsWith("0X")) {
163             result = new BigInteger(number.substring(2).replaceAll("_", ""), 16);
164         } else if (number.startsWith("0b") || number.startsWith("0B")) {
165             result = new BigInteger(number.substring(2).replaceAll("_", ""), 8);
166         } else if (number.charAt(0) == '0' && number.length() > 1) {
167             result = new BigInteger(number.substring(1).replaceAll("_", ""), 8);
168         } else {
169             result = new BigInteger(number.replaceAll("_", ""));
170         }
171         if (negative) {
172             result = result.negate();
173         }
174         return result;
175     }
176 }