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.ASTClassOrInterfaceBodyDeclaration; 8 import net.sourceforge.pmd.ast.ASTClassOrInterfaceDeclaration; 9 import net.sourceforge.pmd.ast.ASTConstructorDeclaration; 10 import net.sourceforge.pmd.ast.ASTDoStatement; 11 import net.sourceforge.pmd.ast.ASTForStatement; 12 import net.sourceforge.pmd.ast.ASTMethodDeclaration; 13 import net.sourceforge.pmd.ast.ASTTryStatement; 14 import net.sourceforge.pmd.ast.ASTVariableInitializer; 15 import net.sourceforge.pmd.ast.ASTWhileStatement; 16 import net.sourceforge.pmd.ast.SimpleNode; 17 import net.sourceforge.pmd.symboltable.NameOccurrence; 18 import net.sourceforge.pmd.symboltable.VariableNameDeclaration; 19 20 import java.util.HashSet; 21 import java.util.Iterator; 22 import java.util.List; 23 import java.util.Map; 24 import java.util.Set; 25 import java.util.ArrayList; 26 27 /*** 28 * @author Olander 29 */ 30 public class ImmutableField extends AbstractRule { 31 32 private static final int MUTABLE = 0; 33 private static final int IMMUTABLE = 1; 34 private static final int CHECKDECL = 2; 35 36 public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { 37 Map vars = node.getScope().getVariableDeclarations(); 38 List constructors = findAllConstructors(node); 39 for (Iterator i = vars.keySet().iterator(); i.hasNext();) { 40 VariableNameDeclaration field = (VariableNameDeclaration) i.next(); 41 if (field.getAccessNodeParent().isStatic() || !field.getAccessNodeParent().isPrivate() || field.getAccessNodeParent().isFinal()) { 42 continue; 43 } 44 45 int result = initializedInConstructor((List) vars.get(field), new HashSet(constructors)); 46 if (result == MUTABLE) { 47 continue; 48 } 49 if (result == IMMUTABLE || (result == CHECKDECL && initializedWhenDeclared(field))) { 50 addViolation(data, field.getNode(), field.getImage()); 51 } 52 } 53 return super.visit(node, data); 54 } 55 56 private boolean initializedWhenDeclared(VariableNameDeclaration field) { 57 return !field.getAccessNodeParent().findChildrenOfType(ASTVariableInitializer.class).isEmpty(); 58 } 59 60 private int initializedInConstructor(List usages, Set allConstructors) { 61 int result = MUTABLE, methodInitCount = 0; 62 Set consSet = new HashSet(); 63 for (Iterator j = usages.iterator(); j.hasNext();) { 64 NameOccurrence occ = (NameOccurrence) j.next(); 65 if (occ.isOnLeftHandSide() || occ.isSelfAssignment()) { 66 SimpleNode node = occ.getLocation(); 67 SimpleNode constructor = (SimpleNode) node.getFirstParentOfType(ASTConstructorDeclaration.class); 68 if (constructor != null) { 69 if (inLoopOrTry(node)) { 70 continue; 71 } 72 if (inAnonymousInnerClass(node)) { 73 methodInitCount++; 74 } else { 75 consSet.add(constructor); 76 } 77 } else { 78 if (node.getFirstParentOfType(ASTMethodDeclaration.class) != null) { 79 methodInitCount++; 80 } 81 } 82 } 83 } 84 if (usages.isEmpty() || ((methodInitCount == 0) && consSet.isEmpty())) { 85 result = CHECKDECL; 86 } else { 87 allConstructors.removeAll(consSet); 88 if (allConstructors.isEmpty() && (methodInitCount == 0)) { 89 result = IMMUTABLE; 90 } 91 } 92 return result; 93 } 94 95 private boolean inLoopOrTry(SimpleNode node) { 96 return (SimpleNode) node.getFirstParentOfType(ASTTryStatement.class) != null || 97 (SimpleNode) node.getFirstParentOfType(ASTForStatement.class) != null || 98 (SimpleNode) node.getFirstParentOfType(ASTWhileStatement.class) != null || 99 (SimpleNode) node.getFirstParentOfType(ASTDoStatement.class) != null; 100 } 101 102 private boolean inAnonymousInnerClass(SimpleNode node) { 103 ASTClassOrInterfaceBodyDeclaration parent = (ASTClassOrInterfaceBodyDeclaration) node.getFirstParentOfType(ASTClassOrInterfaceBodyDeclaration.class); 104 return parent != null && parent.isAnonymousInnerClass(); 105 } 106 107 private List findAllConstructors(ASTClassOrInterfaceDeclaration node) { 108 List cons = new ArrayList(); 109 node.findChildrenOfType(ASTConstructorDeclaration.class, cons, false); 110 return cons; 111 } 112 }