/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.rule.coupling;
import java.util.HashSet;
import java.util.Set;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter;
import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTReferenceType;
import net.sourceforge.pmd.lang.java.ast.ASTResultType;
import net.sourceforge.pmd.lang.java.ast.ASTType;
import net.sourceforge.pmd.lang.java.ast.JavaNode;
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
import net.sourceforge.pmd.lang.java.symboltable.ClassScope;
import net.sourceforge.pmd.lang.rule.properties.IntegerProperty;
/**
* CouplingBetweenObjects 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 gauge and isn't a hard and fast rule. The threshold value
* is configurable and should be determined accordingly
*
* @author aglover
* @since Feb 20, 2003
*/
public class CouplingBetweenObjectsRule extends AbstractJavaRule {
private int couplingCount;
private Set<String> typesFoundSoFar;
private static final IntegerProperty THRESHOLD_DESCRIPTOR = new IntegerProperty("threshold",
"Unique type reporting threshold", 2, 100, 20, 1.0f);
public CouplingBetweenObjectsRule() {
definePropertyDescriptor(THRESHOLD_DESCRIPTOR);
}
@Override
public Object visit(ASTCompilationUnit cu, Object data) {
typesFoundSoFar = new HashSet<>();
couplingCount = 0;
Object returnObj = cu.childrenAccept(this, data);
if (couplingCount > getProperty(THRESHOLD_DESCRIPTOR)) {
addViolation(data, cu,
"A value of " + couplingCount + " may denote a high amount of coupling within the class");
}
return returnObj;
}
@Override
public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
if (node.isInterface()) {
return data;
}
return super.visit(node, data);
}
@Override
public Object visit(ASTResultType node, Object data) {
for (int x = 0; x < node.jjtGetNumChildren(); x++) {
Node tNode = node.jjtGetChild(x);
if (tNode instanceof ASTType) {
Node reftypeNode = tNode.jjtGetChild(0);
if (reftypeNode instanceof ASTReferenceType) {
Node classOrIntType = reftypeNode.jjtGetChild(0);
if (classOrIntType instanceof ASTClassOrInterfaceType) {
Node nameNode = classOrIntType;
this.checkVariableType(nameNode, nameNode.getImage());
}
}
}
}
return super.visit(node, data);
}
@Override
public Object visit(ASTLocalVariableDeclaration node, Object data) {
handleASTTypeChildren(node);
return super.visit(node, data);
}
@Override
public Object visit(ASTFormalParameter node, Object data) {
handleASTTypeChildren(node);
return super.visit(node, data);
}
@Override
public Object visit(ASTFieldDeclaration node, Object data) {
for (int x = 0; x < node.jjtGetNumChildren(); ++x) {
Node firstStmt = node.jjtGetChild(x);
if (firstStmt instanceof ASTType) {
ASTType tp = (ASTType) firstStmt;
Node nd = tp.jjtGetChild(0);
checkVariableType(nd, nd.getImage());
}
}
return super.visit(node, data);
}
/**
* convience method to handle hierarchy. This is probably too much work and
* will go away once I figure out the framework
*/
private void handleASTTypeChildren(Node node) {
for (int x = 0; x < node.jjtGetNumChildren(); x++) {
Node sNode = node.jjtGetChild(x);
if (sNode instanceof ASTType) {
Node nameNode = sNode.jjtGetChild(0);
checkVariableType(nameNode, nameNode.getImage());
}
}
}
/**
* performs a check on the variable and updates the counter. Counter is
* instance for a class and is reset upon new class scan.
*
* @param variableType
* The variable type.
*/
private void checkVariableType(Node nameNode, String variableType) {
// TODO - move this into the symbol table somehow?
if (nameNode.getParentsOfType(ASTClassOrInterfaceDeclaration.class).isEmpty()) {
return;
}
// if the field is of any type other than the class type
// increment the count
ClassScope clzScope = ((JavaNode) nameNode).getScope().getEnclosingScope(ClassScope.class);
if (!clzScope.getClassName().equals(variableType) && !this.filterTypes(variableType)
&& !this.typesFoundSoFar.contains(variableType)) {
couplingCount++;
typesFoundSoFar.add(variableType);
}
}
/**
* Filters variable type - we don't want primitives, wrappers, strings, etc.
* This needs more work. I'd like to filter out super types and perhaps
* interfaces
*
* @param variableType
* The variable type.
* @return boolean true if variableType is not what we care about
*/
private boolean filterTypes(String variableType) {
return variableType != null && (variableType.startsWith("java.lang.") || "String".equals(variableType)
|| filterPrimitivesAndWrappers(variableType));
}
/**
* @param variableType
* The variable type.
* @return boolean true if variableType is a primitive or wrapper
*/
private boolean filterPrimitivesAndWrappers(String variableType) {
return "int".equals(variableType) || "Integer".equals(variableType) || "char".equals(variableType)
|| "Character".equals(variableType) || "double".equals(variableType) || "long".equals(variableType)
|| "short".equals(variableType) || "float".equals(variableType) || "byte".equals(variableType)
|| "boolean".equals(variableType);
}
}