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