/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.rules;
import net.sourceforge.pmd.AbstractRule;
import net.sourceforge.pmd.RuleContext;
import net.sourceforge.pmd.ast.ASTClassDeclaration;
import net.sourceforge.pmd.ast.ASTCompilationUnit;
import net.sourceforge.pmd.ast.ASTFieldDeclaration;
import net.sourceforge.pmd.ast.ASTFormalParameter;
import net.sourceforge.pmd.ast.ASTLocalVariableDeclaration;
import net.sourceforge.pmd.ast.ASTName;
import net.sourceforge.pmd.ast.ASTResultType;
import net.sourceforge.pmd.ast.ASTType;
import net.sourceforge.pmd.ast.SimpleNode;
import java.util.HashSet;
import java.util.Set;
/**
* CouplingBetweenObjectsRule attempts to capture all unique Class attributes,
* local variables, and return types to determine how many objects a class is
* coupled to. This is only a guage and isn't a hard and fast rule. The threshold
* value is configurable and should be determined accordingly
*
* @since Feb 20, 2003
* @author aglover
*
*/
public class CouplingBetweenObjectsRule extends AbstractRule {
private String className;
private int couplingCount;
private Set typesFoundSoFar;
/**
* handles the source file
*
* @return Object
* @param ASTCompilationUnit cu
* @param Object data
*/
public Object visit(ASTCompilationUnit cu, Object data) {
this.typesFoundSoFar = new HashSet();
this.couplingCount = 0;
Object returnObj = cu.childrenAccept(this, data);
if (this.couplingCount > getIntProperty("threshold")) {
RuleContext ctx = (RuleContext) data;
ctx.getReport().addRuleViolation(createRuleViolation(ctx, cu.getBeginLine(), "A value of " + this.couplingCount + " may denote a high amount of coupling within the class"));
}
return returnObj;
}
/**
* handles class declaration. I need this to capture class name. I think
* there is probably a better way to capture it; however, I don't know the
* framework well enough yet...
*
* @return Object
* @param ASTClassDeclaration node
* @param Object data
*/
public Object visit(ASTClassDeclaration node, Object data) {
SimpleNode firstStmt = (SimpleNode) node.jjtGetChild(0);
this.className = firstStmt.getImage();
return super.visit(node, data);
}
/**
* handles a return type of a method
*
* @return Object
* @param ASTResultType node
* @param Object data
*/
public Object visit(ASTResultType node, Object data) {
for (int x = 0; x < node.jjtGetNumChildren(); x++) {
SimpleNode tNode = (SimpleNode) node.jjtGetChild(x);
if (tNode instanceof ASTType) {
SimpleNode nameNode = (SimpleNode) tNode.jjtGetChild(0);
if (nameNode instanceof ASTName) {
this.checkVariableType(nameNode.getImage());
}
}
}
return super.visit(node, data);
}
/**
* handles a local variable found in a method block
*
* @return Object
* @param ASTLocalVariableDeclaration node
* @param Object data
*/
public Object visit(ASTLocalVariableDeclaration node, Object data) {
this.handleASTTypeChildren(node);
return super.visit(node, data);
}
/**
* handles a method parameter
*
* @return Object
* @param ASTFormalParameter node
* @param Object data
*/
public Object visit(ASTFormalParameter node, Object data) {
this.handleASTTypeChildren(node);
return super.visit(node, data);
}
/**
* handles a field declaration - i.e. an instance variable. Method doesn't care if variable
* is public/private/etc
*
* @return Object
* @param ASTFieldDeclaration node
* @param Object data
*/
public Object visit(ASTFieldDeclaration node, Object data) {
for (int x = 0; x < node.jjtGetNumChildren(); ++x) {
SimpleNode firstStmt = (SimpleNode) node.jjtGetChild(x);
if (firstStmt instanceof ASTType) {
ASTType tp = (ASTType) firstStmt;
SimpleNode nd = (SimpleNode) tp.jjtGetChild(0);
this.checkVariableType(nd.getImage());
}
}
return super.visit(node, data);
}
/**
* convience method to handle hiearchy. This is probably too much
* work and will go away once I figure out the framework
*
*/
private void handleASTTypeChildren(SimpleNode node) {
for (int x = 0; x < node.jjtGetNumChildren(); x++) {
SimpleNode sNode = (SimpleNode) node.jjtGetChild(x);
if (sNode instanceof ASTType) {
SimpleNode nameNode = (SimpleNode) sNode.jjtGetChild(0);
this.checkVariableType(nameNode.getImage());
}
}
}
/**
* performs a check on the variable and updates the couter. Counter is
* instance for a class and is reset upon new class scan.
*
* @param String variableType
*/
private void checkVariableType(String variableType) {
//if the field is of any type other than the class type
//increment the count
if (!this.className.equals(variableType) && (!this.filterTypes(variableType)) && !this.typesFoundSoFar.contains(variableType)) {
this.couplingCount++;
this.typesFoundSoFar.add(variableType);
}
}
/**
* Filters variable type - we don't want primatives, wrappers, strings, etc.
* This needs more work. I'd like to filter out super types and perhaps interfaces
*
* @param String variableType
* @return boolean true if variableType is not what we care about
*/
private boolean filterTypes(String variableType) {
return variableType.startsWith("java.lang.") || (variableType.equals("String")) || filterPrimativesAndWrappers(variableType);
}
/**
* @param String variableType
* @return boolean true if variableType is a primative or wrapper
*/
private boolean filterPrimativesAndWrappers(String variableType) {
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"));
}
}