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