1 package net.sourceforge.pmd.rules;
2
3 import net.sourceforge.pmd.AbstractRule;
4 import net.sourceforge.pmd.RuleContext;
5 import net.sourceforge.pmd.ast.ASTArguments;
6 import net.sourceforge.pmd.ast.ASTClassBody;
7 import net.sourceforge.pmd.ast.ASTCompilationUnit;
8 import net.sourceforge.pmd.ast.ASTInterfaceDeclaration;
9 import net.sourceforge.pmd.ast.ASTMethodDeclarator;
10 import net.sourceforge.pmd.ast.ASTName;
11 import net.sourceforge.pmd.ast.ASTPrimaryExpression;
12 import net.sourceforge.pmd.ast.ASTPrimaryPrefix;
13 import net.sourceforge.pmd.ast.ASTPrimarySuffix;
14 import net.sourceforge.pmd.ast.AccessNode;
15 import net.sourceforge.pmd.ast.SimpleNode;
16
17 import java.text.MessageFormat;
18 import java.util.HashSet;
19 import java.util.Iterator;
20 import java.util.Set;
21
22 public class UnusedPrivateMethodRule extends AbstractRule {
23
24 private Set privateMethodNodes = new HashSet();
25
26 // TODO - What I need is a Visitor that does a breadth first search
27 private boolean trollingForDeclarations;
28 private int depth;
29
30 // Skip interfaces because they have no implementation
31 public Object visit(ASTInterfaceDeclaration node, Object data) {
32 return data;
33 }
34
35 // Reset state when we leave an ASTCompilationUnit
36 public Object visit(ASTCompilationUnit node, Object data) {
37 depth = 0;
38 super.visit(node, data);
39 privateMethodNodes.clear();
40 depth = 0;
41 trollingForDeclarations = false;
42 return data;
43 }
44
45 public Object visit(ASTClassBody node, Object data) {
46 depth++;
47
48 // first troll for declarations, but only in the top level class
49 if (depth == 1) {
50 trollingForDeclarations = true;
51 super.visit(node, null);
52 trollingForDeclarations = false;
53 } else {
54 trollingForDeclarations = false;
55 }
56
57 // troll for usages, regardless of depth
58 super.visit(node, null);
59
60 // if we're back at the top level class, harvest
61 if (depth == 1) {
62 RuleContext ctx = (RuleContext) data;
63 harvestUnused(ctx);
64 }
65
66 depth--;
67 return data;
68 }
69
70 //ASTMethodDeclarator
71 // FormalParameters
72 // FormalParameter
73 // FormalParameter
74 public Object visit(ASTMethodDeclarator node, Object data) {
75 if (!trollingForDeclarations) {
76 return super.visit(node, data);
77 }
78
79 AccessNode parent = (AccessNode) node.jjtGetParent();
80 if (!parent.isPrivate()) {
81 return super.visit(node, data);
82 }
83 // exclude these serializable things
84 if (node.getImage().equals("readObject") || node.getImage().equals("writeObject") || node.getImage().equals("readResolve")) {
85 return super.visit(node, data);
86 }
87 privateMethodNodes.add(node);
88 return super.visit(node, data);
89 }
90
91 //PrimarySuffix
92 // Arguments
93 // ArgumentList
94 // Expression
95 // Expression
96 public Object visit(ASTPrimarySuffix node, Object data) {
97 if (!trollingForDeclarations && (node.jjtGetParent() instanceof ASTPrimaryExpression) && (node.getImage() != null)) {
98 if (node.jjtGetNumChildren() > 0) {
99 ASTArguments args = (ASTArguments) node.jjtGetChild(0);
100 removeIfUsed(node.getImage(), args.getArgumentCount());
101 return super.visit(node, data);
102 }
103 // to handle this.foo()
104 //PrimaryExpression
105 // PrimaryPrefix
106 // PrimarySuffix <-- this node has "foo"
107 // PrimarySuffix <-- this node has null
108 // Arguments
109 ASTPrimaryExpression parent = (ASTPrimaryExpression) node.jjtGetParent();
110 int pointer = 0;
111 while (true) {
112 if (parent.jjtGetChild(pointer).equals(node)) {
113 break;
114 }
115 pointer++;
116 }
117 // now move to the next PrimarySuffix and get the number of arguments
118 pointer++;
119 // this.foo = foo;
120 // yields this:
121 // PrimaryExpression
122 // PrimaryPrefix
123 // PrimarySuffix
124 // so we check for that
125 if (parent.jjtGetNumChildren() <= pointer) {
126 return super.visit(node, data);
127 }
128 if (!(parent.jjtGetChild(pointer) instanceof ASTPrimarySuffix)) {
129 return super.visit(node, data);
130 }
131 ASTPrimarySuffix actualMethodNode = (ASTPrimarySuffix) parent.jjtGetChild(pointer);
132 // when does this happen?
133 if (actualMethodNode.jjtGetNumChildren() == 0 || !(actualMethodNode.jjtGetChild(0) instanceof ASTArguments)) {
134 return super.visit(node, data);
135 }
136 ASTArguments args = (ASTArguments) actualMethodNode.jjtGetChild(0);
137 removeIfUsed(node.getImage(), args.getArgumentCount());
138 // what about Outer.this.foo()?
139 }
140 return super.visit(node, data);
141 }
142
143 //PrimaryExpression
144 // PrimaryPrefix
145 // Name
146 // PrimarySuffix
147 // Arguments
148 public Object visit(ASTName node, Object data) {
149 if (!trollingForDeclarations && (node.jjtGetParent() instanceof ASTPrimaryPrefix)) {
150 ASTPrimaryExpression primaryExpression = (ASTPrimaryExpression) node.jjtGetParent().jjtGetParent();
151 if (primaryExpression.jjtGetNumChildren() > 1) {
152 ASTPrimarySuffix primarySuffix = (ASTPrimarySuffix) primaryExpression.jjtGetChild(1);
153 if (primarySuffix.jjtGetNumChildren() > 0 && (primarySuffix.jjtGetChild(0) instanceof ASTArguments)) {
154 ASTArguments arguments = (ASTArguments) primarySuffix.jjtGetChild(0);
155 removeIfUsed(node.getImage(), arguments.getArgumentCount());
156 }
157 }
158 }
159 return super.visit(node, data);
160 }
161
162 private void removeIfUsed(String nodeImage, int args) {
163 String img = (nodeImage.indexOf('.') == -1) ? nodeImage : nodeImage.substring(nodeImage.indexOf('.') + 1, nodeImage.length());
164 for (Iterator i = privateMethodNodes.iterator(); i.hasNext();) {
165 ASTMethodDeclarator methodNode = (ASTMethodDeclarator) i.next();
166 // are name and number of parameters the same?
167 if (methodNode.getImage().equals(img) && methodNode.getParameterCount() == args) {
168 // should check parameter types here, this misses some unused methods
169 i.remove();
170 }
171 }
172 }
173
174 private void harvestUnused(RuleContext ctx) {
175 for (Iterator i = privateMethodNodes.iterator(); i.hasNext();) {
176 SimpleNode node = (SimpleNode) i.next();
177 ctx.getReport().addRuleViolation(createRuleViolation(ctx, node.getBeginLine(), MessageFormat.format(getMessage(), new Object[]{node.getImage()})));
178 }
179 }
180
181 /*
182 TODO this uses the symbol table
183 public Object visit(ASTUnmodifiedClassDeclaration node, Object data) {
184 for (Iterator i = node.getScope().getUnusedMethodDeclarations();i.hasNext();) {
185 VariableNameDeclaration decl = (VariableNameDeclaration)i.next();
186
187 // exclude non-private methods and serializable methods
188 if (!decl.getAccessNodeParent().isPrivate() || decl.getImage().equals("readObject") || decl.getImage().equals("writeObject")|| decl.getImage().equals("readResolve")) {
189 continue;
190 }
191
192 RuleContext ctx = (RuleContext)data;
193 ctx.getReport().addRuleViolation(createRuleViolation(ctx, decl.getNode().getBeginLine(), MessageFormat.format(getMessage(), new Object[] {decl.getNode().getImage()})));
194 }
195 return super.visit(node, data);
196 }
197
198 */
199 }
This page was automatically generated by Maven