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