/*
 * Copyright (c) 2006, 2018 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
//
package org.eclipse.persistence.jpa.jpql.parser;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.persistence.jpa.jpql.ExpressionTools;
import org.eclipse.persistence.jpa.jpql.JPAVersion;
import org.eclipse.persistence.jpa.jpql.WordParser;
import org.eclipse.persistence.jpa.jpql.WordParser.WordType;
import org.eclipse.persistence.jpa.jpql.utility.iterable.ListIterable;
import org.eclipse.persistence.jpa.jpql.utility.iterable.SnapshotCloneListIterable;

/**
 * This is the abstract definition of all the parts used to create the tree hierarchy representing
 * the parsed JPQL query.
 *
 * @see ExpressionFactory
 * @see JPQLGrammar
 *
 * @version 2.6
 * @since 2.3
 * @author Pascal Filion
 */
@SuppressWarnings("nls")
public abstract class AbstractExpression implements Expression {

    /**
     * The string representation of this {@link AbstractExpression} (which includes its children).
     * The string includes characters that are considered virtual, i.e. that was parsed when the
     * query is incomplete and is needed for functionality like content assist.
     *
     * @see #toActualText()
     */
    private String actualText;

    /**
     * The children of this {@link AbstractExpression}.
     *
     * @see #children()
     */
    private List<Expression> children;

    /**
     * The position of this {@link AbstractExpression} in relation to its parent hierarchy by
     * calculating the length of the string representation of what comes before.
     */
    private int offset;

    /**
     * The string representation of this {@link AbstractExpression}.
     *
     * @see #orderedChildren()
     */
    private List<Expression> orderedChildren;

    /**
     * The parent of this {@link AbstractExpression} or <code>null</code> if this object is {@link
     * JPQLExpression} - the root of the parsed tree hierarchy.
     */
    private AbstractExpression parent;

    /**
     * The string representation of this {@link AbstractExpression} (which includes its children).
     * The string does not include characters that are considered virtual, i.e. that was parsed when
     * the query is incomplete.
     *
     * @see #toParsedText()
     */
    private String parsedText;

    /**
     * This attribute can be used to store the {@link AbstractExpression}'s JPQL identifier or a literal.
     */
    private String text;

    /**
     * The constant for ','.
     */
    public static final char COMMA = ',';

    /**
     * The constant for '.'.
     */
    public static final char DOT = '.';

    /**
     * The constant for '"'.
     */
    public static final char DOUBLE_QUOTE = '\"';

    /**
     * The constant for '{'.
     */
    public static final char LEFT_CURLY_BRACKET = '{';

    /**
     * The constant for '('.
     */
    public static final char LEFT_PARENTHESIS = '(';

    /**
     * The constant for a character that is not defined.
     */
    public static final char NOT_DEFINED = '\0';

    /**
     * The constant for '}'.
     */
    public static final char RIGHT_CURLY_BRACKET = '}';

    /**
     * The constant for ')'.
     */
    public static final char RIGHT_PARENTHESIS = ')';

    /**
     * The constant for '''.
     */
    public static final char SINGLE_QUOTE = '\'';

    /**
     * The constant for ' '.
     */
    public static final char SPACE = ' ';

    /**
     * The constant for '_'.
     */
    public static final char UNDERSCORE = '_';

    /**
     * Creates a new <code>AbstractExpression</code>.
     *
     * @param parent The parent of this expression
     */
    protected AbstractExpression(AbstractExpression parent) {
        this(parent, ExpressionTools.EMPTY_STRING);
    }

    /**
     * Creates a new <code>AbstractExpression</code>.
     *
     * @param parent The parent of this expression
     * @param text The text to be stored in this expression, <code>null</code> cannot be passed
     */
    protected AbstractExpression(AbstractExpression parent, String text) {
        super();
        this.offset = -1;
        this.text   = text;
        this.parent = parent;
    }

    /**
     * The given {@link ExpressionVisitor} needs to visit this class but it is defined by a third-
     * party provider. This method will programmatically invoke the <b>visit</b> method defined on
     * the visitor. The method signature should be:
     *
     * <div><code>{public|protected|private} void visit(ThirdPartyExpression expression)</code></div>
     * <p>
     * or
     *
     * <div><code>{public|protected|private} void visit(Expression expression)</code></div>
     * <p>
     * <b>Note:</b> The package protected visibility (default) should be used with care, if the code
     * is running inside OSGi, then the method will not be accessible, even through reflection.
     *
     * @param visitor The {@link ExpressionVisitor} to visit this {@link Expression} programmatically
     * @return <code>true</code> if the call was successfully executed; <code>false</code> otherwise
     * @since 2.4
     */
    protected boolean acceptUnknownVisitor(ExpressionVisitor visitor) {
        try {
            try {
                acceptUnknownVisitor(visitor, visitor.getClass(), getClass());
            }
            catch (NoSuchMethodException e) {
                // Try with Expression as the parameter type
                acceptUnknownVisitor(visitor, visitor.getClass(), Expression.class);
            }
            return true;
        }
        catch (NoSuchMethodException e) {
            // Ignore, just do nothing
            return false;
        }
        catch (IllegalAccessException e) {
            // Ignore, just do nothing
            return false;
        }
        catch (InvocationTargetException e) {
            Throwable cause = e.getCause();
            RuntimeException actual;
            if (cause instanceof RuntimeException) {
                actual = (RuntimeException) cause;
            }
            else {
                actual = new RuntimeException(cause);
            }
            throw actual;
        }
    }

    /**
     * The given {@link ExpressionVisitor} needs to visit this class but it is defined by a third-
     * party provider. This method will programmatically invoke the <b>visit</b> method defined on
     * the visitor. The method signature should be:
     *
     * <div><code>{public|protected|private} void visit(ThirdPartyExpression expression)</code></div>
     * <p>
     * or
     *
     * <div><code>{public|protected|private} void visit(Expression expression)</code></div>
     * <p>
     *
     * @param visitor The {@link ExpressionVisitor} to visit this {@link Expression} programmatically
     * @param type The type found in the hierarchy of the given {@link ExpressionVisitor} that will
     * be used to retrieve the visit method
     * @param parameterType The parameter type of the visit method
     * @see #acceptUnknownVisitor(ExpressionVisitor)
     * @since 2.4
     */
    protected void acceptUnknownVisitor(ExpressionVisitor visitor,
                                        Class<?> type,
                                        Class<?> parameterType) throws NoSuchMethodException,
                                                                       IllegalAccessException,
                                                                       InvocationTargetException{

        try {
            Method visitMethod = type.getDeclaredMethod("visit", parameterType);
            visitMethod.setAccessible(true);
            visitMethod.invoke(visitor, this);
        }
        catch (NoSuchMethodException e) {
            type = type.getSuperclass();
            if (type == Object.class) {
                throw e;
            }
            else {
                acceptUnknownVisitor(visitor, type, parameterType);
            }
        }
    }

    /**
     * Adds the children of this {@link AbstractExpression} to the given collection.
     *
     * @param children The collection used to store the children
     */
    protected void addChildrenTo(Collection<Expression> children) {
    }

    /**
     * Adds the children of this {@link AbstractExpression} to the given list.
     *
     * @param children The list used to store the string representation of this {@link AbstractExpression}
     */
    protected void addOrderedChildrenTo(List<Expression> children) {
    }

    /**
     * No factories were found to create an {@link Expression} with the content of {@link WordParser},
     * this method will retrieve the fallback {@link ExpressionFactory} defined in the given {@link
     * JPQLQueryBNF BNF}.
     *
     * @param wordParser The text to parse based on the current position of the cursor
     * @param word The word that was retrieved from the given text, which is the first word in the text
     * @param queryBNF The {@link JPQLQueryBNF} used to determine how to parse from the current
     * position of the cursor within the JPQL query
     * @param expression The {@link Expression} that has just been parsed or <code>null</code>
     * @param tolerant Determines whether the parsing system should be tolerant, meaning if it should
     * try to parse invalid or incomplete queries
     * @return The {@link Expression} representing the given sub-query
     */
    protected final AbstractExpression buildExpressionFromFallingBack(WordParser wordParser,
                                                                      String word,
                                                                      JPQLQueryBNF queryBNF,
                                                                      AbstractExpression expression,
                                                                      boolean tolerant) {

        ExpressionFactory factory = findFallBackExpressionFactory(queryBNF);

        if (factory == null) {
            return null;
        }

        // When parsing an invalid or incomplete query, it is possible two literals would be parsed
        // but in some cases, a CollectionExpression should not be created and the parsing should
        // actually stop here. Example: BETWEEN 10 20, when parsing 20, it should not be parsed as
        // part of the lower bound expression
        if (tolerant &&
            (factory.getId() == LiteralExpressionFactory.ID) &&
            shouldSkipLiteral(expression)) {

            return null;
        }

        return factory.buildExpression(this, wordParser, word, queryBNF, expression, tolerant);
    }

    /**
     * Creates a new <code>null</code>-{@link Expression} parented with this one.
     *
     * @return A new <code>null</code> version of an {@link Expression}
     */
    protected final AbstractExpression buildNullExpression() {
        return new NullExpression(this);
    }

    /**
     * Creates a new {@link Expression} wrapping the given character value.
     *
     * @param value The character to wrap as a {@link Expression}
     * @return The {@link Expression} representation of the given identifier where the owning
     * {@link Expression} is this one
     */
    protected final Expression buildStringExpression(char value) {
        return buildStringExpression(String.valueOf(value));
    }

    /**
     * Creates a new {@link Expression} wrapping the given string value.
     *
     * @param value The string to wrap as a <code>Expression</code>
     * @return The {@link Expression} representation of the given identifier where the owning
     * {@link Expression} is this one
     */
    protected final Expression buildStringExpression(String value) {
        return new DefaultStringExpression(this, value);
    }

    /**
     * Creates an {@link Expression} that contains a malformed expression.
     *
     * @param text The text causing the expression to be malformed
     * @return A new {@link Expression} where {@link #toActualText()} returns the given text
     */
    protected final AbstractExpression buildUnknownExpression(String text) {
        return new UnknownExpression(this, text);
    }

    /**
     * Calculates the position of the given {@link Expression} by calculating the length of what is before.
     *
     * @param expression The {@link Expression} for which its position within the parsed tree needs
     * to be determined
     * @param length The current cursor position within the JPQL query while digging into the tree
     * until the search reaches the expression
     * @return The length of the string representation for what is coming before the given {@link Expression}
     * @since 2.4
     */
    protected final int calculatePosition(Expression expression, int length) {

        Expression parent = expression.getParent();

        // Reach the root
        if (parent == null) {
            return length;
        }

        // Traverse the child expression until the expression
        for (Expression childExpression : parent.orderedChildren()) {

            // Continue to calculate the position by going up the hierarchy
            if (childExpression == expression) {
                return calculatePosition(parent, length);
            }

            length += childExpression.getLength();
        }

        // It should never reach this
        throw new RuntimeException("The position of the Expression could not be calculated: " + expression);
    }

    /**
     * {@inheritDoc}
     */
    public final ListIterable<Expression> children() {
        if (children == null) {
            children = new LinkedList<Expression>();
            addChildrenTo(children);
        }
        return new SnapshotCloneListIterable<Expression>(children);
    }

    /**
     * Retrieve the {@link ExpressionFactory} from the given {@link JPQLQueryBNF} by following the
     * path of fallback {@link JPQLQueryBNF JPQLQueryBNFs} and then returns the {@link ExpressionFactory}
     * from the leaf {@link JPQLQueryBNF}.
     *
     * @param queryBNF The {@link JPQLQueryBNF} for which its associated fallback {@link ExpressionFactory}
     * will be searched
     * @return Either the fallback {@link ExpressionFactory} linked to the given {@link JPQLQueryBNF}
     * or <code>null</code> if none was declared
     */
    protected final ExpressionFactory findFallBackExpressionFactory(JPQLQueryBNF queryBNF) {

        String fallBackBNFId = queryBNF.getFallbackBNFId();

        // No fall back BNF is defined, then nothing can be done
        if (fallBackBNFId == null) {
            return null;
        }

        JPQLQueryBNF fallBackQueryBNF = getQueryBNF(fallBackBNFId);

        // Traverse the fall back BNF because it has its own fall back BNF
        if (fallBackQueryBNF != queryBNF &&
            fallBackQueryBNF.getFallbackBNFId() != null) {

            return findFallBackExpressionFactory(fallBackQueryBNF);
        }

        // Retrieve the factory associated with the fall back BNF
        return getExpressionRegistry().getExpressionFactory(fallBackQueryBNF.getFallbackExpressionFactoryId());
    }

    /**
     * {@inheritDoc}
     */
    public JPQLQueryBNF findQueryBNF(Expression expression) {
        return getQueryBNF();
    }

    /**
     * Retrieves the registered {@link ExpressionFactory} that was registered for the given unique
     * identifier.
     *
     * @param expressionFactoryId The unique identifier of the {@link ExpressionFactory} to retrieve
     * @return The {@link ExpressionFactory} mapped with the given unique identifier
     * @see ExpressionRegistry#getExpressionFactory(String)
     */
    protected final ExpressionFactory getExpressionFactory(String expressionFactoryId) {
        return getExpressionRegistry().getExpressionFactory(expressionFactoryId);
    }

    /**
     * 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
     */
    protected final ExpressionRegistry getExpressionRegistry() {
        return getGrammar().getExpressionRegistry();
    }

    /**
     * {@inheritDoc}
     */
    public JPQLGrammar getGrammar() {
        return getRoot().getGrammar();
    }

    /**
     * Retrieves the JPA version in which the identifier was first introduced.
     *
     * @return The version in which the identifier was introduced
     */
    public JPAVersion getIdentifierVersion(String identifier) {
        return getRoot().getIdentifierVersion(identifier);
    }

    /**
     * Returns the version of the Java Persistence to support.
     *
     * @return The JPA version supported by the grammar
     * @see JPQLGrammar
     */
    protected JPAVersion getJPAVersion() {
        return getRoot().getJPAVersion();
    }

    /**
     * {@inheritDoc}
     */
    public final int getLength() {
        return toActualText().length();
    }

    /**
     * {@inheritDoc}
     */
    public final int getOffset() {
        if (offset == -1) {
            offset = calculatePosition(this, 0);
        }
        return offset;
    }

    /**
     * {@inheritDoc}
     */
    public final AbstractExpression getParent() {
        return parent;
    }

    /**
     * Retrieves the BNF object that was registered for the given unique identifier.
     *
     * @param queryBNFID The unique identifier of the {@link JPQLQueryBNF} to retrieve
     * @return The {@link JPQLQueryBNF} representing a section of the grammar
     */
    public JPQLQueryBNF getQueryBNF(String queryBNFID) {
        return getExpressionRegistry().getQueryBNF(queryBNFID);
    }

    /**
     * {@inheritDoc}
     */
    public final JPQLExpression getRoot() {
        return (parent == null) ? (JPQLExpression) this : parent.getRoot();
    }

    /**
     * Returns the encapsulated text of this {@link AbstractExpression}, which can be used in various
     * ways, it can be a keyword, a literal, etc.
     *
     * @return Either the JPQL identifier for this {@link AbstractExpression}, the literal it
     * encapsulates or an empty string
     */
    protected String getText() {
        return text;
    }

    /**
     * Determines whether the given {@link JPQLQueryBNF} handles aggregate expressions.
     *
     * @param queryBNF The {@link JPQLQueryBNF} used to determine if the parsing should handle
     * aggregate expressions
     * @return <code>true</code> if the given BNF handles aggregate expressions; <code>false</code>
     * otherwise
     */
    protected boolean handleAggregate(JPQLQueryBNF queryBNF) {
        return queryBNF.handleAggregate();
    }

    /**
     * Determines whether the given {@link JPQLQueryBNF} handles a collection of sub-expressions that
     * are separated by commas.
     *
     * @param queryBNF The {@link JPQLQueryBNF} used to determine if the parsing should handle
     * collection of sub-expressions
     * @return <code>true</code> if the sub-expression to parse might have several sub-expressions
     * separated by commas; <code>false</code> otherwise
     */
    protected boolean handleCollection(JPQLQueryBNF queryBNF) {
        return queryBNF.handleCollection();
    }

    /**
     * {@inheritDoc}
     */
    public boolean isAncestor(Expression expression) {

        if (expression == this) {
            return true;
        }

        if (expression == null) {
            return false;
        }

        return isAncestor(expression.getParent());
    }

    /**
     * Determines if the given word is a JPQL identifier. The check is case insensitive.
     *
     * @param word The word to test if it is a JPQL identifier
     * @return <code>true</code> if the word is an identifier, <code>false</code> otherwise
     * @see ExpressionRegistry#isIdentifier(String)
     */
    protected final boolean isIdentifier(String word) {
        return getExpressionRegistry().isIdentifier(word);
    }

    /**
     * Determines whether this expression is a <code>null</code> {@link Expression} or any other subclass.
     *
     * @return <code>false</code> by default
     */
    protected boolean isNull() {
        return false;
    }

    /**
     * Determines whether the parsing is complete based on what is left in the given text. The text
     * is never empty.
     *
     * @param wordParser The text to parse based on the current position of the cursor
     * @param word The word that was retrieved from the given text, which is the first word in the text
     * @param expression The {@link Expression} that has already been parsed
     * @return <code>true</code> if the text no longer can't be parsed by the current expression;
     * <code>false</code> if more can be parsed
     */
    protected boolean isParsingComplete(WordParser wordParser, String word, Expression expression) {
        // TODO: MAYBE MOVE THIS TO THE JPQL GRAMMAR SO GENERIC JPA DOES
        //       NOT HAVE KNOWLEDGE OF ECLIPSELINK SPECIFIC FUNCTIONS
        return word.equalsIgnoreCase(FROM)                        ||
               word.equalsIgnoreCase(WHERE)                       ||
               word.equalsIgnoreCase(HAVING)                      ||
               wordParser.startsWithIdentifier(GROUP_BY)          ||
               wordParser.startsWithIdentifier(ORDER_BY)          ||
               wordParser.startsWithIdentifier(AS_OF)             ||
               wordParser.startsWithIdentifier(START_WITH)        ||
               wordParser.startsWithIdentifier(CONNECT_BY)        ||
               wordParser.startsWithIdentifier(ORDER_SIBLINGS_BY) ||
               word.equalsIgnoreCase(UNION)                       ||
               word.equalsIgnoreCase(INTERSECT)                   ||
               word.equalsIgnoreCase(EXCEPT);
    }

    /**
     * Determines if the parser is in tolerant mode or is in fast mode. When the tolerant is turned
     * on, it means the parser will attempt to parse incomplete or invalid queries.
     *
     * @return <code>true</code> if the parsing system should parse invalid or incomplete queries;
     * <code>false</code> when the query is well-formed and valid
     */
    protected boolean isTolerant() {
        return getRoot().isTolerant();
    }

    /**
     * Determines whether this expression is an unknown {@link Expression} or any other subclass.
     *
     * @return <code>false</code> by default
     */
    protected boolean isUnknown() {
        return false;
    }

    /**
     * Determines whether this {@link AbstractExpression} is virtual, meaning it's not part of the
     * query but is required for proper navigability.
     *
     * @return <code>true</code> if this {@link AbstractExpression} was virtually created to fully
     * qualify path expression; <code>false</code> if it was parsed
     */
    protected boolean isVirtual() {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    public final ListIterable<Expression> orderedChildren() {
        if (orderedChildren == null) {
            orderedChildren = new LinkedList<Expression>();
            addOrderedChildrenTo(orderedChildren);
        }
        return new SnapshotCloneListIterable<Expression>(orderedChildren);
    }

    /**
     * Parses the query by starting at the current position, which is part of the given {@link WordParser}.
     *
     * @param wordParser The text to parse based on the current position of the cursor
     * @param tolerant Determines whether the parsing system should be tolerant, meaning if it should
     * try to parse invalid or incomplete queries
     */
    protected abstract void parse(WordParser wordParser, boolean tolerant);

    /**
     * Parses the given text by using the specified BNF.
     *
     * @param wordParser The text to parse based on the current position of the cursor
     * @param queryBNFId The unique identifier of the {@link JPQLQueryBNF} that is used to determine
     * how to parse the text at the current cursor position within the JPQL query
     * @param tolerant Determines whether the parsing system should be tolerant, meaning if it should
     * try to parse invalid or incomplete queries
     * @return The {@link Expression} representing the given sub-query
     */
    @SuppressWarnings("null")
    protected AbstractExpression parse(WordParser wordParser, String queryBNFId, boolean tolerant) {

        // Quick check so we don't create some objects for no reasons
        if (tolerant && wordParser.isTail()) {
            return null;
        }

        //
        // NOTE: This method could look better but for performance reason, it is a single method,
        //       which reduces the number of objects created and methods called.
        //

        JPQLQueryBNF queryBNF = getQueryBNF(queryBNFId);

        int count = 0;
        boolean beginning = !tolerant;
        char character = wordParser.character();

        AbstractExpression child;
        AbstractExpression expression = null;

        Info rootInfo = null;
        Info currentInfo = null;

        // Parse the string until the position of the cursor is at
        // the end of the string or until the parsing is complete
        while (!wordParser.isTail()) {

            child = null;

            //
            // Step 1
            //
            // Right away create a SubExpression and parse the encapsulated expression
            if (character == LEFT_PARENTHESIS) {

                // If the JPQLQueryBNF handles parsing the sub-expression, then delegate the parsing
                // to its fallback ExpressionFactory
                if (queryBNF.handleSubExpression()) {
                    expression = buildExpressionFromFallingBack(
                        wordParser,
                        ExpressionTools.EMPTY_STRING,
                        queryBNF,
                        expression,
                        tolerant
                    );
                }
                else {
                    expression = new SubExpression(this, queryBNF);
                    expression.parse(wordParser, tolerant);

                    // Make sure this is not the root and if the parent handles parsing the sub-
                    // expression, then the Expression needs to be returned without further parsing
                    if ((parent != null) && parent.getQueryBNF().handleSubExpression()) {
                        return expression;
                    }
                }

                // Something has been parsed, which means it's not the beginning anymore
                beginning = false;

                // Continue to the next character/word
                count     = wordParser.skipLeadingWhitespace();
                character = wordParser.character();

                // Store the SubExpression
                currentInfo = (rootInfo == null) ? (rootInfo = new Info()) : (currentInfo.next = new Info(currentInfo));
                currentInfo.expression = expression;
                currentInfo.space = count > 0;
            }

            // Retrieve the next word, including any arithmetic symbols
            String word = wordParser.word();

            // A word was parsed, attempt to parse it using first the factory, then the fallback factory
            if (word.length() > 0) {

                // Nothing more to parse
                if (!tolerant && !beginning && isParsingComplete(wordParser, word, expression) ||
                     tolerant &&               isParsingComplete(wordParser, word, expression)) {

                    break;
                }

                //
                // Step 2
                //
                // Parse using the ExpressionFactory that is mapped with a JPQL identifier (word)
                if (shouldParseWithFactoryFirst() &&
                    (wordParser.getWordType() == WordType.WORD)) {

                    ExpressionFactory factory = queryBNF.getExpressionFactory(word);

                    if (factory != null) {
                        child = factory.buildExpression(this, wordParser, word, queryBNF, expression, tolerant);

                        if (child != null) {

                            // The new expression is a child of the previous expression,
                            // remove it from the collection since it's already parented
                            if ((expression != null) && child.isAncestor(expression)) {
                                if (currentInfo == rootInfo) {
                                    rootInfo = null;
                                    currentInfo = null;
                                }
                                else if (currentInfo != null) {
                                    currentInfo = currentInfo.previous;
                                }
                            }

                            // Something has been parsed, which means it's not the beginning anymore
                            beginning = false;

                            // Continue with the next character/word
                            count     = wordParser.skipLeadingWhitespace();
                            character = wordParser.character();

                            // The new expression becomes the previous expression
                            expression = child;
                        }
                    }
                }

                //
                // Step 3
                //
                // No factories could be used, use the fall back ExpressionFactory
                if (child == null) {
                    child = buildExpressionFromFallingBack(wordParser, word, queryBNF, expression, tolerant);

                    if (child != null) {

                        // The new expression is a child of the previous expression,
                        // remove it from the collection since it's already parented
                        if ((expression != null) && child.isAncestor(expression)) {
                            if (currentInfo == rootInfo) {
                                rootInfo = null;
                                currentInfo = null;
                            }
                            else if (currentInfo != null) {
                                currentInfo = currentInfo.previous;
                            }
                        }

                        // Something has been parsed, which means it's not the beginning anymore
                        beginning = false;

                        // Continue with the next character/word
                        count     = wordParser.skipLeadingWhitespace();
                        character = wordParser.character();

                        // The new expression becomes the previous expression
                        expression = child;
                    }
                }

                //
                // Step 4
                //
                // If nothing was parsed, then attempt to parse the fragment by retrieving the factory
                // directory from the JPQL grammar and not from the one registered with the current BNF
                if (tolerant && (child == null)) {

                    ExpressionRegistry expressionRegistry = getExpressionRegistry();

                    if (expressionRegistry.getIdentifierRole(word) != IdentifierRole.AGGREGATE) {

                        ExpressionFactory factory = expressionRegistry.expressionFactoryForIdentifier(word);

                        if (factory != null) {
                            child = factory.buildExpression(this, wordParser, word, queryBNF, expression, tolerant);

                            if (child != null) {
                                child = new BadExpression(this, child);

                                // The new expression is a child of the previous expression,
                                // remove it from the collection since it's already parented
                                if ((expression != null) && child.isAncestor(expression)) {
                                    if (currentInfo == rootInfo) {
                                        rootInfo = null;
                                        currentInfo = null;
                                    }
                                    else if (currentInfo != null) {
                                        currentInfo = currentInfo.previous;
                                    }
                                }

                                // Something has been parsed, which means it's not the beginning anymore
                                beginning = false;

                                // Continue with the next character/word
                                count     = wordParser.skipLeadingWhitespace();
                                character = wordParser.character();

                                // The new expression becomes the previous expression
                                expression = child;
                            }
                        }
                    }
                }
            }

            // Nothing could be parsed, break here so the parent can continue parsing.
            // Example: AVG() and we're parsing what's inside the parenthesis (nothing).
            // But if it's (,), then we have to create a collection of "null" expressions
            // separated by a comma
            if ((child == null) && (character != COMMA)) {
                break;
            }

            // Store the child but skip a very special case, which happens when parsing
            // two subqueries in a collection expression. Example: (SELECT ... ), (SELECT ... )
            if ((expression == null) || (child != null)) {
                currentInfo = (rootInfo == null) ? (rootInfo = new Info()) : (currentInfo.next = new Info(currentInfo));
                currentInfo.expression = child;
                currentInfo.space = count > 1;
            }

            // Nothing else to parse
            if (wordParser.isTail()) {
                break;
            }

            // ','
            if (character == COMMA) {

                // The current expression does not handle collection, then stop the
                // parsing here so the parent can continue to parse
                if (!handleCollection(queryBNF) || tolerant && isParsingComplete(wordParser, word, expression)) {
                    break;
                }

                // Skip the comma
                wordParser.moveForward(1);
                currentInfo.comma = true;

                // Remove leading whitespace
                count = wordParser.skipLeadingWhitespace();
                currentInfo.space = count > 0;

                character = wordParser.character();
                expression = null;

                // Special case: ((), (), ())
                if (character == LEFT_PARENTHESIS) {
                    continue;
                }

                // No more text, the query ends with a comma
                word = wordParser.word();
                boolean stopParsing = tolerant && (word.length() == 0 || isParsingComplete(wordParser, word, null));

                if (wordParser.isTail() || stopParsing) {

                    // Make sure the space is not re-added at the end of the query
                    count = 0;

                    // Add a null Expression since the expression ends with a comma
                    currentInfo = (rootInfo == null) ? (rootInfo = new Info()) : (currentInfo.next = new Info(currentInfo));

                    // Nothing else to parse
                    if (stopParsing) {
                        break;
                    }
                }

                // Nothing more to parse
                if (character == RIGHT_PARENTHESIS) {
                    break;
                }
            }
            else {

                // Continue parsing the collection expression
                if (character != RIGHT_PARENTHESIS &&
                    handleAggregate(queryBNF)) {

                    currentInfo.space = count > 0;
                }
                // Nothing more to parse
                else {
                    break;
                }
            }
        }

        if (count > 0) {
            currentInfo.space = currentInfo.comma;
            if (!currentInfo.comma) {
                wordParser.moveBackward(count);
            }
        }

        // Nothing was parsed
        if (currentInfo == null) {
            return null;
        }

        // Simply return the single expression
        if (currentInfo == rootInfo &&
           !currentInfo.comma &&
           !currentInfo.space) {

            return currentInfo.expression;
        }

        // Return a collection of expressions
        return new CollectionExpression(
            this,
            rootInfo.buildChildren(),
            rootInfo.buildCommas(),
            rootInfo.buildSpaces()
        );
    }

    /**
     * Right away parses the text by retrieving the {@link ExpressionFactory} for the first word that
     * is extracted from {@link WordParser} at the current location.
     *
     * @param wordParser The text to parse based on the current position of the cursor
     * @param queryBNFId The unique identifier of the {@link JPQLQueryBNF} that is used to determine
     * how to parse the text at the current cursor position within the JPQL query
     * @param tolerant Determines whether the parsing system should be tolerant, meaning if it should
     * try to parse invalid or incomplete queries
     * @return The {@link Expression} representing the given sub-query
     */
    protected AbstractExpression parseUsingExpressionFactory(WordParser wordParser,
                                                             String queryBNFId,
                                                             boolean tolerant) {

        String word = wordParser.word();
        JPQLQueryBNF queryBNF = getQueryBNF(queryBNFId);
        ExpressionFactory factory = queryBNF.getExpressionFactory(word);

        if (factory == null) {
            return null;
        }

        return factory.buildExpression(this, wordParser, word, queryBNF, null, tolerant);
    }

    /**
     * {@inheritDoc}
     */
    public void populatePosition(QueryPosition queryPosition, int position) {

        queryPosition.addPosition(this, position);

        // The position is at the beginning of this expression
        if (position == 0) {
            queryPosition.setExpression(this);
        }
        else {
            // Traverse the children in order to find where the cursor is located
            for (Expression expression : orderedChildren()) {

                String expressionText = expression.toParsedText();

                // The position is in the Expression, traverse it
                if (position <= expressionText.length()) {
                    expression.populatePosition(queryPosition, position);
                    return;
                }

                // Continue with the next child by adjusting the position
                position -= expressionText.length();
            }

            throw new IllegalStateException("A problem was encountered while calculating the position.");
        }
    }

    /**
     * Rebuilds the actual parsed text if it has been cached.
     */
    protected final void rebuildActualText() {
        if (actualText != null) {
            toActualText();
        }
    }

    /**
     * Rebuilds the parsed parsed text if it has been cached.
     */
    protected final void rebuildParsedText() {
        if (parsedText != null) {
            toParsedText();
        }
    }

    /**
     * Re-parents this {@link Expression} to be a child of the given {@link Expression}.
     *
     * @param parent The new parent of this object
     */
    protected final void setParent(AbstractExpression parent) {
        this.parent = parent;
    }

    /**
     * Sets the text of this {@link Expression}.
     *
     * @param text The immutable text wrapped by this {@link Expression}, which cannot be <code>null</code>
     */
    protected final void setText(String text) {
        this.text = text;
    }

    /**
     * Determines whether the parsing of the query should be performed using the {@link ExpressionFactory
     * factories} first or it should automatically fallback to the fallback factory.
     *
     * @return <code>true</code> is returned by default so the factories are used before falling back
     */
    protected boolean shouldParseWithFactoryFirst() {
        return true;
    }

    /**
     * When parsing an invalid or incomplete query, it is possible two literals would be parsed but
     * in some cases, a CollectionExpression should not be created and the parsing should actually
     * stop here. Example: BETWEEN 10 20, when parsing 20, it should not be parsed as part of the
     * lower bound expression.
     *
     * @param expression The {@link Expression} that has just been parsed or <code>null</code>
     * @return <code>true</code>
     */
    protected boolean shouldSkipLiteral(AbstractExpression expression) {
        return (expression != null);
    }

    /**
     * {@inheritDoc}
     */
    public String toActualText() {
        if (actualText == null) {
            StringBuilder writer = new StringBuilder();
            toParsedText(writer, true);
            actualText = writer.toString();
        }
        return actualText;
    }

    /**
     * {@inheritDoc}
     */
    public String toParsedText() {
        if (parsedText == null) {
            StringBuilder writer = new StringBuilder();
            toParsedText(writer, false);
            parsedText = writer.toString();
        }
        return parsedText;
    }

    /**
     * Generates a string representation of this {@link Expression}, including its children,
     * if it has any.
     *
     * @param writer The buffer used to append this {@link Expression}'s string representation
     * @param actual Determines whether the string representation should represent what was parsed,
     * i.e. include any "virtual" whitespace (such as ending whitespace) and the actual case of the
     * JPQL identifiers
     */
    protected abstract void toParsedText(StringBuilder writer, boolean actual);

    /**
     * {@inheritDoc}
     */
    @Override
    public final String toString() {
        // toString() should only be called during debugging, thus the cached parsed text
        // should always be recreated in order to reflect the current state while debugging
        parsedText = null;
        return toParsedText();
    }

    /**
     * Rather than creating three lists when performing a single parsing operation ({@link
     * AbstractExpression#parse(WordParser, String, boolean) parse(WordParser, String, boolean)}),
     * this class will act as a simple chained list and postponing the creation of those three lists
     * only when needed (which is when the parsed expression needs to be a collection of expressions).
     */
    private static class Info {

        /**
         * Flag indicating a comma follows the parsed {@link #expression}.
         */
        boolean comma;

        /**
         * The parsed {@link Expression}, which is the first one that was parsed if {@link #next} is
         * <code>null</code>, meaning this <code>Info</code> object is the root of the chained list.
         */
        AbstractExpression expression;

        /**
         * When more than one expression needs to be parsed, it will be chained.
         */
        Info next;

        /**
         * The parent within the chain of this one.
         */
        Info previous;

        /**
         * Flag indicating a whitespace follows the parsed {@link #expression}.
         */
        boolean space;

        /**
         * Creates a new <code>Info</code>.
         */
        Info() {
            super();
        }

        /**
         * Creates a new <code>Info</code>.
         *
         * @param previous The parent within the chain of this one
         */
        Info(Info previous) {
            super();
            this.previous = previous;
        }

        private void addChild(ArrayList<AbstractExpression> children) {
            children.add(expression);
            if (next != null) {
                next.addChild(children);
            }
        }

        private void addComma(ArrayList<Boolean> children) {
            children.add(comma);
            if (next != null) {
                next.addComma(children);
            }
        }

        private void addSpace(ArrayList<Boolean> children) {
            children.add(space);
            if (next != null) {
                next.addSpace(children);
            }
        }

        List<AbstractExpression> buildChildren() {
            ArrayList<AbstractExpression> children = new ArrayList<AbstractExpression>();
            addChild(children);
            return children;
        }

        List<Boolean> buildCommas() {
            ArrayList<Boolean> children = new ArrayList<Boolean>();
            addComma(children);
            return children;
        }

        List<Boolean> buildSpaces() {
            ArrayList<Boolean> children = new ArrayList<Boolean>();
            addSpace(children);
            return children;
        }
    }
}
