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