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.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 RuleContext ctx = (RuleContext) data;
45 ctx.getReport().addRuleViolation(createRuleViolation(ctx, cu, "A value of " + this.couplingCount + " may denote a high amount of coupling within the class"));
46 }
47
48 return returnObj;
49 }
50
51 public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
52 if (node.isInterface()) {
53 return data;
54 }
55 return super.visit(node, data);
56 }
57
58 public Object visit(ASTResultType node, Object data) {
59 for (int x = 0; x < node.jjtGetNumChildren(); x++) {
60 SimpleNode tNode = (SimpleNode) node.jjtGetChild(x);
61 if (tNode instanceof ASTType) {
62 SimpleNode reftypeNode = (SimpleNode) tNode.jjtGetChild(0);
63 if (reftypeNode instanceof ASTReferenceType) {
64 SimpleNode classOrIntType = (SimpleNode)reftypeNode.jjtGetChild(0);
65 if (classOrIntType instanceof ASTClassOrInterfaceType) {
66 SimpleNode nameNode = (ASTClassOrInterfaceType)classOrIntType;
67 this.checkVariableType(nameNode, nameNode.getImage());
68 }
69 }
70 }
71 }
72 return super.visit(node, data);
73 }
74
75 public Object visit(ASTLocalVariableDeclaration node, Object data) {
76 this.handleASTTypeChildren(node);
77 return super.visit(node, data);
78 }
79
80 public Object visit(ASTFormalParameter node, Object data) {
81 this.handleASTTypeChildren(node);
82 return super.visit(node, data);
83 }
84
85 public Object visit(ASTFieldDeclaration node, Object data) {
86 for (int x = 0; x < node.jjtGetNumChildren(); ++x) {
87 SimpleNode firstStmt = (SimpleNode) node.jjtGetChild(x);
88 if (firstStmt instanceof ASTType) {
89 ASTType tp = (ASTType) firstStmt;
90 SimpleNode nd = (SimpleNode) tp.jjtGetChild(0);
91 this.checkVariableType(nd, nd.getImage());
92 }
93 }
94
95 return super.visit(node, data);
96 }
97
98 /***
99 * convience method to handle hierarchy. This is probably too much
100 * work and will go away once I figure out the framework
101 */
102 private void handleASTTypeChildren(SimpleNode node) {
103 for (int x = 0; x < node.jjtGetNumChildren(); x++) {
104 SimpleNode sNode = (SimpleNode) node.jjtGetChild(x);
105 if (sNode instanceof ASTType) {
106 SimpleNode nameNode = (SimpleNode) sNode.jjtGetChild(0);
107 this.checkVariableType(nameNode, nameNode.getImage());
108 }
109 }
110 }
111
112 /***
113 * performs a check on the variable and updates the couter. Counter is
114 * instance for a class and is reset upon new class scan.
115 *
116 * @param String variableType
117 */
118 private void checkVariableType(SimpleNode nameNode, String variableType) {
119
120
121 if (!nameNode.getScope().getEnclosingClassScope().getClassName().equals(variableType) && (!this.filterTypes(variableType)) && !this.typesFoundSoFar.contains(variableType)) {
122 this.couplingCount++;
123 this.typesFoundSoFar.add(variableType);
124 }
125 }
126
127 /***
128 * Filters variable type - we don't want primatives, wrappers, strings, etc.
129 * This needs more work. I'd like to filter out super types and perhaps interfaces
130 *
131 * @param String variableType
132 * @return boolean true if variableType is not what we care about
133 */
134 private boolean filterTypes(String variableType) {
135 return variableType != null && (variableType.startsWith("java.lang.") || (variableType.equals("String")) || filterPrimitivesAndWrappers(variableType));
136 }
137
138 /***
139 * @param String variableType
140 * @return boolean true if variableType is a primative or wrapper
141 */
142 private boolean filterPrimitivesAndWrappers(String variableType) {
143 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"));
144 }
145 }