/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.plsql.dfa;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.dfa.DataFlowNode;
import net.sourceforge.pmd.lang.dfa.StartOrEndDataFlowNode;
import net.sourceforge.pmd.lang.dfa.VariableAccess;
import net.sourceforge.pmd.lang.plsql.ast.ASTCompoundTriggerBlock;
import net.sourceforge.pmd.lang.plsql.ast.ASTFormalParameter;
import net.sourceforge.pmd.lang.plsql.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.lang.plsql.ast.ASTPackageBody;
import net.sourceforge.pmd.lang.plsql.ast.ASTProgramUnit;
import net.sourceforge.pmd.lang.plsql.ast.ASTTriggerTimingPointSection;
import net.sourceforge.pmd.lang.plsql.ast.ASTTriggerUnit;
import net.sourceforge.pmd.lang.plsql.ast.ASTTypeMethod;
import net.sourceforge.pmd.lang.plsql.ast.ASTVariableOrConstantInitializer;
import net.sourceforge.pmd.lang.plsql.ast.PLSQLNode;
import net.sourceforge.pmd.lang.plsql.ast.PLSQLParserVisitorAdapter;
import net.sourceforge.pmd.lang.plsql.symboltable.PLSQLNameOccurrence;
import net.sourceforge.pmd.lang.symboltable.NameDeclaration;
import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
/**
* Searches for special nodes and computes based on the sequence, the
* type of access of a variable.
*
* @author raik, Sven Jacob
*/
public class VariableAccessVisitor extends PLSQLParserVisitorAdapter {
public void compute(ASTMethodDeclaration node) {
// This includes Package Bodies and Object Type Bodies
if (node.jjtGetParent() instanceof ASTPackageBody) {
this.computeNow(node);
}
}
public void compute(ASTProgramUnit node) {
// Treat Compound Trigger as a Package Body
if (node.jjtGetParent() instanceof ASTPackageBody || node.jjtGetParent() instanceof ASTCompoundTriggerBlock) {
this.computeNow(node);
}
}
public void compute(ASTTypeMethod node) {
if (node.jjtGetParent() instanceof ASTPackageBody) {
this.computeNow(node);
}
}
public void compute(ASTTriggerUnit node) {
this.computeNow(node);
}
public void compute(ASTTriggerTimingPointSection node) {
this.computeNow(node);
}
/*
* SRT public void compute(ASTConstructorDeclaration node) {
* this.computeNow(node); }
*/
private void computeNow(Node node) {
DataFlowNode inode = node.getDataFlowNode();
List<VariableAccess> undefinitions = markUsages(inode);
// all variables are first in state undefinition
DataFlowNode firstINode = inode.getFlow().get(0);
firstINode.setVariableAccess(undefinitions);
// all variables are getting undefined when leaving scope
DataFlowNode lastINode = inode.getFlow().get(inode.getFlow().size() - 1);
lastINode.setVariableAccess(undefinitions);
}
private List<VariableAccess> markUsages(DataFlowNode inode) {
// undefinitions was once a field... seems like it works fine as a local
List<VariableAccess> undefinitions = new ArrayList<>();
Set<Map<NameDeclaration, List<NameOccurrence>>> variableDeclarations = collectDeclarations(inode);
for (Map<NameDeclaration, List<NameOccurrence>> declarations : variableDeclarations) {
for (Map.Entry<NameDeclaration, List<NameOccurrence>> entry : declarations.entrySet()) {
NameDeclaration vnd = entry.getKey();
if (vnd.getNode().jjtGetParent() instanceof ASTFormalParameter) {
// no definition/undefinition/references for parameters
continue;
} else if (vnd.getNode().jjtGetParent()
.getFirstDescendantOfType(ASTVariableOrConstantInitializer.class) != null) {
// add definition for initialized variables
addVariableAccess(vnd.getNode(), new VariableAccess(VariableAccess.DEFINITION, vnd.getImage()),
inode.getFlow());
}
undefinitions.add(new VariableAccess(VariableAccess.UNDEFINITION, vnd.getImage()));
for (NameOccurrence occurrence : entry.getValue()) {
addAccess(occurrence, inode);
}
}
}
return undefinitions;
}
private Set<Map<NameDeclaration, List<NameOccurrence>>> collectDeclarations(DataFlowNode inode) {
Set<Map<NameDeclaration, List<NameOccurrence>>> decls = new HashSet<>();
Map<NameDeclaration, List<NameOccurrence>> varDecls;
for (int i = 0; i < inode.getFlow().size(); i++) {
DataFlowNode n = inode.getFlow().get(i);
if (n instanceof StartOrEndDataFlowNode) {
continue;
}
varDecls = ((PLSQLNode) n.getNode()).getScope().getDeclarations();
if (!decls.contains(varDecls)) {
decls.add(varDecls);
}
}
return decls;
}
private void addAccess(NameOccurrence occ, DataFlowNode inode) {
PLSQLNameOccurrence occurrence = (PLSQLNameOccurrence) occ;
if (occurrence.isOnLeftHandSide()) {
this.addVariableAccess(occurrence.getLocation(),
new VariableAccess(VariableAccess.DEFINITION, occurrence.getImage()), inode.getFlow());
} else if (occurrence.isOnRightHandSide()
|| !occurrence.isOnLeftHandSide() && !occurrence.isOnRightHandSide()) {
this.addVariableAccess(occurrence.getLocation(),
new VariableAccess(VariableAccess.REFERENCING, occurrence.getImage()), inode.getFlow());
}
}
/**
* Adds a VariableAccess to a dataflow node.
*
* @param node
* location of the access of a variable
* @param va
* variable access to add
* @param flow
* dataflownodes that can contain the node.
*/
private void addVariableAccess(Node node, VariableAccess va, List<DataFlowNode> flow) {
// backwards to find the right inode (not a method declaration)
for (int i = flow.size() - 1; i > 0; i--) {
DataFlowNode inode = flow.get(i);
if (inode.getNode() == null) {
continue;
}
List<? extends Node> children = inode.getNode().findDescendantsOfType(node.getClass());
for (Node n : children) {
if (node.equals(n)) {
List<VariableAccess> v = new ArrayList<>();
v.add(va);
inode.setVariableAccess(v);
return;
}
}
}
}
}