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