001: package org.acm.seguin.pmd.rules;
002:
003: import org.acm.seguin.pmd.AbstractRule;
004: import org.acm.seguin.pmd.RuleContext;
005: import net.sourceforge.jrefactory.ast.ASTClassDeclaration;
006: import net.sourceforge.jrefactory.ast.ASTCompilationUnit;
007: import net.sourceforge.jrefactory.ast.ASTFieldDeclaration;
008: import net.sourceforge.jrefactory.ast.ASTFormalParameter;
009: import net.sourceforge.jrefactory.ast.ASTLocalVariableDeclaration;
010: import net.sourceforge.jrefactory.ast.ASTReferenceType;
011: import net.sourceforge.jrefactory.ast.ASTResultType;
012: import net.sourceforge.jrefactory.ast.ASTType;
013: import net.sourceforge.jrefactory.ast.SimpleNode;
014:
015: import java.util.HashSet;
016: import java.util.Set;
017:
018: /**
019: * CouplingBetweenObjectsRule attempts to capture all unique Class attributes, local variables, and return types to
020: * determine how many objects a class is coupled to. This is only a guage and isn't a hard and fast rule. The threshold
021: * value is configurable and should be determined accordingly
022: *
023: *@author aglover
024: *@since Feb 20, 2003
025: */
026: public class CouplingBetweenObjectsRule extends AbstractRule {
027:
028: private String className;
029: private int couplingCount;
030: private Set typesFoundSoFar;
031: private boolean withinClass = false;
032:
033: /**
034: * handles the source file
035: *
036: *@param cu Description of Parameter
037: *@param data Description of Parameter
038: *@return Object
039: */
040: public Object visit(ASTCompilationUnit cu, Object data) {
041: this .typesFoundSoFar = new HashSet();
042: this .couplingCount = 0;
043:
044: Object returnObj = cu.childrenAccept(this , data);
045:
046: if (this .couplingCount > getIntProperty("threshold")) {
047: RuleContext ctx = (RuleContext) data;
048: ctx
049: .getReport()
050: .addRuleViolation(
051: createRuleViolation(
052: ctx,
053: cu.getBeginLine(),
054: "A value of "
055: + this .couplingCount
056: + " may denote a high amount of coupling within the class"));
057: }
058:
059: return returnObj;
060: }
061:
062: /**
063: * handles class declaration. I need this to capture class name. I think there is probably a better way to capture
064: * it; however, I don't know the framework well enough yet...
065: *
066: *@param node Description of Parameter
067: *@param data Description of Parameter
068: *@return Object
069: */
070: public Object visit(ASTClassDeclaration node, Object data) {
071: boolean oldWithinClass = withinClass;
072: withinClass = true;
073: SimpleNode firstStmt = (SimpleNode) node.jjtGetFirstChild();
074: this .className = firstStmt.getImage();
075: Object d = super .visit(node, data);
076: withinClass = oldWithinClass;
077: return d;
078: }
079:
080: /**
081: * handles a return type of a method
082: *
083: *@param node Description of Parameter
084: *@param data Description of Parameter
085: *@return Object
086: */
087: public Object visit(ASTResultType node, Object data) {
088: if (withinClass) {
089: for (int x = 0; x < node.jjtGetNumChildren(); x++) {
090: SimpleNode tNode = (SimpleNode) node.jjtGetChild(x);
091: if (tNode instanceof ASTType) {
092: SimpleNode nameNode = (SimpleNode) tNode
093: .jjtGetFirstChild();
094: if (nameNode instanceof ASTReferenceType) {
095: nameNode = (SimpleNode) nameNode
096: .jjtGetFirstChild();
097: }
098: this .checkVariableType(nameNode.getImage());
099: }
100: }
101: }
102: return super .visit(node, data);
103: }
104:
105: /**
106: * handles a local variable found in a method block
107: *
108: *@param node Description of Parameter
109: *@param data Description of Parameter
110: *@return Object
111: */
112: public Object visit(ASTLocalVariableDeclaration node, Object data) {
113: if (withinClass) {
114: this .handleASTTypeChildren(node);
115: }
116: return super .visit(node, data);
117: }
118:
119: /**
120: * handles a method parameter
121: *
122: *@param node Description of Parameter
123: *@param data Description of Parameter
124: *@return Object
125: */
126: public Object visit(ASTFormalParameter node, Object data) {
127: if (withinClass) {
128: this .handleASTTypeChildren(node);
129: }
130: return super .visit(node, data);
131: }
132:
133: /**
134: * handles a field declaration - i.e. an instance variable. Method doesn't care if variable is public/private/etc
135: *
136: *@param node Description of Parameter
137: *@param data Description of Parameter
138: *@return Object
139: */
140: public Object visit(ASTFieldDeclaration node, Object data) {
141: if (withinClass) {
142: for (int x = 0; x < node.jjtGetNumChildren(); ++x) {
143: SimpleNode firstStmt = (SimpleNode) node.jjtGetChild(x);
144: if (firstStmt instanceof ASTType) {
145: ASTType tp = (ASTType) firstStmt;
146: SimpleNode nd = (SimpleNode) tp.jjtGetFirstChild();
147: if (nd instanceof ASTReferenceType) {
148: nd = (SimpleNode) nd.jjtGetFirstChild();
149: }
150: this .checkVariableType(nd.getImage());
151: }
152: }
153: }
154: return super .visit(node, data);
155: }
156:
157: /**
158: * convience method to handle hiearchy. This is probably too much work and will go away once I figure out the
159: * framework
160: *
161: *@param node Description of Parameter
162: */
163: private void handleASTTypeChildren(SimpleNode node) {
164: for (int x = 0; x < node.jjtGetNumChildren(); x++) {
165: SimpleNode sNode = (SimpleNode) node.jjtGetChild(x);
166: if (sNode instanceof ASTType) {
167: SimpleNode nameNode = (SimpleNode) sNode
168: .jjtGetFirstChild();
169: if (nameNode instanceof ASTReferenceType) {
170: nameNode = (SimpleNode) nameNode.jjtGetFirstChild();
171: }
172: this .checkVariableType(nameNode.getImage());
173: }
174: }
175: }
176:
177: /**
178: * performs a check on the variable and updates the couter. Counter is instance for a class and is reset upon new
179: * class scan.
180: *
181: *@param variableType Description of Parameter
182: */
183: private void checkVariableType(String variableType) {
184: //if the field is of any type other than the class type
185: //increment the count
186: if (!this .className.equals(variableType)
187: && (!this .filterTypes(variableType))
188: && !this .typesFoundSoFar.contains(variableType)) {
189: this .couplingCount++;
190: this .typesFoundSoFar.add(variableType);
191: }
192: }
193:
194: /**
195: * Filters variable type - we don't want primatives, wrappers, strings, etc. This needs more work. I'd like to
196: * filter out super types and perhaps interfaces
197: *
198: *@param variableType Description of Parameter
199: *@return boolean true if variableType is not what we care about
200: */
201: private boolean filterTypes(String variableType) {
202: return variableType.startsWith("java.lang.")
203: || (variableType.equals("String"))
204: || filterPrimativesAndWrappers(variableType);
205: }
206:
207: /**
208: *@param variableType Description of Parameter
209: *@return boolean true if variableType is a primative or wrapper
210: */
211: private boolean filterPrimativesAndWrappers(String variableType) {
212: return (variableType.equals("int")
213: || variableType.equals("Integer")
214: || variableType.equals("char")
215: || variableType.equals("Character")
216: || variableType.equalsIgnoreCase("double")
217: || variableType.equalsIgnoreCase("long")
218: || variableType.equalsIgnoreCase("short")
219: || variableType.equalsIgnoreCase("float")
220: || variableType.equalsIgnoreCase("byte") || variableType
221: .equalsIgnoreCase("boolean"));
222: }
223: }
|