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.ASTClassOrInterfaceDeclaration;
8   import net.sourceforge.pmd.ast.ASTConstructorDeclaration;
9   import net.sourceforge.pmd.ast.ASTDoStatement;
10  import net.sourceforge.pmd.ast.ASTForStatement;
11  import net.sourceforge.pmd.ast.ASTMethodDeclaration;
12  import net.sourceforge.pmd.ast.ASTTryStatement;
13  import net.sourceforge.pmd.ast.ASTVariableInitializer;
14  import net.sourceforge.pmd.ast.ASTWhileStatement;
15  import net.sourceforge.pmd.ast.SimpleNode;
16  import net.sourceforge.pmd.symboltable.NameOccurrence;
17  import net.sourceforge.pmd.symboltable.VariableNameDeclaration;
18  
19  import java.util.HashSet;
20  import java.util.Iterator;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.Set;
24  
25  /***
26   * @author Olander
27   */
28  public class ImmutableField extends AbstractRule {
29      
30      private static final int MUTABLE = 0;
31      private static final int IMMUTABLE = 1;
32      private static final int CHECKDECL = 2;
33  
34      public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
35          Map vars = node.getScope().getVariableDeclarations();
36          Set constructors = findAllConstructors(node);
37          for (Iterator i = vars.keySet().iterator(); i.hasNext();) {
38              VariableNameDeclaration field = (VariableNameDeclaration) i.next();
39              if (field.getAccessNodeParent().isStatic() || !field.getAccessNodeParent().isPrivate() || field.getAccessNodeParent().isFinal()) {
40                  continue;
41              }
42  
43              int result = initializedInConstructor((List)vars.get(field), new HashSet(constructors));
44              if (result == MUTABLE) {
45                  continue;
46              }
47              if (result == IMMUTABLE || ((result == CHECKDECL) && !field.getAccessNodeParent().findChildrenOfType(ASTVariableInitializer.class).isEmpty())) {
48                  addViolation(data, field.getNode(), field.getImage());
49              }
50          }
51          return super.visit(node, data);
52      }
53  
54      private int initializedInConstructor(List usages, Set allConstructors) {
55          int rc = MUTABLE, methodInitCount = 0;
56          Set consSet = new HashSet();
57          for (Iterator j = usages.iterator(); j.hasNext();) {
58              NameOccurrence occ = (NameOccurrence)j.next();
59              if (occ.isOnLeftHandSide() || occ.isSelfAssignment()) {
60                  SimpleNode node = occ.getLocation();
61                  SimpleNode constructor = (SimpleNode)node.getFirstParentOfType(ASTConstructorDeclaration.class);
62                  if (constructor != null) {
63                      if (inLoopOrTry(node)) {
64                          continue;
65                      }
66                      consSet.add(constructor);
67                  } else {
68                      if (node.getFirstParentOfType(ASTMethodDeclaration.class) != null) {
69                          methodInitCount++;
70                      }
71                  }
72              }
73          }
74          if (usages.isEmpty() || ((methodInitCount == 0) && consSet.isEmpty())) {
75              rc = CHECKDECL;
76          } else {
77              allConstructors.removeAll(consSet);
78              if (allConstructors.isEmpty() && (methodInitCount == 0)) {
79                  rc = IMMUTABLE;
80              }
81          }
82          return rc;
83      }
84  
85      private boolean inLoopOrTry(SimpleNode node) {
86          return (SimpleNode)node.getFirstParentOfType(ASTTryStatement.class) != null ||
87                 (SimpleNode)node.getFirstParentOfType(ASTForStatement.class) != null ||
88                 (SimpleNode)node.getFirstParentOfType(ASTWhileStatement.class) != null ||
89                 (SimpleNode)node.getFirstParentOfType(ASTDoStatement.class) != null;
90      }
91  
92      /*** construct a set containing all ASTConstructorDeclaration nodes for this class
93       */
94      private Set findAllConstructors(ASTClassOrInterfaceDeclaration node) {
95          Set set = new HashSet();
96          set.addAll(node.findChildrenOfType(ASTConstructorDeclaration.class));
97          return set;
98      }
99  }