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.Stack;
7   import java.util.logging.Level;
8   import java.util.logging.Logger;
9   
10  import net.sourceforge.pmd.lang.plsql.ast.ASTBlock;
11  import net.sourceforge.pmd.lang.plsql.ast.ASTDeclarativeUnit;
12  import net.sourceforge.pmd.lang.plsql.ast.ASTForAllStatement;
13  import net.sourceforge.pmd.lang.plsql.ast.ASTForStatement;
14  import net.sourceforge.pmd.lang.plsql.ast.ASTID;
15  import net.sourceforge.pmd.lang.plsql.ast.ASTInput;
16  import net.sourceforge.pmd.lang.plsql.ast.ASTMethodDeclarator;
17  import net.sourceforge.pmd.lang.plsql.ast.ASTObjectDeclaration;
18  import net.sourceforge.pmd.lang.plsql.ast.ASTObjectNameDeclaration;
19  import net.sourceforge.pmd.lang.plsql.ast.ASTPackageBody;
20  import net.sourceforge.pmd.lang.plsql.ast.ASTPackageSpecification;
21  import net.sourceforge.pmd.lang.plsql.ast.ASTProgramUnit;
22  import net.sourceforge.pmd.lang.plsql.ast.ASTTriggerTimingPointSection;
23  import net.sourceforge.pmd.lang.plsql.ast.ASTTriggerUnit;
24  import net.sourceforge.pmd.lang.plsql.ast.ASTTypeMethod;
25  import net.sourceforge.pmd.lang.plsql.ast.ASTTypeSpecification;
26  import net.sourceforge.pmd.lang.plsql.ast.ASTVariableOrConstantDeclaratorId;
27  import net.sourceforge.pmd.lang.plsql.ast.PLSQLNode;
28  import net.sourceforge.pmd.lang.plsql.ast.PLSQLParserVisitorAdapter;
29  import net.sourceforge.pmd.lang.symboltable.Scope;
30  
31  /**
32   * Visitor for scope creation.
33   * Visits all nodes of an AST and creates scope objects for nodes representing
34   * syntactic entities which may contain declarations. For example, a block
35   * may contain variable definitions (which are declarations) and
36   * therefore needs a scope object where these declarations can be associated,
37   * whereas an expression can't contain declarations and therefore doesn't need
38   * a scope object.
39   * With the exception of global scopes, each scope object is linked to its
40   * parent scope, which is the scope object of the next embedding syntactic
41   * entity that has a scope.
42   */
43  public class ScopeAndDeclarationFinder extends PLSQLParserVisitorAdapter {
44      private final static Logger LOGGER = Logger.getLogger(ScopeAndDeclarationFinder.class.getName()); 
45  
46      /**
47       * A stack of scopes reflecting the scope hierarchy when a node is visited.
48       * This is used to set the parents of the created scopes correctly.
49       */
50      private Stack<Scope> scopes = new Stack<>();
51  
52      /**
53       * Sets the scope of a node and adjusts the scope stack accordingly.
54       * The scope on top of the stack is set as the parent of the given scope,
55       * which is then also stored on the scope stack.
56       *
57       * @param newScope the scope for the node.
58       * @param node     the AST node for which the scope is to be set.
59       * @throws java.util.EmptyStackException if the scope stack is empty.
60       */
61      private void addScope(Scope newScope, PLSQLNode node) {
62  	newScope.setParent(scopes.peek());
63  	scopes.push(newScope);
64  	node.setScope(newScope);
65      }
66  
67      /**
68       * Creates a new local scope for an AST node.
69       * The scope on top of the stack is set as the parent of the new scope,
70       * which is then also stored on the scope stack.
71       *
72       * @param node the AST node for which the scope has to be created.
73       * @throws java.util.EmptyStackException if the scope stack is empty.
74       */
75      private void createLocalScope(PLSQLNode node) {
76  	addScope(new LocalScope(), node);
77      }
78  
79      /**
80       * Creates a new method scope for an AST node.
81       * The scope on top of the stack is set as the parent of the new scope,
82       * which is then also stored on the scope stack.
83       *
84       * @param node the AST node for which the scope has to be created.
85       * @throws java.util.EmptyStackException if the scope stack is empty.
86       */
87      private void createMethodScope(PLSQLNode node) {
88  	addScope(new MethodScope(node), node);
89      }
90  
91      /**
92       * Creates a new class scope for an AST node.
93       * The scope on top of the stack is set as the parent of the new scope,
94       * which is then also stored on the scope stack.
95       *
96       * @param node the AST node for which the scope has to be created.
97       * @throws java.util.EmptyStackException if the scope stack is empty.
98       */
99      private void createClassScope(PLSQLNode node) {
100 	if (node instanceof ASTDeclarativeUnit) {
101 	    addScope(new ClassScope(), node);
102 	} else {
103 	    addScope(new ClassScope(node.getImage()), node);
104 	}
105     }
106 
107     /**
108      * Creates a new global scope for an AST node.
109      * The new scope is stored on the scope stack.
110      *
111      * @param node the AST node for which the scope has to be created.
112      */
113     private void createSourceFileScope(ASTInput node) {
114 	// When we do full symbol resolution, we'll need to add a truly top-level GlobalScope.
115 	Scope scope;
116 	//%TODO generate a SchemaScope, based on inferred or explcitly specified SchemaName 
117 	ASTObjectDeclaration n = null; // node.getPackageDeclaration();
118 	if (n != null) {
119 	    scope = new SourceFileScope(n.jjtGetChild(0).getImage());
120 	} else {
121 	    scope = new SourceFileScope();
122 	}
123 	scopes.push(scope);
124 	node.setScope(scope);
125     }
126 
127     @Override
128     public Object visit(ASTInput node, Object data) {
129 	createSourceFileScope(node);
130 	cont(node);
131 	return data;
132     }
133 
134     @Override
135     public Object visit(ASTPackageSpecification node, Object data) {
136 	createClassScope(node);
137 	Scope s = ((PLSQLNode)node.jjtGetParent()).getScope();
138 	s.addDeclaration(new ClassNameDeclaration(node));
139 	cont(node);
140 	return data;
141     }
142 
143     @Override
144     public Object visit(ASTPackageBody node, Object data) {
145 	createClassScope(node);
146 	Scope s = ((PLSQLNode)node.jjtGetParent()).getScope();
147 	s.addDeclaration(new ClassNameDeclaration(node));
148 	cont(node);
149 	return data;
150     }
151 
152 
153     @Override
154     public Object visit(ASTTypeSpecification node, Object data) {
155 	createClassScope(node);
156 	Scope s = ((PLSQLNode)node.jjtGetParent()).getScope();
157 	s.addDeclaration(new ClassNameDeclaration(node));
158 	cont(node);
159 	return data;
160     }
161 
162     @Override
163     public Object visit(ASTTriggerUnit node, Object data) {
164 	createClassScope(node);
165 	Scope s = ((PLSQLNode)node.jjtGetParent()).getScope();
166 	s.addDeclaration(new ClassNameDeclaration(node));
167 	cont(node);
168 	return data;
169     }
170 
171     /*
172     @Override
173     public Object visit(ASTCompoundTriggerBlock node, Object data) {
174 	createMethodScope(node);
175 	ASTMethodDeclarator md = node.getFirstChildOfType(ASTMethodDeclarator.class);
176 	node.getScope().getEnclosingClassScope().addDeclaration(new MethodNameDeclaration(md));
177 	cont(node);
178 	return data;
179     }
180     */
181 
182     @Override
183     public Object visit(ASTTriggerTimingPointSection node, Object data) {
184 	createMethodScope(node);
185 	//Treat a Timing Point Section like a packaged FUNCTION or PROCEDURE
186 	node.getScope().getEnclosingScope(ClassScope.class).addDeclaration(new MethodNameDeclaration(node));
187 	cont(node);
188 	return data;
189     }
190 
191     //@Override
192     //public Object visit(ASTEnumDeclaration node, Object data) {
193 	//createClassScope(node);
194 	//cont(node);
195 	//return data;
196     //}
197 
198     //@Override
199     //public Object visit(ASTAnnotationTypeDeclaration node, Object data) {
200 	//createClassScope(node);
201 	//cont(node);
202 	//return data;
203     //}
204 
205     @Override
206     public Object visit(ASTObjectDeclaration node, Object data) {
207         super.visit(node, data);
208 	return data;
209     }
210 
211     @Override
212     public Object visit(ASTBlock node, Object data) {
213 	createLocalScope(node);
214 	cont(node);
215 	return data;
216     }
217 
218 
219 
220     /*
221     @Override
222     public Object visit(ASTMethodDeclaration node, Object data) {
223 	createMethodScope(node);
224 	//
225 	 // A method declaration my be-
226 	 //   ASTProgramUnit - a standalone or packaged FUNCTION or PROCEDURE 
227 	 //   ASTTypeMethod - an OBJECT TYPE method  
228 	 //  
229 	 //  The Method declarator is below the  ASTProgramUnit / ASTTypeMethod 
230 	 ///
231 	List<ASTMethodDeclarator> methodDeclarators = node.findDescendantsOfType(ASTMethodDeclarator.class);
232 	if (!methodDeclarators.isEmpty() )
233 	{
234 	  //Use first Declarator in the list 
235 	  ASTMethodDeclarator md = methodDeclarators.get(0);
236             LOGGER.finest("ClassScope skipped for Schema-level method: methodName=" 
237 		               + node.getMethodName()
238 		               + "; Image=" + node.getImage()
239 		              );
240 	   
241 	}
242 	//ASTMethodDeclarator md = node.getFirstChildOfType(ASTMethodDeclarator.class);
243 	// A PLSQL Method (FUNCTION|PROCEDURE) may be schema-level 
244 	try
245 	{
246 	  node.getScope().getEnclosingClassScope().addDeclaration(new MethodNameDeclaration(md));
247 	}
248 	catch (Exception e)
249 	{
250 	  //@TODO possibly add to a pseudo-ClassScope equivalent to the Schema name 
251 	  LOGGER.finest("ProgramUnit getEnclosingClassScope Exception string=\""+e.getMessage()+"\"");
252 	  if("getEnclosingClassScope() called on SourceFileScope".equals(e.getMessage()))
253 	  {
254             LOGGER.finest("ClassScope skipped for Schema-level method: methodName=" 
255 		               + node.getMethodName()
256 		               + "; Image=" + node.getImage()
257 		              );
258 	   
259 	    //A File-level/Schema-level object may have a Schema-name explicitly specified in the declaration 
260 	    ASTObjectNameDeclaration on = md.getFirstChildOfType(ASTObjectNameDeclaration.class);
261 	    if( 1 < on.jjtGetNumChildren())
262 	    {
263               ASTID schemaName = on.getFirstChildOfType(ASTID.class);
264 	      LOGGER.finest("SchemaName for Schema-level method: methodName=" 
265 				 + node.getMethodName()
266 				 + "; Image=" + node.getImage()
267 				 + "is " + schemaName.getImage()
268 				);
269 	     
270 	    }
271 	  }
272 	}
273 	cont(node);
274 	return data;
275     }
276     */
277 
278 @Override
279 public Object visit(ASTTypeMethod node, Object data) {
280 	createMethodScope(node);
281 	ASTMethodDeclarator md = node.getFirstChildOfType(ASTMethodDeclarator.class);
282 	// A PLSQL Method (FUNCTION|PROCEDURE) may be schema-level 
283 	try
284 	{
285 	  node.getScope().getEnclosingScope(ClassScope.class).addDeclaration(new MethodNameDeclaration(md));
286 	}
287 	catch (Exception e)
288 	{
289 	  //@TODO possibly add to a pseudo-ClassScope equivalent to the Schema name
290 	    if (LOGGER.isLoggable(Level.FINEST)) {
291 	  LOGGER.finest("ProgramUnit getEnclosingClassScope Exception string=\""+e.getMessage()+"\"");
292 	    }
293 	  if("getEnclosingClassScope() called on SourceFileScope".equals(e.getMessage()))
294 	  {
295 	      if (LOGGER.isLoggable(Level.FINEST)) {
296             LOGGER.finest("ClassScope skipped for Schema-level method: methodName=" 
297 		               + node.getMethodName()
298 		               + "; Image=" + node.getImage()
299 		              );
300 	      }
301 	   
302 	    //A File-level/Schema-level object may have a Schema-name explicitly specified in the declaration 
303 	    ASTObjectNameDeclaration on = md.getFirstChildOfType(ASTObjectNameDeclaration.class);
304 	    if( 1 < on.jjtGetNumChildren())
305 	    {
306               ASTID schemaName = on.getFirstChildOfType(ASTID.class);
307               if (LOGGER.isLoggable(Level.FINEST)) {
308 	      LOGGER.finest("SchemaName for Schema-level method: methodName=" 
309 				 + node.getMethodName()
310 				 + "; Image=" + node.getImage()
311 				 + "is " + schemaName.getImage()
312 				);
313               }
314 	     
315 	    }
316 	  }
317 	}
318 	cont(node);
319 	return data;
320   }
321 
322     @Override
323     public Object visit(ASTProgramUnit node, Object data) {
324 	createMethodScope(node);
325 	ASTMethodDeclarator md = node.getFirstChildOfType(ASTMethodDeclarator.class);
326 	// A PLSQL Method (FUNCTION|PROCEDURE) may be schema-level 
327 	try
328 	{
329 	  node.getScope().getEnclosingScope(ClassScope.class).addDeclaration(new MethodNameDeclaration(md));
330 	}
331 	catch (Exception e)
332 	{
333 	  //@TODO possibly add to a pseudo-ClassScope equivalent to the Schema name 
334 	    if (LOGGER.isLoggable(Level.FINEST)) {
335 	  LOGGER.finest("ProgramUnit getEnclosingClassScope Exception string=\""+e.getMessage()+"\"");
336 	    }
337 	  if("getEnclosingClassScope() called on SourceFileScope".equals(e.getMessage()))
338 	  {
339 	      if (LOGGER.isLoggable(Level.FINEST)) {
340             LOGGER.finest("ClassScope skipped for Schema-level method: methodName=" 
341 		               + node.getMethodName()
342 		               + "; Image=" + node.getImage()
343 		              );
344 	      }
345 	   
346 	    //A File-level/Schema-level object may have a Schema-name explicitly specified in the declaration 
347 	    ASTObjectNameDeclaration on = md.getFirstChildOfType(ASTObjectNameDeclaration.class);
348 	    if( 1 < on.jjtGetNumChildren())
349 	    {
350               ASTID schemaName = on.getFirstChildOfType(ASTID.class);
351               if (LOGGER.isLoggable(Level.FINEST)) {
352 	      LOGGER.finest("SchemaName for Schema-level method: methodName=" 
353 				 + node.getMethodName()
354 				 + "; Image=" + node.getImage()
355 				 + "is " + schemaName.getImage()
356 				);
357               }
358 	     
359 	    }
360 	  }
361 	}
362 	cont(node);
363 	return data;
364     }
365 
366     // TODO - what about while loops and do loops?
367     @Override
368     public Object visit(ASTForStatement node, Object data) {
369 	createLocalScope(node);
370 	cont(node);
371 	return data;
372     }
373 
374     @Override
375     public Object visit(ASTForAllStatement node, Object data) {
376 	createLocalScope(node);
377 	cont(node);
378 	return data;
379     }
380 
381     @Override
382     public Object visit(ASTVariableOrConstantDeclaratorId node, Object data) {
383 	VariableNameDeclaration decl = new VariableNameDeclaration(node);
384 	node.getScope().addDeclaration(decl);
385 	node.setNameDeclaration(decl);
386 	return super.visit(node, data);
387     }
388 
389     //@Override
390     //public Object visit(ASTSwitchStatement node, Object data) {
391 	//createLocalScope(node);
392 	//cont(node);
393 	//return data;
394     //}
395 
396     private void cont(PLSQLNode node) {
397 	super.visit(node, null);
398 	scopes.pop();
399     }
400 }