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