001 /**
002 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003 */
004 package net.sourceforge.pmd.lang.java.rule.coupling;
005
006 import java.util.HashSet;
007 import java.util.Set;
008
009 import net.sourceforge.pmd.lang.ast.Node;
010 import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
011 import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
012 import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
013 import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
014 import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter;
015 import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration;
016 import net.sourceforge.pmd.lang.java.ast.ASTReferenceType;
017 import net.sourceforge.pmd.lang.java.ast.ASTResultType;
018 import net.sourceforge.pmd.lang.java.ast.ASTType;
019 import net.sourceforge.pmd.lang.java.ast.JavaNode;
020 import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
021 import net.sourceforge.pmd.lang.java.symboltable.ClassScope;
022 import net.sourceforge.pmd.lang.rule.properties.IntegerProperty;
023
024
025 /**
026 * CouplingBetweenObjects attempts to capture all unique Class attributes,
027 * local variables, and return types to determine how many objects a class is
028 * coupled to. This is only a gauge and isn't a hard and fast rule. The threshold
029 * value is configurable and should be determined accordingly
030 *
031 * @author aglover
032 * @since Feb 20, 2003
033 */
034 public class CouplingBetweenObjectsRule extends AbstractJavaRule {
035
036 private int couplingCount;
037 private Set<String> typesFoundSoFar;
038
039 private static final IntegerProperty THRESHOLD_DESCRIPTOR = new IntegerProperty(
040 "threshold", "Unique type reporting threshold", 2, 100, 20, 1.0f
041 );
042
043 public CouplingBetweenObjectsRule() {
044 definePropertyDescriptor(THRESHOLD_DESCRIPTOR);
045 }
046
047 @Override
048 public Object visit(ASTCompilationUnit cu, Object data) {
049 typesFoundSoFar = new HashSet<String>();
050 couplingCount = 0;
051
052 Object returnObj = cu.childrenAccept(this, data);
053
054 if (couplingCount > getProperty(THRESHOLD_DESCRIPTOR)) {
055 addViolation(data, cu, "A value of " + couplingCount + " may denote a high amount of coupling within the class");
056 }
057
058 return returnObj;
059 }
060
061 @Override
062 public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
063 if (node.isInterface()) {
064 return data;
065 }
066 return super.visit(node, data);
067 }
068
069 @Override
070 public Object visit(ASTResultType node, Object data) {
071 for (int x = 0; x < node.jjtGetNumChildren(); x++) {
072 Node tNode = node.jjtGetChild(x);
073 if (tNode instanceof ASTType) {
074 Node reftypeNode = tNode.jjtGetChild(0);
075 if (reftypeNode instanceof ASTReferenceType) {
076 Node classOrIntType = reftypeNode.jjtGetChild(0);
077 if (classOrIntType instanceof ASTClassOrInterfaceType) {
078 Node nameNode = classOrIntType;
079 this.checkVariableType(nameNode, nameNode.getImage());
080 }
081 }
082 }
083 }
084 return super.visit(node, data);
085 }
086
087 @Override
088 public Object visit(ASTLocalVariableDeclaration node, Object data) {
089 handleASTTypeChildren(node);
090 return super.visit(node, data);
091 }
092
093 @Override
094 public Object visit(ASTFormalParameter node, Object data) {
095 handleASTTypeChildren(node);
096 return super.visit(node, data);
097 }
098
099 @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().getEnclosingClassScope();
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 }
|