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