/** * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.lang.java.symboltable; import java.util.ArrayDeque; import java.util.Deque; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTAnnotationTypeDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTBlock; import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement; import net.sourceforge.pmd.lang.java.ast.ASTCatchStatement; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBody; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTFinallyStatement; import net.sourceforge.pmd.lang.java.ast.ASTForStatement; import net.sourceforge.pmd.lang.java.ast.ASTFormalParameters; import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTLambdaExpression; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator; import net.sourceforge.pmd.lang.java.ast.ASTPackageDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement; import net.sourceforge.pmd.lang.java.ast.ASTTryStatement; import net.sourceforge.pmd.lang.java.ast.ASTTypeParameters; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.java.ast.AbstractJavaNode; import net.sourceforge.pmd.lang.java.ast.JavaNode; import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorAdapter; import net.sourceforge.pmd.lang.symboltable.Scope; /** * Visitor for scope creation. Visits all nodes of an AST and creates scope * objects for nodes representing syntactic entities which may contain * declarations. For example, a block may contain variable definitions (which * are declarations) and therefore needs a scope object where these declarations * can be associated, whereas an expression can't contain declarations and * therefore doesn't need a scope object. With the exception of global scopes, * each scope object is linked to its parent scope, which is the scope object of * the next embedding syntactic entity that has a scope. */ public class ScopeAndDeclarationFinder extends JavaParserVisitorAdapter { private ClassLoader classLoader; /** * A stack of scopes reflecting the scope hierarchy when a node is visited. * This is used to set the parents of the created scopes correctly. */ private Deque<Scope> scopes = new ArrayDeque<>(); /** * Creates a new {@link ScopeAndDeclarationFinder} using the current class * loader. */ public ScopeAndDeclarationFinder() { this(ScopeAndDeclarationFinder.class.getClassLoader()); } /** * Creates a new {@link ScopeAndDeclarationFinder}. * * @param classLoader * the class loader to use to resolve types, see * {@link SourceFileScope} and {@link TypeSet} */ public ScopeAndDeclarationFinder(ClassLoader classLoader) { this.classLoader = classLoader; } /** * Sets the scope of a node and adjusts the scope stack accordingly. The * scope on top of the stack is set as the parent of the given scope, which * is then also stored on the scope stack. * * @param newScope * the scope for the node. * @param node * the AST node for which the scope is to be set. * @throws java.util.EmptyStackException * if the scope stack is empty. */ private void addScope(Scope newScope, JavaNode node) { newScope.setParent(scopes.peek()); scopes.push(newScope); node.setScope(newScope); } /** * Creates a new local scope for an AST node. The scope on top of the stack * is set as the parent of the new scope, which is then also stored on the * scope stack. * * @param node * the AST node for which the scope has to be created. * @throws java.util.EmptyStackException * if the scope stack is empty. */ private void createLocalScope(JavaNode node) { addScope(new LocalScope(), node); } /** * Creates a new method scope for an AST node. The scope on top of the stack * is set as the parent of the new scope, which is then also stored on the * scope stack. * * @param node * the AST node for which the scope has to be created. * @throws java.util.EmptyStackException * if the scope stack is empty. */ private void createMethodScope(JavaNode node) { addScope(new MethodScope(node), node); } /** * Creates a new class scope for an AST node. The scope on top of the stack * is set as the parent of the new scope, which is then also stored on the * scope stack. * * @param node * the AST node for which the scope has to be created. * @throws java.util.EmptyStackException * if the scope stack is empty. */ private void createClassScope(JavaNode node) { Scope s = ((JavaNode) node.jjtGetParent()).getScope(); ClassNameDeclaration classNameDeclaration = new ClassNameDeclaration(node); s.addDeclaration(classNameDeclaration); if (node instanceof ASTClassOrInterfaceBody) { addScope(new ClassScope(classNameDeclaration), node); } else { addScope(new ClassScope(node.getImage(), classNameDeclaration), node); } } /** * Creates a new global scope for an AST node. The new scope is stored on * the scope stack. * * @param node * the AST node for which the scope has to be created. */ private void createSourceFileScope(ASTCompilationUnit node) { // When we do full symbol resolution, we'll need to add a truly // top-level GlobalScope. SourceFileScope scope; ASTPackageDeclaration n = node.getPackageDeclaration(); if (n != null) { scope = new SourceFileScope(classLoader, n.jjtGetChild(0).getImage()); } else { scope = new SourceFileScope(classLoader); } scope.configureImports(node.findChildrenOfType(ASTImportDeclaration.class)); scopes.push(scope); node.setScope(scope); } @Override public Object visit(ASTCompilationUnit node, Object data) { createSourceFileScope(node); cont(node); return data; } @Override public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { createClassScope(node); cont(node); return data; } @Override public Object visit(ASTEnumDeclaration node, Object data) { createClassScope(node); ((ClassScope) node.getScope()).setIsEnum(true); cont(node); return data; } @Override public Object visit(ASTAnnotationTypeDeclaration node, Object data) { createClassScope(node); cont(node); return data; } @Override public Object visit(ASTClassOrInterfaceBody node, Object data) { if (node.isAnonymousInnerClass() || node.isEnumChild()) { createClassScope(node); cont(node); } else { super.visit(node, data); } return data; } @Override public Object visit(ASTBlock node, Object data) { createLocalScope(node); cont(node); return data; } @Override public Object visit(ASTCatchStatement node, Object data) { createLocalScope(node); cont(node); return data; } @Override public Object visit(ASTFinallyStatement node, Object data) { createLocalScope(node); cont(node); return data; } @Override public Object visit(ASTConstructorDeclaration node, Object data) { /* * Local variables declared inside the constructor need to be in a * different scope so special handling is needed */ createMethodScope(node); Scope methodScope = node.getScope(); Node formalParameters = node.jjtGetChild(0); int i = 1; int n = node.jjtGetNumChildren(); if (!(formalParameters instanceof ASTFormalParameters)) { visit((ASTTypeParameters) formalParameters, data); formalParameters = node.jjtGetChild(1); i++; } visit((ASTFormalParameters) formalParameters, data); Scope localScope = null; for (; i < n; i++) { JavaNode b = (JavaNode) node.jjtGetChild(i); if (b instanceof ASTBlockStatement) { if (localScope == null) { createLocalScope(node); localScope = node.getScope(); } b.setScope(localScope); visit(b, data); } else { visit(b, data); } } if (localScope != null) { // pop the local scope scopes.pop(); // reset the correct scope for the constructor node.setScope(methodScope); } // pop the method scope scopes.pop(); return data; } @Override public Object visit(ASTMethodDeclaration node, Object data) { createMethodScope(node); ASTMethodDeclarator md = node.getFirstChildOfType(ASTMethodDeclarator.class); node.getScope().getEnclosingScope(ClassScope.class).addDeclaration(new MethodNameDeclaration(md)); cont(node); return data; } @Override public Object visit(ASTLambdaExpression node, Object data) { createLocalScope(node); cont(node); return data; } @Override public Object visit(ASTTryStatement node, Object data) { createLocalScope(node); cont(node); return data; } // TODO - what about while loops and do loops? @Override public Object visit(ASTForStatement node, Object data) { createLocalScope(node); cont(node); return data; } @Override public Object visit(ASTIfStatement node, Object data) { createLocalScope(node); cont(node); return data; } @Override public Object visit(ASTVariableDeclaratorId node, Object data) { VariableNameDeclaration decl = new VariableNameDeclaration(node); node.getScope().addDeclaration(decl); node.setNameDeclaration(decl); return super.visit(node, data); } @Override public Object visit(ASTSwitchStatement node, Object data) { createLocalScope(node); cont(node); return data; } private void cont(AbstractJavaNode node) { super.visit(node, null); scopes.pop(); } }