/**
* 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.ASTArguments;
import net.sourceforge.pmd.ast.ASTClassBody;
import net.sourceforge.pmd.ast.ASTCompilationUnit;
import net.sourceforge.pmd.ast.ASTInterfaceDeclaration;
import net.sourceforge.pmd.ast.ASTMethodDeclarator;
import net.sourceforge.pmd.ast.ASTName;
import net.sourceforge.pmd.ast.ASTPrimaryExpression;
import net.sourceforge.pmd.ast.ASTPrimaryPrefix;
import net.sourceforge.pmd.ast.ASTPrimarySuffix;
import net.sourceforge.pmd.ast.AccessNode;
import net.sourceforge.pmd.ast.SimpleNode;
import java.text.MessageFormat;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class UnusedPrivateMethodRule extends AbstractRule {
private Set privateMethodNodes = new HashSet();
// TODO - What I need is a Visitor that does a breadth first search
private boolean trollingForDeclarations;
private int depth;
// Skip interfaces because they have no implementation
public Object visit(ASTInterfaceDeclaration node, Object data) {
return data;
}
// Reset state when we leave an ASTCompilationUnit
public Object visit(ASTCompilationUnit node, Object data) {
depth = 0;
super.visit(node, data);
privateMethodNodes.clear();
depth = 0;
trollingForDeclarations = false;
return data;
}
public Object visit(ASTClassBody node, Object data) {
depth++;
// first troll for declarations, but only in the top level class
if (depth == 1) {
trollingForDeclarations = true;
super.visit(node, null);
trollingForDeclarations = false;
} else {
trollingForDeclarations = false;
}
// troll for usages, regardless of depth
super.visit(node, null);
// if we're back at the top level class, harvest
if (depth == 1) {
RuleContext ctx = (RuleContext) data;
harvestUnused(ctx);
}
depth--;
return data;
}
//ASTMethodDeclarator
// FormalParameters
// FormalParameter
// FormalParameter
public Object visit(ASTMethodDeclarator node, Object data) {
if (!trollingForDeclarations) {
return super.visit(node, data);
}
AccessNode parent = (AccessNode) node.jjtGetParent();
if (!parent.isPrivate()) {
return super.visit(node, data);
}
// exclude these serializable things
if (node.getImage().equals("readObject") || node.getImage().equals("writeObject") || node.getImage().equals("readResolve") || node.getImage().equals("writeReplace")) {
return super.visit(node, data);
}
privateMethodNodes.add(node);
return super.visit(node, data);
}
//PrimarySuffix
// Arguments
// ArgumentList
// Expression
// Expression
public Object visit(ASTPrimarySuffix node, Object data) {
if (!trollingForDeclarations && (node.jjtGetParent() instanceof ASTPrimaryExpression) && (node.getImage() != null)) {
if (node.jjtGetNumChildren() > 0) {
ASTArguments args = (ASTArguments) node.jjtGetChild(0);
removeIfUsed(node.getImage(), args.getArgumentCount());
return super.visit(node, data);
}
// to handle this.foo()
//PrimaryExpression
// PrimaryPrefix
// PrimarySuffix <-- this node has "foo"
// PrimarySuffix <-- this node has null
// Arguments
ASTPrimaryExpression parent = (ASTPrimaryExpression) node.jjtGetParent();
int pointer = 0;
while (true) {
if (parent.jjtGetChild(pointer).equals(node)) {
break;
}
pointer++;
}
// now move to the next PrimarySuffix and get the number of arguments
pointer++;
// this.foo = foo;
// yields this:
// PrimaryExpression
// PrimaryPrefix
// PrimarySuffix
// so we check for that
if (parent.jjtGetNumChildren() <= pointer) {
return super.visit(node, data);
}
if (!(parent.jjtGetChild(pointer) instanceof ASTPrimarySuffix)) {
return super.visit(node, data);
}
ASTPrimarySuffix actualMethodNode = (ASTPrimarySuffix) parent.jjtGetChild(pointer);
// when does this happen?
if (actualMethodNode.jjtGetNumChildren() == 0 || !(actualMethodNode.jjtGetChild(0) instanceof ASTArguments)) {
return super.visit(node, data);
}
ASTArguments args = (ASTArguments) actualMethodNode.jjtGetChild(0);
removeIfUsed(node.getImage(), args.getArgumentCount());
// what about Outer.this.foo()?
}
return super.visit(node, data);
}
//PrimaryExpression
// PrimaryPrefix
// Name
// PrimarySuffix
// Arguments
public Object visit(ASTName node, Object data) {
if (!trollingForDeclarations && (node.jjtGetParent() instanceof ASTPrimaryPrefix)) {
ASTPrimaryExpression primaryExpression = (ASTPrimaryExpression) node.jjtGetParent().jjtGetParent();
if (primaryExpression.jjtGetNumChildren() > 1) {
ASTPrimarySuffix primarySuffix = (ASTPrimarySuffix) primaryExpression.jjtGetChild(1);
if (primarySuffix.jjtGetNumChildren() > 0 && (primarySuffix.jjtGetChild(0) instanceof ASTArguments)) {
ASTArguments arguments = (ASTArguments) primarySuffix.jjtGetChild(0);
removeIfUsed(node.getImage(), arguments.getArgumentCount());
}
}
}
return super.visit(node, data);
}
private void removeIfUsed(String nodeImage, int args) {
String img = (nodeImage.indexOf('.') == -1) ? nodeImage : nodeImage.substring(nodeImage.indexOf('.') + 1, nodeImage.length());
for (Iterator i = privateMethodNodes.iterator(); i.hasNext();) {
ASTMethodDeclarator methodNode = (ASTMethodDeclarator) i.next();
// are name and number of parameters the same?
if (methodNode.getImage().equals(img) && methodNode.getParameterCount() == args) {
// should check parameter types here, this misses some unused methods
i.remove();
}
}
}
private void harvestUnused(RuleContext ctx) {
for (Iterator i = privateMethodNodes.iterator(); i.hasNext();) {
SimpleNode node = (SimpleNode) i.next();
ctx.getReport().addRuleViolation(createRuleViolation(ctx, node.getBeginLine(), MessageFormat.format(getMessage(), new Object[]{node.getImage()})));
}
}
/*
TODO this uses the symbol table
public Object visit(ASTUnmodifiedClassDeclaration node, Object data) {
for (Iterator i = node.getScope().getUnusedMethodDeclarations();i.hasNext();) {
VariableNameDeclaration decl = (VariableNameDeclaration)i.next();
// exclude non-private methods and serializable methods
if (!decl.getAccessNodeParent().isPrivate() || decl.getImage().equals("readObject") || decl.getImage().equals("writeObject")|| decl.getImage().equals("readResolve")) {
continue;
}
RuleContext ctx = (RuleContext)data;
ctx.getReport().addRuleViolation(createRuleViolation(ctx, decl.getNode().getBeginLine(), MessageFormat.format(getMessage(), new Object[] {decl.getNode().getImage()})));
}
return super.visit(node, data);
}
*/
}