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 }