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