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.coupling;
    5
    6   import java.util.HashSet;
    7   import java.util.Set;
    8
    9   import net.sourceforge.pmd.lang.ast.Node;
    10  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
    11  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
    12  import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
    13  import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
    14  import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter;
    15  import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration;
    16  import net.sourceforge.pmd.lang.java.ast.ASTReferenceType;
    17  import net.sourceforge.pmd.lang.java.ast.ASTResultType;
    18  import net.sourceforge.pmd.lang.java.ast.ASTType;
    19  import net.sourceforge.pmd.lang.java.ast.JavaNode;
    20  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
    21  import net.sourceforge.pmd.lang.java.symboltable.ClassScope;
    22  import net.sourceforge.pmd.lang.rule.properties.IntegerProperty;
    23
    24
    25  /**
    26   * CouplingBetweenObjects attempts to capture all unique Class attributes,
    27   * local variables, and return types to determine how many objects a class is
    28   * coupled to. This is only a gauge and isn't a hard and fast rule. The threshold
    29   * value is configurable and should be determined accordingly
    30   *
    31   * @author aglover
    32   * @since Feb 20, 2003
    33   */
    34  public class CouplingBetweenObjectsRule extends AbstractJavaRule {
    35
    36      private int couplingCount;
    37      private Set<String> typesFoundSoFar;
    38
    39      private static final IntegerProperty THRESHOLD_DESCRIPTOR = new IntegerProperty(
    40      	"threshold", "Unique type reporting threshold", 2, 100, 20, 1.0f
    41      	);
    42
    43      public CouplingBetweenObjectsRule() {
    44  	definePropertyDescriptor(THRESHOLD_DESCRIPTOR);
    45      }
    46
    47      @Override
    48      public Object visit(ASTCompilationUnit cu, Object data) {
    49          typesFoundSoFar = new HashSet<String>();
    50          couplingCount = 0;
    51
    52          Object returnObj = cu.childrenAccept(this, data);
    53
    54          if (couplingCount > getProperty(THRESHOLD_DESCRIPTOR)) {
    55              addViolation(data, cu, "A value of " + couplingCount + " may denote a high amount of coupling within the class");
    56          }
    57
    58          return returnObj;
    59      }
    60
    61      @Override
    62      public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
    63          if (node.isInterface()) {
    64              return data;
    65          }
    66          return super.visit(node, data);
    67      }
    68
    69      @Override
    70      public Object visit(ASTResultType node, Object data) {
    71          for (int x = 0; x < node.jjtGetNumChildren(); x++) {
    72              Node tNode = node.jjtGetChild(x);
    73              if (tNode instanceof ASTType) {
    74          	Node reftypeNode = tNode.jjtGetChild(0);
    75                  if (reftypeNode instanceof ASTReferenceType) {
    76                      Node classOrIntType = reftypeNode.jjtGetChild(0);
    77                      if (classOrIntType instanceof ASTClassOrInterfaceType) {
    78                  	Node nameNode = classOrIntType;
    79                          this.checkVariableType(nameNode, nameNode.getImage());
    80                      }
    81                  }
    82              }
    83          }
    84          return super.visit(node, data);
    85      }
    86
    87      @Override
    88      public Object visit(ASTLocalVariableDeclaration node, Object data) {
    89          handleASTTypeChildren(node);
    90          return super.visit(node, data);
    91      }
    92
    93      @Override
    94      public Object visit(ASTFormalParameter node, Object data) {
    95          handleASTTypeChildren(node);
    96          return super.visit(node, data);
    97      }
    98
    99      @Override
    100     public Object visit(ASTFieldDeclaration node, Object data) {
    101         for (int x = 0; x < node.jjtGetNumChildren(); ++x) {
    102             Node firstStmt = node.jjtGetChild(x);
    103             if (firstStmt instanceof ASTType) {
    104                 ASTType tp = (ASTType) firstStmt;
    105                 Node nd = tp.jjtGetChild(0);
    106                 checkVariableType(nd, nd.getImage());
    107             }
    108         }
    109
    110         return super.visit(node, data);
    111     }
    112
    113     /**
    114      * convience method to handle hierarchy. This is probably too much
    115      * work and will go away once I figure out the framework
    116      */
    117     private void handleASTTypeChildren(Node node) {
    118         for (int x = 0; x < node.jjtGetNumChildren(); x++) {
    119             Node sNode = node.jjtGetChild(x);
    120             if (sNode instanceof ASTType) {
    121         	Node nameNode = sNode.jjtGetChild(0);
    122                 checkVariableType(nameNode, nameNode.getImage());
    123             }
    124         }
    125     }
    126
    127     /**
    128      * performs a check on the variable and updates the counter. Counter is
    129      * instance for a class and is reset upon new class scan.
    130      *
    131      * @param variableType The variable type.
    132      */
    133     private void checkVariableType(Node nameNode, String variableType) {
    134         // TODO - move this into the symbol table somehow?
    135         if (nameNode.getParentsOfType(ASTClassOrInterfaceDeclaration.class).isEmpty()) {
    136             return;
    137         }
    138         //if the field is of any type other than the class type
    139         //increment the count
    140         ClassScope clzScope = ((JavaNode)nameNode).getScope().getEnclosingClassScope();
    141         if (!clzScope.getClassName().equals(variableType) && !this.filterTypes(variableType) && !this.typesFoundSoFar.contains(variableType)) {
    142             couplingCount++;
    143             typesFoundSoFar.add(variableType);
    144         }
    145     }
    146
    147     /**
    148      * Filters variable type - we don't want primatives, wrappers, strings, etc.
    149      * This needs more work. I'd like to filter out super types and perhaps interfaces
    150      *
    151      * @param variableType The variable type.
    152      * @return boolean true if variableType is not what we care about
    153      */
    154     private boolean filterTypes(String variableType) {
    155         return variableType != null && (variableType.startsWith("java.lang.") || variableType.equals("String") || filterPrimitivesAndWrappers(variableType));
    156     }
    157
    158     /**
    159      * @param variableType The variable type.
    160      * @return boolean true if variableType is a primitive or wrapper
    161      */
    162     private boolean filterPrimitivesAndWrappers(String variableType) {
    163         return variableType.equals("int") || variableType.equals("Integer") || variableType.equals("char") || variableType.equals("Character") || variableType.equalsIgnoreCase("double") || variableType.equalsIgnoreCase("long") || variableType.equalsIgnoreCase("short") || variableType.equalsIgnoreCase("float") || variableType.equalsIgnoreCase("byte") || variableType.equalsIgnoreCase("boolean");
    164     }
    165 }