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.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.ArrayList;
    21  import java.util.HashSet;
    22  import java.util.Iterator;
    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 vars = node.getScope().getVariableDeclarations();
    38          List constructors = findAllConstructors(node);
    39          for (Iterator i = vars.entrySet().iterator(); i.hasNext();) {
    40              Map.Entry entry = (Map.Entry) i.next();
    41              VariableNameDeclaration field = (VariableNameDeclaration) entry.getKey();
    42              if (field.getAccessNodeParent().isStatic() || !field.getAccessNodeParent().isPrivate() || field.getAccessNodeParent().isFinal()) {
    43                  continue;
    44              }
    45
    46              int result = initializedInConstructor((List) entry.getValue(), new HashSet(constructors));
    47              if (result == MUTABLE) {
    48                  continue;
    49              }
    50              if (result == IMMUTABLE || (result == CHECKDECL && initializedWhenDeclared(field))) {
    51                  addViolation(data, field.getNode(), field.getImage());
    52              }
    53          }
    54          return super.visit(node, data);
    55      }
    56
    57      private boolean initializedWhenDeclared(VariableNameDeclaration field) {
    58          return !field.getAccessNodeParent().findChildrenOfType(ASTVariableInitializer.class).isEmpty();
    59      }
    60
    61      private int initializedInConstructor(List usages, Set allConstructors) {
    62          int result = MUTABLE, methodInitCount = 0;
    63          Set consSet = new HashSet();
    64          for (Iterator j = usages.iterator(); j.hasNext();) {
    65              NameOccurrence occ = (NameOccurrence) j.next();
    66              if (occ.isOnLeftHandSide() || occ.isSelfAssignment()) {
    67                  SimpleNode node = occ.getLocation();
    68                  SimpleNode constructor = (SimpleNode) node.getFirstParentOfType(ASTConstructorDeclaration.class);
    69                  if (constructor != null) {
    70                      if (inLoopOrTry(node)) {
    71                          continue;
    72                      }
    73                      if (inAnonymousInnerClass(node)) {
    74                          methodInitCount++;
    75                      } else {
    76                          consSet.add(constructor);
    77                      }
    78                  } else {
    79                      if (node.getFirstParentOfType(ASTMethodDeclaration.class) != null) {
    80                          methodInitCount++;
    81                      }
    82                  }
    83              }
    84          }
    85          if (usages.isEmpty() || ((methodInitCount == 0) && consSet.isEmpty())) {
    86              result = CHECKDECL;
    87          } else {
    88              allConstructors.removeAll(consSet);
    89              if (allConstructors.isEmpty() && (methodInitCount == 0)) {
    90                  result = IMMUTABLE;
    91              }
    92          }
    93          return result;
    94      }
    95
    96      private boolean inLoopOrTry(SimpleNode node) {
    97          return (SimpleNode) node.getFirstParentOfType(ASTTryStatement.class) != null ||
    98                  (SimpleNode) node.getFirstParentOfType(ASTForStatement.class) != null ||
    99                  (SimpleNode) node.getFirstParentOfType(ASTWhileStatement.class) != null ||
    100                 (SimpleNode) node.getFirstParentOfType(ASTDoStatement.class) != null;
    101     }
    102
    103     private boolean inAnonymousInnerClass(SimpleNode node) {
    104         ASTClassOrInterfaceBodyDeclaration parent = (ASTClassOrInterfaceBodyDeclaration) node.getFirstParentOfType(ASTClassOrInterfaceBodyDeclaration.class);
    105         return parent != null && parent.isAnonymousInnerClass();
    106     }
    107
    108     private List findAllConstructors(ASTClassOrInterfaceDeclaration node) {
    109         List cons = new ArrayList();
    110         node.findChildrenOfType(ASTConstructorDeclaration.class, cons, false);
    111         return cons;
    112     }
    113 }