Open Source Repository

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


org/hibernate/hql/classic/PathExpressionParser.java
//$Id: PathExpressionParser.java 10829 2006-11-16 20:21:47Z [email protected] $
package org.hibernate.hql.classic;

import java.util.LinkedList;
import java.util.Map;

import org.hibernate.MappingException;
import org.hibernate.QueryException;
import org.hibernate.engine.JoinSequence;
import org.hibernate.hql.CollectionSubqueryFactory;
import org.hibernate.persister.collection.CollectionPropertyMapping;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.PropertyMapping;
import org.hibernate.persister.entity.Queryable;
import org.hibernate.sql.JoinFragment;
import org.hibernate.type.AssociationType;
import org.hibernate.type.CollectionType;
import org.hibernate.type.EntityType;
import org.hibernate.type.Type;
import org.hibernate.type.TypeFactory;

/**
 * Parses an expression of the form foo.bar.baz and builds up an expression
 * involving two less table joins than there are path components.
 */
public class PathExpressionParser implements Parser {

  //TODO: this class does too many things! we need a different
  //kind of path expression parser for each of the diffferent
  //ways in which path expressions can occur

  //We should actually rework this class to not implement Parser
  //and just process path expressions in the most convenient way.

  //The class is now way to complex!

  private int dotcount;
  private String currentName;
  private String currentProperty;
  private String oneToOneOwnerName;
  private AssociationType ownerAssociationType;
  private String[] columns;
  private String collectionName;
  private String collectionOwnerName;
  private String collectionRole;
  private final StringBuffer componentPath = new StringBuffer();
  private Type type;
  private final StringBuffer path = new StringBuffer();
  private boolean ignoreInitialJoin;
  private boolean continuation;
  private int joinType = JoinFragment.INNER_JOIN; //default mode
  private boolean useThetaStyleJoin = true;
  private PropertyMapping currentPropertyMapping;
  private JoinSequence joinSequence;

  private boolean expectingCollectionIndex;
  private LinkedList collectionElements = new LinkedList();

  void setJoinType(int joinType) {
    this.joinType = joinType;
  }

  void setUseThetaStyleJoin(boolean useThetaStyleJoin) {
    this.useThetaStyleJoin = useThetaStyleJoin;
  }

  private void addJoin(String name, AssociationType joinableTypethrows QueryException {
    try {
      joinSequence.addJoinjoinableType, name, joinType, currentColumns() );
    }
    catch MappingException me ) {
      throw new QueryExceptionme );
    }
  }

  private void addJoin(String name, AssociationType joinableType, String[] foreignKeyColumnsthrows QueryException {
    try {
      joinSequence.addJoinjoinableType, name, joinType, foreignKeyColumns );
    }
    catch MappingException me ) {
      throw new QueryExceptionme );
    }
  }

  String continueFromManyToMany(String entityName, String[] joinColumns, QueryTranslatorImpl qthrows QueryException {
    start);
    continuation = true;
    currentName = q.createNameForentityName );
    q.addTypecurrentName, entityName );
    Queryable classPersister = q.getEntityPersisterentityName );
    //QueryJoinFragment join = q.createJoinFragment(useThetaStyleJoin);
    addJoincurrentName, TypeFactory.manyToOneentityName ), joinColumns );
    currentPropertyMapping = classPersister;
    return currentName;
  }

  public void ignoreInitialJoin() {
    ignoreInitialJoin = true;
  }

  public void token(String token, QueryTranslatorImpl qthrows QueryException {

    if token != null path.appendtoken );

    String alias = q.getPathAliaspath.toString() );
    if alias != null ) {
      reset)//reset the dotcount (but not the path)
      currentName = alias; //after reset!
      currentPropertyMapping = q.getPropertyMappingcurrentName );
      if !ignoreInitialJoin ) {
        JoinSequence ojf = q.getPathJoinpath.toString() );
        try {
          joinSequence.addConditionojf.toJoinFragmentq.getEnabledFilters()true ).toWhereFragmentString() )//after reset!
        }
        catch MappingException me ) {
          throw new QueryExceptionme );
        }
        // we don't need to worry about any condition in the ON clause
        // here (toFromFragmentString), since anything in the ON condition
        // is already applied to the whole query
      }
    }
    else if ".".equalstoken ) ) {
      dotcount++;
    }
    else {
      if dotcount == ) {
        if !continuation ) {
          if !q.isNametoken ) ) throw new QueryException"undefined alias: " + token );
          currentName = token;
          currentPropertyMapping = q.getPropertyMappingcurrentName );
        }
      }
      else if dotcount == ) {
        if currentName != null ) {
          currentProperty = token;
        }
        else if collectionName != null ) {
          //processCollectionProperty(token, q.getCollectionPersister(collectionRole), collectionName);
          continuation = false;
        }
        else {
          throw new QueryException"unexpected" );
        }
      }
      else // dotcount>=2

        // Do the corresponding RHS
        Type propertyType = getPropertyType();

        if propertyType == null ) {
          throw new QueryException"unresolved property: " + path );
        }

        if propertyType.isComponentType() ) {
          dereferenceComponenttoken );
        }
        else if propertyType.isEntityType() ) {
          if !isCollectionValued() ) dereferenceEntitytoken, EntityType propertyType, q );
        }
        else if propertyType.isCollectionType() ) {
          dereferenceCollectiontoken, ( ( CollectionType propertyType ).getRole(), q );

        }
        else {
          if token != null throw new QueryException"dereferenced: " + path );
        }

      }
    }
  }

  private void dereferenceEntity(String propertyName, EntityType propertyType, QueryTranslatorImpl q)
      throws QueryException {
    //NOTE: we avoid joining to the next table if the named property is just the foreign key value

    //if its "id"
    boolean isIdShortcut = EntityPersister.ENTITY_ID.equalspropertyName &&
        propertyType.isReferenceToPrimaryKey();

    //or its the id property name
    final String idPropertyName;
    try {
      idPropertyName = propertyType.getIdentifierOrUniqueKeyPropertyNameq.getFactory() );
    }
    catch MappingException me ) {
      throw new QueryExceptionme );
    }
    boolean isNamedIdPropertyShortcut = idPropertyName != null
        && idPropertyName.equalspropertyName )
        && propertyType.isReferenceToPrimaryKey();

    if isIdShortcut || isNamedIdPropertyShortcut ) {
      // special shortcut for id properties, skip the join!
      // this must only occur at the _end_ of a path expression
      if componentPath.length() componentPath.append'.' );
      componentPath.appendpropertyName );
    }
    else {
      String entityClass = propertyType.getAssociatedEntityName();
      String name = q.createNameForentityClass );
      q.addTypename, entityClass );
      addJoinname, propertyType );
      if propertyType.isOneToOne() ) oneToOneOwnerName = currentName;
      ownerAssociationType = propertyType;
      currentName = name;
      currentProperty = propertyName;
      q.addPathAliasAndJoinpath.substring0, path.toString().lastIndexOf'.' ) ), name, joinSequence.copy() );
      componentPath.setLength);
      currentPropertyMapping = q.getEntityPersisterentityClass );
    }
  }

  private void dereferenceComponent(String propertyName) {
    if propertyName != null ) {
      if componentPath.length() componentPath.append'.' );
      componentPath.appendpropertyName );
    }
  }

  private void dereferenceCollection(String propertyName, String role, QueryTranslatorImpl qthrows QueryException {
    collectionRole = role;
    QueryableCollection collPersister = q.getCollectionPersisterrole );
    String name = q.createNameForCollectionrole );
    addJoinname, collPersister.getCollectionType() );
    //if ( collPersister.hasWhere() ) join.addCondition( collPersister.getSQLWhereString(name) );
    collectionName = name;
    collectionOwnerName = currentName;
    currentName = name;
    currentProperty = propertyName;
    componentPath.setLength);
    currentPropertyMapping = new CollectionPropertyMappingcollPersister );
  }

  private String getPropertyPath() {
    if currentProperty == null ) {
      return EntityPersister.ENTITY_ID;
    }
    else {
      if componentPath.length() ) {
        return new StringBuffer()
            .appendcurrentProperty )
            .append'.' )
            .appendcomponentPath.toString() )
            .toString();
      }
      else {
        return currentProperty;
      }
    }
  }

  private PropertyMapping getPropertyMapping() {
    return currentPropertyMapping;
  }

  private void setType() throws QueryException {
    if currentProperty == null ) {
      type = getPropertyMapping().getType();
    }
    else {
      type = getPropertyType();
    }
  }

  protected Type getPropertyType() throws QueryException {
    String propertyPath = getPropertyPath();
    Type propertyType = getPropertyMapping().toTypepropertyPath );
    if propertyType == null ) {
      throw new QueryException"could not resolve property type: " + propertyPath );
    }
    return propertyType;
  }

  protected String[] currentColumns() throws QueryException {
    String propertyPath = getPropertyPath();
    String[] propertyColumns = getPropertyMapping().toColumnscurrentName, propertyPath );
    if propertyColumns == null ) {
      throw new QueryException"could not resolve property columns: " + propertyPath );
    }
    return propertyColumns;
  }

  private void reset(QueryTranslatorImpl q) {
    //join = q.createJoinFragment(useThetaStyleJoin);
    dotcount = 0;
    currentName = null;
    currentProperty = null;
    collectionName = null;
    collectionRole = null;
    componentPath.setLength);
    type = null;
    collectionName = null;
    columns = null;
    expectingCollectionIndex = false;
    continuation = false;
    currentPropertyMapping = null;
  }

  public void start(QueryTranslatorImpl q) {
    if !continuation ) {
      reset);
      path.setLength);
      joinSequence = new JoinSequenceq.getFactory() ).setUseThetaStyleuseThetaStyleJoin );
    }
  }

  public void end(QueryTranslatorImpl qthrows QueryException {
    ignoreInitialJoin = false;

    Type propertyType = getPropertyType();
    if propertyType != null && propertyType.isCollectionType() ) {
      collectionRole = ( ( CollectionType propertyType ).getRole();
      collectionName = q.createNameForCollectioncollectionRole );
      prepareForIndex);
    }
    else {
      columns = currentColumns();
      setType();
    }

    //important!!
    continuation = false;

  }

  private void prepareForIndex(QueryTranslatorImpl qthrows QueryException {

    QueryableCollection collPersister = q.getCollectionPersistercollectionRole );

    if !collPersister.hasIndex() ) throw new QueryException"unindexed collection before []: " + path );
    String[] indexCols = collPersister.getIndexColumnNames();
    if indexCols.length != throw new QueryException"composite-index appears in []: " + path );
    //String[] keyCols = collPersister.getKeyColumnNames();

    JoinSequence fromJoins = new JoinSequenceq.getFactory() )
        .setUseThetaStyleuseThetaStyleJoin )
        .setRootcollPersister, collectionName )
        .setNextjoinSequence.copy() );

    if !continuation addJoincollectionName, collPersister.getCollectionType() );

    joinSequence.addConditioncollectionName + '.' + indexCols[0" = " )//TODO: get SQL rendering out of here

    CollectionElement elem = new CollectionElement();
    elem.elementColumns = collPersister.getElementColumnNames(collectionName);
    elem.elementType = collPersister.getElementType();
    elem.isOneToMany = collPersister.isOneToMany();
    elem.alias = collectionName;
    elem.joinSequence = joinSequence;
    collectionElements.addLastelem );
    setExpectingCollectionIndex();

    q.addCollectioncollectionName, collectionRole );
    q.addFromJoinOnlycollectionName, fromJoins );
  }

  static final class CollectionElement {
    Type elementType;
    boolean isOneToMany;
    String alias;
    String[] elementColumns;
    JoinSequence joinSequence;
    StringBuffer indexValue = new StringBuffer();
  }

  public CollectionElement lastCollectionElement() {
    return CollectionElement collectionElements.removeLast();
  }

  public void setLastCollectionElementIndexValue(String value) {
    ( ( CollectionElement collectionElements.getLast() ).indexValue.appendvalue );
  }

  public boolean isExpectingCollectionIndex() {
    return expectingCollectionIndex;
  }

  protected void setExpectingCollectionIndex() throws QueryException {
    expectingCollectionIndex = true;
  }

  public JoinSequence getWhereJoin() {
    return joinSequence;
  }

  public String getWhereColumn() throws QueryException {
    if columns.length != ) {
      throw new QueryException"path expression ends in a composite value: " + path );
    }
    return columns[0];
  }

  public String[] getWhereColumns() {
    return columns;
  }

  public Type getWhereColumnType() {
    return type;
  }

  public String getName() {
    return currentName == null ? collectionName : currentName;
  }


  public String getCollectionSubquery(Map enabledFiltersthrows QueryException {
    return CollectionSubqueryFactory.createCollectionSubqueryjoinSequence, enabledFilters, currentColumns() );
  }

  public boolean isCollectionValued() throws QueryException {
    //TODO: is there a better way?
    return collectionName != null && !getPropertyType().isCollectionType();
  }

  public void addAssociation(QueryTranslatorImpl qthrows QueryException {
    q.addJoingetName(), joinSequence );
  }

  public String addFromAssociation(QueryTranslatorImpl qthrows QueryException {
    if isCollectionValued() ) {
      return addFromCollection);
    }
    else {
      q.addFromcurrentName, joinSequence );
      return currentName;
    }
  }

  public String addFromCollection(QueryTranslatorImpl qthrows QueryException {
    Type collectionElementType = getPropertyType();

    if collectionElementType == null ) {
      throw new QueryException"must specify 'elements' for collection valued property in from clause: " + path );
    }

    if collectionElementType.isEntityType() ) {
      // an association
      QueryableCollection collectionPersister = q.getCollectionPersistercollectionRole );
      Queryable entityPersister = Queryable collectionPersister.getElementPersister();
      String clazz = entityPersister.getEntityName();

      final String elementName;
      if collectionPersister.isOneToMany() ) {
        elementName = collectionName;
        //allow index() function:
        q.decoratePropertyMappingelementName, collectionPersister );
      }
      else //many-to-many
        q.addCollectioncollectionName, collectionRole );
        elementName = q.createNameForclazz );
        addJoinelementName, AssociationType collectionElementType );
      }
      q.addFromelementName, clazz, joinSequence );
      currentPropertyMapping = new CollectionPropertyMappingcollectionPersister );
      return elementName;
    }
    else {
      // collections of values
      q.addFromCollectioncollectionName, collectionRole, joinSequence );
      return collectionName;
    }

  }

  String getCollectionName() {
    return collectionName;
  }

  String getCollectionRole() {
    return collectionRole;
  }

  String getCollectionOwnerName() {
    return collectionOwnerName;
  }

  String getOneToOneOwnerName() {
    return oneToOneOwnerName;
  }

  AssociationType getOwnerAssociationType() {
    return ownerAssociationType;
  }

  String getCurrentProperty() {
    return currentProperty;
  }

  String getCurrentName() {
    return currentName;
  }

  public void fetch(QueryTranslatorImpl q, String entityNamethrows QueryException {
    if isCollectionValued() ) {
      q.setCollectionToFetchgetCollectionRole(), getCollectionName(), getCollectionOwnerName(), entityName );
    }
    else {
      q.addEntityToFetchentityName, getOneToOneOwnerName(), getOwnerAssociationType() );
    }
  }
}