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
119 if (nameNode.getParentsOfType(ASTClassOrInterfaceDeclaration.class).isEmpty()) {
120 return;
121 }
122
123
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 }