CouplingBetweenObjectsRule.java
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"2100201.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 = (ASTTypefirstStmt;
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 }