// $Id: HqlSqlWalker.java,v 1.109 2005/10/14 17:18:38 steveebersole Exp $
package org.hibernate.hql.ast;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.QueryException;
import org.hibernate.engine.JoinSequence;
import org.hibernate.engine.ParameterBinder;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.hql.QueryTranslator;
import org.hibernate.hql.antlr.HqlSqlBaseWalker;
import org.hibernate.hql.antlr.HqlSqlTokenTypes;
import org.hibernate.hql.antlr.HqlTokenTypes;
import org.hibernate.hql.antlr.SqlTokenTypes;
import org.hibernate.hql.ast.tree.AssignmentSpecification;
import org.hibernate.hql.ast.tree.CollectionFunction;
import org.hibernate.hql.ast.tree.ConstructorNode;
import org.hibernate.hql.ast.tree.DeleteStatement;
import org.hibernate.hql.ast.tree.DotNode;
import org.hibernate.hql.ast.tree.FromClause;
import org.hibernate.hql.ast.tree.FromElement;
import org.hibernate.hql.ast.tree.FromReferenceNode;
import org.hibernate.hql.ast.tree.IdentNode;
import org.hibernate.hql.ast.tree.IndexNode;
import org.hibernate.hql.ast.tree.InsertStatement;
import org.hibernate.hql.ast.tree.IntoClause;
import org.hibernate.hql.ast.tree.MethodNode;
import org.hibernate.hql.ast.tree.ParameterNode;
import org.hibernate.hql.ast.tree.QueryNode;
import org.hibernate.hql.ast.tree.ResolvableNode;
import org.hibernate.hql.ast.tree.RestrictableStatement;
import org.hibernate.hql.ast.tree.SelectClause;
import org.hibernate.hql.ast.tree.SelectExpression;
import org.hibernate.hql.ast.tree.UpdateStatement;
import org.hibernate.hql.ast.tree.Node;
import org.hibernate.hql.ast.tree.OperatorNode;
import org.hibernate.hql.ast.util.ASTPrinter;
import org.hibernate.hql.ast.util.ASTUtil;
import org.hibernate.hql.ast.util.AliasGenerator;
import org.hibernate.hql.ast.util.JoinProcessor;
import org.hibernate.hql.ast.util.LiteralProcessor;
import org.hibernate.hql.ast.util.SessionFactoryHelper;
import org.hibernate.hql.ast.util.SyntheticAndFactory;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.PostInsertIdentifierGenerator;
import org.hibernate.id.SequenceGenerator;
import org.hibernate.param.NamedParameterSpecification;
import org.hibernate.param.ParameterSpecification;
import org.hibernate.param.PositionalParameterSpecification;
import org.hibernate.param.VersionTypeSeedParameterSpecification;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.Queryable;
import org.hibernate.sql.JoinFragment;
import org.hibernate.type.AssociationType;
import org.hibernate.type.Type;
import org.hibernate.type.VersionType;
import org.hibernate.usertype.UserVersionType;
import org.hibernate.util.ArrayHelper;

import antlr.ASTFactory;
import antlr.RecognitionException;
import antlr.SemanticException;
import antlr.collections.AST;

/**
 * Implements methods used by the HQL->SQL tree transform grammar (a.k.a. the second phase).
 * <ul>
 * <li>Isolates the Hibernate API-specific code from the ANTLR generated code.</li>
 * <li>Handles the SQL framgents generated by the persisters in order to create the SELECT and FROM clauses,
 * taking into account the joins and projections that are implied by the mappings (persister/queryable).</li>
 * <li>Uses SqlASTFactory to create customized AST nodes.</li>
 * </ul>
 *
 * @see SqlASTFactory
 */
public class HqlSqlWalker extends HqlSqlBaseWalker implements ErrorReporter, ParameterBinder.NamedParameterSource {
	private static Log log = LogFactory.getLog( HqlSqlWalker.class );

	/**
	 * A delegate that handles the Hiberanate meta data model.
	 */
	private SessionFactoryHelper sessionFactoryHelper;

	/**
	 * A delegate that handles literal constants.
	 */
	private LiteralProcessor literalProcessor;

	/**
	 * Handles parser errors.
	 */
	private ParseErrorHandler parseErrorHandler;

	/**
	 * The current context.
	 */
	private FromClause currentFromClause = null;

	/**
	 * The top-level SelectClause.
	 */
	private SelectClause selectClause;

	/**
	 * Generates alias names for tables.
	 */
	private AliasGenerator aliasGenerator = new AliasGenerator();

	/**
	 * The set of unique query spaces (a.k.a. table names).
	 */
	private Set querySpaces = new HashSet();

	/**
	 * A (string->string) map is used to substitute function names and literals.
	 */
	private Map tokenReplacements;

	private QueryTranslatorImpl queryTranslatorImpl;

	/**
	 * The number of parameters encountered so far.
	 */
	private int parameterCount;

	/**
	 * A map of lists which associates named and numbered parameters to a list of occurrences in the query.
	 */
	private Map namedParameters = new HashMap();

	/**
	 * The filter collection role, or null if this isn't a filter compilation.
	 */
	private String filterCollectionRole;

	/**
	 * The parser that performed phase 1 - parse the HQL into an HQL tree.
	 */
	private HqlParser hqlParser;

	private ASTPrinter printer;

	/**
	 * The join type for any implied joins.
	 */
	private int impliedJoinType;

	private ArrayList parameters = new ArrayList();
	private int numberOfParametersInSetClause;
	private int positionalParameterCount;

	private ArrayList assignmentSpecifications = new ArrayList();

	/**
	 * Create a new tree transformer.
	 *
	 * @param qti               Back pointer to the query translator implementation that is using this tree transform.
	 * @param sfi               The session factory implementor where the Hibernate mappings can be found.
	 * @param parser
	 * @param tokenReplacements Registers the token replacement map with the walker.  This map will
	 *                          be used to substitute function names and constants.
	 * @param collectionRole    the role name of the collection used as the basis for the filter, NULL if this
	 */
	public HqlSqlWalker(QueryTranslatorImpl qti,
						SessionFactoryImplementor sfi,
						HqlParser parser,
						Map tokenReplacements,
						String collectionRole) {
		setASTFactory( new SqlASTFactory( this ) );
		// Initialize the error handling delegate.
		this.parseErrorHandler = new ErrorCounter();
		this.queryTranslatorImpl = qti;
		this.sessionFactoryHelper = new SessionFactoryHelper( sfi );
		this.literalProcessor = new LiteralProcessor( this );
		this.tokenReplacements = tokenReplacements;
		this.filterCollectionRole = collectionRole;
		this.hqlParser = parser;
		this.printer = new ASTPrinter( SqlTokenTypes.class );
	}


	protected void prepareFromClauseInputTree(AST fromClauseInput) {
		// Handle fiter compilation.
		// IMPORTANT NOTE: This is modifying the INPUT (HQL) tree, not the output tree!
		if ( isFilter() && !isSubQuery() ) {
			QueryableCollection persister = sessionFactoryHelper.getCollectionPersister( filterCollectionRole );
			Type collectionElementType = persister.getElementType();
			if ( !collectionElementType.isEntityType() ) {
				throw new QueryException( "collection of values in filter: this" );
			}

			String collectionElementEntityName = persister.getElementPersister().getEntityName();
			ASTFactory inputAstFactory = hqlParser.getASTFactory();
			AST fromElement = ASTUtil.create( inputAstFactory, HqlTokenTypes.FILTER_ENTITY, collectionElementEntityName );
			ASTUtil.createSibling( inputAstFactory, HqlTokenTypes.ALIAS, "this", fromElement );
			fromClauseInput.addChild( fromElement );
			// Show the modified AST.
			if ( log.isDebugEnabled() ) {
				log.debug( "prepareFromClauseInputTree() : Filter - Added 'this' as a from element..." );
			}
			queryTranslatorImpl.showHqlAst( hqlParser.getAST() );
		}
	}

	private boolean isFilter() {
		return filterCollectionRole != null;
	}

	public SessionFactoryHelper getSessionFactoryHelper() {
		return sessionFactoryHelper;
	}

	public Map getTokenReplacements() {
		return tokenReplacements;
	}

	public AliasGenerator getAliasGenerator() {
		return aliasGenerator;
	}

	public FromClause getCurrentFromClause() {
		return currentFromClause;
	}

	public ParseErrorHandler getParseErrorHandler() {
		return parseErrorHandler;
	}

	public void reportError(RecognitionException e) {
		parseErrorHandler.reportError( e ); // Use the delegate.
	}

	public void reportError(String s) {
		parseErrorHandler.reportError( s ); // Use the delegate.
	}

	public void reportWarning(String s) {
		parseErrorHandler.reportWarning( s );
	}

	/**
	 * Returns the set of unique query spaces (a.k.a.
	 * table names) that occurred in the query.
	 *
	 * @return A set of table names (Strings).
	 */
	public Set getQuerySpaces() {
		return querySpaces;
	}

	protected AST createFromElement(String path, AST alias, AST propertyFetch) throws SemanticException {
		FromElement fromElement = currentFromClause.addFromElement( path, alias );
		fromElement.setAllPropertyFetch(propertyFetch!=null);
		return fromElement;
	}

	protected AST createFromFilterElement(AST filterEntity, AST alias) throws SemanticException {
		FromElement fromElement = currentFromClause.addFromElement( filterEntity.getText(), alias );
		FromClause fromClause = fromElement.getFromClause();
		QueryableCollection persister = sessionFactoryHelper.getCollectionPersister( filterCollectionRole );
		// Get the names of the columns used to link between the collection
		// owner and the collection elements.
		String[] keyColumnNames = persister.getKeyColumnNames();
		String fkTableAlias = persister.isOneToMany()
				? fromElement.getTableAlias()
				: fromClause.getAliasGenerator().createName( filterCollectionRole );
		JoinSequence join = sessionFactoryHelper.createJoinSequence();
		join.setRoot( persister, fkTableAlias );
		if ( !persister.isOneToMany() ) {
			join.addJoin( ( AssociationType ) persister.getElementType(),
					fromElement.getTableAlias(),
					JoinFragment.INNER_JOIN,
					persister.getElementColumnNames( fkTableAlias ) );
		}
		join.addCondition( fkTableAlias, keyColumnNames, " = ?" );
		fromElement.setJoinSequence( join );
		fromElement.setFilter( true );
		if ( log.isDebugEnabled() ) {
			log.debug( "createFromFilterElement() : processed filter FROM element." );
		}
		return fromElement;
	}

	protected void createFromJoinElement(
	        AST path,
	        AST alias,
	        int joinType,
	        AST fetchNode,
	        AST propertyFetch,
	        AST with) throws SemanticException {
		boolean fetch = ( fetchNode != null ) ? true : false;
		// The path AST should be a DotNode, and it should have been evaluated already.
		if ( path.getType() != SqlTokenTypes.DOT ) {
			throw new SemanticException( "Path expected for join!" );
		}
		DotNode dot = ( DotNode ) path;
		int hibernateJoinType = JoinProcessor.toHibernateJoinType( joinType );
		dot.setJoinType( hibernateJoinType );	// Tell the dot node about the join type.
		dot.setFetch( fetch );
		// Generate an explicit join for the root dot node.   The implied joins will be collected and passed up
		// to the root dot node.
		dot.resolve( true, false, alias == null ? null : alias.getText() );
		FromElement fromElement = dot.getImpliedJoin();
		fromElement.setAllPropertyFetch(propertyFetch!=null);

		if ( with != null ) {
			if ( fetch ) {
				throw new SemanticException( "with-clause not allowed on fetched associations; use filters" );
			}
			handleWithFragment( fromElement, with );
		}

		if ( log.isDebugEnabled() ) {
			log.debug( "createFromJoinElement() : " + getASTPrinter().showAsString( fromElement, "-- join tree --" ) );
		}
	}

	private void handleWithFragment(FromElement fromElement, AST hqlWithNode) throws SemanticException
	{
		try {
			withClause( hqlWithNode );
			AST hqlSqlWithNode = returnAST;
			SqlGenerator sql = new SqlGenerator( getSessionFactoryHelper().getFactory() );
			sql.whereExpr( hqlSqlWithNode.getFirstChild() );
			fromElement.setAdHocOnClauseFragment( "(" + sql.getSQL() + ")" );

		} catch ( Exception e) {
			throw new SemanticException( e.getMessage() );
		}
	}

	/**
	 * Sets the current 'FROM' context.
	 *
	 * @param fromNode      The new 'FROM' context.
	 * @param inputFromNode The from node from the input AST.
	 */
	protected void pushFromClause(AST fromNode, AST inputFromNode) {
		FromClause newFromClause = ( FromClause ) fromNode;
		newFromClause.setParentFromClause( currentFromClause );
		currentFromClause = newFromClause;
	}

	/**
	 * Returns to the previous 'FROM' context.
	 */
	private void popFromClause() {
		currentFromClause = currentFromClause.getParentFromClause();
	}

	protected void lookupAlias(AST aliasRef)
			throws SemanticException {
		FromElement alias = currentFromClause.getFromElement( aliasRef.getText() );
		FromReferenceNode aliasRefNode = ( FromReferenceNode ) aliasRef;
		aliasRefNode.setFromElement( alias );
	}

	protected void setImpliedJoinType(int joinType) {
		impliedJoinType = JoinProcessor.toHibernateJoinType( joinType );
	}

	public int getImpliedJoinType() {
		return impliedJoinType;
	}

	protected AST lookupProperty(AST dot, boolean root, boolean inSelect) throws SemanticException {
		DotNode dotNode = ( DotNode ) dot;
		FromReferenceNode lhs = dotNode.getLhs();
		AST rhs = lhs.getNextSibling();
		switch ( rhs.getType() ) {
			case SqlTokenTypes.ELEMENTS:
			case SqlTokenTypes.INDICES:
				if ( log.isDebugEnabled() ) {
					log.debug( "lookupProperty() " + dotNode.getPath() + " => " + rhs.getText() + "(" + lhs.getPath() + ")" );
				}
				CollectionFunction f = ( CollectionFunction ) rhs;
				// Re-arrange the tree so that the collection function is the root and the lhs is the path.
				f.setFirstChild( lhs );
				lhs.setNextSibling( null );
				dotNode.setFirstChild( f );
				resolve( lhs );			// Don't forget to resolve the argument!
				f.resolve( inSelect );	// Resolve the collection function now.
				return f;
			default:
				// Resolve everything up to this dot, but don't resolve the placeholders yet.
				dotNode.resolveFirstChild();
				return dotNode;
		}
	}

	protected boolean isNonQualifiedPropertyRef(AST ident) {
		final String identText = ident.getText();
		if ( currentFromClause.isFromElementAlias( identText ) ) {
			return false;
		}

		List fromElements = currentFromClause.getExplicitFromElements();
		if ( fromElements.size() == 1 ) {
			final FromElement fromElement = ( FromElement ) fromElements.get( 0 );
			try {
				log.trace( "attempting to resolve property [" + identText + "] as a non-qualified ref" );
				return fromElement.getPropertyMapping( identText ).toType( identText ) != null;
			}
			catch( QueryException e ) {
				// Should mean that no such property was found
			}
		}

		return false;
	}

	protected AST lookupNonQualifiedProperty(AST property) throws SemanticException {
		final FromElement fromElement = ( FromElement ) currentFromClause.getExplicitFromElements().get( 0 );
		AST syntheticDotNode = generateSyntheticDotNodeForNonQualifiedPropertyRef( property, fromElement );
		return lookupProperty( syntheticDotNode, false, getCurrentClauseType() == HqlSqlTokenTypes.SELECT );
	}

	private AST generateSyntheticDotNodeForNonQualifiedPropertyRef(AST property, FromElement fromElement) {
		AST dot = getASTFactory().create( DOT, "{non-qualified-property-ref}" );
		// TODO : better way?!?
		( ( DotNode ) dot ).setPropertyPath( ( ( FromReferenceNode ) property ).getPath() );

		IdentNode syntheticAlias = ( IdentNode ) getASTFactory().create( IDENT, "{synthetic-alias}" );
		syntheticAlias.setFromElement( fromElement );
		syntheticAlias.setResolved();

		dot.setFirstChild( syntheticAlias );
		dot.addChild( property );

		return dot;
	}

	protected void processQuery(AST select, AST query) throws SemanticException {
		if ( log.isDebugEnabled() ) {
			log.debug( "processQuery() : " + query.toStringTree() );
		}

		try {
			QueryNode qn = ( QueryNode ) query;

			// Was there an explicit select expression?
			boolean explicitSelect = select != null && select.getNumberOfChildren() > 0;

			if ( !explicitSelect ) {
				// No explicit select expression; render the id and properties
				// projection lists for every persister in the from clause into
				// a single 'token node'.
				//TODO: the only reason we need this stuff now is collection filters,
				//      we should get rid of derived select clause completely!
				createSelectClauseFromFromClause( qn );
			}
			else {
				// Use the explicitly declared select expression; determine the
				// return types indicated by each select token
				useSelectClause( select );
			}

			// After that, process the JOINs.
			// Invoke a delegate to do the work, as this is farily complex.
			JoinProcessor joinProcessor = new JoinProcessor( astFactory, queryTranslatorImpl );
			joinProcessor.processJoins( qn, isSubQuery() );

			// Attach any mapping-defined "ORDER BY" fragments
			Iterator itr = qn.getFromClause().getProjectionList().iterator();
			while ( itr.hasNext() ) {
				final FromElement fromElement = ( FromElement ) itr.next();
//			if ( fromElement.isFetch() && fromElement.isCollectionJoin() ) {
				if ( fromElement.isFetch() && fromElement.getQueryableCollection() != null ) {
					// Does the collection referenced by this FromElement
					// specify an order-by attribute?  If so, attach it to
					// the query's order-by
					if ( fromElement.getQueryableCollection().hasOrdering() ) {
						String orderByFragment = fromElement
								.getQueryableCollection()
								.getSQLOrderByString( fromElement.getCollectionTableAlias() );
						qn.getOrderByClause().addOrderFragment( orderByFragment );
					}
				}
			}
		}
		finally {
			popFromClause();
		}
	}

	protected void postProcessDML(RestrictableStatement statement) throws SemanticException {
		statement.getFromClause().resolve();

		FromElement fromElement = ( FromElement ) statement.getFromClause().getFromElements().get( 0 );
		Queryable persister = fromElement.getQueryable();
		// Make #@%$^#^&# sure no alias is applied to the table name
		fromElement.setText( persister.getTableName() );

		// append any filter fragments; the EMPTY_MAP is used under the assumption that
		// currently enabled filters should not affect this process
		if ( persister.getDiscriminatorType() != null ) {
			new SyntheticAndFactory( getASTFactory() ).addDiscriminatorWhereFragment(
			        statement,
			        persister,
			        java.util.Collections.EMPTY_MAP,
			        fromElement.getTableAlias()
			);
		}

	}

	protected void postProcessUpdate(AST update) throws SemanticException {
		UpdateStatement updateStatement = ( UpdateStatement ) update;

		postProcessDML( updateStatement );
	}

	protected void postProcessDelete(AST delete) throws SemanticException {
		postProcessDML( ( DeleteStatement ) delete );
	}

	public static boolean supportsIdGenWithBulkInsertion(IdentifierGenerator generator) {
		return SequenceGenerator.class.isAssignableFrom( generator.getClass() )
		        || PostInsertIdentifierGenerator.class.isAssignableFrom( generator.getClass() );
	}

	protected void postProcessInsert(AST insert) throws SemanticException, QueryException {
		InsertStatement insertStatement = ( InsertStatement ) insert;
		insertStatement.validate();

		SelectClause selectClause = insertStatement.getSelectClause();
		Queryable persister = insertStatement.getIntoClause().getQueryable();

		if ( !insertStatement.getIntoClause().isExplicitIdInsertion() ) {
			// We need to generate ids as part of this bulk insert.
			//
			// Note that this is only supported for sequence-style generators and
			// post-insert-style generators; basically, only in-db generators
			IdentifierGenerator generator = persister.getIdentifierGenerator();
			if ( !supportsIdGenWithBulkInsertion( generator ) ) {
				throw new QueryException( "can only generate ids as part of bulk insert with either sequence or post-insert style generators" );
			}

			AST idSelectExprNode = null;

			if ( SequenceGenerator.class.isAssignableFrom( generator.getClass() ) ) {
				String seqName = ( String ) ( ( SequenceGenerator ) generator ).generatorKey();
				String nextval = sessionFactoryHelper.getFactory().getDialect().getSelectSequenceNextValString( seqName );
				idSelectExprNode = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, nextval );
			}
			else {
				//Don't need this, because we should never ever be selecting no columns in an insert ... select...
				//and because it causes a bug on DB2
				/*String idInsertString = sessionFactoryHelper.getFactory().getDialect().getIdentityInsertString();
				if ( idInsertString != null ) {
					idSelectExprNode = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, idInsertString );
				}*/
			}

			if ( idSelectExprNode != null ) {
				AST currentFirstSelectExprNode = selectClause.getFirstChild();
				selectClause.setFirstChild( idSelectExprNode );
				idSelectExprNode.setNextSibling( currentFirstSelectExprNode );

				insertStatement.getIntoClause().prependIdColumnSpec();
			}
		}

		final boolean includeVersionProperty = persister.isVersioned() && 
				!insertStatement.getIntoClause().isExplicitVersionInsertion() && 
				persister.isVersionPropertyInsertable();
		if ( includeVersionProperty ) {
			// We need to re-seed the version value as part of this bulk insert
			VersionType versionType = persister.getVersionType();

			AST versionValue = getASTFactory().create( HqlSqlTokenTypes.PARAM, "?" );
			ParameterSpecification paramSpec = new VersionTypeSeedParameterSpecification( versionType );
			( ( ParameterNode ) versionValue ).setHqlParameterSpecification( paramSpec );
			parameters.add( 0, paramSpec );

			AST currentFirstSelectExprNode = selectClause.getFirstChild();
			selectClause.setFirstChild( versionValue );
			versionValue.setNextSibling( currentFirstSelectExprNode );

			insertStatement.getIntoClause().prependVersionColumnSpec();
		}

		if ( insertStatement.getIntoClause().isDiscriminated() ) {
			String sqlValue = insertStatement.getIntoClause().getQueryable().getDiscriminatorSQLValue();
			AST discrimValue = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, sqlValue );
			insertStatement.getSelectClause().addChild( discrimValue );
		}

	}

	private void useSelectClause(AST select) throws SemanticException {
		selectClause = ( SelectClause ) select;
		selectClause.initializeExplicitSelectClause( currentFromClause );
	}

	private void createSelectClauseFromFromClause(QueryNode qn) throws SemanticException {
		AST select = astFactory.create( SELECT_CLAUSE, "{derived select clause}" );
		AST sibling = qn.getFromClause();
		qn.setFirstChild( select );
		select.setNextSibling( sibling );
		selectClause = ( SelectClause ) select;
		selectClause.initializeDerivedSelectClause( currentFromClause );
		if ( log.isDebugEnabled() ) {
			log.debug( "Derived SELECT clause created." );
		}
	}

	protected void resolve(AST node) throws SemanticException {
		if ( node != null ) {
			// This is called when it's time to fully resolve a path expression.
			ResolvableNode r = ( ResolvableNode ) node;
			if ( isInFunctionCall() ) {
				r.resolveInFunctionCall( false, true );
			}
			else {
				r.resolve( false, true );	// Generate implicit joins, only if necessary.
			}
		}
	}

	protected void resolveSelectExpression(AST node) throws SemanticException {
		// This is called when it's time to fully resolve a path expression.
		int type = node.getType();
		switch ( type ) {
			case DOT:
				DotNode dot = ( DotNode ) node;
				dot.resolveSelectExpression();
				break;
			case ALIAS_REF:
				// Notify the FROM element that it is being referenced by the select.
				FromReferenceNode aliasRefNode = ( FromReferenceNode ) node;
				//aliasRefNode.resolve( false, false, aliasRefNode.getText() ); //TODO: is it kosher to do it here?
				aliasRefNode.resolve( false, false ); //TODO: is it kosher to do it here?
				FromElement fromElement = aliasRefNode.getFromElement();
				if ( fromElement != null ) {
					fromElement.setIncludeSubclasses( true );
				}
			default:
				break;
		}
	}

	protected void beforeSelectClause() throws SemanticException {
		// Turn off includeSubclasses on all FromElements.
		FromClause from = getCurrentFromClause();
		List fromElements = from.getFromElements();
		for ( Iterator iterator = fromElements.iterator(); iterator.hasNext(); ) {
			FromElement fromElement = ( FromElement ) iterator.next();
			fromElement.setIncludeSubclasses( false );
		}
	}

	protected AST generatePositionalParameter(AST inputNode) throws SemanticException {
		if ( namedParameters.size() > 0 ) {
			throw new SemanticException( "cannot define positional parameter after any named parameters have been defined" );
		}
		ParameterNode parameter = ( ParameterNode ) astFactory.create( PARAM, "?" );
		PositionalParameterSpecification paramSpec = new PositionalParameterSpecification(
				( ( Node ) inputNode ).getLine(),
		        ( ( Node ) inputNode ).getColumn(),
				positionalParameterCount++
		);
		parameter.setHqlParameterSpecification( paramSpec );
		parameters.add( paramSpec );
		return parameter;
	}

	protected AST generateNamedParameter(AST delimiterNode, AST nameNode) throws SemanticException {
		String name = nameNode.getText();
		trackNamedParameterPositions( name );

		// create the node initially with the param name so that it shows
		// appropriately in the "original text" attribute
		ParameterNode parameter = ( ParameterNode ) astFactory.create( NAMED_PARAM, name );
		parameter.setText( "?" );

		NamedParameterSpecification paramSpec = new NamedParameterSpecification(
				( ( Node ) delimiterNode ).getLine(),
		        ( ( Node ) delimiterNode ).getColumn(),
				name
		);
		parameter.setHqlParameterSpecification( paramSpec );
		parameters.add( paramSpec );
		return parameter;
	}

	private void trackNamedParameterPositions(String name) {
		Integer loc = new Integer( parameterCount++ );
		Object o = namedParameters.get( name );
		if ( o == null ) {
			namedParameters.put( name, loc );
		}
		else if ( o instanceof Integer ) {
			ArrayList list = new ArrayList( 4 );
			list.add( o );
			list.add( loc );
			namedParameters.put( name, list );
		}
		else {
			( ( ArrayList ) o ).add( loc );
		}
	}

	protected void processConstant(AST constant) throws SemanticException {
		literalProcessor.processConstant( constant );  // Use the delegate.
	}

	protected void processBoolean(AST constant) throws SemanticException {
		literalProcessor.processBoolean( constant );  // Use the delegate.
	}

	protected void processIndex(AST indexOp) throws SemanticException {
		IndexNode indexNode = ( IndexNode ) indexOp;
		indexNode.resolve( true, true );
	}

	protected void processFunction(AST functionCall, boolean inSelect) throws SemanticException {
		MethodNode methodNode = ( MethodNode ) functionCall;
		methodNode.resolve( inSelect );
	}

	protected void processConstructor(AST constructor) throws SemanticException {
		ConstructorNode constructorNode = ( ConstructorNode ) constructor;
		constructorNode.prepare();
	}

    protected void setAlias(AST selectExpr, AST ident) {
        ((SelectExpression) selectExpr).setAlias(ident.getText());
    }

    protected void setRoot(AST ast) {
        ( (IdentNode) ast).setRoot();
    }

	/**
	 * Returns the locations of all occurrences of the named parameter.
	 */
	public int[] getNamedParameterLocations(String name) throws QueryException {
		Object o = namedParameters.get( name );
		if ( o == null ) {
			QueryException qe = new QueryException( QueryTranslator.ERROR_NAMED_PARAMETER_DOES_NOT_APPEAR + name );
			qe.setQueryString( queryTranslatorImpl.getQueryString() );
			throw qe;
		}
		if ( o instanceof Integer ) {
			return new int[]{( ( Integer ) o ).intValue()};
		}
		else {
			return ArrayHelper.toIntArray( ( ArrayList ) o );
		}
	}

	public void addQuerySpaces(Serializable[] spaces) {
		for ( int i = 0; i < spaces.length; i++ ) {
			querySpaces.add( spaces[i] );
		}
	}

	public Type[] getReturnTypes() {
		return selectClause.getQueryReturnTypes();
	}

	public String[] getReturnAliases() {
		return selectClause.getQueryReturnAliases();
	}

	public SelectClause getSelectClause() {
		return selectClause;
	}
	
	public FromClause getFinalFromClause() {
		//TODO: more correct implementation
		return currentFromClause;
	}

	public boolean isShallowQuery() {
		return queryTranslatorImpl.isShallowQuery();
	}

	public Map getEnabledFilters() {
		return queryTranslatorImpl.getEnabledFilters();
	}

	public LiteralProcessor getLiteralProcessor() {
		return literalProcessor;
	}

	public ASTPrinter getASTPrinter() {
		return printer;
	}

	public ArrayList getParameters() {
		return parameters;
	}

	public int getNumberOfParametersInSetClause() {
		return numberOfParametersInSetClause;
	}

	protected void evaluateAssignment(AST eq) throws SemanticException {
		Queryable persister = getCurrentFromClause().getFromElement().getQueryable();
		evaluateAssignment( eq, persister, -1 );
	}

	private void evaluateAssignment(AST eq, Queryable persister, int targetIndex) {
		if ( persister.isMultiTable() ) {
			// no need to even collect this information if the persister is considered multi-table
			AssignmentSpecification specification = new AssignmentSpecification( eq, persister );
			if ( targetIndex >= 0 ) {
				assignmentSpecifications.add( targetIndex, specification );
			}
			else {
				assignmentSpecifications.add( specification );
			}
			numberOfParametersInSetClause += specification.getParameters().length;
		}
	}

	public ArrayList getAssignmentSpecifications() {
		return assignmentSpecifications;
	}

	protected AST createIntoClause(String path, AST propertySpec) throws SemanticException {
		Queryable persister = ( Queryable ) getSessionFactoryHelper().requireClassPersister( path );

		IntoClause intoClause = ( IntoClause ) getASTFactory().create( INTO, persister.getEntityName() );
		intoClause.setFirstChild( propertySpec );
		intoClause.initialize( persister );

		addQuerySpaces( persister.getQuerySpaces() );

		return intoClause;
	}

	protected void prepareVersioned(AST updateNode, AST versioned) throws SemanticException {
		UpdateStatement updateStatement = ( UpdateStatement ) updateNode;
		FromClause fromClause = updateStatement.getFromClause();
		if ( versioned != null ) {
			// Make sure that the persister is versioned
			Queryable persister = fromClause.getFromElement().getQueryable();
			if ( !persister.isVersioned() ) {
				throw new SemanticException( "increment option specified for update of non-versioned entity" );
			}

			VersionType versionType = persister.getVersionType();
			if ( versionType instanceof UserVersionType ) {
				throw new SemanticException( "user-defined version types not supported for increment option" );
			}

			AST eq = getASTFactory().create( HqlSqlTokenTypes.EQ, "=" );
			AST versionPropertyNode = generateVersionPropertyNode( persister );

			eq.setFirstChild( versionPropertyNode );

			AST versionIncrementNode = null;
			if ( Date.class.isAssignableFrom( versionType.getReturnedClass() ) ) {
				versionIncrementNode = getASTFactory().create( HqlSqlTokenTypes.PARAM, "?" );
				ParameterSpecification paramSpec = new VersionTypeSeedParameterSpecification( versionType );
				( ( ParameterNode ) versionIncrementNode ).setHqlParameterSpecification( paramSpec );
				parameters.add( 0, paramSpec );
			}
			else {
				// Not possible to simply re-use the versionPropertyNode here as it causes
				// OOM errors due to circularity :(
				versionIncrementNode = getASTFactory().create( HqlSqlTokenTypes.PLUS, "+" );
				versionIncrementNode.setFirstChild( generateVersionPropertyNode( persister ) );
				versionIncrementNode.addChild( getASTFactory().create( HqlSqlTokenTypes.IDENT, "1" ) );
			}

			eq.addChild( versionIncrementNode );

			evaluateAssignment( eq, persister, 0 );

			AST setClause = updateStatement.getSetClause();
			AST currentFirstSetElement = setClause.getFirstChild();
			setClause.setFirstChild( eq );
			eq.setNextSibling( currentFirstSetElement );
		}
	}

	private AST generateVersionPropertyNode(Queryable persister) throws SemanticException {
		String versionPropertyName = persister.getPropertyNames()[ persister.getVersionProperty() ];
		AST versionPropertyRef = getASTFactory().create( HqlSqlTokenTypes.IDENT, versionPropertyName );
		AST versionPropertyNode = lookupNonQualifiedProperty( versionPropertyRef );
		resolve( versionPropertyNode );
		return versionPropertyNode;
	}

	protected void prepareLogicOperator(AST operator) throws SemanticException {
		( ( OperatorNode ) operator ).initialize();
	}

	protected void prepareArithmeticOperator(AST operator) throws SemanticException {
		( ( OperatorNode ) operator ).initialize();
	}

	public static void panic() {
		throw new QueryException( "TreeWalker: panic" );
	}
}
