View Javadoc

1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.java.symboltable;
5   
6   import java.util.ArrayList;
7   import java.util.List;
8   import java.util.Map;
9   
10  import net.sourceforge.pmd.lang.ast.Node;
11  import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
12  import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter;
13  import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
14  import net.sourceforge.pmd.lang.java.ast.ASTName;
15  import net.sourceforge.pmd.lang.symboltable.NameDeclaration;
16  import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
17  import net.sourceforge.pmd.lang.symboltable.Scope;
18  
19  /**
20   * This scope represents one Java class.
21   * It can have variable declarations, method declarations and inner class declarations.
22   */
23  public class ClassScope extends AbstractJavaScope {
24  
25      // FIXME - this breaks given sufficiently nested code
26      private static ThreadLocal<Integer> anonymousInnerClassCounter = new ThreadLocal<Integer>() {
27          protected Integer initialValue() { return Integer.valueOf(1); }
28      };
29  
30      private String className;
31  
32      public ClassScope(String className) {
33          this.className = className;
34          anonymousInnerClassCounter.set(Integer.valueOf(1));
35      }
36  
37      /**
38       * This is only for anonymous inner classes
39       * <p/>
40       * FIXME - should have name like Foo$1, not Anonymous$1
41       * to get this working right, the parent scope needs
42       * to be passed in when instantiating a ClassScope
43       */
44      public ClassScope() {
45          //this.className = getParent().getEnclosingClassScope().getClassName() + "$" + String.valueOf(anonymousInnerClassCounter);
46          int v = anonymousInnerClassCounter.get().intValue();
47          this.className = "Anonymous$" + v;
48          anonymousInnerClassCounter.set(v + 1);
49      }
50  
51      public Map<ClassNameDeclaration, List<NameOccurrence>> getClassDeclarations() {
52          return getDeclarations(ClassNameDeclaration.class);
53      }
54  
55      public Map<MethodNameDeclaration, List<NameOccurrence>> getMethodDeclarations() {
56          return getDeclarations(MethodNameDeclaration.class);
57      }
58  
59      public Map<VariableNameDeclaration, List<NameOccurrence>> getVariableDeclarations() {
60          return getDeclarations(VariableNameDeclaration.class);
61      }
62  
63      public NameDeclaration addNameOccurrence(NameOccurrence occurrence) {
64          JavaNameOccurrence javaOccurrence = (JavaNameOccurrence)occurrence;
65          NameDeclaration decl = findVariableHere(javaOccurrence);
66          if (decl != null && (javaOccurrence.isMethodOrConstructorInvocation() || javaOccurrence.isMethodReference())) {
67              List<NameOccurrence> nameOccurrences = getMethodDeclarations().get(decl);
68              if (nameOccurrences == null) {
69                  // TODO may be a class name: Foo.this.super();
70              } else {
71                  nameOccurrences.add(javaOccurrence);
72                  Node n = javaOccurrence.getLocation();
73                  if (n instanceof ASTName) {
74                      ((ASTName) n).setNameDeclaration(decl);
75                  } // TODO what to do with PrimarySuffix case?
76              }
77  
78          } else if (decl != null && !javaOccurrence.isThisOrSuper()) {
79              List<NameOccurrence> nameOccurrences = getVariableDeclarations().get(decl);
80              if (nameOccurrences == null) {
81                  // TODO may be a class name
82  
83                  // search inner classes
84                  for (ClassNameDeclaration innerClass : getClassDeclarations().keySet()) {
85                      Scope innerClassScope = innerClass.getScope();
86                      if (innerClassScope.contains(javaOccurrence)) {
87                          innerClassScope.addNameOccurrence(javaOccurrence);
88                      }
89                  }
90              } else {
91                  nameOccurrences.add(javaOccurrence);
92                  Node n = javaOccurrence.getLocation();
93                  if (n instanceof ASTName) {
94                      ((ASTName) n).setNameDeclaration(decl);
95                  } // TODO what to do with PrimarySuffix case?
96              }
97          }
98          return decl;
99      }
100 
101     public String getClassName() {
102         return this.className;
103     }
104 
105     protected NameDeclaration findVariableHere(JavaNameOccurrence occurrence) {
106         Map<MethodNameDeclaration, List<NameOccurrence>> methodDeclarations = getMethodDeclarations();
107         Map<VariableNameDeclaration, List<NameOccurrence>> variableDeclarations = getVariableDeclarations();
108         if (occurrence.isThisOrSuper() ||
109                 (occurrence.getImage() != null && occurrence.getImage().equals(className))) {
110             if (variableDeclarations.isEmpty() && methodDeclarations.isEmpty()) {
111                 // this could happen if you do this:
112                 // public class Foo {
113                 //  private String x = super.toString();
114                 // }
115                 return null;
116             }
117             // return any name declaration, since all we really want is to get the scope
118             // for example, if there's a
119             // public class Foo {
120             //  private static final int X = 2;
121             //  private int y = Foo.X;
122             // }
123             // we'll look up Foo just to get a handle to the class scope
124             // and then we'll look up X.
125             if (!variableDeclarations.isEmpty()) {
126                 return variableDeclarations.keySet().iterator().next();
127             }
128             return methodDeclarations.keySet().iterator().next();
129         }
130 
131         if (occurrence.isMethodOrConstructorInvocation()) {
132             for (MethodNameDeclaration mnd: methodDeclarations.keySet()) {
133                 if (mnd.getImage().equals(occurrence.getImage())) {
134                     List<String> parameterTypes = determineParameterTypes(mnd);
135                     List<String> argumentTypes = determineArgumentTypes(occurrence, parameterTypes);
136 
137                     if (!mnd.isVarargs()
138                             && occurrence.getArgumentCount() == mnd.getParameterCount()
139                             && parameterTypes.equals(argumentTypes)) {
140                         return mnd;
141                     } else if (mnd.isVarargs()) {
142                         int varArgIndex = parameterTypes.size() - 1;
143                         String varArgType = parameterTypes.get(varArgIndex);
144                         if (parameterTypes.subList(0, varArgIndex).equals(argumentTypes.subList(0, varArgIndex))) {
145                             boolean sameType = true;
146                             for (int i = varArgIndex; i < argumentTypes.size(); i++) {
147                                 if (!varArgType.equals(argumentTypes.get(i))) {
148                                     sameType = false;
149                                     break;
150                                 }
151                             }
152                             if (sameType) {
153                                 return mnd;
154                             }
155                         }
156                     }
157                 }
158             }
159             return null;
160         }
161         if (occurrence.isMethodReference()) {
162             for (MethodNameDeclaration mnd: methodDeclarations.keySet()) {
163                 if (mnd.getImage().equals(occurrence.getImage())) {
164                     return mnd;
165                 }
166             }
167             return null;
168         }
169 
170         List<String> images = new ArrayList<String>();
171         if (occurrence.getImage() != null) {
172             images.add(occurrence.getImage());
173             if (occurrence.getImage().startsWith(className)) {
174                 images.add(clipClassName(occurrence.getImage()));
175             }
176         }
177         ImageFinderFunction finder = new ImageFinderFunction(images);
178         Applier.apply(finder, variableDeclarations.keySet().iterator());
179         NameDeclaration result = finder.getDecl();
180 
181         // search inner classes
182         Map<ClassNameDeclaration, List<NameOccurrence>> classDeclarations = getClassDeclarations();
183         if (result == null && !classDeclarations.isEmpty()) {
184             for (ClassNameDeclaration innerClass : getClassDeclarations().keySet()) {
185                 Applier.apply(finder, innerClass.getScope().getDeclarations().keySet().iterator());
186                 result = finder.getDecl();
187                 if (result != null) {
188                     break;
189                 }
190             }
191         }
192         return result;
193     }
194 
195     /**
196      * Provide a list of types of the parameters of the given method declaration.
197      * The types are simple type images. 
198      * @param mnd the method declaration.
199      * @return List of types
200      */
201     private List<String> determineParameterTypes(MethodNameDeclaration mnd) {
202         List<String> parameterTypes = new ArrayList<String>();
203         List<ASTFormalParameter> parameters = mnd.getMethodNameDeclaratorNode().findDescendantsOfType(ASTFormalParameter.class);
204         for (ASTFormalParameter p : parameters) {
205             parameterTypes.add(p.getTypeNode().getTypeImage());
206         }
207         return parameterTypes;
208     }
209 
210     /**
211      * Provide a list of types of the arguments of the given method call.
212      * The types are simple type images. If the argument type cannot be determined (e.g. because it is itself
213      * the result of a method call), the parameter type is used - so it is assumed, it is of the correct type.
214      * This might cause confusion when methods are overloaded.
215      * @param occurrence the method call
216      * @param parameterTypes the parameter types of the called method
217      * @return the list of argument types
218      */
219     private List<String> determineArgumentTypes(JavaNameOccurrence occurrence, List<String> parameterTypes) {
220         final String unknown_type = "unknown";
221         List<String> argumentTypes = new ArrayList<String>();
222         ASTArgumentList arguments = occurrence.getLocation().jjtGetParent().jjtGetParent().getFirstDescendantOfType(ASTArgumentList.class);
223         if (arguments != null) {
224             for (int i = 0; i < arguments.jjtGetNumChildren(); i++) {
225                 ASTName name = arguments.jjtGetChild(i).getFirstDescendantOfType(ASTName.class);
226                 String typeImage = unknown_type;
227                 if (name != null) {
228                     Scope s = name.getScope();
229                     while (s != null) {
230                         if (s.contains(new JavaNameOccurrence(name, name.getImage()))) {
231                             break;
232                         }
233                         s = s.getParent();
234                     }
235                     if (s != null) {
236                         Map<VariableNameDeclaration, List<NameOccurrence>> vars = s.getDeclarations(VariableNameDeclaration.class);
237                         for (VariableNameDeclaration d : vars.keySet()) {
238                             if (d.getImage().equals(name.getImage())) {
239                                 typeImage = d.getTypeImage();
240                                 break;
241                             }
242                         }
243                     }
244                 } else {
245                     ASTLiteral literal = arguments.jjtGetChild(i).getFirstDescendantOfType(ASTLiteral.class);
246                     if (literal != null) {
247                         if (literal.isCharLiteral()) {
248                             typeImage = "char";
249                         } else if (literal.isStringLiteral()) {
250                             typeImage = "String";
251                         } else if (literal.isFloatLiteral()) {
252                             typeImage = "float";
253                         } else if (literal.isIntLiteral()) {
254                             typeImage = "int";
255                         }
256                     }
257                 }
258                 argumentTypes.add(typeImage);
259             }
260         }
261         // replace the unknown type with the correct parameter type of the method.
262         // in case the argument is itself a method call, we can't determine the result type of the called
263         // method. Therefore the parameter type is used.
264         // This might cause confusion, if method overloading is used.
265         for (int i = 0; i < argumentTypes.size() && i < parameterTypes.size(); i++) {
266             if (unknown_type.equals(argumentTypes.get(i))) {
267                 argumentTypes.set(i, parameterTypes.get(i));
268             }
269         }
270         return argumentTypes;
271     }
272 
273     public String toString() {
274         StringBuilder res = new StringBuilder("ClassScope (").append(className).append("): ");
275         Map<ClassNameDeclaration, List<NameOccurrence>> classDeclarations = getClassDeclarations();
276         if (classDeclarations.isEmpty()) {
277             res.append("Inner Classes ").append(glomNames(classDeclarations.keySet())).append("; ");
278         }
279         Map<MethodNameDeclaration, List<NameOccurrence>> methodDeclarations = getMethodDeclarations();
280         if (!methodDeclarations.isEmpty()) {
281             for (MethodNameDeclaration mnd: methodDeclarations.keySet()) {
282                 res.append(mnd.toString());
283                 int usages = methodDeclarations.get(mnd).size();
284                 res.append("(begins at line ").append(mnd.getNode().getBeginLine()).append(", ").append(usages).append(" usages)");
285                 res.append(", ");
286             }
287         }
288         Map<VariableNameDeclaration, List<NameOccurrence>> variableDeclarations = getVariableDeclarations();
289         if (!variableDeclarations.isEmpty()) {
290             res.append("Variables ").append(glomNames(variableDeclarations.keySet()));
291         }
292         return res.toString();
293     }
294 
295     private String clipClassName(String s) {
296         return s.substring(s.indexOf('.') + 1);
297     }
298 }