Open Source Repository

Home /hibernate/hibernate-3.2.6.ga | Repository Home


org/hibernate/hql/ast/tree/FromClause.java
// $Id: FromClause.java 14051 2007-10-03 03:51:13Z [email protected] $
package org.hibernate.hql.ast.tree;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
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.hql.antlr.HqlSqlTokenTypes;
import org.hibernate.hql.ast.util.ASTIterator;
import org.hibernate.hql.ast.util.ASTUtil;

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

/**
 * 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 Log log = LogFactory.getLogFromClause.class );
  public static final int ROOT_LEVEL = 1;

  private int level = ROOT_LEVEL;
  private Set fromElements = new HashSet();
  private Map fromElementByClassAlias = new HashMap();
  private Map fromElementByTableAlias = new HashMap();
  private Map fromElementsByPath = new HashMap();

  /**
   * 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.
   */
  private FromClause parentFromClause;
  /**
   * Collection of FROM clauses of which this is the parent.
   */
  private Set childFromClauses;
  /**
   * Counts the from elements as they are added.
   */
  private int fromElementCounter = 0;
  /**
   * Implied FROM elements to add onto the end of the FROM clause.
   */
  private List impliedElements = new LinkedList();

  /**
   * 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 aliasthrows SemanticException {
    // The path may be a reference to an alias defined in the parent query.
    String classAlias = alias == null null : alias.getText();
    checkForDuplicateClassAliasclassAlias );
    FromElementFactory factory = new FromElementFactorythis, null, path, classAlias, null, false );
    return factory.addFromElement();
  }

  void registerFromElement(FromElement element) {
    fromElements.addelement );
    String classAlias = element.getClassAlias();
    if classAlias != null ) {
      // The HQL class alias refers to the class name.
      fromElementByClassAlias.putclassAlias, element );
    }
    // Associate the table alias with the element.
    String tableAlias = element.getTableAlias();
    if tableAlias != null ) {
      fromElementByTableAlias.puttableAlias, element );
    }
  }

  void addDuplicateAlias(String alias, FromElement element) {
    if alias != null ) {
      fromElementByClassAlias.putalias, element );
    }
  }

  private void checkForDuplicateClassAlias(String classAliasthrows SemanticException {
    if classAlias != null && fromElementByClassAlias.containsKeyclassAlias ) ) {
      throw new SemanticException"Duplicate definition of alias '" + classAlias + "'" );
    }
  }

  /**
   * Retreives 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 = FromElement fromElementByClassAlias.getaliasOrClassName );
    if fromElement == null && getSessionFactoryHelper().isStrictJPAQLComplianceEnabled() ) {
      fromElement = findIntendedAliasedFromElementBasedOnCrazyJPARequirementsaliasOrClassName );
    }
    if fromElement == null && parentFromClause != null ) {
      fromElement = parentFromClause.getFromElementaliasOrClassName );
    }
    return fromElement;
  }

  private FromElement findIntendedAliasedFromElementBasedOnCrazyJPARequirements(String specifiedAlias) {
    Iterator itr = fromElementByClassAlias.entrySet().iterator();
    while itr.hasNext() ) {
      Map.Entry entry = Map.Entry itr.next();
      String alias = String entry.getKey();
      if alias.equalsIgnoreCasespecifiedAlias ) ) {
        return FromElement entry.getValue();
      }
    }
    return null;
  }

  /**
   * 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 = containsClassAliaspossibleAlias );
    if !isAlias && parentFromClause != null ) {
      // try the parent FromClause...
      isAlias = parentFromClause.isFromElementAliaspossibleAlias );
    }
    return isAlias;
  }

  /**
   * Returns the list of from elements in order.
   *
   @return the list of from elements (instances of FromElement).
   */
  public List getFromElements() {
    return ASTUtil.collectChildrenthis, 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 (FromElementgetFromElements().get(0);
  }

  /**
   * 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.collectChildrenthis, projectionListPredicate );
  }

  public List getCollectionFetches() {
    return ASTUtil.collectChildrenthis, collectionFetchPredicate );
  }

  public boolean hasCollectionFecthes() {
    return getCollectionFetches().size() 0;
  }

  public List getExplicitFromElements() {
    return ASTUtil.collectChildrenthis, explicitFromPredicate );
  }

  private static ASTUtil.FilterPredicate fromElementPredicate = new ASTUtil.IncludePredicate() {
    public boolean include(AST node) {
      FromElement fromElement = FromElement node;
      return fromElement.isFromOrJoinFragment();
    }
  };

  private static ASTUtil.FilterPredicate projectionListPredicate = new ASTUtil.IncludePredicate() {
    public boolean include(AST node) {
      FromElement fromElement = FromElement node;
      return fromElement.inProjectionList();
    }
  };

  private static ASTUtil.FilterPredicate collectionFetchPredicate = new ASTUtil.IncludePredicate() {
    public boolean include(AST node) {
      FromElement fromElement = FromElement node;
      return fromElement.isFetch() && fromElement.getQueryableCollection() != null;
    }
  };

  private static ASTUtil.FilterPredicate explicitFromPredicate = new ASTUtil.IncludePredicate() {
    public boolean include(AST node) {
      final FromElement fromElement = FromElement node;
      return !fromElement.isImplied();
    }
  };

  FromElement findCollectionJoin(String path) {
    return FromElement collectionJoinFromElementsByPath.getpath );
  }

  /**
   * Look for an existing implicit or explicit join by the
   * given path.
   */
  FromElement findJoinByPath(String path) {
    FromElement elem = findJoinByPathLocalpath );
    if elem == null && parentFromClause != null ) {
      elem = parentFromClause.findJoinByPathpath );
    }
    return elem;
  }

  FromElement findJoinByPathLocal(String path) {
    Map joinsByPath = fromElementsByPath;
    return FromElement joinsByPath.getpath );
  }

  void addJoinByPathMap(String path, FromElement destination) {
    if log.isDebugEnabled() ) {
      log.debug"addJoinByPathMap() : " + path + " -> " + destination );
    }
    fromElementsByPath.putpath, destination );
  }

  /**
   * 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.containsKeyalias );
    if !isAlias && getSessionFactoryHelper().isStrictJPAQLComplianceEnabled() ) {
      isAlias = findIntendedAliasedFromElementBasedOnCrazyJPARequirementsalias != null;
    }
    return isAlias;
  }

  /**
   * 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().containsalias );
  }

  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.addChildthis );
    }
  }

  private void addChild(FromClause fromClause) {
    if childFromClauses == null ) {
      childFromClauses = new HashSet();
    }
    childFromClauses.addfromClause );
  }

  public FromClause locateChildFromClauseWithJoinByPath(String path) {
    if childFromClauses != null && !childFromClauses.isEmpty() ) {
      Iterator children = childFromClauses.iterator();
      while children.hasNext() ) {
        FromClause child = FromClause children.next();
        if child.findJoinByPathLocalpath != null ) {
          return child;
        }
      }
    }
    return null;
  }

  public void promoteJoin(FromElement elem) {
    if log.isDebugEnabled() ) {
      log.debug"Promoting [" + elem + "] to [" 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
    //  perform "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) {
    if log.isDebugEnabled() ) {
      log.debug"addCollectionJoinFromElementByPath() : " + path + " -> " + destination );
    }
    collectionJoinFromElementsByPath.putpath, 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 ASTIteratorthis.getFirstChild() );
    Set childrenInTree = new HashSet();
    while iter.hasNext() ) {
      childrenInTree.additer.next() );
    }
    for Iterator iterator = fromElements.iterator(); iterator.hasNext()) {
      FromElement fromElement = FromElement iterator.next();
      if !childrenInTree.containsfromElement ) ) {
        throw new IllegalStateException"Element not in AST: " + fromElement );
      }
    }
  }

  public void addImpliedFromElement(FromElement element) {
    impliedElements.addelement );
  }

  public String toString() {
    return "FromClause{" +
        "level=" + level +
        "}";
  }
}