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 }