ImmutableFieldRule.java
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 == && 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 }