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