package graphql.validation;
import graphql.language.*;
import java.util.*;
public class RulesVisitor implements QueryLanguageVisitor {
private final List<AbstractRule> rules = new ArrayList<>();
private ValidationContext validationContext;
private boolean subVisitor;
private List<AbstractRule> rulesVisitingFragmentSpreads = new ArrayList<>();
private Map<Node, List<AbstractRule>> rulesToSkipByUntilNode = new IdentityHashMap<>();
private Set<AbstractRule> rulesToSkip = new LinkedHashSet<>();
public RulesVisitor(ValidationContext validationContext, List<AbstractRule> rules) {
this(validationContext, rules, false);
}
public RulesVisitor(ValidationContext validationContext, List<AbstractRule> rules, boolean subVisitor) {
this.validationContext = validationContext;
this.subVisitor = subVisitor;
this.rules.addAll(rules);
this.subVisitor = subVisitor;
findRulesVisitingFragmentSpreads();
}
private void findRulesVisitingFragmentSpreads() {
for (AbstractRule rule : rules) {
if (rule.isVisitFragmentSpreads()) {
rulesVisitingFragmentSpreads.add(rule);
}
}
}
@Override
public void enter(Node node, List<Node> ancestors) {
// System.out.println("enter: " + node);
validationContext.getTraversalContext().enter(node, ancestors);
Set<AbstractRule> tmpRulesSet = new LinkedHashSet<>(this.rules);
tmpRulesSet.removeAll(rulesToSkip);
List<AbstractRule> rulesToConsider = new ArrayList<>(tmpRulesSet);
if (node instanceof Argument) {
checkArgument((Argument) node, rulesToConsider);
} else if (node instanceof TypeName) {
checkTypeName((TypeName) node, rulesToConsider);
} else if (node instanceof VariableDefinition) {
checkVariableDefinition((VariableDefinition) node, rulesToConsider);
} else if (node instanceof Field) {
checkField((Field) node, rulesToConsider);
} else if (node instanceof InlineFragment) {
checkInlineFragment((InlineFragment) node, rulesToConsider);
} else if (node instanceof Directive) {
checkDirective((Directive) node, ancestors, rulesToConsider);
} else if (node instanceof FragmentSpread) {
checkFragmentSpread((FragmentSpread) node, rulesToConsider, ancestors);
} else if (node instanceof FragmentDefinition) {
checkFragmentDefinition((FragmentDefinition) node, rulesToConsider);
} else if (node instanceof OperationDefinition) {
checkOperationDefinition((OperationDefinition) node, rulesToConsider);
} else if (node instanceof VariableReference) {
checkVariable((VariableReference) node, rulesToConsider);
} else if (node instanceof SelectionSet) {
checkSelectionSet((SelectionSet) node, rulesToConsider);
}
}
private void checkArgument(Argument node, List<AbstractRule> rules) {
for (AbstractRule rule : rules) {
rule.checkArgument(node);
}
}
private void checkTypeName(TypeName node, List<AbstractRule> rules) {
for (AbstractRule rule : rules) {
rule.checkTypeName(node);
}
}
private void checkVariableDefinition(VariableDefinition variableDefinition, List<AbstractRule> rules) {
for (AbstractRule rule : rules) {
rule.checkVariableDefinition(variableDefinition);
}
}
private void checkField(Field field, List<AbstractRule> rules) {
for (AbstractRule rule : rules) {
rule.checkField(field);
}
}
private void checkInlineFragment(InlineFragment inlineFragment, List<AbstractRule> rules) {
for (AbstractRule rule : rules) {
rule.checkInlineFragment(inlineFragment);
}
}
private void checkDirective(Directive directive, List<Node> ancestors, List<AbstractRule> rules) {
for (AbstractRule rule : rules) {
rule.checkDirective(directive, ancestors);
}
}
private void checkFragmentSpread(FragmentSpread fragmentSpread, List<AbstractRule> rules, List<Node> ancestors) {
for (AbstractRule rule : rules) {
rule.checkFragmentSpread(fragmentSpread);
}
List<AbstractRule> rulesVisitingFragmentSpreads = getRulesVisitingFragmentSpreads(rules);
if (rulesVisitingFragmentSpreads.size() > 0) {
FragmentDefinition fragment = validationContext.getFragment(fragmentSpread.getName());
if (fragment != null && !ancestors.contains(fragment)) {
new LanguageTraversal(ancestors).traverse(fragment, new RulesVisitor(validationContext, rulesVisitingFragmentSpreads, true));
}
}
}
private List<AbstractRule> getRulesVisitingFragmentSpreads(List<AbstractRule> rules) {
List<AbstractRule> result = new ArrayList<>();
for (AbstractRule rule : rules) {
if (rule.isVisitFragmentSpreads()) result.add(rule);
}
return result;
}
private void checkFragmentDefinition(FragmentDefinition fragmentDefinition, List<AbstractRule> rules) {
if (!subVisitor) {
rulesToSkipByUntilNode.put(fragmentDefinition, new ArrayList<>(rulesVisitingFragmentSpreads));
rulesToSkip.addAll(rulesVisitingFragmentSpreads);
}
for (AbstractRule rule : rules) {
if (!subVisitor && (rule.isVisitFragmentSpreads())) continue;
rule.checkFragmentDefinition(fragmentDefinition);
}
}
private void checkOperationDefinition(OperationDefinition operationDefinition, List<AbstractRule> rules) {
for (AbstractRule rule : rules) {
rule.checkOperationDefinition(operationDefinition);
}
}
private void checkSelectionSet(SelectionSet selectionSet, List<AbstractRule> rules) {
for (AbstractRule rule : rules) {
rule.checkSelectionSet(selectionSet);
}
}
private void checkVariable(VariableReference variableReference, List<AbstractRule> rules) {
for (AbstractRule rule : rules) {
rule.checkVariable(variableReference);
}
}
@Override
public void leave(Node node, List<Node> ancestors) {
validationContext.getTraversalContext().leave(node, ancestors);
if (node instanceof Document) {
documentFinished((Document) node);
} else if (node instanceof OperationDefinition) {
leaveOperationDefinition((OperationDefinition) node);
} else if (node instanceof SelectionSet) {
leaveSelectionSet((SelectionSet) node);
}
if (rulesToSkipByUntilNode.containsKey(node)) {
rulesToSkip.removeAll(rulesToSkipByUntilNode.get(node));
rulesToSkipByUntilNode.remove(node);
}
}
private void leaveSelectionSet(SelectionSet selectionSet) {
for (AbstractRule rule : rules) {
rule.leaveSelectionSet(selectionSet);
}
}
private void leaveOperationDefinition(OperationDefinition operationDefinition) {
for (AbstractRule rule : rules) {
rule.leaveOperationDefinition(operationDefinition);
}
}
private void documentFinished(Document document) {
for (AbstractRule rule : rules) {
rule.documentFinished(document);
}
}
}