code: package it.unibz.inf.ontop.iq.transformer.impl;
  
  import com.google.common.collect.ImmutableList;
  import com.google.common.collect.ImmutableSet;
  import it.unibz.inf.ontop.injection.CoreSingletons;
  import it.unibz.inf.ontop.injection.IntermediateQueryFactory;
  import it.unibz.inf.ontop.iq.IQTree;
  import it.unibz.inf.ontop.iq.LeafIQTree;
  import it.unibz.inf.ontop.iq.UnaryIQTree;
  import it.unibz.inf.ontop.iq.node.*;
  import it.unibz.inf.ontop.iq.visit.IQVisitor;
  import it.unibz.inf.ontop.model.term.ImmutableExpression;
  import it.unibz.inf.ontop.model.term.ImmutableTerm;
  import it.unibz.inf.ontop.model.term.TermFactory;
  import it.unibz.inf.ontop.model.term.Variable;
  import it.unibz.inf.ontop.substitution.ImmutableSubstitution;
  import it.unibz.inf.ontop.utils.ImmutableCollectors;
  
  import java.util.Optional;
  
  /**
   * Tries to push down the expression given.
   * If it succeeds, return a IQTree, otherwise nothing.
   *
   * ONLY CARES about the expression given as input.
   *
   */
  public class BooleanExpressionPusher implements IQVisitor<Optional<IQTree>> {
  
      private final ImmutableExpression expressionToPushDown;
      private final CoreSingletons coreSingletons;
      private final IntermediateQueryFactory iqFactory;
      private final TermFactory termFactory;
  
      protected BooleanExpressionPusher(ImmutableExpression expressionToPushDown,
                                        CoreSingletons coreSingletons) {
          this.expressionToPushDown = expressionToPushDown;
          this.coreSingletons = coreSingletons;
          this.iqFactory = coreSingletons.getIQFactory();
          this.termFactory = coreSingletons.getTermFactory();
      }
  
      @Override
      public Optional<IQTree> visitConstruction(ConstructionNode rootNode, IQTree child) {
          ImmutableSubstitution<ImmutableTerm> substitution = rootNode.getSubstitution();
  
          ImmutableExpression newExpression = substitution.applyToBooleanExpression(expressionToPushDown);
  
          BooleanExpressionPusher newPusher = new BooleanExpressionPusher(newExpression, coreSingletons);
          IQTree newChild = child.acceptVisitor(newPusher)
                  .orElseGet(() ->  iqFactory.createUnaryIQTree(
                          iqFactory.createFilterNode(newExpression),
                          child));
  
          return Optional.of(iqFactory.createUnaryIQTree(rootNode, newChild));
      }
      @Override
      public Optional<IQTree> visitAggregation(AggregationNode aggregationNode, IQTree child) {
          ImmutableSet<Variable> expressionVariables = expressionToPushDown.getVariableStream()
                  .collect(ImmutableCollectors.toSet());
  
          return (aggregationNode.getGroupingVariables().containsAll(expressionVariables))
                  ? visitPassingUnaryNode(aggregationNode, child)
                  : Optional.empty();
      }
  
      protected Optional<IQTree> visitPassingUnaryNode(UnaryOperatorNode rootNode, IQTree child) {
          IQTree newChild = child.acceptVisitor(this)
                  .orElseGet(() ->  iqFactory.createUnaryIQTree(
                          iqFactory.createFilterNode(expressionToPushDown),
                          child));
  
          return Optional.of(iqFactory.createUnaryIQTree(rootNode, newChild));
      }
  
      /**
       * NB: focuses on the expressionToPushDown, NOT on pushing down its own expression
       */
      @Override
      public Optional<IQTree> visitFilter(FilterNode rootNode, IQTree child) {
          Optional<IQTree> newChild = child.acceptVisitor(this);
  
          UnaryIQTree newTree = newChild
                  .map(c -> iqFactory.createUnaryIQTree(rootNode, c))
                  .orElseGet(() ->
                          iqFactory.createUnaryIQTree(
                                  iqFactory.createFilterNode(
                                          termFactory.getConjunction(rootNode.getFilterCondition(), expressionToPushDown)),
                                  child));
  
          return Optional.of(newTree);
      }
  
      @Override
      public Optional<IQTree> visitFlatten(FlattenNode rootNode, IQTree child) {
          Optional<Variable> indexVariable = rootNode.getIndexVariable();
          return expressionToPushDown.getVariableStream()
                      .anyMatch(v -> v.equals(rootNode.getOutputVariable()) ||
                              (indexVariable.isPresent() && v.equals(indexVariable.get())))?
                  Optional.empty():
                  visitPassingUnaryNode(rootNode, child);
      }
  
      @Override
      public Optional<IQTree> visitDistinct(DistinctNode rootNode, IQTree child) {
          return visitPassingUnaryNode(rootNode, child);
      }
  
      /**
       * The slice blocks
       */
      @Override
      public Optional<IQTree> visitSlice(SliceNode sliceNode, IQTree child) {
          return Optional.empty();
      }
  
      @Override
      public Optional<IQTree> visitOrderBy(OrderByNode rootNode, IQTree child) {
          return visitPassingUnaryNode(rootNode, child);
      }
  
      /**
       * Blocks by default
       */
      @Override
      public Optional<IQTree> visitNonStandardUnaryNode(UnaryOperatorNode rootNode, IQTree child) {
          return Optional.empty();
      }
  
      /**
       * Only pushes on the left
       *
       * TODO: consider pushing on the right safe expressions
       */
      @Override
      public Optional<IQTree> visitLeftJoin(LeftJoinNode rootNode, IQTree leftChild, IQTree rightChild) {
          ImmutableSet<Variable> expressionVariables = expressionToPushDown.getVariableStream()
                  .collect(ImmutableCollectors.toSet());
  
          if (leftChild.getVariables().containsAll(expressionVariables)) {
              Optional<IQTree> newLeftChild = leftChild.acceptVisitor(this);
              return newLeftChild
                      .map(l -> iqFactory.createBinaryNonCommutativeIQTree(rootNode, l, rightChild));
          }
          else
              return Optional.empty();
      }
  
      /**
       * Blocks by default
       */
      @Override
      public Optional<IQTree> visitNonStandardBinaryNonCommutativeNode(BinaryNonCommutativeOperatorNode rootNode,
                                                                       IQTree leftChild, IQTree rightChild) {
          return Optional.empty();
      }
  
      @Override
      public Optional<IQTree> visitInnerJoin(InnerJoinNode rootNode, ImmutableList<IQTree> children) {
          ImmutableSet<Variable> expressionVariables = expressionToPushDown.getVariableStream()
                  .collect(ImmutableCollectors.toSet());
  
          ImmutableList<IQTree> newChildren = children.stream()
                  .map(c -> c.getVariables().containsAll(expressionVariables)
                          ? c.acceptVisitor(this).orElse(c)
                          : c)
                  .collect(ImmutableCollectors.toList());
  
          InnerJoinNode newJoinNode = newChildren.equals(children)
                  // Refused by the children
                  ? iqFactory.createInnerJoinNode(
                  rootNode.getOptionalFilterCondition()
                          .map(c -> termFactory.getConjunction(c, expressionToPushDown))
                          .orElse(expressionToPushDown))
                  : rootNode;
  
          return Optional.of(iqFactory.createNaryIQTree(newJoinNode, newChildren));
      }
  
      @Override
      public Optional<IQTree> visitUnion(UnionNode rootNode, ImmutableList<IQTree> children) {
          ImmutableList<IQTree> newChildren = children.stream()
                  .map(c -> c.acceptVisitor(this)
                          .orElseGet(() -> iqFactory.createUnaryIQTree(
                                  iqFactory.createFilterNode(expressionToPushDown),
                                  c)))
                  .collect(ImmutableCollectors.toList());
  
          return Optional.of(iqFactory.createNaryIQTree(rootNode, newChildren));
      }
  
      /**
       * Blocks by default
       */
      @Override
      public Optional<IQTree> visitNonStandardNaryNode(NaryOperatorNode rootNode, ImmutableList<IQTree> children) {
          return Optional.empty();
      }
  
  
      /**
       * Leafs nodes do not accept expressions
       */
      private Optional<IQTree> visitLeafNode() {
          return Optional.empty();
      }
  
      @Override
      public Optional<IQTree> visitIntensionalData(IntensionalDataNode dataNode) {
          return visitLeafNode();
      }
  
      @Override
      public Optional<IQTree> visitExtensionalData(ExtensionalDataNode dataNode) {
          return visitLeafNode();
      }
  
      @Override
      public Optional<IQTree> visitEmpty(EmptyNode node) {
          return visitLeafNode();
      }
  
      @Override
      public Optional<IQTree> visitTrue(TrueNode node) {
          return visitLeafNode();
      }
  
      @Override
      public Optional<IQTree> visitNative(NativeNode nativeNode) {
          return visitLeafNode();
      }
  
      @Override
      public Optional<IQTree> visitValues(ValuesNode valuesNode) {
          return visitLeafNode();
      }
  
      @Override
      public Optional<IQTree> visitNonStandardLeafNode(LeafIQTree leafNode) {
          return visitLeafNode();
      }
  
  }
  