/*
 * Copyright (c) 2006, 2019 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Oracle - initial API and implementation
//
//     09/02/2019-3.0 Alexandre Jacob
//        - 527415: Fix code when locale is tr, az or lt
package org.eclipse.persistence.jpa.jpql.parser;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import org.eclipse.persistence.jpa.jpql.utility.filter.Filter;
import org.eclipse.persistence.jpa.jpql.utility.filter.NullFilter;
import org.eclipse.persistence.jpa.jpql.utility.iterable.ArrayIterable;

/**
 * <p>This defines a single Backus-Naur Form (BNF) of the JPQL grammar. The Java Persistence functional
 * specifications are:</p>
 * <ul>
 * <li>{@link JPQLGrammar1_0}: <a href="http://jcp.org/en/jsr/detail?id=220">JSR 220: Enterprise JavaBeans&trade; version 3.0</a></li>
 * <li>{@link JPQLGrammar2_0}: <a href="http://jcp.org/en/jsr/detail?id=317">JSR 317: Java&trade; Persistence 2.0</a></li>
 * <li>{@link JPQLGrammar2_1}: <a href="http://jcp.org/en/jsr/detail?id=338">JSR 338: Java&trade; Persistence 2.1</a></li>
 * </ul>
 *
 * <p>Provisional API: This interface is part of an interim API that is still under development and
 * expected to change significantly before reaching stability. It is available at this early stage
 * to solicit feedback from pioneering adopters on the understanding that any code that uses this
 * API will almost certainly be broken (repeatedly) as the API evolves.</p>
 *
 * @version 2.5.1
 * @since 2.3
 * @author Pascal Filion
 */
@SuppressWarnings("nls")
public abstract class JPQLQueryBNF {

    /**
     * Caches the {@link ExpressionFactory} mapped by their the JPQL identifier registered with those
     * {@link ExpressionFactory}.
     */
    private Map<String, ExpressionFactory> cachedExpressionFactories;

    /**
     * Caches the collection of unique identifiers of the {@link ExpressionFactory} registered with
     * this BNF rule.
     */
    private String[] cachedExpressionFactoryIds;

    /**
     * The list of JPQL identifiers that are known by this BNF rule and its children, they are
     * retrieved by scanning the list of {@link ExpressionFactory} registered with this BNF rule
     * and its children.
     */
    private String[] cachedIdentifiers;

    /**
     * Caches the children of this BNF rule (which actually includes this one as well).
     */
    private JPQLQueryBNF[] childQueryBNFs;

    /**
     * The children BNF of this one.
     */
    private List<String> children;

    /**
     * Determines whether this BNF has child BNFs registered only to properly parse a query or if the
     * child BNFs are part of the BNF. An example if a compound BNF is {@link BetweenExpressionBNF},
     * it registers a series of children BNFs but they shouldn't be used to determine if they are
     * part of that BNF since the comparator identifiers are.
     */
    private boolean compound;

    /**
     * The list of unique identifiers for the {@link ExpressionFactory} that are registered with this BNF rule.
     */
    private List<String> expressionFactoryIds;

    /**
     * The {@link ExpressionRegistry} with which this {@link JPQLQueryBNF} was registered.
     */
    private ExpressionRegistry expressionRegistry;

    /**
     * The unique identifier of the {@link JPQLQueryBNF} to use in the last resort.
     */
    private String fallbackBNFId;

    /**
     * The unique identifier of the {@link ExpressionFactory} to use when no other factories can be
     * used automatically.
     */
    private String fallbackExpressionFactoryId;

    /**
     * Caches the property since any BNF rule is static.
     */
    private Boolean handleAggregate;

    /**
     * Caches the property since any BNF rule is static.
     */
    private Boolean handleCollection;

    /**
     * Determines whether this BNF handles parsing a nested array or not.
     */
    private boolean handleNestedArray;

    /**
     * This flag can be used to determine if this BNF handles parsing a sub-expression, i.e. an
     * expression encapsulated by parenthesis. See {@link #setHandleSubExpression(boolean)} for more
     * details.
     */
    private boolean handleSubExpression;

    /**
     * The unique identifier of this BNF rule.
     */
    private String id;

    /**
     * Caches the children of this BNF rule (which actually includes this one as well) but do not
     * includes BNF rules that are used for compounding a rule.
     */
    private JPQLQueryBNF[] nonCompoundChildren;

    /**
     * Only keep one instance of the {@link Filter} used when initializing {@link #childNonCompoundQueryBNFs}.
     * This should help with performance since the filter won't be created for each <code>JPQLQueryBNF</code>.
     */
    private static final Filter<JPQLQueryBNF> nonCompoundFilter = buildNonCompoundFilter();

    /**
     * Creates a new <code>JPQLQueryBNF</code>.
     *
     * @param id The unique identifier of this BNF rule
     * @exception NullPointerException The given unique identifier cannot be <code>null</code>
     */
    protected JPQLQueryBNF(String id) {
        super();
        initialize(id);
    }

    private static Filter<JPQLQueryBNF> buildNonCompoundFilter() {
        return new Filter<JPQLQueryBNF>() {
            @Override
            public boolean accept(JPQLQueryBNF queryBNF) {
                return !queryBNF.isCompound();
            }
        };
    }

    /**
     * Adds to the given set the child BNF rules and requests them to add their children as long as
     * they have not been traversed already.
     *
     * @param queryBNFs The set to add the child BNF rules
     * @param filter The {@link Filter} determines if the children of a given BNF should be added
     */
    private void addChildren(Set<JPQLQueryBNF> queryBNFs, Filter<JPQLQueryBNF> filter) {

        // null children means no child JPQLQueryBNF was registered
        if (children != null) {

            for (String id : children) {
                JPQLQueryBNF queryBNF = expressionRegistry.getQueryBNF(id);

                if (queryBNFs.add(queryBNF) && filter.accept(queryBNF)) {
                    queryBNF.addChildren(queryBNFs, filter);
                }
            }
        }
    }

    private JPQLQueryBNF[] buildChildren(Filter<JPQLQueryBNF> filter) {

        Set<JPQLQueryBNF> queryBNFs = new HashSet<JPQLQueryBNF>();
        queryBNFs.add(this);
        addChildren(queryBNFs, filter);

        JPQLQueryBNF[] children = new JPQLQueryBNF[queryBNFs.size()];
        queryBNFs.toArray(children);

        return children;
    }

    private void calculateExpressionFactories() {

        synchronized (this) {

            if (cachedExpressionFactories == null) {

                Map<String, ExpressionFactory> factories = new HashMap<String, ExpressionFactory>();

                // Caches the ExpressionFactory mapped by all the JPQL identifiers that
                // are registered for that ExpressionFactory
                for (String expressionFactoryId : getExpressionFactoryIdsImp()) {
                    ExpressionFactory expressionFactory = expressionRegistry.getExpressionFactory(expressionFactoryId);

                    for (String identifier : expressionFactory.identifiers()) {
                        factories.put(identifier, expressionFactory);
                    }
                }

                cachedIdentifiers = new String[factories.size()];
                factories.keySet().toArray(cachedIdentifiers);

                cachedExpressionFactories = factories;
            }
        }
    }

    private void calculateExpressionFactoryIds(Set<JPQLQueryBNF> queryBNFs, Set<String> factoryIds) {

        if (expressionFactoryIds != null) {
            factoryIds.addAll(expressionFactoryIds);
        }

        for (JPQLQueryBNF queryBNF : getChildren()) {
            if ((queryBNF != this) && (queryBNFs.add(queryBNF))) {
                queryBNF.calculateExpressionFactoryIds(queryBNFs, factoryIds);
            }
        }
    }

    private boolean calculateHandleAggregate(Set<JPQLQueryBNF> queryBNFs) {

        if (handleAggregate != null) {
            return handleAggregate;
        }

        for (JPQLQueryBNF queryBNF : getChildren()) {
            if ((queryBNF != this) && queryBNFs.add(queryBNF)) {
                boolean result = queryBNF.calculateHandleAggregate(queryBNFs);
                if (result) {
                    return true;
                }
            }
        }

        return false;
    }

    private boolean calculateHandleCollection(Set<JPQLQueryBNF> queryBNFs) {

        if (handleCollection != null) {
            return handleCollection;
        }

        for (JPQLQueryBNF queryBNF : getChildren()) {
            if ((queryBNF != this) && queryBNFs.add(queryBNF)) {
                boolean result = queryBNF.calculateHandleCollection(queryBNFs);
                if (result) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Returns the set of all the query BNFs that are part of this BNF. The set always include this
     * BNF as well.
     *
     * @return The children BNFs describing this BNF rule
     */
    public Iterable<JPQLQueryBNF> children() {
        return new ArrayIterable<JPQLQueryBNF>(getChildren());
    }

    private JPQLQueryBNF[] getChildren() {

        // No need to synchronize if the list of children was calculated
        if (childQueryBNFs != null) {
            return childQueryBNFs;
        }

        // Synchronize to make sure only one thread populates the list of children
        synchronized (this) {
            if (childQueryBNFs == null) {
                childQueryBNFs = buildChildren(NullFilter.<JPQLQueryBNF>instance());
            }
        }

        return childQueryBNFs;
    }

    /**
     * Retrieves the {@link ExpressionFactory} that is associated with the given identifier, if the
     * given string is indeed a JPQL identifier.
     *
     * @param identifier The JPQL identifier (in theory) that is used to retrieve the factory
     * responsible to parse a portion of the query starting with that identifier
     * @return The {@link ExpressionFactory} responsible to parse a portion of the query starting
     * with the given identifier; <code>null</code> if nothing was registered for it
     */
    public ExpressionFactory getExpressionFactory(String identifier) {

        // No need to synchronize if the map was calculated
        if (cachedExpressionFactories != null) {
            return cachedExpressionFactories.get(identifier.toUpperCase(Locale.ROOT));
        }

        // Synchronize to make sure only one thread populates the list of JPQL identifiers
        calculateExpressionFactories();

        return cachedExpressionFactories.get(identifier.toUpperCase(Locale.ROOT));
    }

    /**
     * Returns the unique identifiers of the {@link ExpressionFactory} handled by this BNF rule,
     * which includes those from the children as well.
     *
     * @return The list of unique identifiers of the {@link ExpressionFactory} registered with this
     * BNF rule and with its children
     */
    public Iterable<String> getExpressionFactoryIds() {
        return new ArrayIterable<String>(getExpressionFactoryIdsImp());
    }

    private String[] getExpressionFactoryIdsImp() {

        // No need to synchronize if the list of cached ExpressionFactory was calculated
        if (cachedExpressionFactoryIds != null) {
            return cachedExpressionFactoryIds;
        }

        // Synchronize to make sure only one thread populates the list of ExpressionFactory IDs
        synchronized (this) {

            if (cachedExpressionFactoryIds == null) {

                Set<JPQLQueryBNF> queryBNFs = new HashSet<JPQLQueryBNF>();
                Set<String> factoryIds = new HashSet<String>();
                calculateExpressionFactoryIds(queryBNFs, factoryIds);

                cachedExpressionFactoryIds = new String[factoryIds.size()];
                factoryIds.toArray(cachedExpressionFactoryIds);
            }
        }

        return cachedExpressionFactoryIds;
    }

    /**
     * Returns the registry containing the {@link JPQLQueryBNF JPQLQueryBNFs} and the {@link
     * org.eclipse.persistence.jpa.jpql.parser.ExpressionFactory ExpressionFactories} that are used
     * to properly parse a JPQL query.
     *
     * @return The registry containing the information related to the JPQL grammar
     */
    public ExpressionRegistry getExpressionRegistry() {
        return expressionRegistry;
    }

    /**
     * When parsing the query and no {@link JPQLQueryBNF JPQLQueryBNFs} can help to parse the query,
     * then it will fall back on this one.
     *
     * @return The unique identifier of the {@link JPQLQueryBNF} to use in the last resort
     */
    public String getFallbackBNFId() {
        return fallbackBNFId;
    }

    /**
     * Returns the unique identifier of the {@link ExpressionFactory} to use when the fall back BNF
     * ID is not <code>null</code>. This will be used to parse a portion of the query when the
     * registered {@link ExpressionFactory expression factories} cannot parse it.
     * <p>
     * Note: This method is only called if {@link #getFallbackBNFId()} does not return <code>null</code>.
     *
     * @return The unique identifier of the {@link ExpressionFactory}
     */
    public String getFallbackExpressionFactoryId() {
        return fallbackExpressionFactoryId;
    }

    /**
     * Returns the unique identifier of this {@link JPQLQueryBNF}.
     *
     * @return The identifier used to register this {@link JPQLQueryBNF} with {@link AbstractExpression}
     */
    public String getId() {
        return id;
    }

    /**
     * Retrieves the JPQL identifiers that are supported by this BNF rule. The JPQL identifiers are
     * retrieved by scanning the {@link ExpressionFactory} registered with this BNF rule and the
     * child BNF rules.
     *
     * @return The list of JPQL identifiers that are supported by this BNF
     */
    public Iterable<String> getIdentifiers() {

        // No need to synchronize if the cached JPQL identifiers was calculated
        // Note: it could be possible cachedIdentifiers was instantiated but the list of JPQL
        // identifiers has not been copied from cachedExpressionFactoryIds yet.
        // cachedExpressionFactoryIds is set at the end of the method, insuring proper initialization
        if (cachedExpressionFactoryIds != null) {
            return new ArrayIterable<String>(cachedIdentifiers);
        }

        // Synchronize to make sure only one thread populates the list of JPQL identifiers
        calculateExpressionFactories();

        return new ArrayIterable<String>(cachedIdentifiers);
    }

    /**
     * Determines whether the {@link Expression} handles a collection of sub-expressions that
     * are aggregated by logical or arithmetic operators.
     *
     * @return <code>true</code> if the sub-expression to parse might have several logical and/or
     * arithmetic expressions; <code>false</code> otherwise
     */
    public boolean handleAggregate() {

        // No need to synchronize if the property was calculated
        if (handleAggregate != null) {
            return handleAggregate;
        }

        // Synchronize to make sure only one thread calculates it
        synchronized (this) {
            if (handleAggregate == null) {
                Set<JPQLQueryBNF> children = new HashSet<JPQLQueryBNF>();
                handleAggregate = calculateHandleAggregate(children);
            }
        }

        return handleAggregate;
    }

    /**
     * Determines whether the <code>Expression</code> handles a collection of sub-expressions that
     * are separated by commas.
     *
     * @return <code>true</code> if the sub-expression to parse might have several sub-expressions
     * separated by commas; <code>false</code> otherwise
     */
    public boolean handleCollection() {

        // No need to synchronize if the property was calculated
        if (handleCollection != null) {
            return handleCollection;
        }

        // Synchronize to make sure only one thread calculates it
        synchronized (this) {
            if (handleCollection == null) {
                Set<JPQLQueryBNF> children = new HashSet<JPQLQueryBNF>();
                handleCollection = calculateHandleCollection(children);
            }
        }

        return handleCollection;
    }

    /**
     * @since 2.5
     */
    public boolean handlesNestedArray() {
        return handleNestedArray;
    }

    /**
     * Determines whether this BNF handles parsing a sub-expression, i.e. parsing an expression
     * encapsulated by parenthesis. See {@link #setHandleSubExpression(boolean)} for more details.
     *
     * @return <code>true</code> if this BNF handles parsing a sub-expression; <code>false</code> otherwise
     */
    public boolean handleSubExpression() {
        return handleSubExpression;
    }

    /**
     * Determines whether the BNF with the given ID is part of this BNF or not.
     *
     * @param queryBNFId The unique identifier of the BNF rule to check if it's a child of this one
     * @return <code>true</code> if the BNF with the given ID is
     * @since 2.5.1
     */
    public boolean hasChild(String queryBNFId) {

        for (JPQLQueryBNF child : getChildren()) {
            if (child.getId().equals(queryBNFId)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Determines if this query BNF support the given word, which can be an identifier.
     *
     * @param word A word that could be a JPQL identifier or anything else
     * @return <code>true</code> if the given word is a JPQL identifier and it is supported by this
     * BNF; <code>false</code> otherwise
     */
    public boolean hasIdentifier(String word) {

        // No need to synchronize if the map of cached ExpressionFactory was calculated
        if (cachedExpressionFactories != null) {
            return cachedExpressionFactories.containsKey(word);
        }

        // Synchronize to make sure only one thread calculates it
        calculateExpressionFactories();

        return cachedExpressionFactories.containsKey(word);
    }

    /**
     * Initializes this BNF by registering child {@link JPQLQueryBNF JPQLQueryBNFs} and {@link
     * ExpressionFactory ExpressionFactories}.
     */
    protected void initialize() {
    }

    /**
     * Initializes this <code>JPQLQueryBNF</code>.
     *
     * @param id The unique identifier of this BNF rule
     * @exception NullPointerException The given unique identifier cannot be <code>null</code>
     */
    private void initialize(String id) {

        if (id == null) {
            throw new NullPointerException("The unique identifier of this JPQLQueryBNF cannot be null");
        }

        this.id = id;
        initialize();
    }

    /**
     * Determines whether this BNF has child BNFs registered only to properly parse a query or if the
     * child BNFs are part of the BNF. An example if a compound BNF is {@link BetweenExpressionBNF},
     * it registers a series of children BNFs but they shouldn't be used to determine if they are
     * part of that BNF since the comparator identifiers are.
     *
     * @return <code>false</code> by default
     */
    public boolean isCompound() {
        return compound;
    }

    /**
     * Returns the set of all the query BNFs that are part of this BNF. The set always include this
     * BNF as well.
     *
     * @return The children BNFs describing this BNF rule. The set excludes BNF rules that are used
     * to complete a BNF, such as the BNF rules defined for <b>BETWEEN</b> since they are required
     * to properly parse the query
     */
    public Iterable<JPQLQueryBNF> nonCompoundChildren() {

        // No need to synchronize if the list of child BNFs (non-compound) was calculated
        if (nonCompoundChildren != null) {
            return new ArrayIterable<JPQLQueryBNF>(nonCompoundChildren);
        }

        // Synchronize to make sure only one thread populates the list of child BNFs (non-compound)
        synchronized (this) {
            if (nonCompoundChildren == null) {
                nonCompoundChildren = buildChildren(nonCompoundFilter);
            }
        }

        return new ArrayIterable<JPQLQueryBNF>(nonCompoundChildren);
    }

    /**
     * Registers the unique identifier of the BNF rule as a child of this BNF rule.
     *
     * @param queryBNFId The unique identifier of the BNF rule to add as a child
     * @exception NullPointerException The <code>queryBNFId</code> cannot be <code>null</code>
     */
    protected final void registerChild(String queryBNFId) {

        if (queryBNFId == null) {
            throw new NullPointerException("The queryBNFId cannot be null");
        }

        if (children == null) {
            children = new LinkedList<String>();
        }

        children.add(queryBNFId);
    }

    /**
     * Registers a unique identifier of the {@link ExpressionFactory} to register with this BNF rule.
     *
     * @param expressionFactoryId The unique identifier of the {@link ExpressionFactory}
     * @exception NullPointerException The <code>expressionFactoryId</code> cannot be <code>null</code>
     */
    protected final void registerExpressionFactory(String expressionFactoryId) {

        if (expressionFactoryId == null) {
            throw new NullPointerException("The expressionFactoryId cannot be null");
        }

        if (expressionFactoryIds == null) {
            expressionFactoryIds = new LinkedList<String>();
        }

        expressionFactoryIds.add(expressionFactoryId);
    }

    /**
     * Determines whether this BNF has child BNFs registered only to properly parse a query or if the
     * child BNFs are part of the BNF. An example if a compound BNF is {@link BetweenExpressionBNF},
     * it registers a series of children BNFs but they shouldn't be used to determine if they are
     * part of that BNF since the comparator identifiers are.
     *
     * @param compound <code>true</code> if this BNF represents a compound BNF and its children are
     * not part of this BNF but only to support compound expression; <code>false</code> otherwise
     */
    public void setCompound(boolean compound) {
        this.compound = compound;
    }

    /**
     * Sets the backpointer to the {@link ExpressionRegistry} with which this {@link JPQLQueryBNF}
     * was registered.
     *
     * @param expressionRegistry The registry for a given JPQL grammar
     */
    final void setExpressionRegistry(ExpressionRegistry expressionRegistry) {
        this.expressionRegistry = expressionRegistry;
    }

    /**
     * When parsing the query and no {@link JPQLQueryBNF JPQLQueryBNFs} can help to parse the query,
     * then it will fall back on the given one.
     *
     * @param fallbackBNFId The unique identifier of the {@link JPQLQueryBNF} to use in the last resort
     */
    public void setFallbackBNFId(String fallbackBNFId) {
        this.fallbackBNFId = fallbackBNFId;
    }

    /**
     * Sets the unique identifier of the {@link ExpressionFactory} to use when the fall back BNF
     * ID is not <code>null</code>. This will be used to parse a portion of the query when the
     * registered {@link ExpressionFactory expression factories} cannot parse it.
     * <p>
     * Note: This method is only called if {@link #getFallbackBNFId()} does not return <code>null</code>.
     *
     * @param fallbackExpressionFactoryId The unique identifier of the {@link ExpressionFactory}
     */
    public void setFallbackExpressionFactoryId(String fallbackExpressionFactoryId) {
        this.fallbackExpressionFactoryId = fallbackExpressionFactoryId;
    }

    /**
     * Sets whether the {@link Expression} handles a collection of sub-expressions that are
     * aggregated by logical or arithmetic operators.
     *
     * @param handleAggregate <code>true</code> if the sub-expression to parse might have several
     * logical and/or arithmetic expressions; <code>false</code> otherwise
     */
    public void setHandleAggregate(boolean handleAggregate) {
        this.handleAggregate = handleAggregate;
    }

    /**
     * Sets whether the <code>Expression</code> handles a collection of sub-expressions that are
     * separated by commas.
     *
     * @param handleCollection <code>true</code> if the sub-expression to parse might have several
     * sub-expressions separated by commas; <code>false</code> otherwise
     */
    public void setHandleCollection(boolean handleCollection) {
        this.handleCollection = handleCollection;
    }

    /**
     * Sets whether this BNF supports nested array or not. A nested array is a sub-expression with
     * its child being a collection expression: (item_1, item_2, ..., item_n).
     *
     * @param handleNestedArray <code>true</code> if the expression represented by this BNF can be
     * a nested array; <code>false</code> otherwise
     * @since 2.5
     */
    public void setHandleNestedArray(boolean handleNestedArray) {
        this.handleNestedArray = handleNestedArray;
    }

    /**
     * Sets whether this BNF handles parsing a sub-expression, i.e. parsing an expression
     * encapsulated by parenthesis. Which in fact would be handled by the fallback {@link
     * ExpressionFactory}. The default behavior is to not handle it.
     * <p>
     * A good example for using this option is when an {@link Expression} cannot use any {@link
     * ExpressionFactory} for creating a child object, parsing will use the fallback {@link
     * ExpressionFactory}, if one was specified. So when this is set to <code>true</code>, the
     * fallback {@link ExpressionFactory} will be immediately invoked.
     * <p>
     * Let's say we want to parse "SELECT e FROM (SELECT a FROM Address a) e", {@link FromClause}
     * cannot use a factory for parsing the entity name (that's what usually the <code>FROM</code>
     * clause has) so it uses the fallback factory to create {@link IdentificationVariableDeclaration}.
     * Then <code>IdentificationVariableDeclaration</code> also cannot use any factory to create its
     * child object so it uses the fallback factory to create {@link RangeVariableDeclaration}.
     * By changing the status of for handling the sub-expression for the BNFs for those objects, then
     * a subquery can be created by <code>RangeVariableDeclaration</code>.
     *
     * <pre><code>FromClause
     *  |- IdentificationVariableDeclaration
     *       |- RangeVariableDeclaration
     *            |- SubExpression(subquery)</code></pre>
     *
     * In order to get this working, the following would have to be done into the grammar:
     *
     * <pre><code> public class MyJPQLGrammar extends AbstractJPQLGrammar {
     *   &#64;Override
     *   protected void initializeBNFs() {
     *      setHandleSubExpression(InternalFromClauseBNF.ID,                true);
     *      setHandleSubExpression(InternalSimpleFromClauseBNF.ID,          true);
     *      setHandleSubExpression(IdentificationVariableDeclarationBNF.ID, true);
     *      setHandleSubExpression(RangeVariableDeclarationBNF.ID,          true);
     *   }
     * }</code></pre>
     *
     * @param handleSubExpression <code>true</code> to let the creation of a sub-expression be
     * created by the fallback {@link ExpressionFactory} registered with this BNF; <code>false</code>
     * otherwise (which is the default value)
     */
    public void setHandleSubExpression(boolean handleSubExpression) {
        this.handleSubExpression = handleSubExpression;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getSimpleName());
        toString(sb);
        return sb.toString();
    }

    /**
     * Adds to the given builder more information about this {@link JPQLQueryBNF}.
     *
     * @param sb The builder used to add information about this class
     */
    protected void toString(StringBuilder sb) {
        sb.append("(");
        sb.append(id);
        sb.append(")\n\nidentifiers=");
        sb.append(getIdentifiers());
        sb.append("\n\nexpressionFactories=");
        sb.append(Arrays.toString(getExpressionFactoryIdsImp()));
    }
}
