ClassScope.java
001 /**
002  * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003  */
004 package net.sourceforge.pmd.lang.java.symboltable;
005 
006 import java.util.ArrayList;
007 import java.util.HashMap;
008 import java.util.List;
009 import java.util.Map;
010 
011 import net.sourceforge.pmd.lang.ast.Node;
012 import net.sourceforge.pmd.lang.java.ast.ASTName;
013 import net.sourceforge.pmd.util.Applier;
014 
015 public class ClassScope extends AbstractScope {
016 
017     protected Map<ClassNameDeclaration, List<NameOccurrence>> classNames = new HashMap<ClassNameDeclaration, List<NameOccurrence>>();
018     protected Map<MethodNameDeclaration, List<NameOccurrence>> methodNames = new HashMap<MethodNameDeclaration, List<NameOccurrence>>();
019     protected Map<VariableNameDeclaration, List<NameOccurrence>> variableNames = new HashMap<VariableNameDeclaration, List<NameOccurrence>>();
020 
021     // FIXME - this breaks given sufficiently nested code
022     private static ThreadLocal<Integer> anonymousInnerClassCounter = new ThreadLocal<Integer>() {
023         protected Integer initialValue() { return Integer.valueOf(1)}
024     };
025 
026     private String className;
027 
028     public ClassScope(String className) {
029         this.className = className;
030         anonymousInnerClassCounter.set(Integer.valueOf(1));
031     }
032 
033     /**
034      * This is only for anonymous inner classes
035      <p/>
036      * FIXME - should have name like Foo$1, not Anonymous$1
037      * to get this working right, the parent scope needs
038      * to be passed in when instantiating a ClassScope
039      */
040     public ClassScope() {
041         //this.className = getParent().getEnclosingClassScope().getClassName() + "$" + String.valueOf(anonymousInnerClassCounter);
042         int v = anonymousInnerClassCounter.get().intValue();
043         this.className = "Anonymous$" + v;
044         anonymousInnerClassCounter.set(v + 1);
045     }
046 
047     public void addDeclaration(VariableNameDeclaration variableDecl) {
048         if (variableNames.containsKey(variableDecl)) {
049             throw new RuntimeException(variableDecl + " is already in the symbol table");
050         }
051         variableNames.put(variableDecl, new ArrayList<NameOccurrence>());
052     }
053 
054     public NameDeclaration addVariableNameOccurrence(NameOccurrence occurrence) {
055         NameDeclaration decl = findVariableHere(occurrence);
056         if (decl != null && occurrence.isMethodOrConstructorInvocation()) {
057             List<NameOccurrence> nameOccurrences = methodNames.get(decl);
058             if (nameOccurrences == null) {
059                 // TODO may be a class name: Foo.this.super();
060             else {
061                 nameOccurrences.add(occurrence);
062                 Node n = occurrence.getLocation();
063                 if (instanceof ASTName) {
064                     ((ASTNamen).setNameDeclaration(decl);
065                 // TODO what to do with PrimarySuffix case?
066             }
067 
068         else if (decl != null && !occurrence.isThisOrSuper()) {
069             List<NameOccurrence> nameOccurrences = variableNames.get(decl);
070             if (nameOccurrences == null) {
071                 // TODO may be a class name
072             else {
073                 nameOccurrences.add(occurrence);
074                 Node n = occurrence.getLocation();
075                 if (instanceof ASTName) {
076                     ((ASTNamen).setNameDeclaration(decl);
077                 // TODO what to do with PrimarySuffix case?
078             }
079         }
080         return decl;
081     }
082 
083     public Map<VariableNameDeclaration, List<NameOccurrence>> getVariableDeclarations() {
084         VariableUsageFinderFunction f = new VariableUsageFinderFunction(variableNames);
085         Applier.apply(f, variableNames.keySet().iterator());
086         return f.getUsed();
087     }
088 
089     public Map<MethodNameDeclaration, List<NameOccurrence>> getMethodDeclarations() {
090         return methodNames;
091     }
092 
093     public Map<ClassNameDeclaration, List<NameOccurrence>> getClassDeclarations() {
094         return classNames;
095     }
096 
097     public ClassScope getEnclosingClassScope() {
098         return this;
099     }
100 
101     public String getClassName() {
102         return this.className;
103     }
104 
105     public void addDeclaration(MethodNameDeclaration decl) {
106         methodNames.put(decl, new ArrayList<NameOccurrence>());
107     }
108 
109     public void addDeclaration(ClassNameDeclaration decl) {
110         classNames.put(decl, new ArrayList<NameOccurrence>());
111     }
112 
113     protected NameDeclaration findVariableHere(NameOccurrence occurrence) {
114         if (occurrence.isThisOrSuper() || occurrence.getImage().equals(className)) {
115             if (variableNames.isEmpty() && methodNames.isEmpty()) {
116                 // this could happen if you do this:
117                 // public class Foo {
118                 //  private String x = super.toString();
119                 // }
120                 return null;
121             }
122             // return any name declaration, since all we really want is to get the scope
123             // for example, if there's a
124             // public class Foo {
125             //  private static final int X = 2;
126             //  private int y = Foo.X;
127             // }
128             // we'll look up Foo just to get a handle to the class scope
129             // and then we'll look up X.
130             if (!variableNames.isEmpty()) {
131                 return variableNames.keySet().iterator().next();
132             }
133             return methodNames.keySet().iterator().next();
134         }
135 
136         if (occurrence.isMethodOrConstructorInvocation()) {
137             for (MethodNameDeclaration mnd: methodNames.keySet()) {
138                 if (mnd.getImage().equals(occurrence.getImage())) {
139                     int args = occurrence.getArgumentCount();
140                     if (args == mnd.getParameterCount() || (mnd.isVarargs() && args >= mnd.getParameterCount() 1)) {
141                         // FIXME if several methods have the same name
142                         // and parameter count, only one will get caught here
143                         // we need to make some attempt at type lookup and discrimination
144                         // or, failing that, mark this as a usage of all those methods
145                         return mnd;
146                     }
147                 }
148             }
149             return null;
150         }
151 
152         List<String> images = new ArrayList<String>();
153         images.add(occurrence.getImage());
154         if (occurrence.getImage().startsWith(className)) {
155             images.add(clipClassName(occurrence.getImage()));
156         }
157         ImageFinderFunction finder = new ImageFinderFunction(images);
158         Applier.apply(finder, variableNames.keySet().iterator());
159         return finder.getDecl();
160     }
161 
162     public String toString() {
163         String res = "ClassScope (" + className + "): ";
164         if (!classNames.isEmpty()) {
165             res += "(" + glomNames(classNames.keySet()) ")";
166         }
167         if (!methodNames.isEmpty()) {
168             for (MethodNameDeclaration mnd: methodNames.keySet()) {
169                 res += mnd.toString();
170                 int usages = methodNames.get(mnd).size();
171                 res += "(begins at line " + mnd.getNode().getBeginLine() ", " + usages + " usages)";
172                 res += ",";
173             }
174         }
175         if (!variableNames.isEmpty()) {
176             res += "(" + glomNames(variableNames.keySet()) ")";
177         }
178         return res;
179     }
180 
181     private String clipClassName(String s) {
182         return s.substring(s.indexOf('.'1);
183     }
184 }