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