/*******************************************************************************
 * Copyright (c) 2006, 2013 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 v1.0 and Eclipse Distribution License v. 1.0
 * which accompanies this distribution.
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *     Oracle - initial API and implementation
 *
 ******************************************************************************/
package org.eclipse.persistence.jpa.jpql.parser;

import java.util.List;
import org.eclipse.persistence.jpa.jpql.WordParser;

/**
 * An identification variable is a valid identifier declared in the <b>FROM</b> clause of a query.
 * <p>
 * Requirements:
 * <ul>
 * <li>All identification variables must be declared in the <b>FROM</b> clause. Identification
 * variables cannot be declared in other clauses.
 * <li>An identification variable must not be a reserved identifier or have the same name as any
 * entity in the same persistence unit.
 * <li>Identification variables are case insensitive.
 * <li>An identification variable evaluates to a value of the type of the expression used in
 * declaring the variable.
 * </ul>
 * <p>
 * An identification variable can range over an entity, embeddable, or basic abstract schema type.
 * An identification variable designates an instance of an abstract schema type or an element of a
 * collection of abstract schema type instances.
 * <p>
 * Note that for identification variables referring to an instance of an association or collection
 * represented as a {@link java.util.Map}, the identification variable is of the abstract schema
 * type of the map value.
 * <p>
 * An identification variable always designates a reference to a single value. It is declared in one
 * of three ways:
 * <ul>
 * <li>In a range variable declaration;
 * <li>In a join clause;
 * <li>In a collection member declaration.
 * </ul>
 * The identification variable declarations are evaluated from left to right in the <b>FROM</b>
 * clause, and an identification variable declaration can use the result of a preceding
 * identification variable declaration of the query string.
 * <p>
 * All identification variables used in the <b>SELECT</b>, <b>WHERE</b>, <b>ORDER BY</b>,
 * <b>GROUP BY</b>, or <b>HAVING</b> clause of a <b>SELECT</b> or <b>DELETE</b> statement must be
 * declared in the <b>FROM</b> clause. The identification variables used in the <b>WHERE</b> clause
 * of an <b>UPDATE</b> statement must be declared in the <b>UPDATE</b> clause.
 * <p>
 * An identification variable is scoped to the query (or subquery) in which it is defined and is
 * also visible to any subqueries within that query scope that do not define an identification
 * variable of the same name.
 *
 * @version 2.4
 * @since 2.3
 * @author Pascal Filion
 */
public final class IdentificationVariable extends AbstractExpression {

	/**
	 * The virtual state field path expression having
	 */
	private StateFieldPathExpression stateFieldPathExpression;

	/**
	 * The uppercase version of the identification variable.
	 */
	private String variableName;

	/**
	 * Determines whether this identification variable is virtual, meaning it's not part of the query
	 * but is required for proper navigability.
	 */
	private boolean virtual;

	/**
	 * Creates a new <code>IdentificationVariable</code>.
	 *
	 * @param parent The parent of this expression
	 * @param identificationVariable The actual identification variable
	 */
	public IdentificationVariable(AbstractExpression parent, String identificationVariable) {
		super(parent, identificationVariable);
	}

	/**
	 * Creates a new <code>IdentificationVariable</code>.
	 *
	 * @param parent The parent of this expression
	 * @param identificationVariable The actual identification variable
	 * @param virtual Determines whether this identification variable is virtual, meaning it's not
	 * part of the query but is required for proper navigability
	 */
	public IdentificationVariable(AbstractExpression parent, String identificationVariable, boolean virtual) {
		super(parent, identificationVariable);
		this.virtual = virtual;
	}

	/**
	 * {@inheritDoc}
	 */
	public void accept(ExpressionVisitor visitor) {
		visitor.visit(this);
	}

	/**
	 * {@inheritDoc}
	 */
	public void acceptChildren(ExpressionVisitor visitor) {
		// Nothing to traverse
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void addOrderedChildrenTo(List<Expression> children) {
		children.add(buildStringExpression(getText()));
	}

	/**
	 * {@inheritDoc}
	 */
	public JPQLQueryBNF getQueryBNF() {
		return getQueryBNF(IdentificationVariableBNF.ID);
	}

	/**
	 * Returns the actual representation of the parsed information. This method should only be called
	 * if {@link #isVirtual()} returns <code>true</code>. This is valid in an <b>UPDATE</b> and
	 * <b>DELETE</b> queries where the identification variable is not specified.
	 *
	 * @return The path expression that is qualified by the virtual identification variable
	 * @throws IllegalAccessError If this expression does not have a virtual identification variable
	 */
	@SuppressWarnings("nls")
	public StateFieldPathExpression getStateFieldPathExpression() {
		if (!virtual) {
			throw new IllegalAccessError("IdentificationVariable.getStateFieldPathExpression() can only be accessed when it represents an attribute that is not fully qualified, which can be present in an UPDATE or DELETE query.");
		}
		return stateFieldPathExpression;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String getText() {
		return super.getText();
	}

	/**
	 * Returns the identification variable, which has been changed to be upper case.
	 *
	 * @return The uppercase version of the identification variable
	 * @since 2.4
	 */
	public String getVariableName() {
		if (variableName == null) {
			variableName = getText().toUpperCase().intern();
		}
		return variableName;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean isVirtual() {
		return virtual;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void parse(WordParser wordParser, boolean tolerant) {
		wordParser.moveForward(getText());
	}

	/**
	 * Sets a virtual identification variable because the abstract schema name was parsed without
	 * one. This is valid in an <b>UPDATE</b> and <b>DELETE</b> queries. This internally transforms
	 * the what was thought to be an identification variable to a path expression.
	 *
	 * @param variableName The identification variable that was generated to identify the "root" object
	 */
	public void setVirtualIdentificationVariable(String variableName) {

		virtual = true;

		stateFieldPathExpression = new StateFieldPathExpression(getParent(), getText());
		stateFieldPathExpression.setVirtualIdentificationVariable(variableName);

		rebuildActualText();
		rebuildParsedText();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String toParsedText() {
		return getText();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void toParsedText(StringBuilder writer, boolean actual) {
		writer.append(getText());
	}
}