Package org.apache.ojb.broker.accesslayer.sql

Source Code of org.apache.ojb.broker.accesslayer.sql.SqlQueryStatement$AttributeInfo

package org.apache.ojb.broker.accesslayer.sql;

/* Copyright 2002-2005 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.ojb.broker.PersistenceBrokerSQLException;
import org.apache.ojb.broker.accesslayer.JoinSyntaxTypes;
import org.apache.ojb.broker.metadata.ClassDescriptor;
import org.apache.ojb.broker.metadata.CollectionDescriptor;
import org.apache.ojb.broker.metadata.DescriptorRepository;
import org.apache.ojb.broker.metadata.FieldDescriptor;
import org.apache.ojb.broker.metadata.FieldHelper;
import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
import org.apache.ojb.broker.metadata.SuperReferenceDescriptor;
import org.apache.ojb.broker.platforms.Platform;
import org.apache.ojb.broker.query.BetweenCriteria;
import org.apache.ojb.broker.query.Criteria;
import org.apache.ojb.broker.query.ExistsCriteria;
import org.apache.ojb.broker.query.FieldCriteria;
import org.apache.ojb.broker.query.InCriteria;
import org.apache.ojb.broker.query.LikeCriteria;
import org.apache.ojb.broker.query.MtoNQuery;
import org.apache.ojb.broker.query.NullCriteria;
import org.apache.ojb.broker.query.Query;
import org.apache.ojb.broker.query.QueryByCriteria;
import org.apache.ojb.broker.query.QueryBySQL;
import org.apache.ojb.broker.query.SelectionCriteria;
import org.apache.ojb.broker.query.SqlCriteria;
import org.apache.ojb.broker.query.UserAlias;
import org.apache.ojb.broker.util.SqlHelper;
import org.apache.ojb.broker.util.SqlHelper.PathInfo;
import org.apache.ojb.broker.util.logging.Logger;
import org.apache.ojb.broker.util.logging.LoggerFactory;

/**
* Model a Statement based on Query.
*
* @author <a href="mailto:jbraeuchi@gmx.ch">Jakob Braeuchi</a>
* @version $Id: SqlQueryStatement.java,v 1.75.2.23 2005/12/22 18:25:51 brj Exp $
*/
public abstract class SqlQueryStatement implements SqlStatement, JoinSyntaxTypes
{
    private static final String ALIAS_SEPARATOR = ".";
    private static final String M_N_ALIAS = "M_N";
    private String sql;
   
    private SqlQueryStatement m_parentStatement;
    /** the logger */
    private Logger m_logger;
    /** the target table of the query */
    private TableAlias m_root;
    /** the search table of the query */
    private TableAlias m_search;
    /** the query */
    private QueryByCriteria m_query;
    /** the mapping of paths to TableAliases. the key is built using the path and the path class hints. */
    private HashMap m_pathToAlias = new HashMap();
    /** the mapping of ClassDescriptor to TableAliases */
    private HashMap m_cldToAlias = new HashMap();
    /** maps trees of joins to criteria */
    private HashMap m_joinTreeToCriteria = new HashMap();

    private Platform m_platform;
    private ClassDescriptor m_baseCld;
    private ClassDescriptor m_searchCld;

    private int m_aliasCount = 0;
    protected HashMap m_attrToFld = new HashMap();   //attribute -> FieldDescriptor

    /**
     * Constructor for SqlCriteriaStatement.
     *
     * @param pf the Platform
     * @param cld the ClassDescriptor
     * @param query the Query
     * @param logger the Logger
     */
    public SqlQueryStatement(Platform pf, ClassDescriptor cld, Query query, Logger logger)
    {
        this(null, pf, cld, query, logger);
    }

    /**
     * Constructor for SqlCriteriaStatement.
     *
     * @param parent the Parent Query
     * @param pf the Platform
     * @param cld the ClassDescriptor
     * @param query the Query
     * @param logger the Logger
     */
    public SqlQueryStatement(SqlQueryStatement parent, Platform pf, ClassDescriptor cld, Query query, Logger logger)
    {
        m_logger = logger != null ? logger : LoggerFactory.getLogger(SqlQueryStatement.class);
        m_parentStatement = parent;
        m_query = (QueryByCriteria) query;
        m_platform = pf;
        m_searchCld = cld;

        if ((m_query == null) || (m_query.getBaseClass() == m_query.getSearchClass()))
        {
            m_baseCld = m_searchCld;
        }
        else
        {
            m_baseCld = cld.getRepository().getDescriptorFor(query.getBaseClass());
        }

        m_root = createTableAlias(m_baseCld, null, "");
       
        // BRJ: create a special alias for the indirection table
        if (m_query instanceof MtoNQuery)
        {
            MtoNQuery mnQuery = (MtoNQuery)m_query;
            TableAlias mnAlias = new TableAlias(mnQuery.getIndirectionTable(), M_N_ALIAS);
            setTableAliasForPath(mnQuery.getIndirectionTable(), null, mnAlias);       
        }

        if (m_searchCld == m_baseCld)
        {
            m_search = m_root;
        }
        else
        {
      m_search = getTableAlias(m_query.getObjectProjectionAttribute(), false, null, null, m_query.getPathClasses());
        }

        // Walk the super reference-descriptor
        buildSuperJoinTree(m_root, m_baseCld, "" ,false);

        buildMultiJoinTree(m_root, m_baseCld, "", true);

        // In some cases it is necessary to split the query criteria
        // and then to generate UNION of several SELECTs
        // We build the joinTreeToCriteria mapping,
        if (query != null)
        {
            splitCriteria();
        }
    }

    protected ClassDescriptor getBaseClassDescriptor()
    {
        return m_baseCld;
    }

    protected ClassDescriptor getSearchClassDescriptor()
    {
        return m_searchCld;
    }

  /**
   * Return the TableAlias and the PathInfo for an Attribute name<br>
   * field names in functions (ie: sum(name) ) are tried to resolve ie: name
   * from FIELDDESCRIPTOR , UPPER(name_test) from Criteria<br>
   * also resolve pathExpression adress.city or owner.konti.saldo
   * @param attr
   * @param useOuterJoins
   * @param aUserAlias
   * @param pathClasses
   * @return ColumnInfo
   */
  protected AttributeInfo getAttributeInfo(String attr, boolean useOuterJoins, UserAlias aUserAlias, Map pathClasses)
  {
    AttributeInfo result = new AttributeInfo();
    TableAlias tableAlias;
    SqlHelper.PathInfo pathInfo = SqlHelper.splitPath(attr);
    String colName = pathInfo.column;
    int sp;

    // BRJ:
    // check if we refer to an attribute in the parent query
    // this prefix is temporary !
    if (colName.startsWith(Criteria.PARENT_QUERY_PREFIX) && m_parentStatement != null)
    {
      String[] fieldNameRef = {colName.substring(Criteria.PARENT_QUERY_PREFIX.length())};
      return m_parentStatement.getAttributeInfo(fieldNameRef[0], useOuterJoins, aUserAlias, pathClasses);
    }

    sp = colName.lastIndexOf(".");
    if (sp == -1)
    {
      tableAlias = getRoot();
    }
    else
    {
      String pathName = colName.substring(0, sp);
      String[] fieldNameRef = {colName.substring(sp + 1)};

      tableAlias = getTableAlias(pathName, useOuterJoins, aUserAlias, fieldNameRef, pathClasses);
      /**
       * if we have not found an alias by the pathName or
       * aliasName (if given), try again because pathName
       * may be an aliasname. it can be only and only if it is not
       * a path, which means there may be no path separators (,)
       * in the pathName.
       */
      if ((tableAlias == null) && (colName.lastIndexOf(".") == -1))
      {
        /**
         * pathName might be an alias, so check this first
         */
        tableAlias = getTableAlias(pathName, useOuterJoins, new UserAlias(pathName, pathName, pathName), null, pathClasses);
      }

      if (tableAlias != null)
      {
        // correct column name to match the alias
        // productGroup.groupName -> groupName
        pathInfo.column = fieldNameRef[0];
      }
    }

    result.tableAlias = tableAlias;
    result.pathInfo = pathInfo;
    return result;
  }

    /**
     * Answer the column name for alias and path info<br>
     * if translate try to convert attribute name into column name otherwise use attribute name<br>
     * if a FieldDescriptor is found for the attribute name the column name is taken from
     * there prefixed with the alias (firstname -> A0.F_NAME).
     */
    protected String getColName(TableAlias aTableAlias, PathInfo aPathInfo, boolean translate)
    {
        String result = null;

        // no translation required, use attribute name
        if (!translate)
        {
            return aPathInfo.column;
        }

        // BRJ: special alias for the indirection table has no ClassDescriptor
        if (aTableAlias.cld == null && M_N_ALIAS.equals(aTableAlias.alias))
        {
            return getIndirectionTableColName(aTableAlias, aPathInfo.path);
        }

        // translate attribute name into column name
        FieldDescriptor fld = getFieldDescriptor(aTableAlias, aPathInfo);

        if (fld != null)
        {
            m_attrToFld.put(aPathInfo.path, fld);

            // added to suport the super reference descriptor
            if (!fld.getClassDescriptor().getFullTableName().equals(aTableAlias.table) && aTableAlias.hasJoins())
            {
                Iterator itr = aTableAlias.joins.iterator();
                while (itr.hasNext())
                {
                    Join join = (Join) itr.next();
                    if (join.right.table.equals(fld.getClassDescriptor().getFullTableName()))
                    {
                        result = join.right.alias + "." + fld.getColumnName();
                        break;
                    }
                }

                if (result == null)
                {
                    result = aPathInfo.column;
                }
            }
            else
            {
                result = aTableAlias.alias + "." + fld.getColumnName();
            }
        }
        else if ("*".equals(aPathInfo.column))
        {
            result = aPathInfo.column;
        }
        else
        {
            // throw new IllegalArgumentException("No Field found for : " + aPathInfo.column);
            result = aPathInfo.column;
        }

        return result;
    }

    /**
     * Add the Column to the StringBuffer <br>
     *
     * @param aTableAlias
     * @param aPathInfo
     * @param translate flag to indicate translation of pathInfo
     * @param buf
     * @return true if appended
     */
    protected boolean appendColName(TableAlias aTableAlias, PathInfo aPathInfo, boolean translate, StringBuffer buf)
    {
        String prefix = aPathInfo.prefix;
        String suffix = aPathInfo.suffix;
        String colName = getColName(aTableAlias, aPathInfo, translate);

        if (prefix != null) // rebuild function contains (
        {
            buf.append(prefix);
        }

        buf.append(colName);

        if (suffix != null) // rebuild function
        {
            buf.append(suffix);
        }

        return true;
    }

    /**
     * Get the FieldDescriptor for the PathInfo
     *
     * @param aTableAlias
     * @param aPathInfo
     * @return FieldDescriptor
     */
    protected FieldDescriptor getFieldDescriptor(TableAlias aTableAlias, PathInfo aPathInfo)
    {
        FieldDescriptor fld = null;
        String colName = aPathInfo.column;

        if (aTableAlias != null)
        {
            fld = aTableAlias.cld.getFieldDescriptorByName(colName);
            if (fld == null)
            {
                ObjectReferenceDescriptor ord = aTableAlias.cld.getObjectReferenceDescriptorByName(colName);
                if (ord != null)
                {
                    fld = getFldFromReference(aTableAlias, ord);
                }
                else
                {
                    fld = getFldFromJoin(aTableAlias, colName);
                }
            }
        }

        return fld;
    }

    /**
     * Get FieldDescriptor from joined superclass.
     */
    private FieldDescriptor getFldFromJoin(TableAlias aTableAlias, String aColName)
    {
        FieldDescriptor fld = null;

        // Search Join Structure for attribute
        if (aTableAlias.joins != null)
        {
            Iterator itr = aTableAlias.joins.iterator();
            while (itr.hasNext())
            {
                Join join = (Join) itr.next();
                ClassDescriptor cld = join.right.cld;

                if (cld != null)
                {
                    fld = cld.getFieldDescriptorByName(aColName);
                    if (fld != null)
                    {
                        break;
                    }

                }
            }
        }
        return fld;
    }

    /**
     * Get FieldDescriptor from Reference
     */
    private FieldDescriptor getFldFromReference(TableAlias aTableAlias, ObjectReferenceDescriptor anOrd)
    {
        FieldDescriptor fld = null;

        if (aTableAlias == getRoot())
        {
            // no path expression
            FieldDescriptor[] fk = anOrd.getForeignKeyFieldDescriptors(aTableAlias.cld);
            if (fk.length > 0)
            {
                fld = fk[0];
            }
        }
        else
        {
            // attribute with path expression
            /**
             * MBAIRD
             * potentially people are referring to objects, not to the object's primary key,
             * and then we need to take the primary key attribute of the referenced object
             * to help them out.
             */
            ClassDescriptor cld = aTableAlias.cld.getRepository().getDescriptorFor(anOrd.getItemClass());
            if (cld != null)
            {
                fld = aTableAlias.cld.getFieldDescriptorByName(cld.getPkFields()[0].getPersistentField().getName());
            }
        }

        return fld;
    }

    /**
     * Append the appropriate ColumnName to the buffer<br>
     * if a FIELDDESCRIPTOR is found for the Criteria the colName is taken from
     * there otherwise its taken from Criteria. <br>
     * field names in functions (ie: sum(name) ) are tried to resolve
     * ie: name from FIELDDESCRIPTOR , UPPER(name_test) from Criteria<br>
     * also resolve pathExpression adress.city or owner.konti.saldo
     */
  protected boolean appendColName(String attr, boolean useOuterJoins, UserAlias aUserAlias, StringBuffer buf)
    {
    AttributeInfo attrInfo = getAttributeInfo(attr, useOuterJoins, aUserAlias, getQuery().getPathClasses());
        TableAlias tableAlias = attrInfo.tableAlias;

        return appendColName(tableAlias, attrInfo.pathInfo, (tableAlias != null), buf);
    }

    /**
     * Append the appropriate ColumnName to the buffer<br>
     * if a FIELDDESCRIPTOR is found for the Criteria the colName is taken from
     * there otherwise its taken from Criteria. <br>
     * field names in functions (ie: sum(name) ) are tried to resolve
     * ie: name from FIELDDESCRIPTOR , UPPER(name_test) from Criteria<br>
     * also resolve pathExpression adress.city or owner.konti.saldo
     */
  protected boolean appendColName(String attr, String attrAlias, boolean useOuterJoins, UserAlias aUserAlias,
            StringBuffer buf)
    {
    AttributeInfo attrInfo = getAttributeInfo(attr, useOuterJoins, aUserAlias, getQuery().getPathClasses());
        TableAlias tableAlias = attrInfo.tableAlias;
        PathInfo pi = attrInfo.pathInfo;

        if (pi.suffix != null)
        {
            pi.suffix = pi.suffix + " as " + attrAlias;
        }
        else
        {
            pi.suffix = " as " + attrAlias;
        }

        return appendColName(tableAlias, pi, true, buf);
    }

    /**
     * Builds the Join for columns if they are not found among the existingColumns.
     * @param columns the list of columns represented by Criteria.Field to ensure
     * @param existingColumns the list of column names (String) that are already appended
     */
    protected void ensureColumns(List columns, List existingColumns)
    {
        if (columns == null || columns.isEmpty())
        {
            return;
        }
       
        Iterator iter = columns.iterator();

        while (iter.hasNext())
        {
            FieldHelper cf = (FieldHelper) iter.next();
            if (!existingColumns.contains(cf.name))
            {
                getAttributeInfo(cf.name, false, null, getQuery().getPathClasses());
            }
        }
    }

    /**
     * Builds the Join for columns if they are not found among the existingColumns.
     * These <b>columns are added to the statement</b> using a column-alias "ojb_col_x",
     * x being the number of existing columns
     * @param columns the list of columns represented by Criteria.Field to ensure
     * @param existingColumns the list of column names (String) that are already appended
     * @param buf the statement
     * @return List of existingColumns including ojb_col_x
     */
    protected List ensureColumns(List columns, List existingColumns, StringBuffer buf)
    {
        if (columns == null || columns.isEmpty())
        {
            return existingColumns;
        }

        Iterator iter = columns.iterator();
        int ojb_col = existingColumns.size() + 1;

        while (iter.hasNext())
        {
            FieldHelper cf = (FieldHelper) iter.next();
            if (!existingColumns.contains(cf.name))
            {
                existingColumns.add(cf.name);
               
                buf.append(",");
                appendColName(cf.name, "ojb_col_" + ojb_col, false, null, buf);
                ojb_col++;
            }
        }
       
        return existingColumns;
    }


    /**
     * appends a WHERE-clause to the Statement
     * @param where
     * @param crit
     * @param stmt
     */
    protected void appendWhereClause(StringBuffer where, Criteria crit, StringBuffer stmt)
    {
        if (where.length() == 0)
        {
            where = null;
        }

        if (where != null || (crit != null && !crit.isEmpty()))
        {
            stmt.append(" WHERE ");
            appendClause(where, crit, stmt);
        }
    }

    /**
     * appends a HAVING-clause to the Statement
     * @param having
     * @param crit
     * @param stmt
     */
    protected void appendHavingClause(StringBuffer having, Criteria crit, StringBuffer stmt)
    {
        if (having.length() == 0)
        {
            having = null;
        }

        if (having != null || crit != null)
        {
            stmt.append(" HAVING ");
            appendClause(having, crit, stmt);
        }
    }

    /**
     * appends a WHERE/HAVING-clause to the Statement
     * @param clause
     * @param crit
     * @param stmt
     */
    protected void appendClause(StringBuffer clause, Criteria crit, StringBuffer stmt)
    {
        /**
         * MBAIRD
         * when generating the "WHERE/HAVING" clause we need to append the criteria for multi-mapped
         * tables. We only need to do this for the root classdescriptor and not for joined tables
         * because we assume you cannot make a relation of the wrong type upon insertion. Of course,
         * you COULD mess the data up manually and this would cause a problem.
         */

        if (clause != null)
        {
            stmt.append(clause.toString());
        }
        if (crit != null)
        {
            if (clause == null)
            {
                stmt.append(asSQLStatement(crit));
            }
            else
            {
                stmt.append(" AND (");
                stmt.append(asSQLStatement(crit));
                stmt.append(")");
            }

        }
    }

    /**
     * Create SQL-String based on Criteria
     */
    private String asSQLStatement(Criteria crit)
    {
        Enumeration e = crit.getElements();
        StringBuffer statement = new StringBuffer();

        while (e.hasMoreElements())
        {
            Object o = e.nextElement();
            if (o instanceof Criteria)
            {
                Criteria pc = (Criteria) o;
               
                if (pc.isEmpty())
                {
                    continue//skip empty criteria
                }
               
                String addAtStart = "";
                String addAtEnd = "";

                // need to add parenthesises?
                if (pc.isEmbraced())
                {
                    addAtStart = " (";
                    addAtEnd = ")";
                }   

                switch (pc.getType())
                {
                    case (Criteria.OR) :
                        {
                            if (statement.length() > 0)
                            {
                                statement.append(" OR ");
                            }
                            statement.append(addAtStart);
                            statement.append(asSQLStatement(pc));
                            statement.append(addAtEnd);
                            break;
                        }
                    case (Criteria.AND) :
                        {
                            if (statement.length() > 0)
                            {
                                statement.insert(0, "( ");
                                statement.append(") AND ");
                            }
                            statement.append(addAtStart);
                            statement.append(asSQLStatement(pc));
                            statement.append(addAtEnd);
                            break;
                        }
                }
            }
            else
            {
                SelectionCriteria c = (SelectionCriteria) o;
                if (statement.length() > 0)
                {
                    statement.insert(0, "(");
                    statement.append(") AND ");
                }
                appendSQLClause(c, statement);
            }
        } // while

        // BRJ : negative Criteria surrounded by NOT (...)
        if (crit.isNegative())
        {
            statement.insert(0, " NOT (");
            statement.append(")");
        }
       
        return (statement.length() == 0 ? null : statement.toString());
    }

    /**
     * Answer the SQL-Clause for a BetweenCriteria
     *
     * @param alias
     * @param pathInfo
     * @param c BetweenCriteria
     * @param buf
     */
    private void appendBetweenCriteria(TableAlias alias, PathInfo pathInfo, BetweenCriteria c, StringBuffer buf)
    {
        appendColName(alias, pathInfo, c.isTranslateAttribute(), buf);
        buf.append(c.getClause());
        appendParameter(c.getValue(), buf);
        buf.append(" AND ");
        appendParameter(c.getValue2(), buf);
    }

    /**
     * Answer the SQL-Clause for an ExistsCriteria
     * @param c ExistsCriteria
     */
    private void appendExistsCriteria(ExistsCriteria c, StringBuffer buf)
    {
        Query subQuery = (Query) c.getValue();

        buf.append(c.getClause());
        appendSubQuery(subQuery, buf);
    }

    /**
     * Answer the SQL-Clause for a FieldCriteria<br>
     * The value of the FieldCriteria will be translated
     *
     * @param alias
     * @param pathInfo
     * @param c ColumnCriteria
     * @param buf
     */
    private void appendFieldCriteria(TableAlias alias, PathInfo pathInfo, FieldCriteria c, StringBuffer buf)
    {
        appendColName(alias, pathInfo, c.isTranslateAttribute(), buf);
        buf.append(c.getClause());

        if (c.isTranslateField())
        {
      appendColName((String) c.getValue(), false, c.getUserAlias(), buf);
        }
        else
        {
            buf.append(c.getValue());
        }
    }
   
    /**
     * Get the column name from the indirection table.
     * @param mnAlias
     * @param path
     */
    private String getIndirectionTableColName(TableAlias mnAlias, String path)
    {
        int dotIdx = path.lastIndexOf(".");
        String column = path.substring(dotIdx);
        return mnAlias.alias + column;
    }

    /**
     * Answer the SQL-Clause for an InCriteria
     *
     * @param alias
     * @param pathInfo
     * @param c InCriteria
     * @param buf
     */
    private void appendInCriteria(TableAlias alias, PathInfo pathInfo, InCriteria c, StringBuffer buf)
    {
        appendColName(alias, pathInfo, c.isTranslateAttribute(), buf);
        buf.append(c.getClause());

        if (c.getValue() instanceof Collection)
        {
            Object[] values = ((Collection) c.getValue()).toArray();
            int size = ((Collection) c.getValue()).size();

            buf.append("(");
            if (size > 0)
            {
                for (int i = 0; i < size - 1; i++)
                {
                    appendParameter(values[i], buf);
                    buf.append(",");
                }
                appendParameter(values[size - 1], buf);
            }
            buf.append(")");
        }
        else
        {
            appendParameter(c.getValue(), buf);
        }
    }

    /**
     * Answer the SQL-Clause for a NullCriteria
     *
     * @param alias
     * @param pathInfo
     * @param c NullCriteria
     * @param buf
     */
    private void appendNullCriteria(TableAlias alias, PathInfo pathInfo, NullCriteria c, StringBuffer buf)
    {
        appendColName(alias, pathInfo, c.isTranslateAttribute(), buf);
        buf.append(c.getClause());
    }

    /**
     * Answer the SQL-Clause for a SqlCriteria
     *
     */
    private void appendSQLCriteria(SqlCriteria c, StringBuffer buf)
    {
        buf.append(c.getClause());
    }

    /**
     * Answer the SQL-Clause for a SelectionCriteria
     *
     * @param c
     * @param buf
     */
    private void appendSelectionCriteria(TableAlias alias, PathInfo pathInfo, SelectionCriteria c, StringBuffer buf)
    {
        appendColName(alias, pathInfo, c.isTranslateAttribute(), buf);
        buf.append(c.getClause());
        appendParameter(c.getValue(), buf);
    }

    /**
     * Answer the SQL-Clause for a LikeCriteria
     *
     * @param c
     * @param buf
     */
    private void appendLikeCriteria(TableAlias alias, PathInfo pathInfo, LikeCriteria c, StringBuffer buf)
    {
        appendColName(alias, pathInfo, c.isTranslateAttribute(), buf);
        buf.append(c.getClause());
        appendParameter(c.getValue(), buf);

        buf.append(m_platform.getEscapeClause(c));
    }

    /**
     * Answer the SQL-Clause for a SelectionCriteria
     *
     * @param alias
     * @param pathInfo
     * @param c SelectionCriteria
     * @param buf
     */
    protected void appendCriteria(TableAlias alias, PathInfo pathInfo, SelectionCriteria c, StringBuffer buf)
    {
        if (c instanceof FieldCriteria)
        {
            appendFieldCriteria(alias, pathInfo, (FieldCriteria) c, buf);
        }
        else if (c instanceof NullCriteria)
        {
            appendNullCriteria(alias, pathInfo, (NullCriteria) c, buf);
        }
        else if (c instanceof BetweenCriteria)
        {
            appendBetweenCriteria(alias, pathInfo, (BetweenCriteria) c, buf);
        }
        else if (c instanceof InCriteria)
        {
            appendInCriteria(alias, pathInfo, (InCriteria) c, buf);
        }
        else if (c instanceof SqlCriteria)
        {
            appendSQLCriteria((SqlCriteria) c, buf);
        }
        else if (c instanceof ExistsCriteria)
        {
            appendExistsCriteria((ExistsCriteria) c, buf);
        }
        else if (c instanceof LikeCriteria)
        {
            appendLikeCriteria(alias, pathInfo, (LikeCriteria) c, buf);
        }
        else
        {
            appendSelectionCriteria(alias, pathInfo, c, buf);
        }
    }

    /**
     * Answer the SQL-Clause for a SelectionCriteria
     * If the Criteria references a class with extents an OR-Clause is
     * added for each extent
     * @param c SelectionCriteria
     */
    protected void appendSQLClause(SelectionCriteria c, StringBuffer buf)
    {
        // BRJ : handle SqlCriteria
        if (c instanceof SqlCriteria)
        {
            buf.append(c.getAttribute());
            return;
        }
       
        // BRJ : criteria attribute is a query
        if (c.getAttribute() instanceof Query)
        {
            Query q = (Query) c.getAttribute();
            buf.append("(");
            buf.append(getSubQuerySQL(q));
            buf.append(")");
            buf.append(c.getClause());
            appendParameter(c.getValue(), buf);
            return;
        }

    AttributeInfo attrInfo = getAttributeInfo((String) c.getAttribute(), false, c.getUserAlias(), c.getPathClasses());
        TableAlias alias = attrInfo.tableAlias;

        if (alias != null)
        {
            boolean hasExtents = alias.hasExtents();

            if (hasExtents)
            {
                // BRJ : surround with braces if alias has extents
                buf.append("(");
                appendCriteria(alias, attrInfo.pathInfo, c, buf);

                c.setNumberOfExtentsToBind(alias.extents.size());
                Iterator iter = alias.iterateExtents();
                while (iter.hasNext())
                {
                    TableAlias tableAlias = (TableAlias) iter.next();
                    buf.append(" OR ");
                    appendCriteria(tableAlias, attrInfo.pathInfo, c, buf);
                }
                buf.append(")");
            }
            else
            {
                // no extents
                appendCriteria(alias, attrInfo.pathInfo, c, buf);
            }
        }
        else
        {
            // alias null
            appendCriteria(alias, attrInfo.pathInfo, c, buf);
        }

    }

    /**
     * Append the Parameter
     * Add the place holder ? or the SubQuery
     * @param value the value of the criteria
     */
    private void appendParameter(Object value, StringBuffer buf)
    {
        if (value instanceof Query)
        {
            appendSubQuery((Query) value, buf);
        }
        else
        {
            buf.append("?");
        }
    }

    /**
     * Append a SubQuery the SQL-Clause
     * @param subQuery the subQuery value of SelectionCriteria
     */
    private void appendSubQuery(Query subQuery, StringBuffer buf)
    {
        buf.append(" (");
        buf.append(getSubQuerySQL(subQuery));
        buf.append(") ");
    }

    /**
     * Convert subQuery to SQL
     * @param subQuery the subQuery value of SelectionCriteria
     */
    private String getSubQuerySQL(Query subQuery)
    {
        ClassDescriptor cld = getRoot().cld.getRepository().getDescriptorFor(subQuery.getSearchClass());
        String sql;

        if (subQuery instanceof QueryBySQL)
        {
            sql = ((QueryBySQL) subQuery).getSql();
        }
        else
        {
            sql = new SqlSelectStatement(this, m_platform, cld, subQuery, m_logger).getStatement();
        }

        return sql;
    }

  /**
   * Get TableAlias by the path from the target table of the query.
   * @param aPath the path from the target table of the query to this TableAlias.
   * @param useOuterJoins use outer join to join this table with the previous
   * table in the path.
   * @param aUserAlias if specified, overrides alias in crit
   * @param fieldRef String[1] contains the field name.
   * In the case of related table's primary key the "related.pk" attribute
   * must not add new join, but use the value of foreign key
   * @param pathClasses the hints
   */
  private TableAlias getTableAlias(String aPath, boolean useOuterJoins, UserAlias aUserAlias, String[] fieldRef, Map pathClasses)
  {
        TableAlias curr, prev, indirect;
        String attr, attrPath = null;
        ObjectReferenceDescriptor ord;
        CollectionDescriptor cod;
        ClassDescriptor cld;
        Object[] prevKeys;
        Object[] keys;
        ArrayList descriptors;
        boolean outer = useOuterJoins;
        int pathLength;
        List hintClasses = null;      
        String pathAlias = aUserAlias == null ? null : aUserAlias.getAlias(aPath);
       
        if (pathClasses != null)
        {
            hintClasses = (List) pathClasses.get(aPath);
        }   
       
        curr = getTableAliasForPath(aPath, pathAlias, hintClasses);
        if (curr != null)
        {
            return curr;
        }

    descriptors = getRoot().cld.getAttributeDescriptorsForPath(aPath, pathClasses);
    prev = getRoot();

    if (descriptors == null || descriptors.size() == 0)
    {
      if (prev.hasJoins())
      {
        for (Iterator itr = prev.iterateJoins(); itr.hasNext();)
        {
          prev = ((Join) itr.next()).left;
          descriptors = prev.cld.getAttributeDescriptorsForPath(aPath, pathClasses);
          if (descriptors.size() > 0)
          {
            break;
          }
        }
      }
    }

    pathLength = descriptors.size();
    for (int i = 0; i < pathLength; i++)
    {
      if (!(descriptors.get(i) instanceof ObjectReferenceDescriptor))
      {
        // only use Collection- and ObjectReferenceDescriptor
        continue;
      }

      ord = (ObjectReferenceDescriptor) descriptors.get(i);
      attr = ord.getAttributeName();
      if (attrPath == null)
      {
        attrPath = attr;
      }
      else
      {
        attrPath = attrPath + "." + attr;
      }

            // use clas hints for path
            if (pathClasses != null)
            {
                hintClasses = (List) pathClasses.get(attrPath);    
            }   

      // look for outer join hint
      outer = outer || getQuery().isPathOuterJoin(attrPath);

      // look for 1:n or m:n
      if (ord instanceof CollectionDescriptor)
      {
        cod = (CollectionDescriptor) ord;
        cld = getItemClassDescriptor(cod, hintClasses);

        if (!cod.isMtoNRelation())
        {
          prevKeys = prev.cld.getPkFields();
          keys = cod.getForeignKeyFieldDescriptors(cld);
        }
        else
        {
          String mnAttrPath = attrPath + "*";
          String mnUserAlias = (aUserAlias == null ? null : aUserAlias + "*");
          indirect = getTableAliasForPath(mnAttrPath, mnUserAlias, null);
          if (indirect == null)
          {
            indirect = createTableAlias(cod.getIndirectionTable(), mnAttrPath, mnUserAlias);

            // we need two Joins for m:n
            // 1.) prev class to indirectionTable
            prevKeys = prev.cld.getPkFields();
            keys = cod.getFksToThisClass();
            addJoin(prev, prevKeys, indirect, keys, outer, attr + "*");
          }
          // 2.) indirectionTable to the current Class
          prev = indirect;
          prevKeys = cod.getFksToItemClass();
          keys = cld.getPkFields();
        }
      }
      else
      {
        // must be n:1 or 1:1
        cld = getItemClassDescriptor(ord, hintClasses);

          // BRJ : if ord is taken from 'super' we have to change prev accordingly
        if (!prev.cld.equals(ord.getClassDescriptor()))
        {
          TableAlias ordAlias = getTableAliasForClassDescriptor(ord.getClassDescriptor());
          Join join = prev.getJoin(ordAlias);
                    if (join != null)
                    {
                        join.isOuter = join.isOuter || outer;
                    }   
            prev = ordAlias;
       

        prevKeys = ord.getForeignKeyFieldDescriptors(prev.cld);
        keys = cld.getPkFields();

        // [olegnitz]
        // a special case: the last element of the path is
        // reference and the field is one of PK fields =>
        // use the correspondent foreign key field, don't add the join
        if ((fieldRef != null) && (i == (pathLength - 1)))
        {
          FieldDescriptor[] pk = cld.getPkFields();

          for (int j = 0; j < pk.length; j++)
          {
            if (pk[j].getAttributeName().equals(fieldRef[0]))
            {
              fieldRef[0] = ((FieldDescriptor) prevKeys[j]).getAttributeName();
              return prev;
            }
          }
        }
      }

      pathAlias = aUserAlias == null ? null : aUserAlias.getAlias(attrPath);
      curr = getTableAliasForPath(attrPath, pathAlias, hintClasses);

      if (curr == null)
      {
        curr = createTableAlias(cld, attrPath, pathAlias, hintClasses);

        outer = outer || (curr.cld == prev.cld) || curr.hasExtents() || useOuterJoins;
        addJoin(prev, prevKeys, curr, keys, outer, attr);

        buildSuperJoinTree(curr, cld, aPath, outer);
      }

      prev = curr;
    }

    m_logger.debug("Result of getTableAlias(): " + curr);
    return curr;
  }

    /**
     * add a join between two aliases
     *
     * TODO BRJ : This needs refactoring, it looks kind of weird
     *
     * no extents
     * A1   -> A2
     *
     * extents on the right
     * A1   -> A2
     * A1   -> A2E0
     *
     * extents on the left : copy alias on right, extents point to copies
     * A1   -> A2
     * A1E0 -> A2C0
     *
     * extents on the left and right
     * A1   -> A2
     * A1   -> A2E0
     * A1E0 -> A2C0
     * A1E0 -> A2E0C0
     *
     * @param left
     * @param leftKeys
     * @param right
     * @param rightKeys
     * @param outer
     * @param name
     */
    private void addJoin(TableAlias left, Object[] leftKeys, TableAlias right, Object[] rightKeys, boolean outer,
            String name)
    {
        TableAlias extAlias, rightCopy;

        left.addJoin(new Join(left, leftKeys, right, rightKeys, outer, name));

        // build join between left and extents of right
        if (right.hasExtents())
        {
            for (int i = 0; i < right.extents.size(); i++)
            {
                extAlias = (TableAlias) right.extents.get(i);
                FieldDescriptor[] extKeys = getExtentFieldDescriptors(extAlias, (FieldDescriptor[]) rightKeys);

                left.addJoin(new Join(left, leftKeys, extAlias, extKeys, true, name));
            }
        }

        // we need to copy the alias on the right for each extent on the left
        if (left.hasExtents())
        {
            for (int i = 0; i < left.extents.size(); i++)
            {
                extAlias = (TableAlias) left.extents.get(i);
                FieldDescriptor[] extKeys = getExtentFieldDescriptors(extAlias, (FieldDescriptor[]) leftKeys);
                rightCopy = right.copy("C" + i);

                // copies are treated like normal extents
                right.extents.add(rightCopy);
                right.extents.addAll(rightCopy.extents);

                addJoin(extAlias, extKeys, rightCopy, rightKeys, true, name);
            }
        }
    }

    /**
     * Get the FieldDescriptors of the extent based on the FieldDescriptors of the parent.
     */
    private FieldDescriptor[] getExtentFieldDescriptors(TableAlias extAlias, FieldDescriptor[] fds)
    {
        FieldDescriptor[] result = new FieldDescriptor[fds.length];

        for (int i = 0; i < fds.length; i++)
        {
            result[i] = extAlias.cld.getFieldDescriptorByName(fds[i].getAttributeName());
        }

        return result;
    }

    private char getAliasChar()
    {
        char result = 'A';

        if (m_parentStatement != null)
        {
            result = (char) (m_parentStatement.getAliasChar() + 1);
        }

        return result;
    }

    /**
     * Create a TableAlias for path or userAlias
     * @param aCld
     * @param aPath
     * @param aUserAlias
     * @param hints a List os Class objects to be used as hints for path expressions
     * @return TableAlias
     *
     */
    private TableAlias createTableAlias(ClassDescriptor aCld, String aPath, String aUserAlias, List hints)
    {
    if (aUserAlias == null)
    {
      return createTableAlias(aCld, hints, aPath);
    }
    else
    {
      return createTableAlias(aCld, hints, aUserAlias + ALIAS_SEPARATOR + aPath);
    }
    }

    /**
     * Create new TableAlias for path
     * @param cld the class descriptor for the TableAlias
     * @param path the path from the target table of the query to this TableAlias.
     * @param hints a List of Class objects to be used on path expressions
     */
    private TableAlias createTableAlias(ClassDescriptor cld, List hints, String path)
    {
        TableAlias alias;
        boolean lookForExtents = false;

        if (!cld.getExtentClasses().isEmpty() && path.length() > 0)
        {
            lookForExtents = true;
        }

        String aliasName = String.valueOf(getAliasChar()) + m_aliasCount++; // m_pathToAlias.size();
        alias = new TableAlias(cld, aliasName, lookForExtents, hints);

        setTableAliasForPath(path, hints, alias);       
        return alias;
    }

    /**
     * Create a TableAlias for path or userAlias
     * @param aTable
     * @param aPath
     * @param aUserAlias
     * @return TableAlias
     */
    private TableAlias createTableAlias(String aTable, String aPath, String aUserAlias)
    {
    if (aUserAlias == null)
    {
      return createTableAlias(aTable, aPath);
    }
    else
    {
      return createTableAlias(aTable, aUserAlias + ALIAS_SEPARATOR + aPath);
    }
    }

    /**
     * Create new TableAlias for path
     * @param table the table name
     * @param path the path from the target table of the query to this TableAlias.
     */
    private TableAlias createTableAlias(String table, String path)
    {
        TableAlias alias;

        if (table == null)
        {
            getLogger().warn("Creating TableAlias without table for path: " + path);
        }

        String aliasName = String.valueOf(getAliasChar()) + m_aliasCount++; // + m_pathToAlias.size();
        alias = new TableAlias(table, aliasName);
        setTableAliasForPath(path, null, alias);       
        m_logger.debug("createTableAlias2: path: " + path + " tableAlias: " + alias);

        return alias;
    }

    /**
     * Answer the TableAlias for aPath
     * @param aPath
     * @param hintClasses
     * @return TableAlias, null if none
     */
    private TableAlias getTableAliasForPath(String aPath, List hintClasses)
    {
        return (TableAlias) m_pathToAlias.get(buildAliasKey(aPath, hintClasses));
    }

    /**
     * Set the TableAlias for aPath
     * @param aPath
     * @param hintClasses
     * @param TableAlias
     */
    private void setTableAliasForPath(String aPath, List hintClasses, TableAlias anAlias)
    {
        m_pathToAlias.put(buildAliasKey(aPath, hintClasses), anAlias);
    }
   
    /**
     * Build the key for the TableAlias based on the path and the hints
     * @param aPath
     * @param hintClasses
     * @return the key for the TableAlias
     */
    private String buildAliasKey(String aPath, List hintClasses)
    {
        if (hintClasses == null || hintClasses.isEmpty())
        {
            return aPath;
        }
       
        StringBuffer buf = new StringBuffer(aPath);
        for (Iterator iter = hintClasses.iterator(); iter.hasNext();)
        {
            Class hint = (Class) iter.next();
            buf.append(" ");
            buf.append(hint.getName());
        }
        return buf.toString();
    }

    /**
     * Answer the TableAlias for ClassDescriptor.
     */
    protected TableAlias getTableAliasForClassDescriptor(ClassDescriptor aCld)
    {
        return (TableAlias) m_cldToAlias.get(aCld);
    }

    /**
     * Set the TableAlias for ClassDescriptor
     */
    private void setTableAliasForClassDescriptor(ClassDescriptor aCld, TableAlias anAlias)
    {
        if (m_cldToAlias.get(aCld) == null)
        {
            m_cldToAlias.put(aCld, anAlias);
        }   
    }

    /**
     * Answer the TableAlias for aPath or aUserAlias
     * @param aPath
     * @param aUserAlias
     * @param hintClasses
     * @return TableAlias, null if none
     */
    private TableAlias getTableAliasForPath(String aPath, String aUserAlias, List hintClasses)
    {
        if (aUserAlias == null)
        {
            return getTableAliasForPath(aPath, hintClasses);
        }
        else
        {
      return getTableAliasForPath(aUserAlias + ALIAS_SEPARATOR + aPath, hintClasses);
        }
    }

  /**
     * Answer the ClassDescriptor for itemClass for an ObjectReferenceDescriptor
     * check optional hint. The returned Class is to highest superclass contained in the hint list.
   * TODO: add super ClassDescriptor
   */
    private ClassDescriptor getItemClassDescriptor(ObjectReferenceDescriptor ord, List hintClasses)
    {  
        DescriptorRepository repo = ord.getClassDescriptor().getRepository();

        if (hintClasses == null || hintClasses.isEmpty())
        {
            return repo.getDescriptorFor(ord.getItemClass());
        }
       
        Class resultClass = (Class) hintClasses.get(0);
       
        for (Iterator iter = hintClasses.iterator(); iter.hasNext();)
        {
            Class clazz = (Class) iter.next();
            Class superClazz = clazz.getSuperclass();

            if (superClazz != null && resultClass.equals(superClazz.getSuperclass()))
            {
                continue; // skip if we already have a super superclass
            }
          
            if (hintClasses.contains(superClazz))
            {
                resultClass = superClazz;   // use superclass if it's in the hints
            }
        }

        return repo.getDescriptorFor(resultClass);
    }

  /**
     * Appends the ORDER BY clause for the Query.
     * <br>
     * If the orderByField is found in the list of selected fields it's index is added.
     * Otherwise it's name is added.
   * @param orderByFields
   * @param selectedFields the names of the fields in the SELECT clause
   * @param buf
   */
    protected void appendOrderByClause(List orderByFields, List selectedFields, StringBuffer buf)
    {

        if (orderByFields == null || orderByFields.size() == 0)
        {
            return;
        }
       
        buf.append(" ORDER BY ");
        for (int i = 0; i < orderByFields.size(); i++)
        {
            FieldHelper cf = (FieldHelper) orderByFields.get(i);
            int colNumber = selectedFields.indexOf(cf.name);
           
            if (i > 0)
            {
                buf.append(",");
            }
           
            if (colNumber >= 0)
            {
                buf.append(colNumber + 1);               
            }
            else
            {           
                appendColName(cf.name, false, null, buf);
            }
           
            if (!cf.isAscending)
            {
                buf.append(" DESC");
            }
        }
    }

    /**
     * Appends the GROUP BY clause for the Query
   * @param groupByFields
   * @param buf
     */
    protected void appendGroupByClause(List groupByFields, StringBuffer buf)
    {
        if (groupByFields == null || groupByFields.size() == 0)
        {
            return;
        }

        buf.append(" GROUP BY ");
        for (int i = 0; i < groupByFields.size(); i++)
        {
            FieldHelper cf = (FieldHelper) groupByFields.get(i);
            if (i > 0)
            {
                buf.append(",");
            }

            appendColName(cf.name, false, null, buf);
        }
    }

    /**
     * Appends to the statement table and all tables joined to it.
     * @param alias the table alias
     * @param where append conditions for WHERE clause here
     */
    protected void appendTableWithJoins(TableAlias alias, StringBuffer where, StringBuffer buf)
    {
        int stmtFromPos = 0;
        byte joinSyntax = getJoinSyntaxType();

        if (joinSyntax == SQL92_JOIN_SYNTAX)
        {
            stmtFromPos = buf.length(); // store position of join (by: Terry Dexter)
        }

        if (alias == getRoot())
        {
            // BRJ: also add indirection table to FROM-clause for MtoNQuery
            if (getQuery() instanceof MtoNQuery)
            {
                MtoNQuery mnQuery = (MtoNQuery)m_query;
                buf.append(getTableAliasForPath(mnQuery.getIndirectionTable(), null).getTableAndAlias());
                buf.append(", ");
            }          
            buf.append(alias.getTableAndAlias());
        }
        else if (joinSyntax != SQL92_NOPAREN_JOIN_SYNTAX)
        {
            buf.append(alias.getTableAndAlias());
        }

        if (!alias.hasJoins())
        {
            return;
        }

        for (Iterator it = alias.iterateJoins(); it.hasNext();)
        {
            Join join = (Join) it.next();

            if (joinSyntax == SQL92_JOIN_SYNTAX)
            {
                appendJoinSQL92(join, where, buf);
                if (it.hasNext())
                {
                    buf.insert(stmtFromPos, "(");
                    buf.append(")");
                }
            }
            else if (joinSyntax == SQL92_NOPAREN_JOIN_SYNTAX)
            {
                appendJoinSQL92NoParen(join, where, buf);
            }
            else
            {
                appendJoin(where, buf, join);
            }

        }
    }

    /**
     * Append Join for non SQL92 Syntax
     */
    private void appendJoin(StringBuffer where, StringBuffer buf, Join join)
    {
        buf.append(",");
        appendTableWithJoins(join.right, where, buf);
        if (where.length() > 0)
        {
            where.append(" AND ");
        }
        join.appendJoinEqualities(where);
    }

    /**
     * Append Join for SQL92 Syntax
     */
    private void appendJoinSQL92(Join join, StringBuffer where, StringBuffer buf)
    {
        if (join.isOuter)
        {
            buf.append(" LEFT OUTER JOIN ");
        }
        else
        {
            buf.append(" INNER JOIN ");
        }
        if (join.right.hasJoins())
        {
            buf.append("(");
            appendTableWithJoins(join.right, where, buf);
            buf.append(")");
        }
        else
        {
            appendTableWithJoins(join.right, where, buf);
        }
        buf.append(" ON ");
        join.appendJoinEqualities(buf);
    }

    /**
     * Append Join for SQL92 Syntax without parentheses
     */
    private void appendJoinSQL92NoParen(Join join, StringBuffer where, StringBuffer buf)
    {
        if (join.isOuter)
        {
            buf.append(" LEFT OUTER JOIN ");
        }
        else
        {
            buf.append(" INNER JOIN ");
        }

        buf.append(join.right.getTableAndAlias());
        buf.append(" ON ");
        join.appendJoinEqualities(buf);

        appendTableWithJoins(join.right, where, buf);
    }

    /**
     * Build the tree of joins for the given criteria
     */
    private void buildJoinTree(Criteria crit)
    {
        Enumeration e = crit.getElements();

        while (e.hasMoreElements())
        {
            Object o = e.nextElement();
            if (o instanceof Criteria)
            {
                buildJoinTree((Criteria) o);
            }
            else
            {
                SelectionCriteria c = (SelectionCriteria) o;
               
                // BRJ skip SqlCriteria
                if (c instanceof SqlCriteria)
                {
                    continue;
                }
               
                // BRJ: Outer join for OR
                boolean useOuterJoin = (crit.getType() == Criteria.OR);

                // BRJ: do not build join tree for subQuery attribute                 
                if (c.getAttribute() != null && c.getAttribute() instanceof String)
                {
          //buildJoinTreeForColumn((String) c.getAttribute(), useOuterJoin, c.getAlias(), c.getPathClasses());
          buildJoinTreeForColumn((String) c.getAttribute(), useOuterJoin, c.getUserAlias(), c.getPathClasses());
                }
                if (c instanceof FieldCriteria)
                {
                    FieldCriteria cc = (FieldCriteria) c;
          buildJoinTreeForColumn((String) cc.getValue(), useOuterJoin, c.getUserAlias(), c.getPathClasses());
                }
            }
        }
    }

  /**
   * build the Join-Information for name
   * functions and the last segment are removed
   * ie: avg(accounts.amount) -> accounts
   */
  private void buildJoinTreeForColumn(String aColName, boolean useOuterJoin, UserAlias aUserAlias, Map pathClasses)
  {
    String pathName = SqlHelper.cleanPath(aColName);
    int sepPos = pathName.lastIndexOf(".");

    if (sepPos >= 0)
    {
      getTableAlias(pathName.substring(0, sepPos), useOuterJoin, aUserAlias,
              new String[]{pathName.substring(sepPos + 1)}, pathClasses);
    }
  }

    /**
     * build the Join-Information if a super reference exists
     *
     * @param left
     * @param cld
     * @param name
     */
    protected void buildSuperJoinTree(TableAlias left, ClassDescriptor cld, String name, boolean useOuterJoin)
    {
        ClassDescriptor superCld = cld.getSuperClassDescriptor();
        if (superCld != null)
        {
            SuperReferenceDescriptor superRef = cld.getSuperReference();
            FieldDescriptor[] leftFields = superRef.getForeignKeyFieldDescriptors(cld);
            TableAlias base_alias = getTableAliasForPath(name, null, null);
            String aliasName = String.valueOf(getAliasChar()) + m_aliasCount++;
            TableAlias right = new TableAlias(superCld, aliasName, useOuterJoin, null);

            Join join1to1 = new Join(left, leftFields, right, superCld.getPkFields(), useOuterJoin, "superClass");
            base_alias.addJoin(join1to1);

            buildSuperJoinTree(right, superCld, name, useOuterJoin);
        }
    }

    /**
     * build the Join-Information for Subclasses having a super reference to this class
     *
     * @param left
     * @param cld
     * @param name
     */
    private void buildMultiJoinTree(TableAlias left, ClassDescriptor cld, String name, boolean useOuterJoin)
    {
        DescriptorRepository repository = cld.getRepository();
        Class[] multiJoinedClasses = repository.getSubClassesMultipleJoinedTables(cld, false);

        for (int i = 0; i < multiJoinedClasses.length; i++)
        {
            ClassDescriptor subCld = repository.getDescriptorFor(multiJoinedClasses[i]);
            SuperReferenceDescriptor srd = subCld.getSuperReference();
            if (srd != null)
            {
                FieldDescriptor[] leftFields = subCld.getPkFields();
                FieldDescriptor[] rightFields = srd.getForeignKeyFieldDescriptors(subCld);
                TableAlias base_alias = getTableAliasForPath(name, null, null);

                String aliasName = String.valueOf(getAliasChar()) + m_aliasCount++;
                TableAlias right = new TableAlias(subCld, aliasName, false, null);

                Join join1to1 = new Join(left, leftFields, right, rightFields, useOuterJoin, "subClass");
                base_alias.addJoin(join1to1);

                buildMultiJoinTree(right, subCld, name, useOuterJoin);
            }
        }
    }

    /**
     * First reduce the Criteria to the normal disjunctive form, then
     * calculate the necessary tree of joined tables for each item, then group
     * items with the same tree of joined tables.
     */
    protected void splitCriteria()
    {
        Criteria whereCrit = getQuery().getCriteria();
        Criteria havingCrit = getQuery().getHavingCriteria();

        if (whereCrit == null || whereCrit.isEmpty())
        {
            getJoinTreeToCriteria().put(getRoot(), null);
        }
        else
        {
            // TODO: parameters list shold be modified when the form is reduced to DNF.
            getJoinTreeToCriteria().put(getRoot(), whereCrit);
            buildJoinTree(whereCrit);
        }

        if (havingCrit != null && !havingCrit.isEmpty())
        {
            buildJoinTree(havingCrit);
        }

    }
   
    /**
     * Gets the query.
     * @return Returns a Query
     */
    protected QueryByCriteria getQuery()
    {
        return m_query;
    }

    /**
     * Gets the root.
     * @return Returns a TableAlias
     */
    protected TableAlias getRoot()
    {
        return m_root;
    }

    /**
     * Sets the root.
     * @param root The root to set
     */
    protected void setRoot(TableAlias root)
    {
        this.m_root = root;
    }

    /**
     * Gets the search table of this query.
     * @return Returns a TableAlias
     */
    protected TableAlias getSearchTable()
    {
        return m_search;
    }

    /**
     * Gets the joinTreeToCriteria.
     * @return Returns a HashMap
     */
    protected HashMap getJoinTreeToCriteria()
    {
        return m_joinTreeToCriteria;
    }

    /**
     * Returns the joinSyntaxType.
     * @return byte
     */
    protected byte getJoinSyntaxType()
    {
        return m_platform.getJoinSyntaxType();
    }

    /**
     * Returns the logger.
     * @return Logger
     */
    protected Logger getLogger()
    {
        return m_logger;
    }
   
    public String getStatement()
    {
        if(sql == null)
        {
            sql = buildStatement();
        }
        return sql;
    }

    /**
     * Build the SQL String.
     * @return SQL String
     */
    protected abstract String buildStatement();
   
   
    //-----------------------------------------------------------------
    // ------------------- Inner classes ------------------------------
    //-----------------------------------------------------------------

    /**
     * This class is a helper to return TableAlias and PathInfo
     */
    static final class AttributeInfo
    {
        TableAlias tableAlias;
        PathInfo pathInfo;
    }

    /**
     * This class represents one table (possibly with alias) in the SQL query
     */
    final class TableAlias
    {
        Logger logger = LoggerFactory.getLogger(TableAlias.class);
        ClassDescriptor cld; // Is null for indirection table of M:N relation
        String table;
        final String alias;
        List extents = new ArrayList();
        List hints = new ArrayList();
        List joins;

        TableAlias(String aTable, String anAlias)
        {
            this.cld = null;
            this.table = aTable;
            this.alias = anAlias;
        }

        TableAlias(ClassDescriptor aCld, String anAlias)
        {
            this(aCld, anAlias, false, null);
        }

        TableAlias(ClassDescriptor aCld, String anAlias, boolean lookForExtents, List hints)
        {
            this.cld = aCld;
            this.table = aCld.getFullTableName();
            this.alias = anAlias;
            boolean useHintsOnExtents = false;

            // BRJ: store alias map of in enclosing class
      setTableAliasForClassDescriptor(aCld, this);
     
            //LEANDRO: use hints
            if (hints != null && hints.size() > 0)
            {
                useHintsOnExtents = true;
            }

            logger.debug("TableAlias(): using hints ? " + useHintsOnExtents);

            // BRJ : build alias for extents, only one per Table
            if (lookForExtents)
            {
                ClassDescriptor[] extCLDs = (ClassDescriptor[]) aCld.getRepository().getAllConcreteSubclassDescriptors(
                        aCld).toArray(new ClassDescriptor[0]);

                ClassDescriptor extCd;
                Class extClass;
                String extTable;
                Map extMap = new HashMap(); // only one Alias per Table
                int firstNonAbstractExtentIndex = 0;

                for (int i = 0; i < extCLDs.length; i++)
                {
                    extCd = extCLDs[i];
                    extClass = extCd.getClassOfObject();
                    if (useHintsOnExtents && (!hints.contains(extClass)))
                    {
                        //LEANDRO: don't include this class
                        logger.debug("Skipping class [" + extClass + "] from extents List");
                        firstNonAbstractExtentIndex++;
                        continue;
                    }
                    extTable = extCd.getFullTableName();

                    // BRJ : Use the first non abstract extent
                    // if the main cld is abstract
                    //logger.debug("cld abstract["+aCld.isAbstract()+"] i["+i+"] index ["+firtsNonAbstractExtentIndex+"]");
                    if (aCld.isAbstract() && i == firstNonAbstractExtentIndex)
                    {
                        this.cld = extCd;
                        this.table = extTable;
                    }
                    else
                    {
                        // Add a new extent entry only if the table of the extent
                        // does not match the table of the 'base' class.
                        if (extMap.get(extTable) == null && !extTable.equals(table))
                        {
                            extMap.put(extTable, new TableAlias(extCd, anAlias + "E" + i, false, hints));
                        }
                    }
                }
                extents.addAll(extMap.values());
            }

            if (cld == null)
            {
                throw new PersistenceBrokerSQLException("Table is NULL for alias: " + alias);
            }
        }

        ClassDescriptor getClassDescriptor()
        {
            return cld;
        }

        String getTableAndAlias()
        {
            return table + " " + alias;
        }

        boolean hasExtents()
        {
            return (!extents.isEmpty());
        }

        Iterator iterateExtents()
        {
            return extents.iterator();
        }

        /**
         * Copy the Alias and all it's extents adding a Postfix
         * Joins are not copied.
         */
        TableAlias copy(String aPostfix)
        {
            TableAlias result, temp;
            Iterator iter = iterateExtents();

            if (cld == null)
            {
                result = new TableAlias(table, alias + aPostfix);
            }
            else
            {
                result = new TableAlias(cld, alias + aPostfix);
            }

            while (iter.hasNext())
            {
                temp = (TableAlias) iter.next();
                result.extents.add(temp.copy(aPostfix));
            }

            return result;
        }

        void addJoin(Join join)
        {
            if (joins == null)
            {
                joins = new ArrayList();
            }
            joins.add(join);
        }

        Iterator iterateJoins()
        {
            return joins.iterator();
        }

        boolean hasJoins()
        {
            return (joins != null);
        }

        /**
         * Get the Join ponting to anAlias.
         */
        Join getJoin(TableAlias anAlias)
        {
            Join result = null;

            if (joins != null)
            {
                Iterator iter = joins.iterator();
                while (iter.hasNext())
                {
                    Join join = (Join) iter.next();
                    if (join.right.equals(anAlias))
                    {
                        result = join;
                        break;
                    }
                }
            }
            return result;
        }
       
        public String toString()
        {
            StringBuffer sb = new StringBuffer(1024);
            boolean first = true;

            sb.append(getTableAndAlias());
            if (joins != null)
            {
                sb.append(" [");
                for (Iterator it = joins.iterator(); it.hasNext();)
                {
                    Join join = (Join) it.next();

                    if (first)
                    {
                        first = false;
                    }
                    else
                    {
                        sb.append(", ");
                    }
                    sb.append("-(");
                    sb.append(join.name);
                    sb.append(")->");
                    sb.append(join.right);
                }
                sb.append("]");
            }
            return sb.toString();
        }

        public boolean equals(Object obj)
        {
            TableAlias t = (TableAlias) obj;

            return table.equals(t.table); // BRJ: check table only
        }

        public int hashCode()
        {
            return table.hashCode();
        }

    }

    /**
     * This class represents join between two TableAliases
     */
    final class Join
    {
        final TableAlias left;
        final String[] leftKeys;
        final TableAlias right;
        final String[] rightKeys;
        boolean isOuter;
        /** This is the name of the field corresponding to this join */
        final String name;

        /**
         * leftKeys and rightKeys should be either FieldDescriptor[] or String[]
         */
        Join(TableAlias left, Object[] leftKeys, TableAlias right, Object[] rightKeys, boolean isOuter, String name)
        {
            this.left = left;
            this.leftKeys = getColumns(leftKeys);
            this.right = right;
            this.rightKeys = getColumns(rightKeys);
            this.isOuter = isOuter;
            this.name = name;
        }

        private String[] getColumns(Object[] keys)
        {
            String[] columns = new String[keys.length];

            if (keys instanceof FieldDescriptor[])
            {
                FieldDescriptor[] kd = (FieldDescriptor[]) keys;
                for (int i = 0; i < columns.length; i++)
                {
                    columns[i] = kd[i].getColumnName();
                }
            }
            else
            {
                for (int i = 0; i < columns.length; i++)
                {
                    columns[i] = keys[i].toString();
                }
            }
            return columns;
        }

        void appendJoinEqualities(StringBuffer buf)
        {
            byte joinSyntax = getJoinSyntaxType();

            for (int i = 0; i < leftKeys.length; i++)
            {
                if (i > 0)
                {
                    buf.append(" AND ");
                }
                buf.append(left.alias);
                buf.append(".");
                buf.append(leftKeys[i]);

                if (isOuter && joinSyntax == SYBASE_JOIN_SYNTAX)
                {
                    buf.append("*=");
                }
                else
                {
                    buf.append("=");
                }

                buf.append(right.alias);
                buf.append(".");
                buf.append(rightKeys[i]);

                if (isOuter && joinSyntax == ORACLE_JOIN_SYNTAX)
                {
                    buf.append("(+)");
                }
            }
        }

        public boolean equals(Object obj)
        {
            Join j = (Join) obj;
            return name.equals(j.name) && (isOuter == j.isOuter) && right.equals(j.right);
        }

        public int hashCode()
        {
            return name.hashCode();
        }

        public String toString()
        {
            return left.alias + " -> " + right.alias;
        }
    }
}
TOP

Related Classes of org.apache.ojb.broker.accesslayer.sql.SqlQueryStatement$AttributeInfo

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.