001 /**
002 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003 */
004 package net.sourceforge.pmd.lang.java.rule.design;
005
006 import java.util.ArrayList;
007 import java.util.HashSet;
008 import java.util.List;
009 import java.util.Map;
010 import java.util.Set;
011
012 import net.sourceforge.pmd.lang.ast.Node;
013 import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBodyDeclaration;
014 import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
015 import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
016 import net.sourceforge.pmd.lang.java.ast.ASTDoStatement;
017 import net.sourceforge.pmd.lang.java.ast.ASTForStatement;
018 import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
019 import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
020 import net.sourceforge.pmd.lang.java.ast.ASTTryStatement;
021 import net.sourceforge.pmd.lang.java.ast.ASTVariableInitializer;
022 import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement;
023 import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
024 import net.sourceforge.pmd.lang.java.symboltable.NameOccurrence;
025 import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
026
027 /**
028 * @author Olander
029 */
030 public class ImmutableFieldRule extends AbstractJavaRule {
031
032 private static final int MUTABLE = 0;
033 private static final int IMMUTABLE = 1;
034 private static final int CHECKDECL = 2;
035
036 @Override
037 public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
038 Map<VariableNameDeclaration, List<NameOccurrence>> vars = node.getScope().getVariableDeclarations();
039 List<ASTConstructorDeclaration> constructors = findAllConstructors(node);
040 for (Map.Entry<VariableNameDeclaration, List<NameOccurrence>> entry: vars.entrySet()) {
041 VariableNameDeclaration field = entry.getKey();
042 if (field.getAccessNodeParent().isStatic() || !field.getAccessNodeParent().isPrivate() || field.getAccessNodeParent().isFinal() || field.getAccessNodeParent().isVolatile()) {
043 continue;
044 }
045
046 int result = initializedInConstructor(entry.getValue(), new HashSet<ASTConstructorDeclaration>(constructors));
047 if (result == MUTABLE) {
048 continue;
049 }
050 if (result == IMMUTABLE || result == CHECKDECL && initializedWhenDeclared(field)) {
051 addViolation(data, field.getNode(), field.getImage());
052 }
053 }
054 return super.visit(node, data);
055 }
056
057 private boolean initializedWhenDeclared(VariableNameDeclaration field) {
058 return ((Node)field.getAccessNodeParent()).hasDescendantOfType(ASTVariableInitializer.class);
059 }
060
061 private int initializedInConstructor(List<NameOccurrence> usages, Set<ASTConstructorDeclaration> allConstructors) {
062 int result = MUTABLE;
063 int methodInitCount = 0;
064 Set<Node> consSet = new HashSet<Node>();
065 for (NameOccurrence occ: usages) {
066 if (occ.isOnLeftHandSide() || occ.isSelfAssignment()) {
067 Node node = occ.getLocation();
068 ASTConstructorDeclaration constructor = node.getFirstParentOfType(ASTConstructorDeclaration.class);
069 if (constructor != null) {
070 if (inLoopOrTry(node)) {
071 continue;
072 }
073 //Check for assigns in if-statements, which can depend on constructor
074 //args or other runtime knowledge and can be a valid reason to instantiate
075 //in one constructor only
076 if (node.getFirstParentOfType(ASTIfStatement.class) != null) {
077 methodInitCount++;
078 }
079 if (inAnonymousInnerClass(node)) {
080 methodInitCount++;
081 } else {
082 consSet.add(constructor);
083 }
084 } else {
085 if (node.getFirstParentOfType(ASTMethodDeclaration.class) != null) {
086 methodInitCount++;
087 }
088 }
089 }
090 }
091 if (usages.isEmpty() || methodInitCount == 0 && consSet.isEmpty()) {
092 result = CHECKDECL;
093 } else {
094 allConstructors.removeAll(consSet);
095 if (allConstructors.isEmpty() && methodInitCount == 0) {
096 result = IMMUTABLE;
097 }
098 }
099 return result;
100 }
101
102 private boolean inLoopOrTry(Node node) {
103 return node.getFirstParentOfType(ASTTryStatement.class) != null ||
104 node.getFirstParentOfType(ASTForStatement.class) != null ||
105 node.getFirstParentOfType(ASTWhileStatement.class) != null ||
106 node.getFirstParentOfType(ASTDoStatement.class) != null;
107 }
108
109 private boolean inAnonymousInnerClass(Node node) {
110 ASTClassOrInterfaceBodyDeclaration parent = node.getFirstParentOfType(ASTClassOrInterfaceBodyDeclaration.class);
111 return parent != null && parent.isAnonymousInnerClass();
112 }
113
114 private List<ASTConstructorDeclaration> findAllConstructors(ASTClassOrInterfaceDeclaration node) {
115 List<ASTConstructorDeclaration> cons = new ArrayList<ASTConstructorDeclaration>();
116 node.findDescendantsOfType(ASTConstructorDeclaration.class, cons, false);
117 return cons;
118 }
119 }
|