View Javadoc

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