Open Source Repository

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


org/hibernate/loader/JoinWalker.java
//$Id: JoinWalker.java 9889 2006-05-05 01:24:12Z [email protected] $
package org.hibernate.loader;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.hibernate.FetchMode;
import org.hibernate.LockMode;
import org.hibernate.MappingException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.CascadeStyle;
import org.hibernate.engine.JoinHelper;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Joinable;
import org.hibernate.persister.entity.Loadable;
import org.hibernate.persister.entity.OuterJoinLoadable;
import org.hibernate.sql.ConditionFragment;
import org.hibernate.sql.DisjunctionFragment;
import org.hibernate.sql.InFragment;
import org.hibernate.sql.JoinFragment;
import org.hibernate.type.AbstractComponentType;
import org.hibernate.type.AssociationType;
import org.hibernate.type.EntityType;
import org.hibernate.type.ForeignKeyDirection;
import org.hibernate.type.Type;
import org.hibernate.util.ArrayHelper;
import org.hibernate.util.StringHelper;

/**
 * Walks the metamodel, searching for joins, and collecting
 * together information needed by <tt>OuterJoinLoader</tt>.
 
 @see OuterJoinLoader
 @author Gavin King, Jon Lipsky
 */
public class JoinWalker {
  
  private final SessionFactoryImplementor factory;
  protected final List associations = new ArrayList();
  private final Set visitedAssociationKeys = new HashSet();
  private final Map enabledFilters;

  protected String[] suffixes;
  protected String[] collectionSuffixes;
  protected Loadable[] persisters;
  protected int[] owners;
  protected EntityType[] ownerAssociationTypes;
  protected CollectionPersister[] collectionPersisters;
  protected int[] collectionOwners;
  protected String[] aliases;
  protected LockMode[] lockModeArray;
  protected String sql;
  
  public String[] getCollectionSuffixes() {
    return collectionSuffixes;
  }

  public void setCollectionSuffixes(String[] collectionSuffixes) {
    this.collectionSuffixes = collectionSuffixes;
  }

  public LockMode[] getLockModeArray() {
    return lockModeArray;
  }

  public void setLockModeArray(LockMode[] lockModeArray) {
    this.lockModeArray = lockModeArray;
  }

  public String[] getSuffixes() {
    return suffixes;
  }

  public void setSuffixes(String[] suffixes) {
    this.suffixes = suffixes;
  }

  public String[] getAliases() {
    return aliases;
  }

  public void setAliases(String[] aliases) {
    this.aliases = aliases;
  }

  public int[] getCollectionOwners() {
    return collectionOwners;
  }

  public void setCollectionOwners(int[] collectionOwners) {
    this.collectionOwners = collectionOwners;
  }

  public CollectionPersister[] getCollectionPersisters() {
    return collectionPersisters;
  }

  public void setCollectionPersisters(CollectionPersister[] collectionPersisters) {
    this.collectionPersisters = collectionPersisters;
  }

  public EntityType[] getOwnerAssociationTypes() {
    return ownerAssociationTypes;
  }

  public void setOwnerAssociationTypes(EntityType[] ownerAssociationType) {
    this.ownerAssociationTypes = ownerAssociationType;
  }

  public int[] getOwners() {
    return owners;
  }

  public void setOwners(int[] owners) {
    this.owners = owners;
  }

  public Loadable[] getPersisters() {
    return persisters;
  }

  public void setPersisters(Loadable[] persisters) {
    this.persisters = persisters;
  }

  public String getSQLString() {
    return sql;
  }

  public void setSql(String sql) {
    this.sql = sql;
  }

  protected SessionFactoryImplementor getFactory() {
    return factory;
  }

  protected Dialect getDialect() {
    return factory.getDialect();
  }
  
  protected Map getEnabledFilters() {
    return enabledFilters;
  }

  protected JoinWalker(SessionFactoryImplementor factory, Map enabledFilters) {
    this.factory = factory;
    this.enabledFilters = enabledFilters;
  }

  /**
   * Add on association (one-to-one, many-to-one, or a collection) to a list 
   * of associations to be fetched by outerjoin (if necessary)
   */
  private void addAssociationToJoinTreeIfNecessary(
    final AssociationType type,
    final String[] aliasedLhsColumns,
    final String alias,
    final String path,
    int currentDepth,
    final int joinType)
  throws MappingException {
    
    if (joinType>=0) {  
      addAssociationToJoinTree(
          type, 
          aliasedLhsColumns, 
          alias, 
          path,
          currentDepth,
          joinType
        );
    }

  }

  /**
   * Add on association (one-to-one, many-to-one, or a collection) to a list 
   * of associations to be fetched by outerjoin 
   */
  private void addAssociationToJoinTree(
    final AssociationType type,
    final String[] aliasedLhsColumns,
    final String alias,
    final String path,
    final int currentDepth,
    final int joinType)
  throws MappingException {

    Joinable joinable = type.getAssociatedJoinablegetFactory() );

    String subalias = generateTableAlias(
        associations.size()+1//before adding to collection!
        path, 
        joinable
      );

    OuterJoinableAssociation assoc = new OuterJoinableAssociation(
        type, 
        alias, 
        aliasedLhsColumns, 
        subalias, 
        joinType, 
        getFactory()
        enabledFilters
      );
    assoc.validateJoin(path);
    associations.add(assoc);

    int nextDepth = currentDepth+1;
    if !joinable.isCollection() ) {
      if (joinable instanceof OuterJoinLoadable) {
        walkEntityTree(
          (OuterJoinLoadablejoinable, 
          subalias,
          path, 
          nextDepth
        );
      }
    }
    else {
      if (joinable instanceof QueryableCollection) {
        walkCollectionTree(
          (QueryableCollectionjoinable, 
          subalias, 
          path, 
          nextDepth
        );
      }
    }

  }

  /**
   * For an entity class, return a list of associations to be fetched by outerjoin
   */
  protected final void walkEntityTree(OuterJoinLoadable persister, String alias)
  throws MappingException {
    walkEntityTree(persister, alias, ""0);
  }

  /**
   * For a collection role, return a list of associations to be fetched by outerjoin
   */
  protected final void walkCollectionTree(QueryableCollection persister, String alias)
  throws MappingException {
    walkCollectionTree(persister, alias, ""0);
    //TODO: when this is the entry point, we should use an INNER_JOIN for fetching the many-to-many elements!
  }

  /**
   * For a collection role, return a list of associations to be fetched by outerjoin
   */
  private void walkCollectionTree(
    final QueryableCollection persister,
    final String alias,
    final String path,
    final int currentDepth)
  throws MappingException {

    if persister.isOneToMany() ) {
      walkEntityTree(
          (OuterJoinLoadablepersister.getElementPersister(),
          alias,
          path,
          currentDepth
        );
    }
    else {
      Type type = persister.getElementType();
      if type.isAssociationType() ) {
        // a many-to-many;
        // decrement currentDepth here to allow join across the association table
        // without exceeding MAX_FETCH_DEPTH (i.e. the "currentDepth - 1" bit)
        AssociationType associationType = (AssociationTypetype;
        String[] aliasedLhsColumns = persister.getElementColumnNames(alias);
        String[] lhsColumns = persister.getElementColumnNames();
        // if the current depth is 0, the root thing being loaded is the
        // many-to-many collection itself.  Here, it is alright to use
        // an inner join...
        boolean useInnerJoin = currentDepth == 0;
        final int joinType = getJoinType(
            associationType,
            persister.getFetchMode(),
            path,
            persister.getTableName(),
            lhsColumns,
            !useInnerJoin,
            currentDepth - 1
            null //operations which cascade as far as the collection also cascade to collection elements
          );
        addAssociationToJoinTreeIfNecessary(
            associationType,
            aliasedLhsColumns,
            alias,
            path,
            currentDepth - 1,
            joinType
          );
      }
      else if type.isComponentType() ) {
        walkCompositeElementTree(
            (AbstractComponentTypetype,
            persister.getElementColumnNames(),
            persister,
            alias,
            path,
            currentDepth
          );
      }
    }

  }
  
  /**
   * Walk the tree for a particular entity association
   */
  private final void walkEntityAssociationTree(
    final AssociationType associationType,
    final OuterJoinLoadable persister,
    final int propertyNumber,
    final String alias,
    final String path,
    final boolean nullable,
    final int currentDepth)
  throws MappingException {

    String[] aliasedLhsColumns = JoinHelper.getAliasedLHSColumnNames(
        associationType, alias, propertyNumber, persister, getFactory()
      );

    String[] lhsColumns = JoinHelper.getLHSColumnNames(
        associationType, propertyNumber, persister, getFactory()
      );
    String lhsTable = JoinHelper.getLHSTableName(associationType, propertyNumber, persister);

    String subpath = subPathpath, persister.getSubclassPropertyName(propertyNumber) );
    int joinType = getJoinType(
        associationType,
        persister.getFetchMode(propertyNumber),
        subpath,
        lhsTable,
        lhsColumns,
        nullable,
        currentDepth, 
        persister.getCascadeStyle(propertyNumber)
      );
    addAssociationToJoinTreeIfNecessary(
        associationType,
        aliasedLhsColumns,
        alias,
        subpath,
        currentDepth,
        joinType
      );

  }

  /**
   * For an entity class, add to a list of associations to be fetched 
   * by outerjoin
   */
  private final void walkEntityTree(
    final OuterJoinLoadable persister,
    final String alias,
    final String path,
    final int currentDepth
  throws MappingException {

    int n = persister.countSubclassProperties();
    for int i=0; i<n; i++ ) {
      Type type = persister.getSubclassPropertyType(i);
      if type.isAssociationType() ) {
        walkEntityAssociationTree(
          (AssociationTypetype,
          persister,
          i,
          alias,
          path,
          persister.isSubclassPropertyNullable(i),
          currentDepth
        );
      }
      else if type.isComponentType() ) {
        walkComponentTree(
          (AbstractComponentTypetype,
          i,
          0,
          persister,
          alias,
          subPathpath, persister.getSubclassPropertyName(i) ),
          currentDepth
        );
      }
    }
  }

  /**
   * For a component, add to a list of associations to be fetched by outerjoin
   */
  private void walkComponentTree(
    final AbstractComponentType componentType,
    final int propertyNumber,
    int begin,
    final OuterJoinLoadable persister,
    final String alias,
    final String path,
    final int currentDepth
  throws MappingException {

    Type[] types = componentType.getSubtypes();
    String[] propertyNames = componentType.getPropertyNames();
    for int i=0; i <types.length; i++ ) {

      if types[i].isAssociationType() ) {
        AssociationType associationType = (AssociationTypetypes[i];

        String[] aliasedLhsColumns = JoinHelper.getAliasedLHSColumnNames(
          associationType, alias, propertyNumber, begin, persister, getFactory()
        );

        String[] lhsColumns = JoinHelper.getLHSColumnNames(
          associationType, propertyNumber, begin, persister, getFactory()
        );
        String lhsTable = JoinHelper.getLHSTableName(associationType, propertyNumber, persister);

        String subpath = subPathpath, propertyNames[i] );
        final boolean[] propertyNullability = componentType.getPropertyNullability();
        final int joinType = getJoinType(
            associationType,
            componentType.getFetchMode(i),
            subpath,
            lhsTable,
            lhsColumns,
            propertyNullability==null || propertyNullability[i],
            currentDepth, 
            componentType.getCascadeStyle(i)
          );
        addAssociationToJoinTreeIfNecessary(      
            associationType,
            aliasedLhsColumns,
            alias,
            subpath,
            currentDepth,
            joinType
          );

      }
      else if types[i].isComponentType() ) {
        String subpath = subPathpath, propertyNames[i] );
        walkComponentTree(
            (AbstractComponentTypetypes[i],
            propertyNumber,
            begin,
            persister,
            alias,
            subpath,
            currentDepth
          );
      }
      
      begin+=types[i].getColumnSpangetFactory() );
    }

  }

  /**
   * For a composite element, add to a list of associations to be fetched by outerjoin
   */
  private void walkCompositeElementTree(
    final AbstractComponentType compositeType,
    final String[] cols,
    final QueryableCollection persister,
    final String alias,
    final String path,
    final int currentDepth
  throws MappingException {

    Type[] types = compositeType.getSubtypes();
    String[] propertyNames = compositeType.getPropertyNames();
    int begin = 0;
    for int i=0; i <types.length; i++ ) {
      int length = types[i].getColumnSpangetFactory() );
      String[] lhsColumns = ArrayHelper.slice(cols, begin, length);

      if types[i].isAssociationType() ) {
        AssociationType associationType = (AssociationTypetypes[i];

        // simple, because we can't have a one-to-one or a collection 
        // (or even a property-ref) in a composite-element:
        String[] aliasedLhsColumns = StringHelper.qualify(alias, lhsColumns);

        String subpath = subPathpath, propertyNames[i] );
        final boolean[] propertyNullability = compositeType.getPropertyNullability();
        final int joinType = getJoinType(
            associationType,
            compositeType.getFetchMode(i),
            subpath,
            persister.getTableName(),
            lhsColumns,
            propertyNullability==null || propertyNullability[i],
            currentDepth, 
            compositeType.getCascadeStyle(i)
          );
        addAssociationToJoinTreeIfNecessary(
            associationType,
            aliasedLhsColumns,
            alias,
            subpath,
            currentDepth,
            joinType
          );
      }
      else if types[i].isComponentType() ) {
        String subpath = subPathpath, propertyNames[i] );
        walkCompositeElementTree(
            (AbstractComponentTypetypes[i],
            lhsColumns,
            persister,
            alias,
            subpath,
            currentDepth
          );
      }
      begin+=length;
    }

  }

  /**
   * Extend the path by the given property name
   */
  private static String subPath(String path, String property) {
    if path==null || path.length()==0) {
      return property;
    }
    else {
      return StringHelper.qualify(path, property);
    }
  }

  /**
   * Get the join type (inner, outer, etc) or -1 if the
   * association should not be joined. Override on
   * subclasses.
   */
  protected int getJoinType(
      AssociationType type, 
      FetchMode config, 
      String path, 
      String lhsTable,
      String[] lhsColumns,
      boolean nullable,
      int currentDepth, 
      CascadeStyle cascadeStyle)
  throws MappingException {
    
    if  !isJoinedFetchEnabled(type, config, cascadeStyle) ) return -1;
    
    if isTooDeep(currentDepth|| type.isCollectionType() && isTooManyCollections() ) ) return -1;
    
    final boolean dupe = isDuplicateAssociation(lhsTable,  lhsColumns, type);
    if (dupereturn -1;
    
    return getJoinType(nullable, currentDepth);
    
  }
  
  /**
   * Use an inner join if it is a non-null association and this
   * is the "first" join in a series
   */
  protected int getJoinType(boolean nullable, int currentDepth) {
    //TODO: this is too conservative; if all preceding joins were 
    //      also inner joins, we could use an inner join here
    return !nullable && currentDepth==
          JoinFragment.INNER_JOIN : 
          JoinFragment.LEFT_OUTER_JOIN;
  }

  protected boolean isTooDeep(int currentDepth) {
    Integer maxFetchDepth = getFactory().getSettings().getMaximumFetchDepth();
    return maxFetchDepth!=null && currentDepth >= maxFetchDepth.intValue();
  }
  
  protected boolean isTooManyCollections() {
    return false;
  }
  
  /**
   * Does the mapping, and Hibernate default semantics, specify that
   * this association should be fetched by outer joining
   */
  protected boolean isJoinedFetchEnabledInMapping(FetchMode config, AssociationType type
  throws MappingException {
    if !type.isEntityType() && !type.isCollectionType() ) {
      return false;
    }
    else {
      if (config==FetchMode.JOINreturn true;
      if (config==FetchMode.SELECTreturn false;
      if type.isEntityType() ) {
        //TODO: look at the owning property and check that it 
        //      isn't lazy (by instrumentation)
        EntityType entityType =(EntityTypetype;
        EntityPersister persister = getFactory().getEntityPersisterentityType.getAssociatedEntityName() );
        return !persister.hasProxy();
      }
      else {
        return false;
      }
    }
  }

  /**
   * Override on subclasses to enable or suppress joining 
   * of certain association types
   */
  protected boolean isJoinedFetchEnabled(AssociationType type, FetchMode config, CascadeStyle cascadeStyle) {
    return type.isEntityType() && isJoinedFetchEnabledInMapping(config, type;
  }
  
  protected String generateTableAlias(
      final int n,
      final String path,
      final Joinable joinable
  ) {
    return StringHelper.generateAliasjoinable.getName(), n );
  }

  protected String generateRootAlias(final String description) {
    return StringHelper.generateAlias(description, 0);
  }

  /**
   * Used to detect circularities in the joined graph, note that 
   * this method is side-effecty
   */
  protected boolean isDuplicateAssociation(
    final String foreignKeyTable, 
    final String[] foreignKeyColumns
  ) {
    AssociationKey associationKey = new AssociationKey(foreignKeyColumns, foreignKeyTable);
    return !visitedAssociationKeys.addassociationKey );
  }
  
  /**
   * Used to detect circularities in the joined graph, note that 
   * this method is side-effecty
   */
  protected boolean isDuplicateAssociation(
    final String lhsTable,
    final String[] lhsColumnNames,
    final AssociationType type
  ) {
    final String foreignKeyTable;
    final String[] foreignKeyColumns;
    if type.getForeignKeyDirection()==ForeignKeyDirection.FOREIGN_KEY_FROM_PARENT ) {
      foreignKeyTable = lhsTable;
      foreignKeyColumns = lhsColumnNames;
    }
    else {
      foreignKeyTable = type.getAssociatedJoinablegetFactory() ).getTableName();
      foreignKeyColumns = JoinHelper.getRHSColumnNamestype, getFactory() );
    }
    return isDuplicateAssociation(foreignKeyTable, foreignKeyColumns);
  }
  
  /**
   * Uniquely identifier a foreign key, so that we don't
   * join it more than once, and create circularities
   */
  private static final class AssociationKey {
    private String[] columns;
    private String table;
    private AssociationKey(String[] columns, String table) {
      this.columns = columns;
      this.table = table;
    }
    public boolean equals(Object other) {
      AssociationKey that = (AssociationKeyother;
      return that.table.equals(table&& Arrays.equals(columns, that.columns);
    }
    public int hashCode() {
      return table.hashCode()//TODO: inefficient
    }
  }
  
  /**
   * Should we join this association?
   */
  protected boolean isJoinable(
    final int joinType,
    final Set visitedAssociationKeys, 
    final String lhsTable,
    final String[] lhsColumnNames,
    final AssociationType type,
    final int depth
  ) {
    if (joinType<0return false;
    
    if (joinType==JoinFragment.INNER_JOINreturn true;
    
    Integer maxFetchDepth = getFactory().getSettings().getMaximumFetchDepth();
    final boolean tooDeep = maxFetchDepth!=null && 
      depth >= maxFetchDepth.intValue();
    
    return !tooDeep && !isDuplicateAssociation(lhsTable, lhsColumnNames, type);
  }
  
  protected String orderBy(final List associations, final String orderBy) {
    return mergeOrderingsorderByassociations ), orderBy );
  }

  protected static String mergeOrderings(String ordering1, String ordering2) {
    if ordering1.length() == ) {
      return ordering2;
    }
    else if ordering2.length() == ) {
      return ordering1;
    }
    else {
      return ordering1 + ", " + ordering2;
    }
  }
  
  /**
   * Generate a sequence of <tt>LEFT OUTER JOIN</tt> clauses for the given associations.
   */
  protected final JoinFragment mergeOuterJoins(List associations)
  throws MappingException {
    JoinFragment outerjoin = getDialect().createOuterJoinFragment();
    Iterator iter = associations.iterator();
    OuterJoinableAssociation last = null;
    while iter.hasNext() ) {
      OuterJoinableAssociation oj = (OuterJoinableAssociationiter.next();
      if last != null && last.isManyToManyWithoj ) ) {
        oj.addManyToManyJoinouterjoin, QueryableCollection last.getJoinable() );
      }
      else {
        oj.addJoins(outerjoin);
      }
      last = oj;
    }
    last = null;
    return outerjoin;
  }

  /**
   * Count the number of instances of Joinable which are actually
   * also instances of Loadable, or are one-to-many associations
   */
  protected static final int countEntityPersisters(List associations)
  throws MappingException {
    int result = 0;
    Iterator iter = associations.iterator();
    while iter.hasNext() ) {
      OuterJoinableAssociation oj = (OuterJoinableAssociationiter.next();
      if oj.getJoinable().consumesEntityAlias() ) {
        result++;
      }
    }
    return result;
  }
  
  /**
   * Count the number of instances of Joinable which are actually
   * also instances of PersistentCollection which are being fetched
   * by outer join
   */
  protected static final int countCollectionPersisters(List associations)
  throws MappingException {
    int result = 0;
    Iterator iter = associations.iterator();
    while iter.hasNext() ) {
      OuterJoinableAssociation oj = (OuterJoinableAssociationiter.next();
      if oj.getJoinType()==JoinFragment.LEFT_OUTER_JOIN && oj.getJoinable().isCollection() ) {
        result++;
      }
    }
    return result;
  }
  
  /**
   * Get the order by string required for collection fetching
   */
  protected static final String orderBy(List associations)
  throws MappingException {
    StringBuffer buf = new StringBuffer();
    Iterator iter = associations.iterator();
    OuterJoinableAssociation last = null;
    while iter.hasNext() ) {
      OuterJoinableAssociation oj = (OuterJoinableAssociationiter.next();
      if oj.getJoinType() == JoinFragment.LEFT_OUTER_JOIN ) { // why does this matter?
        if oj.getJoinable().isCollection() ) {
          final QueryableCollection queryableCollection = (QueryableCollectionoj.getJoinable();
          if queryableCollection.hasOrdering() ) {
            final String orderByString = queryableCollection.getSQLOrderByStringoj.getRHSAlias() );
            buf.appendorderByString ).append(", ");
          }
        }
        else {
          // it might still need to apply a collection ordering based on a
          // many-to-many defined order-by...
          if last != null && last.getJoinable().isCollection() ) {
            final QueryableCollection queryableCollection = (QueryableCollectionlast.getJoinable();
            if queryableCollection.isManyToMany() && last.isManyToManyWithoj ) ) {
              if queryableCollection.hasManyToManyOrdering() ) {
                final String orderByString = queryableCollection.getManyToManyOrderByStringoj.getRHSAlias() );
                buf.appendorderByString ).append(", ");
              }
            }
          }
        }
      }
      last = oj;
    }
    if buf.length()>buf.setLengthbuf.length()-);
    return buf.toString();
  }
  
  /**
   * Render the where condition for a (batch) load by identifier / collection key
   */
  protected StringBuffer whereString(String alias, String[] columnNames, int batchSize) {
    if columnNames.length==) {
      // if not a composite key, use "foo in (?, ?, ?)" for batching
      // if no batch, and not a composite key, use "foo = ?"
      InFragment in = new InFragment().setColumnalias, columnNames[0] );
      for int i=0; i<batchSize; i++ in.addValue("?");
      return new StringBufferin.toFragmentString() );
    }
    else {
      //a composite key
      ConditionFragment byId = new ConditionFragment()
          .setTableAlias(alias)
          .setConditioncolumnNames, "?" );
  
      StringBuffer whereString = new StringBuffer();
      if batchSize==) {
        // if no batch, use "foo = ? and bar = ?"
        whereString.appendbyId.toFragmentString() );
      }
      else {
        // if a composite key, use "( (foo = ? and bar = ?) or (foo = ? and bar = ?) )" for batching
        whereString.append('(')//TODO: unnecessary for databases with ANSI-style joins
        DisjunctionFragment df = new DisjunctionFragment();
        for int i=0; i<batchSize; i++ ) {
          df.addCondition(byId);
        }
        whereString.appenddf.toFragmentString() );
        whereString.append(')')//TODO: unnecessary for databases with ANSI-style joins
      }
      return whereString;
    }
  }

  protected void initPersisters(final List associations, final LockMode lockModethrows MappingException {
    
    final int joins = countEntityPersisters(associations);
    final int collections = countCollectionPersisters(associations);

    collectionOwners = collections==null new int[collections];
    collectionPersisters = collections==null new CollectionPersister[collections];
    collectionSuffixes = BasicLoader.generateSuffixesjoins + 1, collections );

    persisters = new Loadable[joins];
    aliases = new String[joins];
    owners = new int[joins];
    ownerAssociationTypes = new EntityType[joins];
    lockModeArray = ArrayHelper.fillArray(lockMode, joins);
    
    int i=0;
    int j=0;
    Iterator iter = associations.iterator();
    while iter.hasNext() ) {
      final OuterJoinableAssociation oj = (OuterJoinableAssociationiter.next();
      if !oj.isCollection() ) {
        
        persisters[i(Loadableoj.getJoinable();
        aliases[i= oj.getRHSAlias();
        owners[i= oj.getOwner(associations);
        ownerAssociationTypes[i(EntityTypeoj.getJoinableType();
        i++;
        
      }
      else {
        
        QueryableCollection collPersister = (QueryableCollectionoj.getJoinable();
        if oj.getJoinType()==JoinFragment.LEFT_OUTER_JOIN ) {
          //it must be a collection fetch
          collectionPersisters[j= collPersister;
          collectionOwners[j= oj.getOwner(associations);
          j++;
        }
  
        if collPersister.isOneToMany() ) {
          persisters[i(LoadablecollPersister.getElementPersister();
          aliases[i= oj.getRHSAlias();
          i++;
        }
      }
    }
  
    if ArrayHelper.isAllNegative(owners) ) owners = null;
    if collectionOwners!=null && ArrayHelper.isAllNegative(collectionOwners) ) {
      collectionOwners = null;
    }
  }

  /**
   * Generate a select list of columns containing all properties of the entity classes
   */
  protected final String selectString(List associations)
  throws MappingException {

    if associations.size()==) {
      return "";
    }
    else {
      StringBuffer buf = new StringBufferassociations.size() 100 )
        .append(", ");
      int entityAliasCount=0;
      int collectionAliasCount=0;
      for int i=0; i<associations.size(); i++ ) {
        OuterJoinableAssociation join = (OuterJoinableAssociationassociations.get(i);
        OuterJoinableAssociation next = (i == associations.size() 1)
                null
                OuterJoinableAssociation associations.geti + );
        final Joinable joinable = join.getJoinable();
        final String entitySuffix = suffixes == null || entityAliasCount >= suffixes.length )
                null
                : suffixes[entityAliasCount];
        final String collectionSuffix = collectionSuffixes == null || collectionAliasCount >= collectionSuffixes.length )
                null
                : collectionSuffixes[collectionAliasCount];
        final String selectFragment = joinable.selectFragment(
            next == null null : next.getJoinable(),
            next == null null : next.getRHSAlias(),
            join.getRHSAlias(),
            entitySuffix,
                collectionSuffix,
            join.getJoinType()==JoinFragment.LEFT_OUTER_JOIN
        );
        buf.append(selectFragment);
        if joinable.consumesEntityAlias() ) entityAliasCount++;
        if joinable.consumesCollectionAlias() && join.getJoinType()==JoinFragment.LEFT_OUTER_JOIN collectionAliasCount++;
        if (
          i<associations.size()-&&
          selectFragment.trim().length()>0
        ) {
          buf.append(", ");
        }
      }
      return buf.toString();
    }
  }

}