code: package it.unibz.inf.ontop.answering.reformulation.generation.impl;
  
  
  import com.google.inject.assistedinject.Assisted;
  import com.google.inject.assistedinject.AssistedInject;
  import it.unibz.inf.ontop.injection.OntopReformulationSQLSettings;
  import it.unibz.inf.ontop.iq.transform.IQTree2NativeNodeGenerator;
  import it.unibz.inf.ontop.answering.reformulation.generation.NativeQueryGenerator;
  import it.unibz.inf.ontop.answering.reformulation.generation.PostProcessingProjectionSplitter;
  import it.unibz.inf.ontop.generation.normalization.DialectExtraNormalizer;
  import it.unibz.inf.ontop.datalog.UnionFlattener;
  import it.unibz.inf.ontop.dbschema.DBParameters;
  import it.unibz.inf.ontop.injection.IntermediateQueryFactory;
  import it.unibz.inf.ontop.injection.OptimizerFactory;
  import it.unibz.inf.ontop.iq.IQ;
  import it.unibz.inf.ontop.iq.IQTree;
  import it.unibz.inf.ontop.iq.UnaryIQTree;
  import it.unibz.inf.ontop.iq.node.*;
  import it.unibz.inf.ontop.iq.optimizer.PostProcessableFunctionLifter;
  import it.unibz.inf.ontop.iq.optimizer.TermTypeTermLifter;
  import it.unibz.inf.ontop.iq.transformer.BooleanExpressionPushDownTransformer;
  import it.unibz.inf.ontop.iq.transformer.EmptyRowsValuesNodeTransformer;
  import it.unibz.inf.ontop.utils.VariableGenerator;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  /**
   * TODO: explain
   *
   * See TranslationFactory for creating a new instance.
   *
   */
  public class SQLGeneratorImpl implements NativeQueryGenerator {
  
      private static final Logger LOGGER = LoggerFactory.getLogger(SQLGeneratorImpl.class);
      private final DBParameters dbParameters;
      private final IntermediateQueryFactory iqFactory;
      private final UnionFlattener unionFlattener;
      private final OptimizerFactory optimizerFactory;
      private final PostProcessingProjectionSplitter projectionSplitter;
      private final TermTypeTermLifter rdfTypeLifter;
      private final PostProcessableFunctionLifter functionLifter;
      private final IQTree2NativeNodeGenerator defaultIQTree2NativeNodeGenerator;
      private final OntopReformulationSQLSettings settings;
      private final DialectExtraNormalizer extraNormalizer;
      private final BooleanExpressionPushDownTransformer pushDownTransformer;
      private final EmptyRowsValuesNodeTransformer valuesNodeTransformer;
  
      @AssistedInject
      private SQLGeneratorImpl(@Assisted DBParameters dbParameters,
                               IntermediateQueryFactory iqFactory,
                               UnionFlattener unionFlattener,
                               OptimizerFactory optimizerFactory,
                               PostProcessingProjectionSplitter projectionSplitter,
                               TermTypeTermLifter rdfTypeLifter, PostProcessableFunctionLifter functionLifter,
                               IQTree2NativeNodeGenerator defaultIQTree2NativeNodeGenerator,
                               DialectExtraNormalizer extraNormalizer, BooleanExpressionPushDownTransformer pushDownTransformer,
                               EmptyRowsValuesNodeTransformer valuesNodeTransformer,
                               OntopReformulationSQLSettings settings)
      {
          this.functionLifter = functionLifter;
          this.extraNormalizer = extraNormalizer;
          this.pushDownTransformer = pushDownTransformer;
          this.valuesNodeTransformer = valuesNodeTransformer;
          this.dbParameters = dbParameters;
          this.iqFactory = iqFactory;
          this.unionFlattener = unionFlattener;
          this.optimizerFactory = optimizerFactory;
          this.projectionSplitter = projectionSplitter;
          this.rdfTypeLifter = rdfTypeLifter;
          this.defaultIQTree2NativeNodeGenerator = defaultIQTree2NativeNodeGenerator;
          this.settings = settings;
      }
  
      @Override
      public IQ generateSourceQuery(IQ query) {
          return generateSourceQuery(query, settings.isPostProcessingAvoided());
      }
  
      @Override
      public IQ generateSourceQuery(IQ query, boolean avoidPostProcessing) {
          return generateSourceQuery(query, avoidPostProcessing, false);
      }
  
      @Override
      public IQ generateSourceQuery(IQ query, boolean avoidPostProcessing, boolean tolerateUnknownTypes) {
          if (query.getTree().isDeclaredAsEmpty())
              return query;
  
          IQ rdfTypeLiftedIQ = rdfTypeLifter.optimize(query);
          LOGGER.debug("After lifting the RDF types:\n{}\n", rdfTypeLiftedIQ);
  
          IQ liftedIQ = functionLifter.optimize(rdfTypeLiftedIQ);
          LOGGER.debug("After lifting the post-processable function symbols:\n{}\n", liftedIQ);
  
          PostProcessingProjectionSplitter.PostProcessingSplit split = projectionSplitter.split(liftedIQ, avoidPostProcessing);
  
          IQTree normalizedSubTree = normalizeSubTree(split.getSubTree(), split.getVariableGenerator());
          // Late detection of emptiness
          if (normalizedSubTree.isDeclaredAsEmpty())
              return iqFactory.createIQ(query.getProjectionAtom(),
                      iqFactory.createEmptyNode(query.getProjectionAtom().getVariables()));
  
          NativeNode nativeNode = generateNativeNode(normalizedSubTree, tolerateUnknownTypes);
  
          UnaryIQTree newTree = iqFactory.createUnaryIQTree(split.getPostProcessingConstructionNode(), nativeNode);
  
          return iqFactory.createIQ(query.getProjectionAtom(), newTree);
      }
  
      /**
       * TODO: what about the distinct?
       * TODO: move the distinct and slice lifting to the post-processing splitter
       */
      private IQTree normalizeSubTree(IQTree subTree, VariableGenerator variableGenerator) {
  
          IQTree sliceLiftedTree = liftSlice(subTree);
          LOGGER.debug("New query after lifting the slice:\n{}\n", sliceLiftedTree);
  
          // TODO: check if still needed
          IQTree flattenSubTree = unionFlattener.optimize(sliceLiftedTree, variableGenerator);
          LOGGER.debug("New query after flattening the union:\n{}\n", flattenSubTree);
  
          IQTree pushedDownSubTree = pushDownTransformer.transform(flattenSubTree);
          LOGGER.debug("New query after pushing down:\n{}\n", pushedDownSubTree);
  
          IQTree treeAfterPullOut = optimizerFactory.createEETransformer(variableGenerator).transform(pushedDownSubTree);
          LOGGER.debug("Query tree after pulling out equalities:\n{}\n", treeAfterPullOut);
  
          // Top construction elimination when it causes problems
          // Pattern: [LIMIT], CONSTRUCTION, DISTINCT, [CONSTRUCTION] and ORDER BY
          IQTree treeAfterTopConstructionNormalization = dropTopConstruct(treeAfterPullOut);
          LOGGER.debug("New query after top construction elimination in order by cases: \n" + treeAfterTopConstructionNormalization);
  
          // Handle VALUES [] () () edge case
          IQTree treeAfterEmptyRowsValuesNodeNormalization = valuesNodeTransformer.transform(
                  treeAfterTopConstructionNormalization, variableGenerator);
          LOGGER.debug("New query after empty rows values node transformation:\n{}\n", treeAfterEmptyRowsValuesNodeNormalization);
  
          // Dialect specific
          IQTree afterDialectNormalization = extraNormalizer.transform(treeAfterEmptyRowsValuesNodeNormalization, variableGenerator);
          LOGGER.debug("New query after the dialect-specific extra normalization:\n{}\n", afterDialectNormalization);
  
          return afterDialectNormalization;
      }
  
      private IQTree liftSlice(IQTree subTree) {
          if (subTree.getRootNode() instanceof ConstructionNode) {
              ConstructionNode constructionNode = (ConstructionNode) subTree.getRootNode();
              IQTree childTree = ((UnaryIQTree) subTree).getChild();
              if (childTree.getRootNode() instanceof SliceNode) {
                  /*
                   * Swap the top construction node and the slice
                   */
                  SliceNode sliceNode = (SliceNode) childTree.getRootNode();
                  IQTree grandChildTree = ((UnaryIQTree) childTree).getChild();
  
                  return iqFactory.createUnaryIQTree(sliceNode,
                          iqFactory.createUnaryIQTree(constructionNode, grandChildTree));
              }
          }
          return subTree;
      }
  
      private IQTree dropTopConstruct(IQTree subTree) {
          // Check if it starts with [LIMIT]
          if (subTree.getRootNode() instanceof SliceNode) {
              SliceNode sliceNode = (SliceNode) subTree.getRootNode();
              // Add slice node to trimmed childtree
              return iqFactory.createUnaryIQTree(sliceNode,
                      dropTopConstruct(((UnaryIQTree) subTree).getChild()));
          } else {
              // Check for pattern CONSTRUCT, DISTINCT, [CONSTRUCT], ORDER BY
              if (subTree.getRootNode() instanceof ConstructionNode) {
                  ConstructionNode constructionNode = (ConstructionNode) subTree.getRootNode();
                  // If there is variable substitution in the top construction do not normalize
                  IQTree childTree = ((UnaryIQTree) subTree).getChild();
                  if (childTree.getRootNode() instanceof DistinctNode && constructionNode.getSubstitution().isEmpty()) {
                      IQTree grandChildTree = ((UnaryIQTree) childTree).getChild();
                      // CASE 1: CONSTRUCT, DISTINCT, CONSTRUCT, ORDER BY
                      if (grandChildTree.getRootNode() instanceof ConstructionNode) {
                          IQTree grandGrandChildTree = ((UnaryIQTree) grandChildTree).getChild();
                          if (grandGrandChildTree.getRootNode() instanceof OrderByNode) {
                              /*
                               * Drop the top construction node
                               */
                              return childTree;
                          }
                      // CASE 2: CONSTRUCT, DISTINCT, ORDER BY
                      } else if (grandChildTree.getRootNode() instanceof OrderByNode) {
                          /*
                           * Drop the top construction node
                           */
                          return childTree;
                      }
                  }
              }
              return subTree;
          }
      }
  
      private NativeNode generateNativeNode(IQTree normalizedSubTree, boolean tolerateUnknownTypes) {
          return defaultIQTree2NativeNodeGenerator.generate(normalizedSubTree, dbParameters, tolerateUnknownTypes);
      }
  }
  