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.ASTMethodDeclaration;
13  import net.sourceforge.pmd.ast.ASTTryStatement;
14  import net.sourceforge.pmd.ast.ASTVariableInitializer;
15  import net.sourceforge.pmd.ast.ASTWhileStatement;
16  import net.sourceforge.pmd.ast.SimpleNode;
17  import net.sourceforge.pmd.symboltable.NameOccurrence;
18  import net.sourceforge.pmd.symboltable.VariableNameDeclaration;
19  
20  import java.util.ArrayList;
21  import java.util.HashSet;
22  import java.util.Iterator;
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 vars = node.getScope().getVariableDeclarations();
38          List constructors = findAllConstructors(node);
39          for (Iterator i = vars.entrySet().iterator(); i.hasNext();) {
40              Map.Entry entry = (Map.Entry) i.next();
41              VariableNameDeclaration field = (VariableNameDeclaration) entry.getKey();
42              if (field.getAccessNodeParent().isStatic() || !field.getAccessNodeParent().isPrivate() || field.getAccessNodeParent().isFinal()) {
43                  continue;
44              }
45  
46              int result = initializedInConstructor((List) entry.getValue(), new HashSet(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 !field.getAccessNodeParent().findChildrenOfType(ASTVariableInitializer.class).isEmpty();
59      }
60  
61      private int initializedInConstructor(List usages, Set allConstructors) {
62          int result = MUTABLE, methodInitCount = 0;
63          Set consSet = new HashSet();
64          for (Iterator j = usages.iterator(); j.hasNext();) {
65              NameOccurrence occ = (NameOccurrence) j.next();
66              if (occ.isOnLeftHandSide() || occ.isSelfAssignment()) {
67                  SimpleNode node = occ.getLocation();
68                  SimpleNode constructor = (SimpleNode) node.getFirstParentOfType(ASTConstructorDeclaration.class);
69                  if (constructor != null) {
70                      if (inLoopOrTry(node)) {
71                          continue;
72                      }
73                      if (inAnonymousInnerClass(node)) {
74                          methodInitCount++;
75                      } else {
76                          consSet.add(constructor);
77                      }
78                  } else {
79                      if (node.getFirstParentOfType(ASTMethodDeclaration.class) != null) {
80                          methodInitCount++;
81                      }
82                  }
83              }
84          }
85          if (usages.isEmpty() || ((methodInitCount == 0) && consSet.isEmpty())) {
86              result = CHECKDECL;
87          } else {
88              allConstructors.removeAll(consSet);
89              if (allConstructors.isEmpty() && (methodInitCount == 0)) {
90                  result = IMMUTABLE;
91              }
92          }
93          return result;
94      }
95  
96      private boolean inLoopOrTry(SimpleNode node) {
97          return (SimpleNode) node.getFirstParentOfType(ASTTryStatement.class) != null ||
98                  (SimpleNode) node.getFirstParentOfType(ASTForStatement.class) != null ||
99                  (SimpleNode) node.getFirstParentOfType(ASTWhileStatement.class) != null ||
100                 (SimpleNode) node.getFirstParentOfType(ASTDoStatement.class) != null;
101     }
102 
103     private boolean inAnonymousInnerClass(SimpleNode node) {
104         ASTClassOrInterfaceBodyDeclaration parent = (ASTClassOrInterfaceBodyDeclaration) node.getFirstParentOfType(ASTClassOrInterfaceBodyDeclaration.class);
105         return parent != null && parent.isAnonymousInnerClass();
106     }
107 
108     private List findAllConstructors(ASTClassOrInterfaceDeclaration node) {
109         List cons = new ArrayList();
110         node.findChildrenOfType(ASTConstructorDeclaration.class, cons, false);
111         return cons;
112     }
113 }