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.ASTArguments;
006: import net.sourceforge.jrefactory.ast.ASTClassBody;
007: import net.sourceforge.jrefactory.ast.ASTCompilationUnit;
008: import net.sourceforge.jrefactory.ast.ASTInterfaceDeclaration;
009: import net.sourceforge.jrefactory.ast.ASTMethodDeclarator;
010: import net.sourceforge.jrefactory.ast.ASTName;
011: import net.sourceforge.jrefactory.ast.ASTPrimaryExpression;
012: import net.sourceforge.jrefactory.ast.ASTPrimaryPrefix;
013: import net.sourceforge.jrefactory.ast.ASTPrimarySuffix;
014: import net.sourceforge.jrefactory.ast.AccessNode;
015: import net.sourceforge.jrefactory.ast.SimpleNode;
016:
017: import java.text.MessageFormat;
018: import java.util.HashSet;
019: import java.util.Iterator;
020: import java.util.Set;
021:
022: public class UnusedPrivateMethodRule extends AbstractRule {
023:
024: private Set privateMethodNodes = new HashSet();
025:
026: // TODO - What I need is a Visitor that does a breadth first search
027: private boolean trollingForDeclarations;
028: private int depth;
029:
030: // Skip interfaces because they have no implementation
031: public Object visit(ASTInterfaceDeclaration node, Object data) {
032: return data;
033: }
034:
035: // Reset state when we leave an ASTCompilationUnit
036: public Object visit(ASTCompilationUnit node, Object data) {
037: depth = 0;
038: super .visit(node, data);
039: privateMethodNodes.clear();
040: depth = 0;
041: trollingForDeclarations = false;
042: return data;
043: }
044:
045: public Object visit(ASTClassBody node, Object data) {
046: depth++;
047:
048: // first troll for declarations, but only in the top level class
049: if (depth == 1) {
050: trollingForDeclarations = true;
051: super .visit(node, null);
052: trollingForDeclarations = false;
053: } else {
054: trollingForDeclarations = false;
055: }
056:
057: // troll for usages, regardless of depth
058: super .visit(node, null);
059:
060: // if we're back at the top level class, harvest
061: if (depth == 1) {
062: RuleContext ctx = (RuleContext) data;
063: harvestUnused(ctx);
064: }
065:
066: depth--;
067: return data;
068: }
069:
070: //ASTMethodDeclarator
071: // FormalParameters
072: // FormalParameter
073: // FormalParameter
074: public Object visit(ASTMethodDeclarator node, Object data) {
075: if (!trollingForDeclarations) {
076: return super .visit(node, data);
077: }
078:
079: AccessNode parent = (AccessNode) node.jjtGetParent();
080: if (!parent.isPrivate()) {
081: return super .visit(node, data);
082: }
083: // exclude these serializable things
084: if (node.getImage().equals("readObject")
085: || node.getImage().equals("writeObject")
086: || node.getImage().equals("readResolve")) {
087: return super .visit(node, data);
088: }
089: privateMethodNodes.add(node);
090: return super .visit(node, data);
091: }
092:
093: //PrimarySuffix
094: // Arguments
095: // ArgumentList
096: // Expression
097: // Expression
098: public Object visit(ASTPrimarySuffix node, Object data) {
099: if (!trollingForDeclarations
100: && (node.jjtGetParent() instanceof ASTPrimaryExpression)
101: && !(node.getImage().equals(""))) { // MRA
102: if (node.jjtGetNumChildren() > 0) {
103: ASTArguments args = (ASTArguments) node
104: .jjtGetFirstChild();
105: removeIfUsed(node.getImage(), args.getArgumentCount());
106: return super .visit(node, data);
107: }
108: // to handle this.foo()
109: //PrimaryExpression
110: // PrimaryPrefix
111: // PrimarySuffix <-- this node has "foo"
112: // PrimarySuffix <-- this node has null
113: // Arguments
114: ASTPrimaryExpression parent = (ASTPrimaryExpression) node
115: .jjtGetParent();
116: int pointer = 0;
117: while (true) {
118: if (parent.jjtGetChild(pointer).equals(node)) {
119: break;
120: }
121: pointer++;
122: }
123: // now move to the next PrimarySuffix and get the number of arguments
124: pointer++;
125: // this.foo = foo;
126: // yields this:
127: // PrimaryExpression
128: // PrimaryPrefix
129: // PrimarySuffix
130: // so we check for that
131: if (parent.jjtGetNumChildren() <= pointer) {
132: return super .visit(node, data);
133: }
134: if (!(parent.jjtGetChild(pointer) instanceof ASTPrimarySuffix)) {
135: return super .visit(node, data);
136: }
137: ASTPrimarySuffix actualMethodNode = (ASTPrimarySuffix) parent
138: .jjtGetChild(pointer);
139: // when does this happen?
140: if (actualMethodNode.jjtGetNumChildren() == 0
141: || !(actualMethodNode.jjtGetFirstChild() instanceof ASTArguments)) {
142: return super .visit(node, data);
143: }
144: ASTArguments args = (ASTArguments) actualMethodNode
145: .jjtGetFirstChild();
146: removeIfUsed(node.getImage(), args.getArgumentCount());
147: // what about Outer.this.foo()?
148: }
149: return super .visit(node, data);
150: }
151:
152: //PrimaryExpression
153: // PrimaryPrefix
154: // Name
155: // PrimarySuffix
156: // Arguments
157: public Object visit(ASTName node, Object data) {
158: if (!trollingForDeclarations
159: && (node.jjtGetParent() instanceof ASTPrimaryPrefix)) {
160: ASTPrimaryExpression primaryExpression = (ASTPrimaryExpression) node
161: .jjtGetParent().jjtGetParent();
162: if (primaryExpression.jjtGetNumChildren() > 1) {
163: ASTPrimarySuffix primarySuffix = (ASTPrimarySuffix) primaryExpression
164: .jjtGetChild(1);
165: if (primarySuffix.jjtGetNumChildren() > 0
166: && (primarySuffix.jjtGetFirstChild() instanceof ASTArguments)) {
167: ASTArguments arguments = (ASTArguments) primarySuffix
168: .jjtGetFirstChild();
169: removeIfUsed(node.getImage(), arguments
170: .getArgumentCount());
171: }
172: }
173: }
174: return super .visit(node, data);
175: }
176:
177: private void removeIfUsed(String nodeImage, int args) {
178: String img = (nodeImage.indexOf('.') == -1) ? nodeImage
179: : nodeImage.substring(nodeImage.indexOf('.') + 1,
180: nodeImage.length());
181: for (Iterator i = privateMethodNodes.iterator(); i.hasNext();) {
182: ASTMethodDeclarator methodNode = (ASTMethodDeclarator) i
183: .next();
184: // are name and number of parameters the same?
185: if (methodNode.getImage().equals(img)
186: && methodNode.getParameterCount() == args) {
187: // should check parameter types here, this misses some unused methods
188: i.remove();
189: }
190: }
191: }
192:
193: private void harvestUnused(RuleContext ctx) {
194: for (Iterator i = privateMethodNodes.iterator(); i.hasNext();) {
195: SimpleNode node = (SimpleNode) i.next();
196: ctx.getReport().addRuleViolation(
197: createRuleViolation(ctx, node.getBeginLine(),
198: MessageFormat.format(getMessage(),
199: new Object[] { node.getImage() })));
200: }
201: }
202:
203: /*
204: TODO this uses the symbol table
205: public Object visit(ASTUnmodifiedClassDeclaration node, Object data) {
206: for (Iterator i = node.getScope().getUnusedMethodDeclarations();i.hasNext();) {
207: VariableNameDeclaration decl = (VariableNameDeclaration)i.next();
208:
209: // exclude non-private methods and serializable methods
210: if (!decl.getAccessNodeParent().isPrivate() || decl.getImage().equals("readObject") || decl.getImage().equals("writeObject")|| decl.getImage().equals("readResolve")) {
211: continue;
212: }
213:
214: RuleContext ctx = (RuleContext)data;
215: ctx.getReport().addRuleViolation(createRuleViolation(ctx, decl.getNode().getBeginLine(), MessageFormat.format(getMessage(), new Object[] {decl.getNode().getImage()})));
216: }
217: return super.visit(node, data);
218: }
219:
220: */
221: }
|