code: package it.unibz.inf.ontop.iq.optimizer.impl;
  
  import com.google.common.collect.ImmutableList;
  import it.unibz.inf.ontop.injection.CoreSingletons;
  import it.unibz.inf.ontop.iq.IQTree;
  import it.unibz.inf.ontop.iq.node.*;
  import it.unibz.inf.ontop.iq.transform.IQTreeTransformer;
  import it.unibz.inf.ontop.iq.transform.impl.DefaultNonRecursiveIQTreeTransformer;
  import it.unibz.inf.ontop.model.term.ImmutableExpression;
  import it.unibz.inf.ontop.utils.ImmutableCollectors;
  
  import java.util.Optional;
  
  /**
   * Transformer where the cardinality does matter for the current tree
   *
   */
  public abstract class AbstractBelowDistinctTransformer extends DefaultNonRecursiveIQTreeTransformer {
  
      private final IQTreeTransformer lookForDistinctOrLimit1Transformer;
      protected final CoreSingletons coreSingletons;
  
      protected AbstractBelowDistinctTransformer(IQTreeTransformer lookForDistinctOrLimit1Transformer,
                                                 CoreSingletons coreSingletons) {
          this.coreSingletons = coreSingletons;
          this.lookForDistinctOrLimit1Transformer = lookForDistinctOrLimit1Transformer;
      }
  
      @Override
      public IQTree transformInnerJoin(IQTree tree, InnerJoinNode rootNode, ImmutableList<IQTree> children) {
          ImmutableList<IQTree> transformedChildren = children.stream()
                  .map(this::transform)
                  .collect(ImmutableCollectors.toList());
  
  
          return furtherSimplifyInnerJoinChildren(
                      rootNode.getOptionalFilterCondition(),
                      transformedChildren)
                  .orElseGet(() -> transformedChildren.equals(children)
                          ? tree
                          : coreSingletons.getIQFactory().createNaryIQTree(rootNode, transformedChildren));
      }
  
      /**
       * Takes in account the interaction between children
       *
       * Returns empty() if no further optimization has been applied
       *
       */
      protected abstract Optional<IQTree> furtherSimplifyInnerJoinChildren(
              Optional<ImmutableExpression> optionalFilterCondition, ImmutableList<IQTree> children);
  
      @Override
      public IQTree transformConstruction(IQTree tree, ConstructionNode rootNode, IQTree child) {
          IQTree newChild = transform(child);
  
          return (newChild.equals(child))
                  ? tree
                  : coreSingletons.getIQFactory().createUnaryIQTree(rootNode, newChild);
      }
  
  
      @Override
      public IQTree transformSlice(IQTree tree, SliceNode sliceNode, IQTree child) {
          IQTree newChild = transform(child);
          return (newChild.equals(child))
                  ? tree
                  : coreSingletons.getIQFactory().createUnaryIQTree(sliceNode, newChild);
      }
  
      @Override
      public IQTree transformOrderBy(IQTree tree, OrderByNode rootNode, IQTree child) {
          IQTree newChild = transform(child);
          return (newChild.equals(child))
                  ? tree
                  : coreSingletons.getIQFactory().createUnaryIQTree(rootNode, newChild);
      }
  
      @Override
      public IQTree transformFilter(IQTree tree, FilterNode rootNode, IQTree child) {
          IQTree newChild = transform(child);
  
          return (newChild.equals(child))
                  ? tree
                  : coreSingletons.getIQFactory().createUnaryIQTree(rootNode, newChild);
      }
  
      @Override
      public IQTree transformUnion(IQTree tree, UnionNode rootNode, ImmutableList<IQTree> children) {
          ImmutableList<IQTree> newChildren = children.stream()
                  .map(this::transform)
                  .collect(ImmutableCollectors.toList());
  
          return newChildren.equals(children)
                  ? tree
                  : coreSingletons.getIQFactory().createNaryIQTree(rootNode, newChildren);
      }
  
      @Override
      public IQTree transformLeftJoin(IQTree tree, LeftJoinNode rootNode, IQTree leftChild, IQTree rightChild) {
          IQTree newLeftChild = transform(leftChild);
          IQTree newRightChild = transform(rightChild);
  
          return (newLeftChild.equals(leftChild) && newRightChild.equals(rightChild))
                  ? tree
                  : coreSingletons.getIQFactory().createBinaryNonCommutativeIQTree(rootNode, newLeftChild, newRightChild);
      }
  
      /**
       * By default, switch back to the "LookForDistinctTransformer"
       */
      protected IQTree transformUnaryNode(IQTree tree, UnaryOperatorNode rootNode, IQTree child) {
          return lookForDistinctOrLimit1Transformer.transform(tree);
      }
  
      /**
       * By default, switch back to the "LookForDistinctTransformer"
       */
      protected IQTree transformNaryCommutativeNode(IQTree tree, NaryOperatorNode rootNode, ImmutableList<IQTree> children) {
          return lookForDistinctOrLimit1Transformer.transform(tree);
      }
  
      /**
       * By default, switch back to the "LookForDistinctTransformer"
       */
      protected IQTree transformBinaryNonCommutativeNode(IQTree tree, BinaryNonCommutativeOperatorNode rootNode,
                                                         IQTree leftChild, IQTree rightChild) {
          return lookForDistinctOrLimit1Transformer.transform(tree);
      }
  }
  