/** {@inheritDoc} */
  @Override
  public Expression buildSelectionCriteria(
      String entityName, String selectionCriteria, AbstractSession session) {

    try {
      // Create the parsed tree representation of the selection criteria
      JPQLExpression jpqlExpression =
          new JPQLExpression(
              selectionCriteria,
              DefaultEclipseLinkJPQLGrammar.instance(),
              ConditionalExpressionBNF.ID,
              isTolerant());

      // Caches the info and add a virtual range variable declaration
      JPQLQueryContext queryContext = new JPQLQueryContext(jpqlGrammar());
      queryContext.cache(session, null, jpqlExpression, selectionCriteria);
      queryContext.addRangeVariableDeclaration(entityName, "this");

      // Validate the JPQL query, which will use the JPQL grammar matching the validation
      // level, for now, only validate the query statement because there could be an unknown
      // ending that is an order by clause
      validate(queryContext, jpqlExpression.getQueryStatement());

      // Create the Expression representing the selection criteria
      return queryContext.buildExpression(jpqlExpression.getQueryStatement());
    } catch (JPQLException exception) {
      throw exception;
    } catch (Exception exception) {
      throw buildUnexpectedException(selectionCriteria, exception);
    }
  }
  private DatabaseQuery populateQueryImp(
      CharSequence jpqlQuery, DatabaseQuery query, AbstractSession session) {

    try {
      // Parse the JPQL query with the most recent JPQL grammar
      JPQLExpression jpqlExpression =
          new JPQLExpression(jpqlQuery, DefaultEclipseLinkJPQLGrammar.instance(), isTolerant());

      // Create a context that caches the information contained in the JPQL query
      // (especially from the FROM clause)
      JPQLQueryContext queryContext = new JPQLQueryContext(jpqlGrammar());
      queryContext.cache(session, query, jpqlExpression, jpqlQuery);

      // Validate the JPQL query, which will use the JPQL grammar matching the validation level
      validate(queryContext, jpqlExpression);

      // Create the DatabaseQuery by visiting the parsed tree
      DatabaseQueryVisitor visitor = new DatabaseQueryVisitor(queryContext, jpqlQuery);
      jpqlExpression.accept(visitor);

      // Add the input parameter types to the DatabaseQuery
      if (query == null) {
        query = queryContext.getDatabaseQuery();
        addArguments(queryContext, query);
      }

      return query;
    } catch (JPQLException exception) {
      throw exception;
    } catch (Exception exception) {
      throw buildUnexpectedException(jpqlQuery, exception);
    }
  }
    /** {@inheritDoc} */
    @Override
    public void visit(SelectStatement expression) {

      ObjectLevelReadQuery query = queryContext.getDatabaseQuery();

      // Create and prepare the query
      if (query == null) {
        query = buildReadAllQuery(expression);
        queryContext.setDatabasQuery(query);
        query.setJPQLString(jpqlQuery);
        ((JPQLCallQueryMechanism) query.getQueryMechanism()).getJPQLCall().setIsParsed(true);
      }

      // Now populate it
      expression.accept(buildVisitor(query));
    }
    /** {@inheritDoc} */
    @Override
    public void visit(UpdateStatement expression) {

      UpdateAllQuery query = queryContext.getDatabaseQuery();

      // Create and prepare the query
      if (query == null) {
        query = new UpdateAllQuery();
        queryContext.setDatabasQuery(query);
        query.setJPQLString(jpqlQuery);
        ((JPQLCallQueryMechanism) query.getQueryMechanism()).getJPQLCall().setIsParsed(true);
      }

      query.setSession(queryContext.getSession());
      query.setShouldDeferExecutionInUOW(false);

      // Now populate it
      UpdateQueryVisitor visitor = new UpdateQueryVisitor(queryContext, query);
      expression.accept(visitor);
    }
  /**
   * Creates a {@link JPQLException} indicating the problems with the JPQL query.
   *
   * @param queryContext The {@link JPQLQueryContext} containing the information about the JPQL
   *     query
   * @param problems The {@link JPQLQueryProblem problems} found in the JPQL query that are
   *     translated into an exception
   * @param messageKey The key used to retrieve the localized message
   * @return The {@link JPQLException} indicating the problems with the JPQL query
   */
  private JPQLException buildException(
      JPQLQueryContext queryContext, Collection<JPQLQueryProblem> problems, String messageKey) {

    ResourceBundle bundle = resourceBundle();
    StringBuilder sb = new StringBuilder();

    for (JPQLQueryProblem problem : problems) {

      // Retrieve the localized message
      String message;

      try {
        message = bundle.getString(problem.getMessageKey());
      } catch (NullPointerException e) {
        // In case the resource bundle was not updated
        message = problem.getMessageKey();
      }

      // Now format the localized message
      String[] arguments = problem.getMessageArguments();

      if (arguments.length > 0) {
        message = MessageFormat.format(message, (Object[]) arguments);
      }

      // Append the description
      sb.append("\n");
      sb.append("[");
      sb.append(problem.getStartPosition());
      sb.append(", ");
      sb.append(problem.getEndPosition());
      sb.append("] ");
      sb.append(message);
    }

    String errorMessage = bundle.getString(messageKey);
    errorMessage = MessageFormat.format(errorMessage, queryContext.getJPQLQuery(), sb);
    return new JPQLException(errorMessage);
  }