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 }