View Javadoc

    1   /**
    2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
    3    */
    4   package net.sourceforge.pmd.lang.java.rule.design;
    5
    6   import java.util.ArrayList;
    7   import java.util.HashSet;
    8   import java.util.List;
    9   import java.util.Map;
    10  import java.util.Set;
    11
    12  import net.sourceforge.pmd.lang.ast.Node;
    13  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBody;
    14  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBodyDeclaration;
    15  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
    16  import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
    17  import net.sourceforge.pmd.lang.java.ast.ASTDoStatement;
    18  import net.sourceforge.pmd.lang.java.ast.ASTForStatement;
    19  import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
    20  import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
    21  import net.sourceforge.pmd.lang.java.ast.ASTTryStatement;
    22  import net.sourceforge.pmd.lang.java.ast.ASTVariableInitializer;
    23  import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement;
    24  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
    25  import net.sourceforge.pmd.lang.java.symboltable.NameOccurrence;
    26  import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
    27
    28  /**
    29   * @author Olander
    30   */
    31  public class ImmutableFieldRule extends AbstractJavaRule {
    32
    33      private static final int MUTABLE = 0;
    34      private static final int IMMUTABLE = 1;
    35      private static final int CHECKDECL = 2;
    36
    37      @Override
    38      public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
    39          Map<VariableNameDeclaration, List<NameOccurrence>> vars = node.getScope().getVariableDeclarations();
    40          List<ASTConstructorDeclaration> constructors = findAllConstructors(node);
    41          for (Map.Entry<VariableNameDeclaration, List<NameOccurrence>> entry: vars.entrySet()) {
    42              VariableNameDeclaration field = entry.getKey();
    43              if (field.getAccessNodeParent().isStatic() || !field.getAccessNodeParent().isPrivate() || field.getAccessNodeParent().isFinal() || field.getAccessNodeParent().isVolatile()) {
    44                  continue;
    45              }
    46
    47              int result = initializedInConstructor(entry.getValue(), new HashSet<ASTConstructorDeclaration>(constructors));
    48              if (result == MUTABLE) {
    49                  continue;
    50              }
    51              if (result == IMMUTABLE || result == CHECKDECL && initializedWhenDeclared(field)) {
    52                  addViolation(data, field.getNode(), field.getImage());
    53              }
    54          }
    55          return super.visit(node, data);
    56      }
    57
    58      private boolean initializedWhenDeclared(VariableNameDeclaration field) {
    59          return ((Node)field.getAccessNodeParent()).hasDescendantOfType(ASTVariableInitializer.class);
    60      }
    61
    62      private int initializedInConstructor(List<NameOccurrence> usages, Set<ASTConstructorDeclaration> allConstructors) {
    63          int result = MUTABLE;
    64          int methodInitCount = 0;
    65          Set<Node> consSet = new HashSet<Node>();
    66          for (NameOccurrence occ: usages) {
    67              if (occ.isOnLeftHandSide() || occ.isSelfAssignment()) {
    68          	Node node = occ.getLocation();
    69                  ASTConstructorDeclaration constructor = node.getFirstParentOfType(ASTConstructorDeclaration.class);
    70                  if (constructor != null) {
    71                      if (inLoopOrTry(node)) {
    72                          continue;
    73                      }
    74                      //Check for assigns in if-statements, which can depend on constructor
    75                      //args or other runtime knowledge and can be a valid reason to instantiate
    76                      //in one constructor only
    77                      if (node.getFirstParentOfType(ASTIfStatement.class) != null) {
    78                      	methodInitCount++;
    79                      }
    80                      if (inAnonymousInnerClass(node)) {
    81                          methodInitCount++;
    82                      } else {
    83                          consSet.add(constructor);
    84                      }
    85                  } else {
    86                      if (node.getFirstParentOfType(ASTMethodDeclaration.class) != null) {
    87                          methodInitCount++;
    88                      }
    89                  }
    90              }
    91          }
    92          if (usages.isEmpty() || methodInitCount == 0 && consSet.isEmpty()) {
    93              result = CHECKDECL;
    94          } else {
    95              allConstructors.removeAll(consSet);
    96              if (allConstructors.isEmpty() && methodInitCount == 0) {
    97                  result = IMMUTABLE;
    98              }
    99          }
    100         return result;
    101     }
    102
    103     private boolean inLoopOrTry(Node node) {
    104         return node.getFirstParentOfType(ASTTryStatement.class) != null ||
    105                 node.getFirstParentOfType(ASTForStatement.class) != null ||
    106                 node.getFirstParentOfType(ASTWhileStatement.class) != null ||
    107                 node.getFirstParentOfType(ASTDoStatement.class) != null;
    108     }
    109
    110     private boolean inAnonymousInnerClass(Node node) {
    111         ASTClassOrInterfaceBodyDeclaration parent = node.getFirstParentOfType(ASTClassOrInterfaceBodyDeclaration.class);
    112         return parent != null && parent.isAnonymousInnerClass();
    113     }
    114
    115     private List<ASTConstructorDeclaration> findAllConstructors(ASTClassOrInterfaceDeclaration node) {
    116         List<ASTConstructorDeclaration> cons = new ArrayList<ASTConstructorDeclaration>();
    117         node.getFirstChildOfType(ASTClassOrInterfaceBody.class)
    118             .findDescendantsOfType(ASTConstructorDeclaration.class, cons, false);
    119         return cons;
    120     }
    121 }