/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.hql.internal.ast.tree;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes;
import org.hibernate.hql.internal.ast.util.ASTIterator;
import org.hibernate.hql.internal.ast.util.ASTUtil;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;

import antlr.SemanticException;
import antlr.collections.AST;

Represents the 'FROM' part of a query or subquery, containing all mapped class references.
Author:josh
/** * Represents the 'FROM' part of a query or subquery, containing all mapped class references. * * @author josh */
public class FromClause extends HqlSqlWalkerNode implements HqlSqlTokenTypes, DisplayableNode { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( FromClause.class ); public static final int ROOT_LEVEL = 1; private int level = ROOT_LEVEL; private Set<FromElement> fromElements = new HashSet<FromElement>(); private Map<String,FromElement> fromElementByClassAlias = new HashMap<String,FromElement>(); private Map<String,FromElement> fromElementByTableAlias = new HashMap<String,FromElement>(); private Map<String,FromElement> fromElementsByPath = new HashMap<String,FromElement>();
All of the implicit FROM xxx JOIN yyy elements that are the destination of a collection. These are created from index operators on collection property references.
/** * All of the implicit FROM xxx JOIN yyy elements that are the destination of a collection. These are created from * index operators on collection property references. */
private Map collectionJoinFromElementsByPath = new HashMap();
Pointer to the parent FROM clause, if there is one.
/** * Pointer to the parent FROM clause, if there is one. */
private FromClause parentFromClause;
Collection of FROM clauses of which this is the parent.
/** * Collection of FROM clauses of which this is the parent. */
private Set<FromClause> childFromClauses;
Counts the from elements as they are added.
/** * Counts the from elements as they are added. */
private int fromElementCounter;
Implied FROM elements to add onto the end of the FROM clause.
/** * Implied FROM elements to add onto the end of the FROM clause. */
private List impliedElements = new LinkedList(); private List<EntityJoinFromElement> entityJoinFromElements;
Adds a new from element to the from node.
Params:
  • path – The reference to the class.
  • alias – The alias AST.
Returns:FromElement - The new FROM element.
/** * Adds a new from element to the from node. * * @param path The reference to the class. * @param alias The alias AST. * * @return FromElement - The new FROM element. */
public FromElement addFromElement(String path, AST alias) throws SemanticException { // The path may be a reference to an alias defined in the parent query. String classAlias = ( alias == null ) ? null : alias.getText(); checkForDuplicateClassAlias( classAlias ); FromElementFactory factory = new FromElementFactory( this, null, path, classAlias, null, false ); return factory.addFromElement(); } void registerFromElement(FromElement element) { fromElements.add( element ); String classAlias = element.getClassAlias(); if ( classAlias != null ) { // The HQL class alias refers to the class name. fromElementByClassAlias.put( classAlias, element ); } // Associate the table alias with the element. String tableAlias = element.getTableAlias(); if ( tableAlias != null ) { fromElementByTableAlias.put( tableAlias, element ); } if ( element instanceof EntityJoinFromElement ) { if ( entityJoinFromElements == null ) { entityJoinFromElements = new ArrayList<EntityJoinFromElement>(); } entityJoinFromElements.add( (EntityJoinFromElement) element ); } } public void finishInit() { if ( entityJoinFromElements == null ) { return; } for ( EntityJoinFromElement entityJoinFromElement : entityJoinFromElements ) { ASTUtil.appendChild( this, entityJoinFromElement ); } entityJoinFromElements.clear(); } void addDuplicateAlias(String alias, FromElement element) { if ( alias != null ) { fromElementByClassAlias.put( alias, element ); } } private void checkForDuplicateClassAlias(String classAlias) throws SemanticException { if ( classAlias != null && fromElementByClassAlias.containsKey( classAlias ) ) { throw new SemanticException( "Duplicate definition of alias '" + classAlias + "'" ); } }
Retrieve the from-element represented by the given alias.
Params:
  • aliasOrClassName – The alias by which to locate the from-element.
Returns:The from-element assigned the given alias, or null if none.
/** * Retrieve the from-element represented by the given alias. * * @param aliasOrClassName The alias by which to locate the from-element. * * @return The from-element assigned the given alias, or null if none. */
public FromElement getFromElement(String aliasOrClassName) { FromElement fromElement = fromElementByClassAlias.get( aliasOrClassName ); if ( fromElement == null && getSessionFactoryHelper().isStrictJPAQLComplianceEnabled() ) { fromElement = findIntendedAliasedFromElementBasedOnCrazyJPARequirements( aliasOrClassName ); } if ( fromElement == null && parentFromClause != null ) { fromElement = parentFromClause.getFromElement( aliasOrClassName ); } return fromElement; } public FromElement findFromElementBySqlAlias(String sqlAlias) { FromElement fromElement = fromElementByTableAlias.get( sqlAlias ); if ( fromElement == null && parentFromClause != null ) { fromElement = parentFromClause.getFromElement( sqlAlias ); } return fromElement; } public FromElement findFromElementByUserOrSqlAlias(String userAlias, String sqlAlias) { FromElement fromElement = null; if ( userAlias != null ) { fromElement = getFromElement( userAlias ); } if ( fromElement == null ) { fromElement = findFromElementBySqlAlias( sqlAlias ); } return fromElement; } private FromElement findIntendedAliasedFromElementBasedOnCrazyJPARequirements(String specifiedAlias) { for ( Map.Entry<String, FromElement> entry : fromElementByClassAlias.entrySet() ) { final String alias = entry.getKey(); if ( alias.equalsIgnoreCase( specifiedAlias ) ) { return entry.getValue(); } } return null; }
Convenience method to check whether a given token represents a from-element alias.
Params:
  • possibleAlias – The potential from-element alias to check.
Returns:True if the possibleAlias is an alias to a from-element visible from this point in the query graph.
/** * Convenience method to check whether a given token represents a from-element alias. * * @param possibleAlias The potential from-element alias to check. * * @return True if the possibleAlias is an alias to a from-element visible * from this point in the query graph. */
public boolean isFromElementAlias(String possibleAlias) { boolean isAlias = containsClassAlias( possibleAlias ); if ( !isAlias && parentFromClause != null ) { // try the parent FromClause... isAlias = parentFromClause.isFromElementAlias( possibleAlias ); } return isAlias; }
Returns the list of from elements in order.
Returns:the list of from elements (instances of FromElement).
/** * Returns the list of from elements in order. * * @return the list of from elements (instances of FromElement). */
public List getFromElements() { return ASTUtil.collectChildren( this, fromElementPredicate ); } public FromElement getFromElement() { // TODO: not sure about this one // List fromElements = getFromElements(); // if ( fromElements == null || fromElements.isEmpty() ) { // throw new QueryException( "Unable to locate from element" ); // } return (FromElement) getFromElements().get( 0 ); }
Returns the list of from elements that will be part of the result set.
Returns:the list of from elements that will be part of the result set.
/** * Returns the list of from elements that will be part of the result set. * * @return the list of from elements that will be part of the result set. */
public List getProjectionList() { return ASTUtil.collectChildren( this, projectionListPredicate ); } public List getCollectionFetches() { return ASTUtil.collectChildren( this, collectionFetchPredicate ); } public boolean hasCollectionFecthes() { return getCollectionFetches().size() > 0; } public List getExplicitFromElements() { return ASTUtil.collectChildren( this, explicitFromPredicate ); } private static ASTUtil.FilterPredicate fromElementPredicate = new ASTUtil.IncludePredicate() { @Override public boolean include(AST node) { FromElement fromElement = (FromElement) node; return fromElement.isFromOrJoinFragment(); } }; private static ASTUtil.FilterPredicate projectionListPredicate = new ASTUtil.IncludePredicate() { @Override public boolean include(AST node) { FromElement fromElement = (FromElement) node; return fromElement.inProjectionList(); } }; private static ASTUtil.FilterPredicate collectionFetchPredicate = new ASTUtil.IncludePredicate() { @Override public boolean include(AST node) { FromElement fromElement = (FromElement) node; return fromElement.isFetch() && fromElement.getQueryableCollection() != null; } }; private static ASTUtil.FilterPredicate explicitFromPredicate = new ASTUtil.IncludePredicate() { @Override public boolean include(AST node) { final FromElement fromElement = (FromElement) node; return !fromElement.isImplied(); } }; FromElement findCollectionJoin(String path) { return (FromElement) collectionJoinFromElementsByPath.get( path ); }
Look for an existing implicit or explicit join by the given path.
/** * Look for an existing implicit or explicit join by the * given path. */
FromElement findJoinByPath(String path) { FromElement elem = findJoinByPathLocal( path ); if ( elem == null && parentFromClause != null ) { elem = parentFromClause.findJoinByPath( path ); } return elem; } FromElement findJoinByPathLocal(String path) { Map joinsByPath = fromElementsByPath; return (FromElement) joinsByPath.get( path ); } void addJoinByPathMap(String path, FromElement destination) { if ( LOG.isDebugEnabled() ) { LOG.debugf( "addJoinByPathMap() : %s -> %s", path, destination.getDisplayText() ); } fromElementsByPath.put( path, destination ); }
Returns true if the from node contains the class alias name.
Params:
  • alias – The HQL class alias name.
Returns:true if the from node contains the class alias name.
/** * Returns true if the from node contains the class alias name. * * @param alias The HQL class alias name. * * @return true if the from node contains the class alias name. */
public boolean containsClassAlias(String alias) { boolean isAlias = fromElementByClassAlias.containsKey( alias ); if ( !isAlias && getSessionFactoryHelper().isStrictJPAQLComplianceEnabled() ) { isAlias = findIntendedAliasedFromElementBasedOnCrazyJPARequirements( alias ) != null; } return isAlias; }
Returns true if the from node contains the table alias name.
Params:
  • alias – The SQL table alias name.
Returns:true if the from node contains the table alias name.
/** * Returns true if the from node contains the table alias name. * * @param alias The SQL table alias name. * * @return true if the from node contains the table alias name. */
public boolean containsTableAlias(String alias) { return fromElementByTableAlias.keySet().contains( alias ); } public String getDisplayText() { return "FromClause{" + "level=" + level + ", fromElementCounter=" + fromElementCounter + ", fromElements=" + fromElements.size() + ", fromElementByClassAlias=" + fromElementByClassAlias.keySet() + ", fromElementByTableAlias=" + fromElementByTableAlias.keySet() + ", fromElementsByPath=" + fromElementsByPath.keySet() + ", collectionJoinFromElementsByPath=" + collectionJoinFromElementsByPath.keySet() + ", impliedElements=" + impliedElements + "}"; } public void setParentFromClause(FromClause parentFromClause) { this.parentFromClause = parentFromClause; if ( parentFromClause != null ) { level = parentFromClause.getLevel() + 1; parentFromClause.addChild( this ); } } private void addChild(FromClause fromClause) { if ( childFromClauses == null ) { childFromClauses = new HashSet<FromClause>(); } childFromClauses.add( fromClause ); } public FromClause locateChildFromClauseWithJoinByPath(String path) { if ( childFromClauses != null && !childFromClauses.isEmpty() ) { for ( FromClause child : childFromClauses ) { if ( child.findJoinByPathLocal( path ) != null ) { return child; } } } return null; } public void promoteJoin(FromElement elem) { LOG.debugf( "Promoting [%s] to [%s]", elem, this ); //TODO: implement functionality // this might be painful to do here, as the "join post processing" for // the subquery has already been performed (meaning that for // theta-join dialects, the join conditions have already been moved // over to the where clause). A "simple" solution here might to // doAfterTransactionCompletion "join post processing" once for the entire query (including // any subqueries) at one fell swoop } public boolean isSubQuery() { // TODO : this is broke for subqueries in statements other than selects... return parentFromClause != null; } void addCollectionJoinFromElementByPath(String path, FromElement destination) { LOG.debugf( "addCollectionJoinFromElementByPath() : %s -> %s", path, destination ); collectionJoinFromElementsByPath.put( path, destination ); // Add the new node to the map so that we don't create it twice. } public FromClause getParentFromClause() { return parentFromClause; } public int getLevel() { return level; } public int nextFromElementCounter() { return fromElementCounter++; } public void resolve() { // Make sure that all from elements registered with this FROM clause are actually in the AST. ASTIterator iter = new ASTIterator( this.getFirstChild() ); Set childrenInTree = new HashSet(); while ( iter.hasNext() ) { childrenInTree.add( iter.next() ); } for ( FromElement fromElement : fromElements ) { if ( !childrenInTree.contains( fromElement ) ) { throw new IllegalStateException( "Element not in AST: " + fromElement ); } } } public void addImpliedFromElement(FromElement element) { impliedElements.add( element ); } @Override public String toString() { return "FromClause{level=" + level + "}"; } }