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 (n instanceof ASTName) {
064 ((ASTName) n).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 (n instanceof ASTName) {
076 ((ASTName) n).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 }
|