View Javadoc

1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.rules.design;
5   
6   import net.sourceforge.pmd.AbstractRule;
7   import net.sourceforge.pmd.ast.ASTClassOrInterfaceBodyDeclaration;
8   import net.sourceforge.pmd.ast.ASTClassOrInterfaceDeclaration;
9   import net.sourceforge.pmd.ast.ASTConstructorDeclaration;
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.ASTMethodDeclaration;
14  import net.sourceforge.pmd.ast.ASTTryStatement;
15  import net.sourceforge.pmd.ast.ASTVariableInitializer;
16  import net.sourceforge.pmd.ast.ASTWhileStatement;
17  import net.sourceforge.pmd.ast.SimpleNode;
18  import net.sourceforge.pmd.symboltable.NameOccurrence;
19  import net.sourceforge.pmd.symboltable.VariableNameDeclaration;
20  
21  import java.util.ArrayList;
22  import java.util.HashSet;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Set;
26  
27  /**
28   * @author Olander
29   */
30  public class ImmutableField extends AbstractRule {
31  
32      private static final int MUTABLE = 0;
33      private static final int IMMUTABLE = 1;
34      private static final int CHECKDECL = 2;
35  
36      public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
37          Map<VariableNameDeclaration, List<NameOccurrence>> vars = node.getScope().getVariableDeclarations();
38          List<ASTConstructorDeclaration> constructors = findAllConstructors(node);
39          for (Map.Entry<VariableNameDeclaration, List<NameOccurrence>> entry: vars.entrySet()) {
40              VariableNameDeclaration field = entry.getKey();
41              if (field.getAccessNodeParent().isStatic() || !field.getAccessNodeParent().isPrivate() || field.getAccessNodeParent().isFinal() || field.getAccessNodeParent().isVolatile()) {
42                  continue;
43              }
44  
45              int result = initializedInConstructor(entry.getValue(), new HashSet<ASTConstructorDeclaration>(constructors));
46              if (result == MUTABLE) {
47                  continue;
48              }
49              if (result == IMMUTABLE || (result == CHECKDECL && initializedWhenDeclared(field))) {
50                  addViolation(data, field.getNode(), field.getImage());
51              }
52          }
53          return super.visit(node, data);
54      }
55  
56      private boolean initializedWhenDeclared(VariableNameDeclaration field) {
57          return !field.getAccessNodeParent().findChildrenOfType(ASTVariableInitializer.class).isEmpty();
58      }
59  
60      private int initializedInConstructor(List<NameOccurrence> usages, Set<ASTConstructorDeclaration> allConstructors) {
61          int result = MUTABLE, methodInitCount = 0;
62          Set<SimpleNode> consSet = new HashSet<SimpleNode>();
63          for (NameOccurrence occ: usages) {
64              if (occ.isOnLeftHandSide() || occ.isSelfAssignment()) {
65                  SimpleNode node = occ.getLocation();
66                  ASTConstructorDeclaration constructor = node.getFirstParentOfType(ASTConstructorDeclaration.class);
67                  if (constructor != null) {
68                      if (inLoopOrTry(node)) {
69                          continue;
70                      }
71                      //Check for assigns in if-statements, which can depend on constructor 
72                      //args or other runtime knowledge and can be a valid reason to instantiate
73                      //in one constructor only
74                      if (node.getFirstParentOfType(ASTIfStatement.class) != null) {
75                      	methodInitCount++;
76                      }
77                      if (inAnonymousInnerClass(node)) {
78                          methodInitCount++;
79                      } else {
80                          consSet.add(constructor);
81                      }
82                  } else {
83                      if (node.getFirstParentOfType(ASTMethodDeclaration.class) != null) {
84                          methodInitCount++;
85                      }
86                  }
87              }
88          }
89          if (usages.isEmpty() || ((methodInitCount == 0) && consSet.isEmpty())) {
90              result = CHECKDECL;
91          } else {
92              allConstructors.removeAll(consSet);
93              if (allConstructors.isEmpty() && (methodInitCount == 0)) {
94                  result = IMMUTABLE;
95              }
96          }
97          return result;
98      }
99  
100     private boolean inLoopOrTry(SimpleNode node) {
101         return node.getFirstParentOfType(ASTTryStatement.class) != null ||
102                 node.getFirstParentOfType(ASTForStatement.class) != null ||
103                 node.getFirstParentOfType(ASTWhileStatement.class) != null ||
104                 node.getFirstParentOfType(ASTDoStatement.class) != null;
105     }
106 
107     private boolean inAnonymousInnerClass(SimpleNode node) {
108         ASTClassOrInterfaceBodyDeclaration parent = node.getFirstParentOfType(ASTClassOrInterfaceBodyDeclaration.class);
109         return parent != null && parent.isAnonymousInnerClass();
110     }
111 
112     private List<ASTConstructorDeclaration> findAllConstructors(ASTClassOrInterfaceDeclaration node) {
113         List<ASTConstructorDeclaration> cons = new ArrayList<ASTConstructorDeclaration>();
114         node.findChildrenOfType(ASTConstructorDeclaration.class, cons, false);
115         return cons;
116     }
117 }