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