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.ASTName;
12  import net.sourceforge.pmd.lang.symboltable.NameDeclaration;
13  import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
14  import net.sourceforge.pmd.lang.symboltable.Scope;
15  
16  /**
17   * This scope represents one Java class.
18   * It can have variable declarations, method declarations and inner class declarations.
19   */
20  public class ClassScope extends AbstractJavaScope {
21  
22      // FIXME - this breaks given sufficiently nested code
23      private static ThreadLocal<Integer> anonymousInnerClassCounter = new ThreadLocal<Integer>() {
24          protected Integer initialValue() { return Integer.valueOf(1); }
25      };
26  
27      private String className;
28  
29      public ClassScope(String className) {
30          this.className = className;
31          anonymousInnerClassCounter.set(Integer.valueOf(1));
32      }
33  
34      /**
35       * This is only for anonymous inner classes
36       * <p/>
37       * FIXME - should have name like Foo$1, not Anonymous$1
38       * to get this working right, the parent scope needs
39       * to be passed in when instantiating a ClassScope
40       */
41      public ClassScope() {
42          //this.className = getParent().getEnclosingClassScope().getClassName() + "$" + String.valueOf(anonymousInnerClassCounter);
43          int v = anonymousInnerClassCounter.get().intValue();
44          this.className = "Anonymous$" + v;
45          anonymousInnerClassCounter.set(v + 1);
46      }
47  
48      public Map<ClassNameDeclaration, List<NameOccurrence>> getClassDeclarations() {
49          return getDeclarations(ClassNameDeclaration.class);
50      }
51  
52      public Map<MethodNameDeclaration, List<NameOccurrence>> getMethodDeclarations() {
53          return getDeclarations(MethodNameDeclaration.class);
54      }
55  
56      public Map<VariableNameDeclaration, List<NameOccurrence>> getVariableDeclarations() {
57          return getDeclarations(VariableNameDeclaration.class);
58      }
59  
60      public NameDeclaration addNameOccurrence(NameOccurrence occurrence) {
61          JavaNameOccurrence javaOccurrence = (JavaNameOccurrence)occurrence;
62          NameDeclaration decl = findVariableHere(javaOccurrence);
63          if (decl != null && javaOccurrence.isMethodOrConstructorInvocation()) {
64              List<NameOccurrence> nameOccurrences = getMethodDeclarations().get(decl);
65              if (nameOccurrences == null) {
66                  // TODO may be a class name: Foo.this.super();
67              } else {
68                  nameOccurrences.add(javaOccurrence);
69                  Node n = javaOccurrence.getLocation();
70                  if (n instanceof ASTName) {
71                      ((ASTName) n).setNameDeclaration(decl);
72                  } // TODO what to do with PrimarySuffix case?
73              }
74  
75          } else if (decl != null && !javaOccurrence.isThisOrSuper()) {
76              List<NameOccurrence> nameOccurrences = getVariableDeclarations().get(decl);
77              if (nameOccurrences == null) {
78                  // TODO may be a class name
79  
80                  // search inner classes
81                  for (ClassNameDeclaration innerClass : getClassDeclarations().keySet()) {
82                      Scope innerClassScope = innerClass.getScope();
83                      if (innerClassScope.contains(javaOccurrence)) {
84                          innerClassScope.addNameOccurrence(javaOccurrence);
85                      }
86                  }
87              } else {
88                  nameOccurrences.add(javaOccurrence);
89                  Node n = javaOccurrence.getLocation();
90                  if (n instanceof ASTName) {
91                      ((ASTName) n).setNameDeclaration(decl);
92                  } // TODO what to do with PrimarySuffix case?
93              }
94          }
95          return decl;
96      }
97  
98      public String getClassName() {
99          return this.className;
100     }
101 
102     protected NameDeclaration findVariableHere(JavaNameOccurrence occurrence) {
103         Map<MethodNameDeclaration, List<NameOccurrence>> methodDeclarations = getMethodDeclarations();
104         Map<VariableNameDeclaration, List<NameOccurrence>> variableDeclarations = getVariableDeclarations();
105         if (occurrence.isThisOrSuper() ||
106                 (occurrence.getImage() != null && occurrence.getImage().equals(className))) {
107             if (variableDeclarations.isEmpty() && methodDeclarations.isEmpty()) {
108                 // this could happen if you do this:
109                 // public class Foo {
110                 //  private String x = super.toString();
111                 // }
112                 return null;
113             }
114             // return any name declaration, since all we really want is to get the scope
115             // for example, if there's a
116             // public class Foo {
117             //  private static final int X = 2;
118             //  private int y = Foo.X;
119             // }
120             // we'll look up Foo just to get a handle to the class scope
121             // and then we'll look up X.
122             if (!variableDeclarations.isEmpty()) {
123                 return variableDeclarations.keySet().iterator().next();
124             }
125             return methodDeclarations.keySet().iterator().next();
126         }
127 
128         if (occurrence.isMethodOrConstructorInvocation()) {
129             for (MethodNameDeclaration mnd: methodDeclarations.keySet()) {
130                 if (mnd.getImage().equals(occurrence.getImage())) {
131                     int args = occurrence.getArgumentCount();
132                     if (args == mnd.getParameterCount() || (mnd.isVarargs() && args >= mnd.getParameterCount() - 1)) {
133                         // FIXME if several methods have the same name
134                         // and parameter count, only one will get caught here
135                         // we need to make some attempt at type lookup and discrimination
136                         // or, failing that, mark this as a usage of all those methods
137                         return mnd;
138                     }
139                 }
140             }
141             return null;
142         }
143 
144         List<String> images = new ArrayList<String>();
145         if (occurrence.getImage() != null) {
146             images.add(occurrence.getImage());
147             if (occurrence.getImage().startsWith(className)) {
148                 images.add(clipClassName(occurrence.getImage()));
149             }
150         }
151         ImageFinderFunction finder = new ImageFinderFunction(images);
152         Applier.apply(finder, variableDeclarations.keySet().iterator());
153         NameDeclaration result = finder.getDecl();
154 
155         // search inner classes
156         Map<ClassNameDeclaration, List<NameOccurrence>> classDeclarations = getClassDeclarations();
157         if (result == null && !classDeclarations.isEmpty()) {
158             for (ClassNameDeclaration innerClass : getClassDeclarations().keySet()) {
159                 Applier.apply(finder, innerClass.getScope().getDeclarations().keySet().iterator());
160                 result = finder.getDecl();
161                 if (result != null) {
162                     break;
163                 }
164             }
165         }
166         return result;
167     }
168 
169     public String toString() {
170         StringBuilder res = new StringBuilder("ClassScope (").append(className).append("): ");
171         Map<ClassNameDeclaration, List<NameOccurrence>> classDeclarations = getClassDeclarations();
172         if (classDeclarations.isEmpty()) {
173             res.append("Inner Classes ").append(glomNames(classDeclarations.keySet())).append("; ");
174         }
175         Map<MethodNameDeclaration, List<NameOccurrence>> methodDeclarations = getMethodDeclarations();
176         if (!methodDeclarations.isEmpty()) {
177             for (MethodNameDeclaration mnd: methodDeclarations.keySet()) {
178                 res.append(mnd.toString());
179                 int usages = methodDeclarations.get(mnd).size();
180                 res.append("(begins at line ").append(mnd.getNode().getBeginLine()).append(", ").append(usages).append(" usages)");
181                 res.append(", ");
182             }
183         }
184         Map<VariableNameDeclaration, List<NameOccurrence>> variableDeclarations = getVariableDeclarations();
185         if (!variableDeclarations.isEmpty()) {
186             res.append("Variables ").append(glomNames(variableDeclarations.keySet()));
187         }
188         return res.toString();
189     }
190 
191     private String clipClassName(String s) {
192         return s.substring(s.indexOf('.') + 1);
193     }
194 }