View Javadoc

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