1
2
3
4 package net.sourceforge.pmd.lang.java.rule.design;
5
6 import java.util.ArrayList;
7 import java.util.HashSet;
8 import java.util.List;
9 import java.util.Map;
10 import java.util.Set;
11
12 import net.sourceforge.pmd.lang.ast.Node;
13 import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBody;
14 import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBodyDeclaration;
15 import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
16 import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
17 import net.sourceforge.pmd.lang.java.ast.ASTDoStatement;
18 import net.sourceforge.pmd.lang.java.ast.ASTForStatement;
19 import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
20 import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
21 import net.sourceforge.pmd.lang.java.ast.ASTTryStatement;
22 import net.sourceforge.pmd.lang.java.ast.ASTVariableInitializer;
23 import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement;
24 import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
25 import net.sourceforge.pmd.lang.java.symboltable.NameOccurrence;
26 import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
27
28
29
30
31 public class ImmutableFieldRule extends AbstractJavaRule {
32
33 private static final int MUTABLE = 0;
34 private static final int IMMUTABLE = 1;
35 private static final int CHECKDECL = 2;
36
37 @Override
38 public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
39 Map<VariableNameDeclaration, List<NameOccurrence>> vars = node.getScope().getVariableDeclarations();
40 List<ASTConstructorDeclaration> constructors = findAllConstructors(node);
41 for (Map.Entry<VariableNameDeclaration, List<NameOccurrence>> entry: vars.entrySet()) {
42 VariableNameDeclaration field = entry.getKey();
43 if (field.getAccessNodeParent().isStatic() || !field.getAccessNodeParent().isPrivate() || field.getAccessNodeParent().isFinal() || field.getAccessNodeParent().isVolatile()) {
44 continue;
45 }
46
47 int result = initializedInConstructor(entry.getValue(), new HashSet<ASTConstructorDeclaration>(constructors));
48 if (result == MUTABLE) {
49 continue;
50 }
51 if (result == IMMUTABLE || result == CHECKDECL && initializedWhenDeclared(field)) {
52 addViolation(data, field.getNode(), field.getImage());
53 }
54 }
55 return super.visit(node, data);
56 }
57
58 private boolean initializedWhenDeclared(VariableNameDeclaration field) {
59 return ((Node)field.getAccessNodeParent()).hasDescendantOfType(ASTVariableInitializer.class);
60 }
61
62 private int initializedInConstructor(List<NameOccurrence> usages, Set<ASTConstructorDeclaration> allConstructors) {
63 int result = MUTABLE;
64 int methodInitCount = 0;
65 Set<Node> consSet = new HashSet<Node>();
66 for (NameOccurrence occ: usages) {
67 if (occ.isOnLeftHandSide() || occ.isSelfAssignment()) {
68 Node node = occ.getLocation();
69 ASTConstructorDeclaration constructor = node.getFirstParentOfType(ASTConstructorDeclaration.class);
70 if (constructor != null) {
71 if (inLoopOrTry(node)) {
72 continue;
73 }
74
75
76
77 if (node.getFirstParentOfType(ASTIfStatement.class) != null) {
78 methodInitCount++;
79 }
80 if (inAnonymousInnerClass(node)) {
81 methodInitCount++;
82 } else {
83 consSet.add(constructor);
84 }
85 } else {
86 if (node.getFirstParentOfType(ASTMethodDeclaration.class) != null) {
87 methodInitCount++;
88 }
89 }
90 }
91 }
92 if (usages.isEmpty() || methodInitCount == 0 && consSet.isEmpty()) {
93 result = CHECKDECL;
94 } else {
95 allConstructors.removeAll(consSet);
96 if (allConstructors.isEmpty() && methodInitCount == 0) {
97 result = IMMUTABLE;
98 }
99 }
100 return result;
101 }
102
103 private boolean inLoopOrTry(Node node) {
104 return node.getFirstParentOfType(ASTTryStatement.class) != null ||
105 node.getFirstParentOfType(ASTForStatement.class) != null ||
106 node.getFirstParentOfType(ASTWhileStatement.class) != null ||
107 node.getFirstParentOfType(ASTDoStatement.class) != null;
108 }
109
110 private boolean inAnonymousInnerClass(Node node) {
111 ASTClassOrInterfaceBodyDeclaration parent = node.getFirstParentOfType(ASTClassOrInterfaceBodyDeclaration.class);
112 return parent != null && parent.isAnonymousInnerClass();
113 }
114
115 private List<ASTConstructorDeclaration> findAllConstructors(ASTClassOrInterfaceDeclaration node) {
116 List<ASTConstructorDeclaration> cons = new ArrayList<ASTConstructorDeclaration>();
117 node.getFirstChildOfType(ASTClassOrInterfaceBody.class)
118 .findDescendantsOfType(ASTConstructorDeclaration.class, cons, false);
119 return cons;
120 }
121 }