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.RuleContext; 8 import net.sourceforge.pmd.ast.ASTClassOrInterfaceDeclaration; 9 import net.sourceforge.pmd.ast.ASTConstructorDeclaration; 10 import net.sourceforge.pmd.ast.ASTMethodDeclaration; 11 import net.sourceforge.pmd.ast.ASTVariableInitializer; 12 import net.sourceforge.pmd.ast.SimpleNode; 13 import net.sourceforge.pmd.symboltable.NameOccurrence; 14 import net.sourceforge.pmd.symboltable.VariableNameDeclaration; 15 16 import java.text.MessageFormat; 17 import java.util.HashSet; 18 import java.util.Iterator; 19 import java.util.List; 20 import java.util.Map; 21 import java.util.Set; 22 23 /*** 24 * @author Olander 25 */ 26 public class ImmutableField extends AbstractRule { 27 28 static private final int MUTABLE = 0; 29 static private final int IMMUTABLE = 1; 30 static private final int CHECKDECL = 2; 31 32 public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { 33 Map vars = node.getScope().getVariableDeclarations(); 34 Set constructors = findAllConstructors(node); 35 for (Iterator i = vars.keySet().iterator(); i.hasNext();) { 36 VariableNameDeclaration decl = (VariableNameDeclaration) i.next(); 37 if (decl.getAccessNodeParent().isStatic() || !decl.getAccessNodeParent().isPrivate() || decl.getAccessNodeParent().isFinal()) { 38 continue; 39 } 40 41 int result = initializedInConstructor((List)vars.get(decl), new HashSet(constructors)); 42 if (result == MUTABLE) { 43 continue; 44 } 45 if (result == IMMUTABLE || ((result == CHECKDECL) && initializedInDeclaration(decl.getAccessNodeParent()))) { 46 ((RuleContext) data).getReport().addRuleViolation(createRuleViolation((RuleContext) data, decl, MessageFormat.format(getMessage(), new Object[]{decl.getImage()}))); 47 } 48 } 49 return super.visit(node, data); 50 } 51 52 private int initializedInConstructor(List usages, Set allConstructors) { 53 int rc = MUTABLE, methodInitCount = 0; 54 boolean foundUsage = false; 55 Set consSet = new HashSet(); 56 57 for (Iterator j = usages.iterator(); j.hasNext();) { 58 foundUsage = true; 59 NameOccurrence occ = (NameOccurrence)j.next(); 60 if (occ.isOnLeftHandSide() || occ.isSelfAssignment()) { 61 SimpleNode node = occ.getLocation(); 62 SimpleNode constructor = (SimpleNode)node.getFirstParentOfType(ASTConstructorDeclaration.class); 63 if (constructor != null) { 64 consSet.add(constructor); 65 } else { 66 if (node.getFirstParentOfType(ASTMethodDeclaration.class) != null) { 67 methodInitCount++; 68 } 69 } 70 } 71 } 72 if (!foundUsage || ((methodInitCount == 0) && consSet.isEmpty())) { 73 rc = CHECKDECL; 74 } else { 75 allConstructors.removeAll(consSet); 76 if (allConstructors.isEmpty() && (methodInitCount == 0)) { 77 rc = IMMUTABLE; 78 } 79 } 80 return rc; 81 } 82 83 private boolean initializedInDeclaration(SimpleNode node) { 84 return !node.findChildrenOfType(ASTVariableInitializer.class).isEmpty(); 85 } 86 87 /*** construct a set containing all ASTConstructorDeclaration nodes for this class 88 */ 89 private Set findAllConstructors(ASTClassOrInterfaceDeclaration node) { 90 Set set = new HashSet(); 91 set.addAll(node.findChildrenOfType(ASTConstructorDeclaration.class)); 92 return set; 93 } 94 }