View Javadoc

    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.ASTIfStatement;
    13  import net.sourceforge.pmd.ast.ASTMethodDeclaration;
    14  import net.sourceforge.pmd.ast.ASTTryStatement;
    15  import net.sourceforge.pmd.ast.ASTVariableInitializer;
    16  import net.sourceforge.pmd.ast.ASTWhileStatement;
    17  import net.sourceforge.pmd.ast.SimpleNode;
    18  import net.sourceforge.pmd.symboltable.NameOccurrence;
    19  import net.sourceforge.pmd.symboltable.VariableNameDeclaration;
    20
    21  import java.util.ArrayList;
    22  import java.util.HashSet;
    23  import java.util.List;
    24  import java.util.Map;
    25  import java.util.Set;
    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<VariableNameDeclaration, List<NameOccurrence>> vars = node.getScope().getVariableDeclarations();
    38          List<ASTConstructorDeclaration> constructors = findAllConstructors(node);
    39          for (Map.Entry<VariableNameDeclaration, List<NameOccurrence>> entry: vars.entrySet()) {
    40              VariableNameDeclaration field = entry.getKey();
    41              if (field.getAccessNodeParent().isStatic() || !field.getAccessNodeParent().isPrivate() || field.getAccessNodeParent().isFinal() || field.getAccessNodeParent().isVolatile()) {
    42                  continue;
    43              }
    44
    45              int result = initializedInConstructor(entry.getValue(), new HashSet<ASTConstructorDeclaration>(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<NameOccurrence> usages, Set<ASTConstructorDeclaration> allConstructors) {
    61          int result = MUTABLE, methodInitCount = 0;
    62          Set<SimpleNode> consSet = new HashSet<SimpleNode>();
    63          for (NameOccurrence occ: usages) {
    64              if (occ.isOnLeftHandSide() || occ.isSelfAssignment()) {
    65                  SimpleNode node = occ.getLocation();
    66                  ASTConstructorDeclaration constructor = node.getFirstParentOfType(ASTConstructorDeclaration.class);
    67                  if (constructor != null) {
    68                      if (inLoopOrTry(node)) {
    69                          continue;
    70                      }
    71                      //Check for assigns in if-statements, which can depend on constructor
    72                      //args or other runtime knowledge and can be a valid reason to instantiate
    73                      //in one constructor only
    74                      if (node.getFirstParentOfType(ASTIfStatement.class) != null) {
    75                      	methodInitCount++;
    76                      }
    77                      if (inAnonymousInnerClass(node)) {
    78                          methodInitCount++;
    79                      } else {
    80                          consSet.add(constructor);
    81                      }
    82                  } else {
    83                      if (node.getFirstParentOfType(ASTMethodDeclaration.class) != null) {
    84                          methodInitCount++;
    85                      }
    86                  }
    87              }
    88          }
    89          if (usages.isEmpty() || ((methodInitCount == 0) && consSet.isEmpty())) {
    90              result = CHECKDECL;
    91          } else {
    92              allConstructors.removeAll(consSet);
    93              if (allConstructors.isEmpty() && (methodInitCount == 0)) {
    94                  result = IMMUTABLE;
    95              }
    96          }
    97          return result;
    98      }
    99
    100     private boolean inLoopOrTry(SimpleNode node) {
    101         return node.getFirstParentOfType(ASTTryStatement.class) != null ||
    102                 node.getFirstParentOfType(ASTForStatement.class) != null ||
    103                 node.getFirstParentOfType(ASTWhileStatement.class) != null ||
    104                 node.getFirstParentOfType(ASTDoStatement.class) != null;
    105     }
    106
    107     private boolean inAnonymousInnerClass(SimpleNode node) {
    108         ASTClassOrInterfaceBodyDeclaration parent = node.getFirstParentOfType(ASTClassOrInterfaceBodyDeclaration.class);
    109         return parent != null && parent.isAnonymousInnerClass();
    110     }
    111
    112     private List<ASTConstructorDeclaration> findAllConstructors(ASTClassOrInterfaceDeclaration node) {
    113         List<ASTConstructorDeclaration> cons = new ArrayList<ASTConstructorDeclaration>();
    114         node.findChildrenOfType(ASTConstructorDeclaration.class, cons, false);
    115         return cons;
    116     }
    117 }