001: /**
002: * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003: */package net.sourceforge.pmd.rules;
004:
005: import net.sourceforge.pmd.AbstractRule;
006: import net.sourceforge.pmd.PropertyDescriptor;
007: import net.sourceforge.pmd.ast.ASTClassOrInterfaceDeclaration;
008: import net.sourceforge.pmd.ast.ASTClassOrInterfaceType;
009: import net.sourceforge.pmd.ast.ASTCompilationUnit;
010: import net.sourceforge.pmd.ast.ASTFieldDeclaration;
011: import net.sourceforge.pmd.ast.ASTFormalParameter;
012: import net.sourceforge.pmd.ast.ASTLocalVariableDeclaration;
013: import net.sourceforge.pmd.ast.ASTReferenceType;
014: import net.sourceforge.pmd.ast.ASTResultType;
015: import net.sourceforge.pmd.ast.ASTType;
016: import net.sourceforge.pmd.ast.SimpleNode;
017: import net.sourceforge.pmd.properties.IntegerProperty;
018: import net.sourceforge.pmd.symboltable.ClassScope;
019:
020: import java.util.HashSet;
021: import java.util.Map;
022: import java.util.Set;
023:
024: /**
025: * CouplingBetweenObjects attempts to capture all unique Class attributes,
026: * local variables, and return types to determine how many objects a class is
027: * coupled to. This is only a guage and isn't a hard and fast rule. The threshold
028: * value is configurable and should be determined accordingly
029: *
030: * @author aglover
031: * @since Feb 20, 2003
032: */
033: public class CouplingBetweenObjects extends AbstractRule {
034:
035: private int couplingCount;
036: private Set<String> typesFoundSoFar;
037:
038: private static final PropertyDescriptor thresholdDescriptor = new IntegerProperty(
039: "threshold", "Coupling threshold value", 2, 1.0f);
040:
041: private static final Map<String, PropertyDescriptor> propertyDescriptorsByName = asFixedMap(thresholdDescriptor);
042:
043: public Object visit(ASTCompilationUnit cu, Object data) {
044: typesFoundSoFar = new HashSet<String>();
045: couplingCount = 0;
046:
047: Object returnObj = cu.childrenAccept(this , data);
048:
049: if (couplingCount > getIntProperty(thresholdDescriptor)) {
050: addViolation(
051: data,
052: cu,
053: "A value of "
054: + couplingCount
055: + " may denote a high amount of coupling within the class");
056: }
057:
058: return returnObj;
059: }
060:
061: public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
062: if (node.isInterface()) {
063: return data;
064: }
065: return super .visit(node, data);
066: }
067:
068: public Object visit(ASTResultType node, Object data) {
069: for (int x = 0; x < node.jjtGetNumChildren(); x++) {
070: SimpleNode tNode = (SimpleNode) node.jjtGetChild(x);
071: if (tNode instanceof ASTType) {
072: SimpleNode reftypeNode = (SimpleNode) tNode
073: .jjtGetChild(0);
074: if (reftypeNode instanceof ASTReferenceType) {
075: SimpleNode classOrIntType = (SimpleNode) reftypeNode
076: .jjtGetChild(0);
077: if (classOrIntType instanceof ASTClassOrInterfaceType) {
078: SimpleNode nameNode = classOrIntType;
079: this .checkVariableType(nameNode, nameNode
080: .getImage());
081: }
082: }
083: }
084: }
085: return super .visit(node, data);
086: }
087:
088: public Object visit(ASTLocalVariableDeclaration node, Object data) {
089: handleASTTypeChildren(node);
090: return super .visit(node, data);
091: }
092:
093: public Object visit(ASTFormalParameter node, Object data) {
094: handleASTTypeChildren(node);
095: return super .visit(node, data);
096: }
097:
098: public Object visit(ASTFieldDeclaration node, Object data) {
099: for (int x = 0; x < node.jjtGetNumChildren(); ++x) {
100: SimpleNode firstStmt = (SimpleNode) node.jjtGetChild(x);
101: if (firstStmt instanceof ASTType) {
102: ASTType tp = (ASTType) firstStmt;
103: SimpleNode nd = (SimpleNode) tp.jjtGetChild(0);
104: checkVariableType(nd, nd.getImage());
105: }
106: }
107:
108: return super .visit(node, data);
109: }
110:
111: /**
112: * convience method to handle hierarchy. This is probably too much
113: * work and will go away once I figure out the framework
114: */
115: private void handleASTTypeChildren(SimpleNode node) {
116: for (int x = 0; x < node.jjtGetNumChildren(); x++) {
117: SimpleNode sNode = (SimpleNode) node.jjtGetChild(x);
118: if (sNode instanceof ASTType) {
119: SimpleNode nameNode = (SimpleNode) sNode.jjtGetChild(0);
120: checkVariableType(nameNode, nameNode.getImage());
121: }
122: }
123: }
124:
125: /**
126: * performs a check on the variable and updates the counter. Counter is
127: * instance for a class and is reset upon new class scan.
128: *
129: * @param String variableType
130: */
131: private void checkVariableType(SimpleNode nameNode,
132: String variableType) {
133: // TODO - move this into the symbol table somehow?
134: if (nameNode.getParentsOfType(
135: 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 = nameNode.getScope()
141: .getEnclosingClassScope();
142: if (!clzScope.getClassName().equals(variableType)
143: && (!this .filterTypes(variableType))
144: && !this .typesFoundSoFar.contains(variableType)) {
145: couplingCount++;
146: typesFoundSoFar.add(variableType);
147: }
148: }
149:
150: /**
151: * Filters variable type - we don't want primatives, wrappers, strings, etc.
152: * This needs more work. I'd like to filter out super types and perhaps interfaces
153: *
154: * @param String variableType
155: * @return boolean true if variableType is not what we care about
156: */
157: private boolean filterTypes(String variableType) {
158: return variableType != null
159: && (variableType.startsWith("java.lang.")
160: || (variableType.equals("String")) || filterPrimitivesAndWrappers(variableType));
161: }
162:
163: /**
164: * @param String variableType
165: * @return boolean true if variableType is a primative or wrapper
166: */
167: private boolean filterPrimitivesAndWrappers(String variableType) {
168: return (variableType.equals("int")
169: || variableType.equals("Integer")
170: || variableType.equals("char")
171: || variableType.equals("Character")
172: || variableType.equalsIgnoreCase("double")
173: || variableType.equalsIgnoreCase("long")
174: || variableType.equalsIgnoreCase("short")
175: || variableType.equalsIgnoreCase("float")
176: || variableType.equalsIgnoreCase("byte") || variableType
177: .equalsIgnoreCase("boolean"));
178: }
179:
180: /**
181: * @return Map
182: */
183: protected Map<String, PropertyDescriptor> propertiesByName() {
184: return propertyDescriptorsByName;
185: }
186: }
|