/**
* 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();
}
}