package org.fugazi.ql.type_checker.visitor;
import org.fugazi.ql.ast.expression.Expression;
import org.fugazi.ql.ast.expression.literal.ID;
import org.fugazi.ql.ast.form.form_data.QLFormDataStorage;
import org.fugazi.ql.ast.form.form_data.visitor.FullQLFormVisitor;
import org.fugazi.ql.ast.statement.ComputedQuestion;
import org.fugazi.ql.type_checker.dependency.DependencyManager;
import org.fugazi.ql.type_checker.issue.error.CyclicDependenciesError;
import java.util.ArrayList;
import java.util.List;
public class CyclicDependenciesVisitor extends FullQLFormVisitor {
// used to detect circular dependencies
private final DependencyManager questionDependencies;
private ID assignableIdLiteral;
public CyclicDependenciesVisitor(QLFormDataStorage _formData){
super(_formData);
this.questionDependencies = new DependencyManager();
}
/**
* =======================
* General visitors
* =======================
*/
@Override
public Void visitComputedQuestion(ComputedQuestion assignQuest) {
ID identifier = assignQuest.getIdentifier();
Expression computed = assignQuest.getComputedExpression();
// check if no circular reference
// is performed while visiting idLiterals
// from the computed expression
// first - mark which identifier is dependent on
// each of the identifiers that will appear while
// visiting the computed expression
this.assignableIdLiteral = identifier;
// this is the only part of visitedQuestion
// that needs further visiting
computed.accept(this);
// analyzing dependencies finished for
// identifier from this computed question
this.assignableIdLiteral = null;
return null;
}
/**
* =======================
* Literal visitors
* =======================
*/
@Override
public Void visitID(ID idLiteral) {
// if we are inside a computed expression
// a dependency needs to be added and marked
if (this.assignableIdLiteral != null) {
// assignableIdLiteral is dependent on
// the current idLiteral
this.addAndCheckDependency(this.assignableIdLiteral, idLiteral);
}
return null;
}
/**
* =======================
* Checker functions
* =======================
*/
// a = b, a - to, b - from
private boolean checkDependency(ID to, ID from) {
List<String> toDependencies =
this.questionDependencies.getIdDependencyNames(to);
if ((toDependencies != null)
&& toDependencies.contains(from.getName()))
{
return true;
}
return false;
}
/**
* =======================
* Private data handling functions
* =======================
*/
private void updateDependencyGraph(ID to, ID from) {
// get all indirectly affected nodes (that depend on the to)
List<ID> idsToAddNewDependencyTo = this.getAllIdsWithNewIndirectDependency(to);
// for a new to add also all dependencies of from (propagate backwards)
for (ID newDependant : idsToAddNewDependencyTo) {
this.addSingleDependencyForId(newDependant, from);
}
// for a new to add also all dependencies of from (propagate forward)
this.addDependenciesForId(to, this.questionDependencies.getIdDependencies(from));
}
private List<ID> getAllIdsWithNewIndirectDependency(ID depender) {
// all the ids that are dependent on depender directly or indirectly
// ids depending on them need to be updated too with the new dependee
List<ID> idsToAddNewDependencyTo = new ArrayList<>();
// temporary list used for traversing the graph.
// pop first element, update all it's dependencies and add them
// used to traverse the graph until all elements indirectly affected
// by new dependence relation found
List<ID> idsToAnalyze = new ArrayList<>();
idsToAnalyze.add(depender);
while (idsToAnalyze.size() > 0) {
ID indirectFrom = idsToAnalyze.remove(0);
// check all elements that depend on from and therefore indirectly on passed depender
for (ID from : this.questionDependencies.getIds()) {
List<String> dependenciesForKey = this.questionDependencies.getIdDependencyNames(from);
if ((dependenciesForKey != null)
&& dependenciesForKey.contains(indirectFrom.getName())) {
idsToAddNewDependencyTo.add(from);
}
}
idsToAddNewDependencyTo.add(depender);
}
return idsToAddNewDependencyTo;
}
private void addDependenciesForId(ID to, List<ID> fromIds) {
if (fromIds != null) {
for (ID newDependee : fromIds) {
this.addSingleDependencyForId(to, newDependee);
}
}
}
private void addSingleDependencyForId(ID to, ID from) {
this.questionDependencies.addIdDependenant(to, from);
}
private void addAndCheckDependency(ID to, ID from) {
boolean revertedDependencyExists = this.checkDependency(from, to);
if (revertedDependencyExists) {
this.astIssueHandler.registerNewError(
new CyclicDependenciesError(), to,
"Circular dependency between this node and " +
from.toString() + "."
);
}
this.updateDependencyGraph(to, from);
}
}