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().getEnclosingScope(ClassScope.class);
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 }