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