/*******************************************************************************
 * Copyright (c) 2006, 2014 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;

/**
 * This visitor makes sure that all path expressions are fully qualified with a "virtual"
 * identification variable if the range variable declaration does not define one. This only applies
 * to an <code><b>UPDATE</b></code> or <code><b>DELETE</b></code> queries.
 *
 * @version 2.5
 * @since 2.3
 * @author Pascal Filion
 */
public final class FullyQualifyPathExpressionVisitor extends AbstractTraverseChildrenVisitor {

	/**
	 * The "virtual" identification variable if none was defined.
	 */
	private String variableName;

	/**
	 * Caches this visitor, which is used to determine if the general identification variable is not
	 * a map key, map value or map entry expression.
	 */
	private GeneralIdentificationVariableVisitor visitor;

	private GeneralIdentificationVariableVisitor generalIdentificationVariableVisitor() {
		if (visitor == null) {
			visitor = new GeneralIdentificationVariableVisitor();
		}
		return visitor;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(AbstractSchemaName expression) {
		// The "virtual" variable name will be the entity name
		variableName = expression.toActualText().toLowerCase();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(CollectionMemberDeclaration expression) {
		// Do nothing, prevent to do anything for invalid queries
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(CollectionValuedPathExpression expression) {
		visitAbstractPathExpression(expression);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(DeleteClause expression) {
		expression.getRangeVariableDeclaration().accept(this);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(DeleteStatement expression) {

		expression.getDeleteClause().accept(this);

		// Don't traverse the tree if the path expressions don't need to be virtually qualified
		if ((variableName != null) && expression.hasWhereClause()) {
			expression.getWhereClause().accept(this);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(IdentificationVariable expression) {

		// A null check is required because the query could be invalid/incomplete
		// The identification variable should become a state field path expression
		expression.setVirtualIdentificationVariable(variableName);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(Join expression) {
		// Do nothing, prevent to do anything for invalid queries
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(RangeVariableDeclaration expression) {

		// The "root" object does not have an identification variable,
		// then we'll assume all path expressions are unqualified
		if (!expression.hasIdentificationVariable()) {
			expression.getRootObject().accept(this);
			expression.setVirtualIdentificationVariable(variableName);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(SelectStatement expression) {
		// Nothing to do because a SELECT query has to have its path expressions fully qualified
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(SimpleSelectStatement expression) {
		// Nothing to do because a subquery query has to have its path expressions fully qualified
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(StateFieldPathExpression expression) {
		// A null check is required because the query could be invalid/incomplete
		visitAbstractPathExpression(expression);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(UpdateClause expression) {

		expression.getRangeVariableDeclaration().accept(this);

		// Don't traverse the tree if the path expressions don't need to be virtually qualified
		if ((variableName != null) && expression.hasUpdateItems()) {
			expression.getUpdateItems().accept(this);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(UpdateStatement expression) {

		expression.getUpdateClause().accept(this);

		// Don't traverse the tree if the path expressions don't need to be virtually qualified
		if ((variableName != null) && expression.hasWhereClause()) {
			expression.getWhereClause().accept(this);
		}
	}

	private void visitAbstractPathExpression(AbstractPathExpression expression) {

		if (!expression.startsWithDot()) {

			// Visit the general identification variable to make sure it's not a map key, map entry
			// or map value expression
			GeneralIdentificationVariableVisitor visitor = generalIdentificationVariableVisitor();
			expression.getIdentificationVariable().accept(visitor);

			if (visitor.expression == null) {
				expression.setVirtualIdentificationVariable(variableName);
			}
		}
	}

    // Made static final for performance reasons.
	private static final class GeneralIdentificationVariableVisitor extends AbstractExpressionVisitor {

		/**
		 * The {@link Expression} that was visited, which is a general identification variable but is
		 * not a identification variable, it's either a map value, map key or map value expression.
		 */
		private Expression expression;

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void visit(EntryExpression expression) {
			this.expression = expression;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void visit(KeyExpression expression) {
			this.expression = expression;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void visit(ValueExpression expression) {
			this.expression = expression;
		}
	}
}