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.RuleContext;
8   import net.sourceforge.pmd.ast.ASTClassDeclaration;
9   import net.sourceforge.pmd.ast.ASTCompilationUnit;
10  import net.sourceforge.pmd.ast.ASTFieldDeclaration;
11  import net.sourceforge.pmd.ast.ASTFormalParameter;
12  import net.sourceforge.pmd.ast.ASTLocalVariableDeclaration;
13  import net.sourceforge.pmd.ast.ASTName;
14  import net.sourceforge.pmd.ast.ASTResultType;
15  import net.sourceforge.pmd.ast.ASTType;
16  import net.sourceforge.pmd.ast.SimpleNode;
17  
18  import java.util.HashSet;
19  import java.util.Set;
20  
21  
22  /***
23   * CouplingBetweenObjectsRule attempts to capture all unique Class attributes,
24   * local variables, and return types to determine how many objects a class is
25   * coupled to. This is only a guage and isn't a hard and fast rule. The threshold
26   * value is configurable and should be determined accordingly
27   *
28   * @since Feb 20, 2003
29   * @author aglover
30   *
31   */
32  public class CouplingBetweenObjectsRule extends AbstractRule {
33  
34      private String className;
35      private int couplingCount;
36      private Set typesFoundSoFar;
37  
38      /***
39       * handles the source file
40       *
41       * @return Object
42       * @param ASTCompilationUnit cu
43       * @param Object data
44       */
45      public Object visit(ASTCompilationUnit cu, Object data) {
46          this.typesFoundSoFar = new HashSet();
47          this.couplingCount = 0;
48  
49          Object returnObj = cu.childrenAccept(this, data);
50  
51          if (this.couplingCount > getIntProperty("threshold")) {
52              RuleContext ctx = (RuleContext) data;
53              ctx.getReport().addRuleViolation(createRuleViolation(ctx, cu.getBeginLine(), "A value of " + this.couplingCount + " may denote a high amount of coupling within the class"));
54          }
55  
56          return returnObj;
57      }
58  
59      /***
60       * handles class declaration. I need this to capture class name. I think
61       * there is probably a better way to capture it; however, I don't know the
62       * framework well enough yet...
63       *
64       * @return Object
65       * @param ASTClassDeclaration node
66       * @param Object data
67       */
68      public Object visit(ASTClassDeclaration node, Object data) {
69          SimpleNode firstStmt = (SimpleNode) node.jjtGetChild(0);
70          this.className = firstStmt.getImage();
71          return super.visit(node, data);
72      }
73  
74      /***
75       * handles a return type of a method
76       *
77       * @return Object
78       * @param ASTResultType node
79       * @param Object data
80       */
81      public Object visit(ASTResultType node, Object data) {
82          for (int x = 0; x < node.jjtGetNumChildren(); x++) {
83              SimpleNode tNode = (SimpleNode) node.jjtGetChild(x);
84              if (tNode instanceof ASTType) {
85                  SimpleNode nameNode = (SimpleNode) tNode.jjtGetChild(0);
86                  if (nameNode instanceof ASTName) {
87                      this.checkVariableType(nameNode.getImage());
88                  }
89              }
90          }
91          return super.visit(node, data);
92      }
93  
94      /***
95       * handles a local variable found in a method block
96       *
97       * @return Object
98       * @param ASTLocalVariableDeclaration node
99       * @param Object data
100      */
101     public Object visit(ASTLocalVariableDeclaration node, Object data) {
102         this.handleASTTypeChildren(node);
103         return super.visit(node, data);
104     }
105 
106     /***
107      * handles a method parameter
108      *
109      * @return Object
110      * @param ASTFormalParameter node
111      * @param Object data
112      */
113     public Object visit(ASTFormalParameter node, Object data) {
114         this.handleASTTypeChildren(node);
115         return super.visit(node, data);
116     }
117 
118     /***
119      * handles a field declaration - i.e. an instance variable. Method doesn't care if variable
120      * is public/private/etc
121      *
122      * @return Object
123      * @param ASTFieldDeclaration node
124      * @param Object data
125      */
126     public Object visit(ASTFieldDeclaration node, Object data) {
127         for (int x = 0; x < node.jjtGetNumChildren(); ++x) {
128             SimpleNode firstStmt = (SimpleNode) node.jjtGetChild(x);
129             if (firstStmt instanceof ASTType) {
130                 ASTType tp = (ASTType) firstStmt;
131                 SimpleNode nd = (SimpleNode) tp.jjtGetChild(0);
132                 this.checkVariableType(nd.getImage());
133             }
134         }
135 
136         return super.visit(node, data);
137     }
138 
139     /***
140      * convience method to handle hiearchy. This is probably too much
141      * work and will go away once I figure out the framework
142      *
143      */
144     private void handleASTTypeChildren(SimpleNode node) {
145         for (int x = 0; x < node.jjtGetNumChildren(); x++) {
146             SimpleNode sNode = (SimpleNode) node.jjtGetChild(x);
147             if (sNode instanceof ASTType) {
148                 SimpleNode nameNode = (SimpleNode) sNode.jjtGetChild(0);
149                 this.checkVariableType(nameNode.getImage());
150             }
151         }
152     }
153 
154     /***
155      * performs a check on the variable and updates the couter. Counter is
156      * instance for a class and is reset upon new class scan.
157      *
158      * @param String variableType
159      */
160     private void checkVariableType(String variableType) {
161         //if the field is of any type other than the class type
162         //increment the count
163         if (!this.className.equals(variableType) && (!this.filterTypes(variableType)) && !this.typesFoundSoFar.contains(variableType)) {
164             this.couplingCount++;
165             this.typesFoundSoFar.add(variableType);
166         }
167     }
168 
169     /***
170      * Filters variable type - we don't want primatives, wrappers, strings, etc.
171      * This needs more work. I'd like to filter out super types and perhaps interfaces
172      *
173      * @param String variableType
174      * @return boolean true if variableType is not what we care about
175      */
176     private boolean filterTypes(String variableType) {
177         return variableType.startsWith("java.lang.") || (variableType.equals("String")) || filterPrimativesAndWrappers(variableType);
178     }
179 
180     /***
181      * @param String variableType
182      * @return boolean true if variableType is a primative or wrapper
183      */
184     private boolean filterPrimativesAndWrappers(String variableType) {
185         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"));
186     }
187 }