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