package org.apache.torque.util;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.io.Serializable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.torque.Column;
import org.apache.torque.ColumnImpl;
import org.apache.torque.Torque;
import org.apache.torque.TorqueException;
import org.apache.torque.criteria.CriteriaInterface;
import org.apache.torque.criteria.Join;
import org.apache.torque.criteria.JoinType;
import org.apache.torque.criteria.SqlEnum;
import org.apache.torque.om.ObjectKey;
import org.apache.torque.sql.OrderBy;
import org.apache.torque.sql.Query;
import org.apache.torque.sql.SqlBuilder;

/**
 * Encapsulates conditions to access rows in database tables.
 *
 * @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a>
 * @author <a href="mailto:jmcnally@collab.net">John D. McNally</a>
 * @author <a href="mailto:bmclaugh@algx.net">Brett McLaughlin</a>
 * @author <a href="mailto:eric@dobbse.net">Eric Dobbs</a>
 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
 * @author <a href="mailto:sam@neurogrid.com">Sam Joseph</a>
 * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
 * @author <a href="mailto:fischer@seitenbau.de">Thomas Fischer</a>
 * @author <a href="mailto:seade@backstagetech.com.au">Scott Eade</a>
 * @author <a href="mailto:tv@apache.org">Thomas Vandahl</a>
 * @version $Id: Criteria.java 1449216 2013-02-22 21:16:37Z tfischer $
 *
 * @deprecated This class will be removed in a future version of Torque.
 *             Use org.apache.torque.criteria.Criteria instead; note that
 *             that class has another semantics of the add.. and or.. methods.
 */
@Deprecated
public class Criteria implements Serializable, CriteriaInterface<Criteria>
{
    /** Serial version. */
    private static final long serialVersionUID = -9001666575933085601L;

    /** Comparison type. */
    public static final SqlEnum EQUAL = SqlEnum.EQUAL;

    /** Comparison type. */
    public static final SqlEnum NOT_EQUAL = SqlEnum.NOT_EQUAL;

    /** Comparison type. */
    public static final SqlEnum ALT_NOT_EQUAL = SqlEnum.ALT_NOT_EQUAL;

    /** Comparison type. */
    public static final SqlEnum GREATER_THAN = SqlEnum.GREATER_THAN;

    /** Comparison type. */
    public static final SqlEnum LESS_THAN = SqlEnum.LESS_THAN;

    /** Comparison type. */
    public static final SqlEnum GREATER_EQUAL = SqlEnum.GREATER_EQUAL;

    /** Comparison type. */
    public static final SqlEnum LESS_EQUAL = SqlEnum.LESS_EQUAL;

    /** Comparison type. */
    public static final SqlEnum LIKE = SqlEnum.LIKE;

    /** Comparison type. */
    public static final SqlEnum NOT_LIKE = SqlEnum.NOT_LIKE;

    /** Comparison type. */
    public static final SqlEnum ILIKE = SqlEnum.ILIKE;

    /** Comparison type. */
    public static final SqlEnum NOT_ILIKE = SqlEnum.NOT_ILIKE;

    /** Comparison type. */
    public static final SqlEnum CUSTOM = SqlEnum.CUSTOM;

    /** Comparison type. */
    public static final SqlEnum DISTINCT = SqlEnum.DISTINCT;

    /** Comparison type. */
    public static final SqlEnum IN = SqlEnum.IN;

    /** Comparison type. */
    public static final SqlEnum NOT_IN = SqlEnum.NOT_IN;

    /** Comparison type. */
    public static final SqlEnum ALL = SqlEnum.ALL;

    /** Comparison type. */
    public static final SqlEnum JOIN = SqlEnum.JOIN;

    /** &quot;IS NULL&quot; null comparison */
    public static final SqlEnum ISNULL = SqlEnum.ISNULL;

    /** &quot;IS NOT NULL&quot; null comparison */
    public static final SqlEnum ISNOTNULL = SqlEnum.ISNOTNULL;

    /** &quot;CURRENT_DATE&quot; ANSI SQL function */
    public static final SqlEnum CURRENT_DATE = SqlEnum.CURRENT_DATE;

    /** &quot;CURRENT_TIME&quot; ANSI SQL function */
    public static final SqlEnum CURRENT_TIME = SqlEnum.CURRENT_TIME;

    /** &quot;LEFT JOIN&quot; SQL statement */
    public static final JoinType LEFT_JOIN = JoinType.LEFT_JOIN;

    /** &quot;RIGHT JOIN&quot; SQL statement */
    public static final JoinType RIGHT_JOIN = JoinType.RIGHT_JOIN;

    /** &quot;INNER JOIN&quot; SQL statement */
    public static final JoinType INNER_JOIN = JoinType.INNER_JOIN;

    /** Whether to ignore the case in all String conditions in the criteria. */
    private boolean ignoreCase = false;

    /** Whether the result must be a single record. */
    private boolean singleRecord = false;

    /** List of modifiers like DISTICT. */
    private final UniqueList<String> selectModifiers = new UniqueList<String>();

    /** List of all columns to select. */
    private final UniqueColumnList selectColumns = new UniqueColumnList();

    /** All "order by" clauses, containing the order ASC or DESC. */
    private final UniqueList<OrderBy> orderByColumns = new UniqueList<OrderBy>();

    /** The names of columns to add to a groupBy clause */
    private final UniqueColumnList groupByColumns = new UniqueColumnList();

    /** The having clause in a query. */
    private Criterion having = null;

    /** The criterion objects, keyed by the column. */
    private final Map<Column, Criteria.Criterion> criterionMap
            = new HashMap<Column, Criteria.Criterion>();

    /**
     * Maps column alias names to the real column names.
     * The key of the map is the alias and the value is the real column.
     */
    private final Map<String, Column> asColumns
            = new LinkedHashMap<String, Column>();

    /** Contains all joins. */
    private final List<Join> joins = new ArrayList<Join>();

    /** The name of the database. */
    private String dbName;

    /** The name of the database as given in the contructor. */
    private final String originalDbName;

    /**
     * To limit the number of rows to return.  <code>-1</code> means return all
     * rows.
     */
    private int limit = -1;

    /** To start the results at a row other than the first one. */
    private long offset = 0;

    /**
     * Aliases for table names. The key of the map is the alias,
     * and the value is the real name of the table.
     */
    private final Map<String, String> aliases = new HashMap<String, String>();

    /** the log. */
    private static Log log = LogFactory.getLog(Criteria.class);

    /**
     * Creates a new instance with the default capacity.
     */
    public Criteria()
    {
        this(Torque.getDefaultDB());
    }

    /**
     * Creates a new instance with the specified capacity.
     *
     * @param initialCapacity An int.
     *
     * @deprecated initialCapacity is disregarded
     */
    @Deprecated
    public Criteria(int initialCapacity)
    {
        this(Torque.getDefaultDB());
    }

    /**
     * Creates a new instance with the default capacity which corresponds to
     * the specified database.
     *
     * @param dbName The dabase name.
     */
    public Criteria(String dbName)
    {
        this.dbName = dbName;
        this.originalDbName = dbName;
    }

    /**
     * Creates a new instance with the specified capacity which corresponds to
     * the specified database.
     *
     * @param dbName          The dabase name.
     * @param initialCapacity The initial capacity.
     *
     * @deprecated initialCapacity is disregarded
     */
    @Deprecated
    public Criteria(String dbName, int initialCapacity)
    {
        this.dbName = dbName;
        this.originalDbName = dbName;
    }

    /**
     * Brings this criteria back to its initial state, so that it
     * can be reused as if it was new. Except if the criteria has grown in
     * capacity, it is left at the current capacity.
     */
    public void clear()
    {
        criterionMap.clear();
        ignoreCase = false;
        singleRecord = false;
        selectModifiers.clear();
        selectColumns.clear();
        orderByColumns.clear();
        groupByColumns.clear();
        having = null;
        asColumns.clear();
        joins.clear();
        dbName = originalDbName;
        offset = 0;
        limit = -1;
        aliases.clear();
    }

    /**
     * Add an AS clause to the select columns. Usage:
     * <p>
     * <code>
     *
     * Criteria myCrit = new Criteria();
     * myCrit.addAsColumn(&quot;alias&quot;, &quot;ALIAS(&quot;+MyPeer.ID+&quot;)&quot;);
     *
     * </code>
     *
     * @param name wanted Name of the column
     * @param clause SQL clause to select from the table
     *
     * If the name already exists, it is replaced by the new clause.
     *
     * @return A modified Criteria object.
     */
    public Criteria addAsColumn(String name, Column clause)
    {
        asColumns.put(name, clause);
        return this;
    }

    /**
     * Add an AS clause to the select columns. Usage:
     * <p>
     * <code>
     *
     * Criteria myCrit = new Criteria();
     * myCrit.addAsColumn(&quot;alias&quot;, &quot;ALIAS(&quot;+MyPeer.ID+&quot;)&quot;);
     *
     * </code>
     *
     * @param name wanted Name of the column
     * @param clause SQL clause to select from the table
     *
     * If the name already exists, it is replaced by the new clause.
     *
     * @return A modified Criteria object.
     */
    public Criteria addAsColumn(String name, String clause)
    {
        asColumns.put(name, new ColumnImpl(clause));
        return this;
    }

    /**
     * Get the column aliases.
     *
     * @return A Map which map the column alias names
     * to the alias clauses.
     */
    public Map<String, Column> getAsColumns()
    {
        return asColumns;
    }

    /**
     * Get the table aliases.
     *
     * @return A Map which maps the table alias names to the actual table names.
     */
    public Map<String, String> getAliases()
    {
        return aliases;
    }

    /**
     * Allows one to specify an alias for a table that can
     * be used in various parts of the SQL.
     *
     * @param alias a <code>String</code> value
     * @param table a <code>String</code> value
     *
     * @return A modified Criteria object.
     */
    public Criteria addAlias(String alias, String table)
    {
        aliases.put(alias, table);
        return this;
    }

    /**
     * Returns the table name associated with an alias.
     *
     * @param alias a <code>String</code> value
     * @return a <code>String</code> value
     */
    public String getTableForAlias(String alias)
    {
        return aliases.get(alias);
    }

    /**
     * Does this Criteria Object contain the specified key?
     *
     * @param table The name of the table.
     * @param column The name of the column.
     * @return True if this Criteria Object contain the specified key.
     *
     * @deprecated this method does not check in all cases whether a matching
     *             criterion is contained (e.g. if the criterion has been added
     *             manually with another key).
     */
    @Deprecated
    public boolean containsKey(String table, String column)
    {
        return containsTopLevelColumn(new ColumnImpl(table, column));
    }

    /**
     * Checks whether a Criteria contains a column with the given column's
     * SQL expression a a top level Criterion.
     *
     * @param column the column to check, not null.
     *
     * @return true if a top-level criterion with the same column sql expression
     *         is contained, false otherwise.
     */
    private boolean containsTopLevelColumn(Column column)
    {
        for (Column candidate : criterionMap.keySet())
        {
            if (candidate.getSqlExpression().equals(
                    column.getSqlExpression()))
            {
                return true;
            }
        }
        return false;
    }

    /**
     * Return the criterion for a column.
     *
     * @param column the column to get the criterion for.
     * @return the Criterion, or null if no criterion is defined
     *         for this column.
     */
    public Criterion getCriterion(Column column)
    {
        for (Map.Entry<Column, Criterion> mapEntry : criterionMap.entrySet())
        {
            if (mapEntry.getKey().getSqlExpression().equals(
                    column.getSqlExpression()))
            {
                return mapEntry.getValue();
            }
        }
        return null;
    }

    /**
     * Method to return criteria related to a column in a table.
     *
     * @param column the column to get the criterion for.
     * @return A Criterion.
     */
    public Criterion getCriterion(String column)
    {
        return getCriterion(new ColumnImpl(column));
    }

    /**
     * Method to return criteria related to a column in a table.
     *
     * @param table String name of table.
     * @param column String name of column.
     * @return A Criterion.
     */
    public Criterion getCriterion(String table, String column)
    {
        return getCriterion(new ColumnImpl(table, column));
    }

    /**
     * Method to return criterion that is not added automatically
     * to this Criteria.  This can be used to chain the
     * Criterions to form a more complex where clause.
     *
     * @param column String full name of column (for example TABLE.COLUMN).
     * @param value The value to compare to.
     * @param comparison the comparison to use.
     *
     * @return A Criterion.
     */
    public Criterion getNewCriterion(Column column, Object value,
            SqlEnum comparison)
    {
        return new Criterion(column, value, comparison);
    }

    /**
     * Method to return criterion that is not added automatically
     * to this Criteria.  This can be used to chain the
     * Criterions to form a more complex where clause.
     *
     * @param column String full name of column (for example TABLE.COLUMN).
     * @param value The value to compare to.
     * @param comparison the comparison to use.
     *
     * @return A Criterion.
     */
    public Criterion getNewCriterion(String column, Object value,
            SqlEnum comparison)
    {
        return new Criterion(new ColumnImpl(column), value, comparison);
    }

    /**
     * Method to return criterion that is not added automatically
     * to this Criteria.  This can be used to chain the
     * Criterions to form a more complex where clause.
     *
     * @param table String name of table.
     * @param column String name of column.
     * @return A Criterion.
     */
    public Criterion getNewCriterion(String table, String column,
            Object value, SqlEnum comparison)
    {
        return new Criterion(table, column, value, comparison);
    }

    /**
     * This method adds a prepared Criterion object to the Criteria.
     * You can get a new, empty Criterion object with the
     * getNewCriterion() method. If a criterion for the requested column
     * already exists, it is replaced. This is used as follows:
     *
     * <p>
     * <code>
     * Criteria crit = new Criteria();
     * Criteria.Criterion c = crit
     * .getNewCriterion(XXXPeer.ID, new Integer(5), Criteria.LESS_THAN);
     * crit.add(c);
     * </code>
     *
     * @param c A Criterion object
     *
     * @return A modified Criteria object.
     */
    public Criteria add(Criterion c)
    {
        criterionMap.put(c.getColumn(), c);
        return this;
    }

    /**
     * Method to return a comparison String.
     *
     * @param key String name of the key.
     * @return A String with the value of the object at key.
     */
    public SqlEnum getComparison(Column key)
    {
        return getCriterion(key).getComparison();
    }

    /**
     * Method to return a comparison String.
     *
     * @param table String name of table.
     * @param column String name of column.
     * @return A String with the value of the object at key.
     */
    public SqlEnum getComparison(String table, String column)
    {
        return getComparison(new ColumnImpl(table, column));
    }

    /**
     * Get the Database(Map) name.
     *
     * @return A String with the Database(Map) name.  By default, this
     * is PoolBrokerService.DEFAULT.
     */
    public String getDbName()
    {
        return dbName;
    }

    /**
     * Set the DatabaseMap name.  If <code>null</code> is supplied, uses value
     * provided by <code>Torque.getDefaultDB()</code>.
     *
     * @param dbName A String with the Database(Map) name.
     */
    public void setDbName(String dbName)
    {
        this.dbName = (dbName == null ? Torque.getDefaultDB() : dbName.trim());
    }

    /**
     * Convenience method to return value as a boolean.
     *
     * @param column The column to retrieve the value for.
     *
     * @return A boolean.
     */
    public boolean getBoolean(Column column)
    {
        return ((Boolean) getCriterion(column).getValue()).booleanValue();
    }

    /**
     * Convenience method to return value as a boolean.
     *
     * @param table String name of table.
     * @param column String name of column.
     * @return A boolean.
     */
    public boolean getBoolean(String table, String column)
    {
        return getBoolean(new ColumnImpl(table, column));
    }

    /**
     * Convenience method to return a Date.
     *
     * @param column The column to retrieve the value for.
     *
     * @return A java.util.Date with the value of object at key.
     */
    public java.util.Date getDate(Column column)
    {
        return (java.util.Date) getCriterion(column).getValue();
    }

    /**
     * Convenience method to return a Date.
     *
     * @param table String name of table.
     * @param column String name of column.
     * @return A java.util.Date with the value of object at key.
     */
    public java.util.Date getDate(String table, String column)
    {
        return getDate(new ColumnImpl(table, column));
    }

    /**
     * Convenience method to return a double.
     *
     * @param column The column to retrieve the value for.
     *
     * @return A double with the value of object at key.
     */
    public double getDouble(Column column)
    {
        Object obj = getCriterion(column).getValue();
        if (obj instanceof String)
        {
            return new Double((String) obj).doubleValue();
        }
        return ((Double) obj).doubleValue();
    }

    /**
     * Convenience method to return a double.
     *
     * @param table String name of table.
     * @param column String name of column.
     * @return A double with the value of object at key.
     */
    public double getDouble(String table, String column)
    {
        return getDouble(new ColumnImpl(table, column));
    }

    /**
     * Convenience method to return a float.
     *
     * @param column The column to retrieve the value for.
     *
     * @return A float with the value of object at key.
     */
    public float getFloat(Column column)
    {
        Object obj = getCriterion(column).getValue();
        if (obj instanceof String)
        {
            return new Float((String) obj).floatValue();
        }
        return ((Float) obj).floatValue();
    }

    /**
     * Convenience method to return a float.
     *
     * @param table String name of table.
     * @param column String name of column.
     * @return A float with the value of object at key.
     */
    public float getFloat(String table, String column)
    {
        return getFloat(new ColumnImpl(table, column));
    }

    /**
     * Convenience method to return an Integer.
     *
     * @param column The column to retrieve the value for.
     *
     * @return An Integer with the value of object at key.
     */
    public Integer getInteger(Column column)
    {
        Object obj = getCriterion(column).getValue();
        if (obj instanceof String)
        {
            return new Integer((String) obj);
        }
        return ((Integer) obj);
    }

    /**
     * Convenience method to return an Integer.
     *
     * @param table String name of table.
     * @param column String name of column.
     * @return An Integer with the value of object at key.
     */
    public Integer getInteger(String table, String column)
    {
        return getInteger(new ColumnImpl(table, column));
    }

    /**
     * Convenience method to return an int.
     *
     * @param column The column to retrieve the value for.
     *
     * @return An int with the value of object at key.
     */
    public int getInt(Column column)
    {
        Object obj = getCriterion(column).getValue();
        if (obj instanceof String)
        {
            return new Integer((String) obj).intValue();
        }
        return ((Integer) obj).intValue();
    }

    /**
     * Convenience method to return an int.
     *
     * @param table String name of table.
     * @param column String name of column.
     * @return An int with the value of object at key.
     */
    public int getInt(String table, String column)
    {
        return getInt(new ColumnImpl(table, column));
    }

    /**
     * Convenience method to return a BigDecimal.
     *
     * @param column The column to retrieve the value for.
     *
     * @return A BigDecimal with the value of object at key.
     */
    public BigDecimal getBigDecimal(Column column)
    {
        Object obj = getCriterion(column).getValue();
        if (obj instanceof String)
        {
            return new BigDecimal((String) obj);
        }
        return (BigDecimal) obj;
    }

    /**
     * Convenience method to return a BigDecimal.
     *
     * @param table String name of table.
     * @param column String name of column.
     * @return A BigDecimal with the value of object at key.
     */
    public BigDecimal getBigDecimal(String table, String column)
    {
        return getBigDecimal(new ColumnImpl(table, column));
    }

    /**
     * Convenience method to return a long.
     *
     * @param column The column to retrieve the value for.
     *
     * @return A long with the value of object at key.
     */
    public long getLong(Column column)
    {
        Object obj = getCriterion(column).getValue();
        if (obj instanceof String)
        {
            return new Long((String) obj).longValue();
        }
        return ((Long) obj).longValue();
    }

    /**
     * Convenience method to return a long.
     *
     * @param table String name of table.
     * @param column String name of column.
     * @return A long with the value of object at key.
     */
    public long getLong(String table, String column)
    {
        return getLong(new ColumnImpl(table, column));
    }

    /**
     * Convenience method to return a String.
     *
     * @param column The column to retrieve the value for.
     *
     * @return A String with the value of object at key.
     */
    public String getString(Column column)
    {
        return (String) getCriterion(column).getValue();
    }

    /**
     * Convenience method to return a String.
     *
     * @param table String name of table.
     * @param column String name of column.
     * @return A String with the value of object at key.
     */
    public String getString(String table, String column)
    {
        return getString(new ColumnImpl(table, column));
    }

    /**
     * Convenience method to return a List.
     *
     * @param column The column to retrieve the value for.
     *
     * @return A List with the value of object at key.
     */
    @SuppressWarnings("unchecked")
    public List<Object> getList(Column column)
    {
        return (List<Object>) getCriterion(column).getValue();
    }

    /**
     * Convenience method to return a List.
     *
     * @param table String name of table.
     * @param column String name of column.
     * @return A List with the value of object at key.
     */
    public List<Object> getList(String table, String column)
    {
        return getList(new ColumnImpl(table, column));
    }

    /**
     * Method to return the value that was added to Criteria.
     *
     * @param column The column to retrieve the value for.
     *
     * @return An Object with the value of object at key.
     */
    public Object getValue(Column column)
    {
        return getCriterion(column).getValue();
    }

    /**
     * Method to return the value that was added to Criteria.
     *
     * @param table String name of table.
     * @param column String name of column.
     * @return An Object with the value of object at key.
     */
    public Object getValue(String table, String column)
    {
        return getValue(new ColumnImpl(table, column));
    }

    /**
     * Convenience method to return an ObjectKey.
     *
     * @param column The column to retrieve the value for.
     *
     * @return An ObjectKey with the value of object at key.
     */
    public ObjectKey getObjectKey(Column column)
    {
        return (ObjectKey) getCriterion(column).getValue();
    }

    /**
     * Convenience method to return an ObjectKey.
     *
     * @param table String name of table.
     * @param column String name of column.
     * @return A String with the value of object at key.
     */
    public ObjectKey getObjectKey(String table, String column)
    {
        return getObjectKey(new ColumnImpl(table, column));
    }

    /**
     * Returns the value in the top-level criterion for the given key.
     *
     * @param key the key for the criterion.
     *
     * @return the value of the criterion.
     *
     * @throws NullPointerException if no Criterion is stored under the key.
     *
     * @deprecated this method only works if a criterion exists for the key.
     */
    @Deprecated
    public Object get(Column key)
    {
        return getValue(key);
    }

    /**
     * Returns whether there are any criterions in this criteria.
     *
     * @return true if there are criterions, false otherwise.
     */
    public boolean isEmpty()
    {
        return criterionMap.isEmpty();
    }

    /**
     * Returns how many top-level criterions are in this criteria.
     *
     * @return the number of op-level criterions.
     *
     * @deprecated this method counts only top-level criterions, not all.
     */
    @Deprecated
    public int size()
    {
        return criterionMap.size();
    }

    /**
     * Returns all the top-level Criterions in this criteria
     * (but not the chained Criterions).
     *
     * @return all top-level Criterions, not null.
     */
    public Collection<Criterion> values()
    {
        return criterionMap.values();
    }

    /**
     * Equivalent to add(key, value).
     *
     * @param column the column.
     * @param value An Object to which the column should be equal.
     *
     * @return Instance of self.
     *
     * @throws NullPointerException if key is null.
     *
     * @deprecated use add(String, Object) instead
     */
    @Deprecated
    public Object put(Column column, Object value)
    {
        return add(column, value);
    }

    /**
     * Copies all of the mappings from the specified Map to this Criteria
     * These mappings will replace any mappings that this Criteria had for any
     * of the keys currently in the specified Map.
     *
     * @param t Objects to be stored in this criteria.
     */
    public synchronized void putAll(Map<Column, Criterion> t)
    {
        Iterator<Map.Entry<Column, Criterion>> i = t.entrySet().iterator();
        while (i.hasNext())
        {
            Map.Entry<Column, Criterion> e = i.next();
            Criterion val = e.getValue();
            criterionMap.put(e.getKey(), val);
        }
    }

    /**
     * This method adds a new criterion to the list of criterias. If a
     * criterion for the requested column already exists, it is
     * replaced. This is used as follows:
     *
     * <p>
     * <code>
     * Criteria crit = new Criteria().add(&quot;column&quot;,
     *                                      &quot;value&quot;);
     * </code>
     *
     * An EQUAL comparison is used for column and value.
     *
     * The name of the table must be used implicitly in the column name,
     * so the Column name must be something like 'TABLE.id'. If you
     * don't like this, you can use the add(table, column, value) method.
     *
     * @param column The column to run the comparison on
     * @param value An Object.
     *
     * @return A modified Criteria object.
     */
    public Criteria add(Column column, Object value)
    {
        add(column, value, EQUAL);
        return this;
    }

    /**
     * This method adds a new criterion to the list of criterias.
     * If a criterion for the requested column already exists, it is
     * replaced. If is used as follow:
     *
     * <p>
     * <code>
     * Criteria crit = new Criteria().add(&quot;column&quot;,
     *                                      &quot;value&quot;
     *                                      Criteria.GREATER_THAN);
     * </code>
     *
     * Any comparison can be used.
     *
     * The name of the table must be used implicitly in the column name,
     * so the Column name must be something like 'TABLE.id'. If you
     * don't like this, you can use the add(table, column, value) method.
     *
     * @param column The column to run the comparison on
     * @param value An Object.
     * @param comparison A String.
     *
     * @return A modified Criteria object.
     */
    public Criteria add(Column column, Object value, SqlEnum comparison)
    {
        criterionMap.put(column, new Criterion(column, value, comparison));
        return this;
    }

    /**
     * This method adds a new criterion to the list of criterias. If a
     * criterion for the requested column already exists, it is
     * replaced. This is used as follows:
     *
     * <p>
     * <code>
     * Criteria crit = new Criteria().add(&quot;column&quot;,
     *                                      &quot;value&quot;);
     * </code>
     *
     * An EQUAL comparison is used for column and value.
     *
     * The name of the table must be used implicitly in the column name,
     * so the Column name must be something like 'TABLE.id'. If you
     * don't like this, you can use the add(table, column, value) method.
     *
     * @param column The column to run the comparison on
     * @param value An Object.
     *
     * @return A modified Criteria object.
     */
    public Criteria add(String column, Object value)
    {
        add(column, value, EQUAL);
        return this;
    }

    /**
     * This method adds a new criterion to the list of criterias.
     * If a criterion for the requested column already exists, it is
     * replaced. If is used as follow:
     *
     * <p>
     * <code>
     * Criteria crit = new Criteria().add(&quot;column&quot;,
     *                                      &quot;value&quot;
     *                                      Criteria.GREATER_THAN);
     * </code>
     *
     * Any comparison can be used.
     *
     * The name of the table must be used implicitly in the column name,
     * so the Column name must be something like 'TABLE.id'. If you
     * don't like this, you can use the add(table, column, value) method.
     *
     * @param column The column to run the comparison on
     * @param value An Object.
     * @param comparison A String.
     *
     * @return A modified Criteria object.
     */
    public Criteria add(String column, Object value, SqlEnum comparison)
    {
        Column columnImpl = new ColumnImpl(column);
        criterionMap.put(
                columnImpl,
                new Criterion(columnImpl, value, comparison));
        return this;
    }

    /**
     * This method adds a new criterion to the list of criterias.
     * If a criterion for the requested column already exists, it is
     * replaced. If is used as follows:
     *
     * <p>
     * <code>
     * Criteria crit = new Criteria().add(&quot;table&quot;,
     *                                      &quot;column&quot;,
     *                                      &quot;value&quot;);
     * </code>
     *
     * An EQUAL comparison is used for column and value.
     *
     * @param table Name of the table which contains the column
     * @param column The column to run the comparison on
     * @param value An Object.
     *
     * @return A modified Criteria object.
     */
    public Criteria add(String table, String column, Object value)
    {
        add(table, column, value, EQUAL);
        return this;
    }

    /**
     * This method adds a new criterion to the list of criterias.
     * If a criterion for the requested column already exists, it is
     * replaced. If is used as follows:
     *
     * <p>
     * <code>
     * Criteria crit = new Criteria().add(&quot;table&quot;,
     *                                      &quot;column&quot;,
     *                                      &quot;value&quot;,
     *                                      Criteria.GREATER_THAN);
     * </code>
     *
     * Any comparison can be used.
     *
     * @param table Name of table which contains the column
     * @param column The column to run the comparison on
     * @param value An Object.
     * @param comparison String describing how to compare the column with
     *        the value
     * @return A modified Criteria object.
     */
    public Criteria add(String table,
            String column,
            Object value,
            SqlEnum comparison)
    {
        Column columnImpl = new ColumnImpl(table, column);
        criterionMap.put(columnImpl,
                new Criterion(columnImpl, value, comparison));
        return this;
    }

    /**
     * Convenience method to add a Date object specified by
     * year, month, and date into the Criteria.
     * Equal to
     *
     * <p>
     * <code>
     * add(column, new GregorianCalendar(year, month,date), EQUAL);
     * </code>
     *
     * @param column A String value to use as column.
     * @param year An int with the year.
     * @param month An int with the month. Month value is 0-based.
     *        e.g., 0 for January
     * @param day An int with the day.
     * @return A modified Criteria object.
     */
    public Criteria addDate(Column column, int year, int month, int day)
    {
        add(column, new GregorianCalendar(year, month, day).getTime());
        return this;
    }

    /**
     * Convenience method to add a Date object specified by
     * year, month, and date into the Criteria.
     * Equal to
     *
     * <p>
     * <code>
     * add(column, new GregorianCalendar(year, month,date), comparison);
     * </code>
     *
     * @param column The column to run the comparison on
     * @param year An int with the year.
     * @param month An int with the month. Month value is 0-based.
     *        e.g., 0 for January
     * @param date An int with the date.
     * @param comparison String describing how to compare the column with
     *        the value
     * @return A modified Criteria object.
     */
    public Criteria addDate(Column column, int year, int month, int date,
            SqlEnum comparison)
    {
        add(column, new GregorianCalendar(year, month, date).getTime(),
                comparison);
        return this;
    }

    /**
     * Convenience method to add a Date object specified by
     * year, month, and date into the Criteria.
     * Equal to
     *
     * <p>
     * <code>
     * add(column, new GregorianCalendar(year, month,date), EQUAL);
     * </code>
     *
     * @param column A String value to use as column.
     * @param year An int with the year.
     * @param month An int with the month. Month value is 0-based.
     *        e.g., 0 for January
     * @param day An int with the day.
     * @return A modified Criteria object.
     */
    public Criteria addDate(String column, int year, int month, int day)
    {
        add(column, new GregorianCalendar(year, month, day).getTime());
        return this;
    }

    /**
     * Convenience method to add a Date object specified by
     * year, month, and date into the Criteria.
     * Equal to
     *
     * <p>
     * <code>
     * add(column, new GregorianCalendar(year, month,date), comparison);
     * </code>
     *
     * @param column The column to run the comparison on
     * @param year An int with the year.
     * @param month An int with the month. Month value is 0-based.
     *        e.g., 0 for January
     * @param date An int with the date.
     * @param comparison String describing how to compare the column with
     *        the value
     * @return A modified Criteria object.
     */
    public Criteria addDate(String column, int year, int month, int date,
            SqlEnum comparison)
    {
        add(column, new GregorianCalendar(year, month, date).getTime(),
                comparison);
        return this;
    }

    /**
     * This is the way that you should add a join of two tables.  For
     * example:
     *
     * <p>
     * AND PROJECT.PROJECT_ID=FOO.PROJECT_ID
     * <p>
     *
     * left = PROJECT.PROJECT_ID
     * right = FOO.PROJECT_ID
     *
     * @param left A String with the left side of the join.
     * @param right A String with the right side of the join.
     * @return A modified Criteria object.
     */
    public Criteria addJoin(Column left, Column right)
    {
        return addJoin(left, right, null);
    }

    /**
     * This is the way that you should add a join of two tables.  For
     * example:
     *
     * <p>
     * PROJECT LEFT JOIN FOO ON PROJECT.PROJECT_ID=FOO.PROJECT_ID
     * <p>
     *
     * left = &quot;PROJECT.PROJECT_ID&quot;
     * right = &quot;FOO.PROJECT_ID&quot;
     * operator = Criteria.LEFT_JOIN
     *
     * @param left A String with the left side of the join.
     * @param right A String with the right side of the join.
     * @param operator The operator used for the join: must be one of null,
     *        Criteria.LEFT_JOIN, Criteria.RIGHT_JOIN, Criteria.INNER_JOIN
     * @return A modified Criteria object.
     */
    public Criteria addJoin(Column left, Column right, JoinType operator)
    {
        joins.add(new Join(left, right, Criteria.EQUAL, operator));

        return this;
    }

    /**
     * This is the way that you should add a join of two tables.  For
     * example:
     *
     * <p>
     * AND PROJECT.PROJECT_ID=FOO.PROJECT_ID
     * <p>
     *
     * left = PROJECT.PROJECT_ID
     * right = FOO.PROJECT_ID
     *
     * @param left A String with the left side of the join.
     * @param right A String with the right side of the join.
     * @return A modified Criteria object.
     */
    public Criteria addJoin(String left, String right)
    {
        return addJoin(left, right, null);
    }

    /**
     * This is the way that you should add a join of two tables.  For
     * example:
     *
     * <p>
     * PROJECT LEFT JOIN FOO ON PROJECT.PROJECT_ID=FOO.PROJECT_ID
     * <p>
     *
     * left = &quot;PROJECT.PROJECT_ID&quot;
     * right = &quot;FOO.PROJECT_ID&quot;
     * operator = Criteria.LEFT_JOIN
     *
     * @param left A String with the left side of the join.
     * @param right A String with the right side of the join.
     * @param operator The operator used for the join: must be one of null,
     *        Criteria.LEFT_JOIN, Criteria.RIGHT_JOIN, Criteria.INNER_JOIN
     * @return A modified Criteria object.
     */
    public Criteria addJoin(String left, String right, JoinType operator)
    {
        joins.add(new Join(
                new ColumnImpl(left),
                new ColumnImpl(right),
                Criteria.EQUAL,
                operator));

        return this;
    }

    /**
     * get the List of Joins.  This method is meant to
     * be called by BasePeerImpl.
     * @return a List which contains objects of type Join.
     *         If the criteria does not contains any joins, the list is empty
     */
    public List<Join> getJoins()
    {
        return joins;
    }

    /**
     * Adds an 'IN' clause with the criteria supplied as an Object
     * array.  For example:
     *
     * <p>
     * FOO.NAME IN ('FOO', 'BAR', 'ZOW')
     * <p>
     *
     * where 'values' contains three objects that evaluate to the
     * respective strings above when .toString() is called.
     *
     * If a criterion for the requested column already exists, it is
     * replaced.
     *
     * @param column The column to run the comparison on
     * @param values An Object[] with the allowed values.
     * @return A modified Criteria object.
     */
    public Criteria addIn(Column column, Object[] values)
    {
        add(column, values, Criteria.IN);
        return this;
    }

    /**
     * Adds an 'IN' clause with the criteria supplied as an Object
     * array.  For example:
     *
     * <p>
     * FOO.NAME IN ('FOO', 'BAR', 'ZOW')
     * <p>
     *
     * where 'values' contains three objects that evaluate to the
     * respective strings above when .toString() is called.
     *
     * If a criterion for the requested column already exists, it is
     * replaced.
     *
     * @param column The column to run the comparison on
     * @param values An Object[] with the allowed values.
     * @return A modified Criteria object.
     */
    public Criteria addIn(String column, Object[] values)
    {
        add(column, values, Criteria.IN);
        return this;
    }

    /**
     * Adds an 'IN' clause with the criteria supplied as a Collection.
     * For example:
     *
     * <p>
     * FOO.NAME IN ('FOO', 'BAR', 'ZOW')
     * <p>
     *
     * where 'values' contains three Strings "FOO", "BAR" and "ZOW".
     *
     * If a criterion for the requested column already exists, it is
     * replaced.
     *
     * @param column The column to run the comparison on
     * @param values A Collection with the allowed values.
     *
     * @return A modified Criteria object.
     */
    public Criteria addIn(Column column, Collection<?> values)
    {
        add(column, values, Criteria.IN);
        return this;
    }

    /**
     * Adds an 'IN' clause with the criteria supplied as a Collection.
     * For example:
     *
     * <p>
     * FOO.NAME IN ('FOO', 'BAR', 'ZOW')
     * <p>
     *
     * where 'values' contains three Strings "FOO", "BAR" and "ZOW".
     *
     * If a criterion for the requested column already exists, it is
     * replaced.
     *
     * @param column The column to run the comparison on
     * @param values A Collection with the allowed values.
     *
     * @return A modified Criteria object.
     */
    public Criteria addIn(String column, Collection<?> values)
    {
        add(column, values, Criteria.IN);
        return this;
    }

    /**
     * Adds a 'NOT IN' clause with the criteria supplied as an Object
     * array.  For example:
     *
     * <p>
     * FOO.NAME NOT IN ('FOO', 'BAR', 'ZOW')
     * <p>
     *
     * where 'values' contains three objects that evaluate to the
     * respective strings above when .toString() is called.
     *
     * If a criterion for the requested column already exists, it is
     * replaced.
     *
     * @param column The column to run the comparison on
     * @param values An Object[] with the disallowed values.
     * @return A modified Criteria object.
     */
    public Criteria addNotIn(Column column, Object[] values)
    {
        add(column, values, Criteria.NOT_IN);
        return this;
    }

    /**
     * Adds a 'NOT IN' clause with the criteria supplied as an Object
     * array.  For example:
     *
     * <p>
     * FOO.NAME NOT IN ('FOO', 'BAR', 'ZOW')
     * <p>
     *
     * where 'values' contains three objects that evaluate to the
     * respective strings above when .toString() is called.
     *
     * If a criterion for the requested column already exists, it is
     * replaced.
     *
     * @param column The column to run the comparison on
     * @param values An Object[] with the disallowed values.
     * @return A modified Criteria object.
     */
    public Criteria addNotIn(String column, Object[] values)
    {
        add(column, values, Criteria.NOT_IN);
        return this;
    }

    /**
     * Adds a 'NOT IN' clause with the criteria supplied as a Collection.
     * For example:
     *
     * <p>
     * FOO.NAME NOT IN ('FOO', 'BAR', 'ZOW')
     * <p>
     *
     * where 'values' contains three Strings "FOO", "BAR" and "ZOW".
     *
     * If a criterion for the requested column already exists, it is
     * replaced.
     *
     * @param column The column to run the comparison on
     * @param values A Collection with the disallowed values.
     *
     * @return A modified Criteria object.
     */
    public Criteria addNotIn(Column column, Collection<?> values)
    {
        add(column, values, Criteria.NOT_IN);
        return this;
    }

    /**
     * Adds a 'NOT IN' clause with the criteria supplied as a Collection.
     * For example:
     *
     * <p>
     * FOO.NAME NOT IN ('FOO', 'BAR', 'ZOW')
     * <p>
     *
     * where 'values' contains three Strings "FOO", "BAR" and "ZOW".
     *
     * If a criterion for the requested column already exists, it is
     * replaced.
     *
     * @param column The column to run the comparison on
     * @param values A Collection with the disallowed values.
     *
     * @return A modified Criteria object.
     */
    public Criteria addNotIn(String column, Collection<?> values)
    {
        add(column, values, Criteria.NOT_IN);
        return this;
    }

    /**
     * Adds &quot;ALL &quot; to the SQL statement.
     */
    public void setAll()
    {
        selectModifiers.add(ALL.toString());
    }

    /**
     * Adds &quot;DISTINCT &quot; to the SQL statement.
     */
    public void setDistinct()
    {
        selectModifiers.add(DISTINCT.toString());
    }

    /**
     * Sets whether case should be ignored in where clauses and order by
     * whenever String columns are encountered.
     *
     * @param b True if case should be ignored.
     * @return A modified Criteria object.
     */
    public Criteria setIgnoreCase(boolean b)
    {
        ignoreCase = b;
        return this;
    }

    /**
     * Returns whether case should be ignored in where clauses and order by
     * whenever String columns are encountered.
     *
     * @return True if case is ignored.
     */
    public boolean isIgnoreCase()
    {
        return ignoreCase;
    }

    /**
     * Set single record?  Set this to <code>true</code> if you expect the query
     * to result in only a single result record (the default behaviour is to
     * throw a TorqueException if multiple records are returned when the query
     * is executed).  This should be used in situations where returning multiple
     * rows would indicate an error of some sort.  If your query might return
     * multiple records but you are only interested in the first one then you
     * should be using setLimit(1).
     *
     * @param b set to <code>true</code> if you expect the query to select just
     * one record.
     * @return A modified Criteria object.
     */
    public Criteria setSingleRecord(boolean b)
    {
        singleRecord = b;
        return this;
    }

    /**
     * Is single record?
     *
     * @return True if a single record is being returned.
     */
    public boolean isSingleRecord()
    {
        return singleRecord;
    }

    /**
     * Set limit.
     *
     * @param limit An int with the value for limit.
     * @return A modified Criteria object.
     */
    public Criteria setLimit(int limit)
    {
        this.limit = limit;
        return this;
    }

    /**
     * Get limit.
     *
     * @return An int with the value for limit.
     */
    public int getLimit()
    {
        return limit;
    }

    /**
     * Set offset.
     *
     * @param offset An int with the value for offset.
     * @return A modified Criteria object.
     */
    public Criteria setOffset(long offset)
    {
        this.offset = offset;
        return this;
    }

    /**
     * Get offset.
     *
     * @return An int with the value for offset.
     */
    public long getOffset()
    {
        return offset;
    }

    /**
     * Add select column.
     *
     * @param column The select column to add.
     * @return A modified Criteria object.
     */
    public Criteria addSelectColumn(Column column)
    {
        selectColumns.add(column);
        return this;
    }

    /**
     * Add select column.
     *
     * @param column A String with the name of the select column.
     * @return A modified Criteria object.
     */
    public Criteria addSelectColumn(String column)
    {
        selectColumns.add(new ColumnImpl(column));
        return this;
    }

    /**
     * Get select columns.
     *
     * @return An List with the names of the select columns.
     */
    public UniqueColumnList getSelectColumns()
    {
        return selectColumns;
    }

    /**
     * Get select modifiers.
     *
     * @return An UniqueList with the select modifiers.
     */
    public UniqueList<String> getSelectModifiers()
    {
        return selectModifiers;
    }

    /**
     * Add group by column name.
     *
     * @param groupBy The name of the column to group by.
     * @return A modified Criteria object.
     */
    public Criteria addGroupByColumn(Column groupBy)
    {
        groupByColumns.add(groupBy);
        return this;
    }

    /**
     * Add group by column name.
     *
     * @param groupBy The name of the column to group by.
     * @return A modified Criteria object.
     */
    public Criteria addGroupByColumn(String groupBy)
    {
        groupByColumns.add(new ColumnImpl(groupBy));
        return this;
    }

    /**
     * Add order by column name, explicitly specifying ascending.
     *
     * @param column The column to order by.
     * @return A modified Criteria object.
     */
    public Criteria addAscendingOrderByColumn(Column column)
    {
        orderByColumns.add(new OrderBy(column, SqlEnum.ASC, false));
        return this;
    }


    /**
     * Add order by column name, explicitly specifying ascending.
     *
     * @param column The column to order by.
     * @param ignoreCase whether to ignore case on String columns.
     *
     * @return A modified Criteria object.
     */
    public Criteria addAscendingOrderByColumn(Column column, boolean ignoreCase)
    {
        orderByColumns.add(new OrderBy(column, SqlEnum.ASC, ignoreCase));
        return this;
    }
    /**
     * Add order by column name, explicitly specifying ascending.
     *
     * @param column The column to order by.
     * @return A modified Criteria object.
     */
    public Criteria addAscendingOrderByColumn(String column)
    {
        orderByColumns.add(
                new OrderBy(new ColumnImpl(column), SqlEnum.ASC, false));
        return this;
    }

    /**
     * Add order by column name, explicitly specifying descending.
     *
     * @param column The column to order by.
     * @return A modified Criteria object.
     */
    public Criteria addDescendingOrderByColumn(Column column)
    {
        orderByColumns.add(new OrderBy(column, SqlEnum.DESC, false));
        return this;
    }

    /**
     * Add order by column name, explicitly specifying descending.
     *
     * @param column The column to order by.
     * @param ignoreCase whether to ignore case on String columns.
     *
     * @return A modified Criteria object.
     */
    public Criteria addDescendingOrderByColumn(
            Column column,
            boolean ignoreCase)
    {
        orderByColumns.add(new OrderBy(column, SqlEnum.DESC, ignoreCase));
        return this;
    }

    /**
     * Add order by column name, explicitly specifying descending.
     *
     * @param column The column to order by.
     * @return A modified Criteria object.
     */
    public Criteria addDescendingOrderByColumn(String column)
    {
        orderByColumns.add(
                new OrderBy(new ColumnImpl(column), SqlEnum.DESC, false));
        return this;
    }

    /**
     * Get order by columns.
     *
     * @return An UniqueList with the name of the order columns, not null.
     */
    public UniqueList<OrderBy> getOrderByColumns()
    {
        return orderByColumns;
    }

    /**
     * Get group by columns.
     *
     * @return An UniqueList with the name of the groupBy clause, not null.
     */
    public UniqueColumnList getGroupByColumns()
    {
        return groupByColumns;
    }

    /**
     * Get Having Criterion.
     *
     * @return A Criterion that is the having clause.
     */
    public Criterion getHaving()
    {
        return having;
    }

    /**
     * Remove all objects with the given key from the criteria.
     *
     * @param key A String with the key to be removed.
     * @return The value of the removed criterion, or null.
     */
    public Object remove(Column key)
    {
        Iterator<Map.Entry<Column, Criterion>> entryIt
                = criterionMap.entrySet().iterator();
        while (entryIt.hasNext())
        {
            Map.Entry<Column, Criterion> entry = entryIt.next();
            if (entry.getKey().getSqlExpression().equals(
                    key.getSqlExpression()))
            {
                entryIt.remove();
                return entry.getValue().getValue();
            }
        }
        return null;
    }

    /**
     * Build a string representation of the Criteria.
     *
     * @return A String with the representation of the Criteria.
     */
    @Override
    public String toString()
    {
        StringBuffer sb = new StringBuffer("Criteria:: ");
        Iterator<?> it = keySet().iterator();
        while (it.hasNext())
        {
            Object key = it.next();
            sb.append(key).append("<=>")
                    .append(criterionMap.get(key)).append(":  ");
        }

        try
        {
            Query query = SqlBuilder.buildQuery(this);
            sb.append("\nCurrent Query SQL (may not be complete or applicable): ")
                    .append(query.getDisplayString());
        }
        catch (Exception exc)
        {
            log.debug("Exception when evaluating a Criteria", exc);
        }

        return sb.toString();
    }

    /**
     * Returns all keys in the Criterion map.
     *
     * @return all keys, not null.
     */
    public Set<Column> keySet()
    {
        return criterionMap.keySet();
    }

    /**
     * Checks whether an object is equal to this Criteria.
     * This is the case if the other object is also a Criteria and has
     * the same attributes and criterions.
     *
     * @param object the other object to check, can be null.
     *
     * @return true if the object is equal to this Criteria, false otherwise.
     */
    @Override
    public boolean equals(Object object)
    {
        if (object == null)
        {
            return false;
        }
        if (this == object)
        {
            return true;
        }
        if (object.getClass() != this.getClass())
        {
            return false;
        }
        Criteria criteria = (Criteria) object;
        if (criterionMap.size() !=  criteria.criterionMap.size())
        {
            return false;
        }
        if (this.offset != criteria.getOffset()
                || this.limit != criteria.getLimit()
                || ignoreCase != criteria.isIgnoreCase()
                || singleRecord != criteria.isSingleRecord()
                || !dbName.equals(criteria.getDbName())
                || !selectModifiers.equals(criteria.getSelectModifiers())
                || !selectColumns.equals(criteria.getSelectColumns())
                || !orderByColumns.equals(criteria.getOrderByColumns())
                || !aliases.equals(criteria.getAliases())
                || !asColumns.equals(criteria.getAsColumns())
                || !joins.equals(criteria.getJoins())
            )
        {
            return false;
        }
        for (Iterator<Column> it = criteria.keySet().iterator(); it.hasNext();)
        {
            Column key = it.next();
            if (containsTopLevelColumn(key))
            {
                Criterion a = this.getCriterion(key);
                Criterion b = criteria.getCriterion(key);
                if (!a.equals(b))
                {
                    return false;
                }
            }
            else
            {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns the hash code value for this Join.
     *
     * @return a hash code value for this object.
     */
    @Override
    public int hashCode()
    {
        int result = 16;
        result = 37 * result + Long.valueOf(offset).hashCode();
        result = 37 * result + limit;
        result = 37 * result + (ignoreCase ? 0 : 1);
        result = 37 * result + (singleRecord ? 0 : 1);
        result = 37 * result + dbName.hashCode();
        result = 37 * result + selectModifiers.hashCode();
        result = 37 * result + selectColumns.hashCode();
        result = 37 * result + orderByColumns.hashCode();
        result = 37 * result + aliases.hashCode();
        result = 37 * result + asColumns.hashCode();
        result = 37 * result + joins.hashCode();
        result = 37 * result + criterionMap.hashCode();
        return result;
    }

    /*
     * ------------------------------------------------------------------------
     *
     * Start of the "and" methods
     *
     * ------------------------------------------------------------------------
     */

    /**
     * This method adds a prepared Criterion object to the Criteria as a having
     * clause. You can get a new, empty Criterion object with the
     * getNewCriterion() method.
     *
     * <p>
     * <code>
     * Criteria crit = new Criteria();
     * Criteria.Criterion c = crit.getNewCriterion(XXXPeer.ID, new Integer(5),
     *         Criteria.LESS_THAN);
     * crit.addHaving(c);
     * </code>
     *
     * @param having A Criterion object
     * @return A modified Criteria object.
     */
    public Criteria addHaving(Criterion having)
    {
        this.having = having;
        return this;
    }

    /**
     * This method adds a prepared Criterion object to the Criteria.
     * You can get a new, empty Criterion object with the
     * getNewCriterion() method. If a criterion for the requested column
     * already exists, it is &quot;AND&quot;ed to the existing criterion.
     * This is used as follows:
     *
     * <p>
     * <code>
     * Criteria crit = new Criteria();
     * Criteria.Criterion c = crit.getNewCriterion(XXXPeer.ID, new Integer(5),
     *         Criteria.LESS_THAN);
     * crit.and(c);
     * </code>
     *
     * @param c A Criterion object
     * @return A modified Criteria object.
     */
    public Criteria and(Criterion c)
    {
        Criterion oc = getCriterion(c.getColumn());

        if (oc == null)
        {
            add(c);
        }
        else
        {
            oc.and(c);
        }
        return this;
    }

    /**
     * This method adds a new criterion to the list of criterias. If a
     * criterion for the requested column already exists, it is
     * &quot;AND&quot;ed to the existing criterion. This is used as follows:
     *
     * <p>
     * <code>
     * Criteria crit = new Criteria().and(&quot;column&quot;,
     *                                      &quot;value&quot;);
     * </code>
     *
     * An EQUAL comparison is used for column and value.
     *
     * The name of the table must be used implicitly in the column name,
     * so the Column name must be something like 'TABLE.id'. If you
     * don't like this, you can use the and(table, column, value) method.
     *
     * @param column The column to run the comparison on
     * @param value An Object.
     *
     * @return A modified Criteria object.
     */
    public Criteria and(Column column, Object value)
    {
        and(column, value, EQUAL);
        return this;
    }

    /**
     * This method adds a new criterion to the list of criterias.
     * If a criterion for the requested column already exists, it is
     * &quot;AND&quot;ed to the existing criterion. If is used as follow:
     *
     * <p>
     * <code>
     * Criteria crit = new Criteria().and(&quot;column&quot;,
     *                                      &quot;value&quot;
     *                                      Criteria.GREATER_THAN);
     * </code>
     *
     * Any comparison can be used.
     *
     * The name of the table must be used implicitly in the column name,
     * so the Column name must be something like 'TABLE.id'. If you
     * don't like this, you can use the and(table, column, value) method.
     *
     * @param column The column to run the comparison on
     * @param value An Object.
     * @param comparison A String.
     *
     * @return A modified Criteria object.
     */
    public Criteria and(Column column, Object value, SqlEnum comparison)
    {
        Criterion oc = getCriterion(column);
        Criterion nc = new Criterion(column, value, comparison);

        if (oc == null)
        {
            criterionMap.put(column, nc);
        }
        else
        {
            oc.and(nc);
        }
        return this;
    }

    /**
     * This method adds a new criterion to the list of criterias. If a
     * criterion for the requested column already exists, it is
     * &quot;AND&quot;ed to the existing criterion. This is used as follows:
     *
     * <p>
     * <code>
     * Criteria crit = new Criteria().and(&quot;column&quot;,
     *                                      &quot;value&quot;);
     * </code>
     *
     * An EQUAL comparison is used for column and value.
     *
     * The name of the table must be used implicitly in the column name,
     * so the Column name must be something like 'TABLE.id'. If you
     * don't like this, you can use the and(table, column, value) method.
     *
     * @param column The column to run the comparison on
     * @param value An Object.
     *
     * @return A modified Criteria object.
     */
    public Criteria and(String column, Object value)
    {
        return and(column, value, EQUAL);
    }

    /**
     * This method adds a new criterion to the list of criterias.
     * If a criterion for the requested column already exists, it is
     * &quot;AND&quot;ed to the existing criterion. If is used as follow:
     *
     * <p>
     * <code>
     * Criteria crit = new Criteria().and(&quot;column&quot;,
     *                                      &quot;value&quot;
     *                                      Criteria.GREATER_THAN);
     * </code>
     *
     * Any comparison can be used.
     *
     * The name of the table must be used implicitly in the column name,
     * so the Column name must be something like 'TABLE.id'. If you
     * don't like this, you can use the and(table, column, value) method.
     *
     * @param column The column to run the comparison on
     * @param value An Object.
     * @param comparison A String.
     *
     * @return A modified Criteria object.
     */
    public Criteria and(String column, Object value, SqlEnum comparison)
    {
        ColumnImpl columnImpl = new ColumnImpl(column);
        return and(columnImpl, value, comparison);
    }

    /**
     * This method adds a new criterion to the list of criterias.
     * If a criterion for the requested column already exists, it is
     * &quot;AND&quot;ed to the existing criterion. If is used as follows:
     *
     * <p>
     * <code>
     * Criteria crit = new Criteria().and(&quot;table&quot;,
     *                                      &quot;column&quot;,
     *                                      &quot;value&quot;);
     * </code>
     *
     * An EQUAL comparison is used for column and value.
     *
     * @param table Name of the table which contains the column
     * @param column The column to run the comparison on
     * @param value An Object.
     * @return A modified Criteria object.
     *
     * @deprecated use and(Column, Object) instead
     */
    @Deprecated
    public Criteria and(String table, String column, Object value)
    {
        and(table, column, value, EQUAL);
        return this;
    }

    /**
     * This method adds a new criterion to the list of criterias.
     * If a criterion for the requested column already exists, it is
     * &quot;AND&quot;ed to the existing criterion. If is used as follows:
     *
     * <p>
     * <code>
     * Criteria crit = new Criteria().and(&quot;table&quot;,
     *                                      &quot;column&quot;,
     *                                      &quot;value&quot;,
     *                                      &quot;Criterion.GREATER_THAN&quot;);
     * </code>
     *
     * Any comparison can be used.
     *
     * @param table Name of table which contains the column
     * @param column The column to run the comparison on
     * @param value An Object.
     * @param comparison String describing how to compare the column with
     *        the value
     * @return A modified Criteria object.
     *
     * @deprecated use and(Column, Object, comparison) instead
     */
    @Deprecated
    public Criteria and(String table, String column, Object value,
            SqlEnum comparison)
    {
        Criterion oc = getCriterion(table, column);
        Criterion nc = new Criterion(table, column, value, comparison);

        if (oc == null)
        {
            criterionMap.put(new ColumnImpl(table, column), nc);
        }
        else
        {
            oc.and(nc);
        }
        return this;
    }

    /**
     * Convenience method to add a Date object specified by
     * year, month, and date into the Criteria.
     * Equal to
     *
     * <p>
     * <code>
     * and(column, new GregorianCalendar(year, month,date), EQUAL);
     * </code>
     *
     * @param column A String value to use as column.
     * @param year An int with the year.
     * @param month An int with the month.
     * @param date An int with the date.
     * @return A modified Criteria object.
     */
    public Criteria andDate(Column column, int year, int month, int date)
    {
        and(column, new GregorianCalendar(year, month, date).getTime());
        return this;
    }

    /**
     * Convenience method to add a Date object specified by
     * year, month, and date into the Criteria.
     * Equal to
     *
     * <p>
     * <code>
     * and(column, new GregorianCalendar(year, month,date), comparison);
     * </code>
     *
     * @param column The column to run the comparison on
     * @param year An int with the year.
     * @param month An int with the month.
     * @param date An int with the date.
     * @param comparison String describing how to compare the column with
     *        the value
     * @return A modified Criteria object.
     */
    public Criteria andDate(Column column, int year, int month, int date,
            SqlEnum comparison)
    {
        and(column, new GregorianCalendar(year, month, date).getTime(), comparison);
        return this;
    }

    /**
     * Convenience method to add a Date object specified by
     * year, month, and date into the Criteria.
     * Equal to
     *
     * <p>
     * <code>
     * and(column, new GregorianCalendar(year, month,date), EQUAL);
     * </code>
     *
     * @param column A String value to use as column.
     * @param year An int with the year.
     * @param month An int with the month.
     * @param date An int with the date.
     * @return A modified Criteria object.
     */
    public Criteria andDate(String column, int year, int month, int date)
    {
        and(column, new GregorianCalendar(year, month, date).getTime());
        return this;
    }

    /**
     * Convenience method to add a Date object specified by
     * year, month, and date into the Criteria.
     * Equal to
     *
     * <p>
     * <code>
     * and(column, new GregorianCalendar(year, month,date), comparison);
     * </code>
     *
     * @param column The column to run the comparison on
     * @param year An int with the year.
     * @param month An int with the month.
     * @param date An int with the date.
     * @param comparison String describing how to compare the column with
     *        the value
     * @return A modified Criteria object.
     */
    public Criteria andDate(String column, int year, int month, int date,
            SqlEnum comparison)
    {
        and(column, new GregorianCalendar(year, month, date).getTime(), comparison);
        return this;
    }

    /**
     * Adds an 'IN' clause with the criteria supplied as an Object array.
     * For example:
     *
     * <p>
     * FOO.NAME IN ('FOO', 'BAR', 'ZOW')
     * <p>
     *
     * where 'values' contains three objects that evaluate to the
     * respective strings above when .toString() is called.
     *
     * If a criterion for the requested column already exists, it is
     * &quot;AND&quot;ed to the existing criterion.
     *
     * @param column The column to run the comparison on
     * @param values An Object[] with the allowed values.
     * @return A modified Criteria object.
     */
    public Criteria andIn(Column column, Object[] values)
    {
        and(column, values, Criteria.IN);
        return this;
    }

    /**
     * Adds an 'IN' clause with the criteria supplied as an Object array.
     * For example:
     *
     * <p>
     * FOO.NAME IN ('FOO', 'BAR', 'ZOW')
     * <p>
     *
     * where 'values' contains three objects that evaluate to the
     * respective strings above when .toString() is called.
     *
     * If a criterion for the requested column already exists, it is
     * &quot;AND&quot;ed to the existing criterion.
     *
     * @param column The column to run the comparison on
     * @param values An Object[] with the allowed values.
     * @return A modified Criteria object.
     */
    public Criteria andIn(String column, Object[] values)
    {
        return and(column, values, Criteria.IN);
    }

    /**
     * Adds an 'IN' clause with the criteria supplied as a List.
     * For example:
     *
     * <p>
     * FOO.NAME IN ('FOO', 'BAR', 'ZOW')
     * <p>
     *
     * where 'values' contains three objects that evaluate to the
     * respective strings above when .toString() is called.
     *
     * If a criterion for the requested column already exists, it is
     * &quot;AND&quot;ed to the existing criterion.
     *
     * @param column The column to run the comparison on
     * @param values A List with the allowed values.
     * @return A modified Criteria object.
     */
    public Criteria andIn(Column column, Collection<?> values)
    {
        and(column, values, Criteria.IN);
        return this;
    }

    /**
     * Adds an 'IN' clause with the criteria supplied as a List.
     * For example:
     *
     * <p>
     * FOO.NAME IN ('FOO', 'BAR', 'ZOW')
     * <p>
     *
     * where 'values' contains three objects that evaluate to the
     * respective strings above when .toString() is called.
     *
     * If a criterion for the requested column already exists, it is
     * &quot;AND&quot;ed to the existing criterion.
     *
     * @param column The column to run the comparison on
     * @param values A List with the allowed values.
     * @return A modified Criteria object.
     */
    public Criteria andIn(String column, Collection<?> values)
    {
        return and(column, values, Criteria.IN);
    }

    /**
     * Adds a 'NOT IN' clause with the criteria supplied as an Object
     * array.  For example:
     *
     * <p>
     * FOO.NAME NOT IN ('FOO', 'BAR', 'ZOW')
     * <p>
     *
     * where 'values' contains three objects that evaluate to the
     * respective strings above when .toString() is called.
     *
     * If a criterion for the requested column already exists, it is
     * &quot;AND&quot;ed to the existing criterion.
     *
     * @param column The column to run the comparison on
     * @param values An Object[] with the disallowed values.
     * @return A modified Criteria object.
     */
    public Criteria andNotIn(Column column, Object[] values)
    {
        and(column, values, Criteria.NOT_IN);
        return this;
    }

    /**
     * Adds a 'NOT IN' clause with the criteria supplied as an Object
     * array.  For example:
     *
     * <p>
     * FOO.NAME NOT IN ('FOO', 'BAR', 'ZOW')
     * <p>
     *
     * where 'values' contains three objects that evaluate to the
     * respective strings above when .toString() is called.
     *
     * If a criterion for the requested column already exists, it is
     * &quot;AND&quot;ed to the existing criterion.
     *
     * @param column The column to run the comparison on
     * @param values An Object[] with the disallowed values.
     * @return A modified Criteria object.
     */
    public Criteria andNotIn(String column, Object[] values)
    {
        ColumnImpl columnImpl = new ColumnImpl(column);
        return and(columnImpl, values, Criteria.NOT_IN);
    }

    /**
     * Adds a 'NOT IN' clause with the criteria supplied as a List.
     * For example:
     *
     * <p>
     * FOO.NAME NOT IN ('FOO', 'BAR', 'ZOW')
     * <p>
     *
     * where 'values' contains three objects that evaluate to the
     * respective strings above when .toString() is called.
     *
     * If a criterion for the requested column already exists, it is
     * &quot;AND&quot;ed to the existing criterion.
     *
     * @param column The column to run the comparison on
     * @param values A List with the disallowed values.
     * @return A modified Criteria object.
     */
    public Criteria andNotIn(Column column, Collection<?> values)
    {
        and(column, values, Criteria.NOT_IN);
        return this;
    }

    /**
     * Adds a 'NOT IN' clause with the criteria supplied as a List.
     * For example:
     *
     * <p>
     * FOO.NAME NOT IN ('FOO', 'BAR', 'ZOW')
     * <p>
     *
     * where 'values' contains three objects that evaluate to the
     * respective strings above when .toString() is called.
     *
     * If a criterion for the requested column already exists, it is
     * &quot;AND&quot;ed to the existing criterion.
     *
     * @param column The column to run the comparison on
     * @param values A List with the disallowed values.
     * @return A modified Criteria object.
     */
    public Criteria andNotIn(String column, Collection<?> values)
    {
        return and(column, values, Criteria.NOT_IN);
    }

    /*
     * ------------------------------------------------------------------------
     *
     * Start of the "or" methods
     *
     * ------------------------------------------------------------------------
     */

    /**
     * This method adds a prepared Criterion object to the Criteria.
     * You can get a new, empty Criterion object with the
     * getNewCriterion() method. If a criterion for the requested column
     * already exists, it is &quot;OR&quot;ed to the existing criterion.
     * This is used as follows:
     *
     * <p>
     * <code>
     * Criteria crit = new Criteria();
     * Criteria.Criterion c = crit.getNewCriterion(XXXPeer.ID, new Integer(5), Criteria.LESS_THAN);
     * crit.or(c);
     * </code>
     *
     * @param c A Criterion object
     * @return A modified Criteria object.
     */
    public Criteria or(Criterion c)
    {
        Criterion oc = getCriterion(c.getColumn());

        if (oc == null)
        {
            add(c);
        }
        else
        {
            oc.or(c);
        }
        return this;
    }

    /**
     * This method adds a new criterion to the list of criterias. If a
     * criterion for the requested column already exists, it is
     * &quot;OR&quot;ed to the existing criterion. This is used as follows:
     *
     * <p>
     * <code>
     * Criteria crit = new Criteria().or(&quot;column&quot;,
     *                                      &quot;value&quot;);
     * </code>
     *
     * An EQUAL comparison is used for column and value.
     *
     * The name of the table must be used implicitly in the column name,
     * so the Column name must be something like 'TABLE.id'. If you
     * don't like this, you can use the or(table, column, value) method.
     *
     * @param column The column to run the comparison on
     * @param value An Object.
     *
     * @return A modified Criteria object.
     */
    public Criteria or(Column column, Object value)
    {
        or(column, value, EQUAL);
        return this;
    }

    /**
     * This method adds a new criterion to the list of criterias. If a
     * criterion for the requested column already exists, it is
     * &quot;OR&quot;ed to the existing criterion. This is used as follows:
     *
     * <p>
     * <code>
     * Criteria crit = new Criteria().or(&quot;column&quot;,
     *                                      &quot;value&quot;);
     * </code>
     *
     * An EQUAL comparison is used for column and value.
     *
     * The name of the table must be used implicitly in the column name,
     * so the Column name must be something like 'TABLE.id'. If you
     * don't like this, you can use the or(table, column, value) method.
     *
     * @param column The column to run the comparison on
     * @param value An Object.
     *
     * @return A modified Criteria object.
     */
    public Criteria or(String column, Object value)
    {
        or(column, value, EQUAL);
        return this;
    }

    /**
     * This method adds a new criterion to the list of criterias.
     * If a criterion for the requested column already exists, it is
     * &quot;OR&quot;ed to the existing criterion. If is used as follow:
     *
     * <p>
     * <code>
     * Criteria crit = new Criteria().or(&quot;column&quot;,
     *                                      &quot;value&quot;
     *                                      &quot;Criterion.GREATER_THAN&quot;);
     * </code>
     *
     * Any comparison can be used.
     *
     * The name of the table must be used implicitly in the column name,
     * so the Column name must be something like 'TABLE.id'. If you
     * don't like this, you can use the or(table, column, value) method.
     *
     * @param column The column to run the comparison on
     * @param value An Object.
     * @param comparison A String.
     * @return A modified Criteria object.
     */
    public Criteria or(Column column, Object value, SqlEnum comparison)
    {
        Criterion oc = getCriterion(column);
        Criterion nc = new Criterion(column, value, comparison);

        if (oc == null)
        {
            criterionMap.put(column, nc);
        }
        else
        {
            oc.or(nc);
        }
        return this;
    }

    /**
     * This method adds a new criterion to the list of criterias.
     * If a criterion for the requested column already exists, it is
     * &quot;OR&quot;ed to the existing criterion. If is used as follow:
     *
     * <p>
     * <code>
     * Criteria crit = new Criteria().or(&quot;column&quot;,
     *                                      &quot;value&quot;
     *                                      &quot;Criterion.GREATER_THAN&quot;);
     * </code>
     *
     * Any comparison can be used.
     *
     * The name of the table must be used implicitly in the column name,
     * so the Column name must be something like 'TABLE.id'. If you
     * don't like this, you can use the or(table, column, value) method.
     *
     * @param column The column to run the comparison on
     * @param value An Object.
     * @param comparison A String.
     * @return A modified Criteria object.
     */
    public Criteria or(String column, Object value, SqlEnum comparison)
    {
        return or(new ColumnImpl(column), value, comparison);
    }

    /**
     * This method adds a new criterion to the list of criterias.
     * If a criterion for the requested column already exists, it is
     * &quot;OR&quot;ed to the existing criterion. If is used as follows:
     *
     * <p>
     * <code>
     * Criteria crit = new Criteria().or(&quot;table&quot;,
     *                                      &quot;column&quot;,
     *                                      &quot;value&quot;);
     * </code>
     *
     * An EQUAL comparison is used for column and value.
     *
     * @param table Name of the table which contains the column
     * @param column The column to run the comparison on
     * @param value An Object.
     * @return A modified Criteria object.
     *
     * @deprecated use or(Column, Object) instead
     */
    @Deprecated
    public Criteria or(String table, String column, Object value)
    {
        or(table, column, value, EQUAL);
        return this;
    }

    /**
     * This method adds a new criterion to the list of criterias.
     * If a criterion for the requested column already exists, it is
     * &quot;OR&quot;ed to the existing criterion. If is used as follows:
     *
     * <p>
     * <code>
     * Criteria crit = new Criteria().or(&quot;table&quot;,
     *                                      &quot;column&quot;,
     *                                      &quot;value&quot;,
     *                                      &quot;Criterion.GREATER_THAN&quot;);
     * </code>
     *
     * Any comparison can be used.
     *
     * @param table Name of table which contains the column
     * @param column The column to run the comparison on
     * @param value An Object.
     * @param comparison String describing how to compare the column with the value
     * @return A modified Criteria object.
     *
     * @deprecated use or(Column, Object, SqlEnum) instead
     */
    @Deprecated
    public Criteria or(String table, String column, Object value,
            SqlEnum comparison)
    {
        Criterion oc = getCriterion(table, column);
        Criterion nc = new Criterion(table, column, value, comparison);
        if (oc == null)
        {
            criterionMap.put(new ColumnImpl(table, column), nc);
        }
        else
        {
            oc.or(nc);
        }
        return this;
    }

    /**
     * Convenience method to add a Date object specified by
     * year, month, and date into the Criteria.
     * Equal to
     *
     * <p>
     * <code>
     * or(column, new GregorianCalendar(year, month,date), EQUAL);
     * </code>
     *
     * @param column A String value to use as column.
     * @param year An int with the year.
     * @param month An int with the month.
     * @param date An int with the date.
     * @return A modified Criteria object.
     */
    public Criteria orDate(Column column, int year, int month, int date)
    {
        or(column, new GregorianCalendar(year, month, date));
        return this;
    }

    /**
     * Convenience method to add a Date object specified by
     * year, month, and date into the Criteria.
     * Equal to
     *
     * <p>
     * <code>
     * or(column, new GregorianCalendar(year, month,date), EQUAL);
     * </code>
     *
     * @param column A String value to use as column.
     * @param year An int with the year.
     * @param month An int with the month.
     * @param date An int with the date.
     * @return A modified Criteria object.
     */
    public Criteria orDate(String column, int year, int month, int date)
    {
        return or(column, new GregorianCalendar(year, month, date));
    }

    /**
     * Convenience method to add a Date object specified by
     * year, month, and date into the Criteria.
     * Equal to
     *
     * <p>
     * <code>
     * or(column, new GregorianCalendar(year, month,date), comparison);
     * </code>
     *
     * @param column The column to run the comparison on
     * @param year An int with the year.
     * @param month An int with the month.
     * @param date An int with the date.
     * @param comparison String describing how to compare the column
     * with the value
     * @return A modified Criteria object.
     */
    public Criteria orDate(Column column, int year, int month, int date,
            SqlEnum comparison)
    {
        or(column, new GregorianCalendar(year, month, date), comparison);
        return this;
    }

    /**
     * Convenience method to add a Date object specified by
     * year, month, and date into the Criteria.
     * Equal to
     *
     * <p>
     * <code>
     * or(column, new GregorianCalendar(year, month,date), comparison);
     * </code>
     *
     * @param column The column to run the comparison on
     * @param year An int with the year.
     * @param month An int with the month.
     * @param date An int with the date.
     * @param comparison String describing how to compare the column
     * with the value
     * @return A modified Criteria object.
     */
    public Criteria orDate(String column, int year, int month, int date,
            SqlEnum comparison)
    {
        return or(column, new GregorianCalendar(year, month, date), comparison);
    }

    /**
     * Adds an 'IN' clause with the criteria supplied as an Object
     * array.  For example:
     *
     * <p>
     * FOO.NAME IN ('FOO', 'BAR', 'ZOW')
     * <p>
     *
     * where 'values' contains three objects that evaluate to the
     * respective strings above when .toString() is called.
     *
     * If a criterion for the requested column already exists, it is
     * &quot;OR&quot;ed to the existing criterion.
     *
     * @param column The column to run the comparison on
     * @param values An Object[] with the allowed values.
     * @return A modified Criteria object.
     */
    public Criteria orIn(Column column, Object[] values)
    {
        or(column, values, Criteria.IN);
        return this;
    }

    /**
     * Adds an 'IN' clause with the criteria supplied as an Object
     * array.  For example:
     *
     * <p>
     * FOO.NAME IN ('FOO', 'BAR', 'ZOW')
     * <p>
     *
     * where 'values' contains three objects that evaluate to the
     * respective strings above when .toString() is called.
     *
     * If a criterion for the requested column already exists, it is
     * &quot;OR&quot;ed to the existing criterion.
     *
     * @param column The column to run the comparison on
     * @param values An Object[] with the allowed values.
     * @return A modified Criteria object.
     */
    public Criteria orIn(String column, Object[] values)
    {
        return or(column, values, Criteria.IN);
    }

    /**
     * Adds an 'IN' clause with the criteria supplied as a List.
     * For example:
     *
     * <p>
     * FOO.NAME IN ('FOO', 'BAR', 'ZOW')
     * <p>
     *
     * where 'values' contains three objects that evaluate to the
     * respective strings above when .toString() is called.
     *
     * If a criterion for the requested column already exists, it is
     * &quot;OR&quot;ed to the existing criterion.
     *
     * @param column The column to run the comparison on
     * @param values A List with the allowed values.
     * @return A modified Criteria object.
     */
    public Criteria orIn(Column column, Collection<?> values)
    {
        or(column, values, Criteria.IN);
        return this;
    }

    /**
     * Adds an 'IN' clause with the criteria supplied as a List.
     * For example:
     *
     * <p>
     * FOO.NAME IN ('FOO', 'BAR', 'ZOW')
     * <p>
     *
     * where 'values' contains three objects that evaluate to the
     * respective strings above when .toString() is called.
     *
     * If a criterion for the requested column already exists, it is
     * &quot;OR&quot;ed to the existing criterion.
     *
     * @param column The column to run the comparison on
     * @param values A List with the allowed values.
     * @return A modified Criteria object.
     */
    public Criteria orIn(String column, Collection<?> values)
    {
        return or(column, values, Criteria.IN);
    }

    /**
     * Adds a 'NOT IN' clause with the criteria supplied as an Object
     * array.  For example:
     *
     * <p>
     * FOO.NAME NOT IN ('FOO', 'BAR', 'ZOW')
     * <p>
     *
     * where 'values' contains three objects that evaluate to the
     * respective strings above when .toString() is called.
     *
     * If a criterion for the requested column already exists, it is
     * &quot;OR&quot;ed to the existing criterion.
     *
     * @param column The column to run the comparison on
     * @param values An Object[] with the disallowed values.
     * @return A modified Criteria object.
     */
    public Criteria orNotIn(Column column, Object[] values)
    {
        or(column, values, Criteria.NOT_IN);
        return this;
    }

    /**
     * Adds a 'NOT IN' clause with the criteria supplied as an Object
     * array.  For example:
     *
     * <p>
     * FOO.NAME NOT IN ('FOO', 'BAR', 'ZOW')
     * <p>
     *
     * where 'values' contains three objects that evaluate to the
     * respective strings above when .toString() is called.
     *
     * If a criterion for the requested column already exists, it is
     * &quot;OR&quot;ed to the existing criterion.
     *
     * @param column The column to run the comparison on
     * @param values An Object[] with the disallowed values.
     * @return A modified Criteria object.
     */
    public Criteria orNotIn(String column, Object[] values)
    {
        return or(column, values, Criteria.NOT_IN);
    }

    /**
     * Adds a 'NOT IN' clause with the criteria supplied as a List.
     * For example:
     *
     * <p>
     * FOO.NAME NOT IN ('FOO', 'BAR', 'ZOW')
     * <p>
     *
     * where 'values' contains three objects that evaluate to the
     * respective strings above when .toString() is called.
     *
     * If a criterion for the requested column already exists, it is
     * &quot;OR&quot;ed to the existing criterion.
     *
     * @param column The column to run the comparison on
     * @param values A List with the disallowed values.
     * @return A modified Criteria object.
     */
    public Criteria orNotIn(Column column, Collection<?> values)
    {
        or(column, values, Criteria.NOT_IN);
        return this;
    }

    /**
     * Adds a 'NOT IN' clause with the criteria supplied as a List.
     * For example:
     *
     * <p>
     * FOO.NAME NOT IN ('FOO', 'BAR', 'ZOW')
     * <p>
     *
     * where 'values' contains three objects that evaluate to the
     * respective strings above when .toString() is called.
     *
     * If a criterion for the requested column already exists, it is
     * &quot;OR&quot;ed to the existing criterion.
     *
     * @param column The column to run the comparison on
     * @param values A List with the disallowed values.
     * @return A modified Criteria object.
     */
    public Criteria orNotIn(String column, Collection<?> values)
    {
        return or(column, values, Criteria.NOT_IN);
    }

    /**
     * This is an inner class that describes an object in the criteria.
     */
    public final class Criterion implements Serializable
    {
        /** Serial version. */
        private static final long serialVersionUID = 7157097965404611710L;

        /** Constant for thze operator " AND ". */
        public static final String AND = " AND ";

        /** Constant for thze operator " OR ". */
        public static final String OR = " OR ";

        /** Value of the CO. */
        private Object value;

        /** Comparison value. */
        private SqlEnum comparison;

        /** Column. */
        private Column column;

        /** flag to ignore case in comparision */
        private boolean ignoreStringCase = false;

        /**
         * other connected criteria.
         */
        private final List<Criterion> clauses = new ArrayList<Criterion>();

        /** The operators (AND, OR...) how the other Criteria are connected. */
        private final List<String> conjunctions = new ArrayList<String>();

        /**
         * Create a new instance.
         *
         * @param table A String with the name of the table,
         *        not null or blank.
         * @param column A String with the name of the column,
         *        not null or blank.
         * @param val An Object with the value for the Criteria.
         * @param comp A String with the comparison value.
         *
         * @throws NullPointerException if columnName or tableName are null.
         * @throws IllegalArgumentException if columnName or tableName are
         *         blank.
         */
        Criterion(String table, String column, Object val, SqlEnum comp)
        {
            this.value = val;
            setComparison(comp);
            this.column = new ColumnImpl(table, column);
        }

        /**
         * Create a new instance.
         *
         * @param column the column description, not null.
         * @param val An Object with the value for the Criteria, may be null.
         * @param comp A String with the comparison value, not null.
         *
         * @throws NullPointerException if column is null.
         */
        Criterion(Column column, Object val, SqlEnum comp)
        {
            this.value = val;
            setComparison(comp);
            setColumn(column);
        }

        /**
         * Create a new instance.
         *
         * @param table A String with the name of the table.
         * @param column A String with the name of the column.
         * @param val An Object with the value for the Criteria.
         */
        Criterion(String table, String column, Object val)
        {
            this(table, column, val, EQUAL);
        }

        /**
         * Create a new instance.
         *
         * @param tableColumn the column description.
         * @param val An Object with the value for the Criteria.
         */
        Criterion(Column column, Object val)
        {
            this(column, val, EQUAL);
        }

        /**
         * Sets the column.
         *
         * @param column the column, not null.
         *
         * @throws NullPointerException if column is null.
         */
        private void setColumn(Column column)
        {
            if (column == null)
            {
                throw new NullPointerException("column must not be null");
            }
            this.column = column;
        }

        /**
         * Get the column.
         *
         * @return the column.
         */
        public Column getColumn()
        {
            return this.column;
        }

        /**
         * Sets the comparison.
         *
         * @param comparison the comparison, not null.
         *
         * @throws NullPointerException if comparison is null.
         */
        private void setComparison(SqlEnum comparison)
        {
            if (comparison == null)
            {
                throw new NullPointerException("comparison must not be null");
            }
            this.comparison = comparison;
        }
        /**
         * Get the comparison.
         *
         * @return A String with the comparison.
         */
        public SqlEnum getComparison()
        {
            return this.comparison;
        }

        /**
         * Get the value.
         *
         * @return An Object with the value.
         */
        public Object getValue()
        {
            return this.value;
        }

        /**
         * Set the value of the criterion.
         *
         * @param value the new value.
         */
        public void setValue(Object value)
        {
            this.value = value;
        }

        /**
         * Sets ignore case.
         *
         * @param b True if case should be ignored.
         * @return A modified Criteria object.
         */
        public Criterion setIgnoreCase(boolean b)
        {
            ignoreStringCase = b;
            return this;
        }

        /**
         * Is ignore case on or off?
         *
         * @return True if case is ignored.
         */
        public boolean isIgnoreCase()
        {
            return ignoreStringCase;
        }

        /**
         * Returns the list of clauses in this Criterion.
         *
         * @return an unmodifiable list of the clauses, not null.
         */
        public List<Criterion> getClauses()
        {
            return Collections.unmodifiableList(clauses);
        }

        /**
         * Returns the list of conjunctions in this Criterion
         *
         * @return an unmodifiable list of the conjunctions, not null.
         */
        public List<String> getConjunctions()
        {
            return Collections.unmodifiableList(conjunctions);
        }

        /**
         * Append an AND Criterion onto this Criterion's list.
         */
        public Criterion and(Criterion criterion)
        {
            this.clauses.add(criterion);
            this.conjunctions.add(AND);
            return this;
        }

        /**
         * Append an OR Criterion onto this Criterion's list.
         */
        public Criterion or(Criterion criterion)
        {
            this.clauses.add(criterion);
            this.conjunctions.add(OR);
            return this;
        }

        /**
         * Appends a representation of the Criterion onto the buffer.
         */
        public void appendTo(StringBuffer sb) throws TorqueException
        {
            //
            // it is alright if value == null
            //

            if (column == null)
            {
                return;
            }

            Criterion clause = null;
            for (int j = 0; j < this.clauses.size(); j++)
            {
                sb.append('(');
            }
            if (CUSTOM == comparison)
            {
                if (value != null && !"".equals(value))
                {
                    sb.append((String) value);
                }
            }
            else
            {
                String field = column.getSqlExpression();
                sb.append(field).append(comparison).append(value);
            }

            for (int i = 0; i < this.clauses.size(); i++)
            {
                sb.append(this.conjunctions.get(i));
                clause = this.clauses.get(i);
                clause.appendTo(sb);
                sb.append(')');
            }
        }

        /**
         * Build a string representation of the Criterion.
         *
         * @return A String with the representation of the Criterion.
         */
        @Override
        public String toString()
        {
            //
            // it is alright if value == null
            //
            if (column == null)
            {
                return "";
            }

            StringBuffer expr = new StringBuffer();
            try
            {
                appendTo(expr);
            }
            catch (TorqueException e)
            {
                return "Criterion cannot be evaluated";
            }
            return expr.toString();
        }

        /**
         * This method checks another Criteria.Criterion to see if they contain
         * the same attributes.
         */
        @Override
        public boolean equals(Object obj)
        {
            if (this == obj)
            {
                return true;
            }

            if ((obj == null) || !(obj instanceof Criterion))
            {
                return false;
            }

            Criterion crit = (Criterion) obj;

            boolean isEquiv = column.getSqlExpression().equals(
                        crit.getColumn().getSqlExpression())
                    && comparison.equals(crit.getComparison());

            // we need to check for value equality
            if (isEquiv)
            {
                Object b = crit.getValue();
                if (value instanceof Object[] && b instanceof Object[])
                {
                    isEquiv &= Arrays.equals((Object[]) value, (Object[]) b);
                }
                else if (value instanceof int[] && b instanceof int[])
                {
                    isEquiv &= Arrays.equals((int[]) value, (int[]) b);
                }
                else
                {
                    isEquiv &= value.equals(b);
                }
            }

            // check chained criterion

            isEquiv &= this.clauses.size() == crit.getClauses().size();
            for (int i = 0; i < this.clauses.size(); i++)
            {
                isEquiv &=  conjunctions.get(i)
                        .equals((crit.getConjunctions().get(i)));
                isEquiv &=  clauses.get(i)
                        .equals(crit.getClauses().get(i));
            }

            return isEquiv;
        }

        /**
         * Returns a hash code value for the object.
         */
        @Override
        public int hashCode()
        {
            int h = value.hashCode() ^ comparison.hashCode();

            h ^= column.getSqlExpression().hashCode();

            for (int i = 0; i < this.clauses.size(); i++)
            {
                h ^= (clauses.get(i)).hashCode();
            }

            return h;
        }

        /**
         * Get the name of all tables from nested criterion objects
         *
         * @return the list of tables
         */
        public List<String> getAllTables()
        {
            UniqueList<String> tables = new UniqueList<String>();
            addCriterionTable(this, tables);
            return tables;
        }

        /**
         * method supporting recursion through all criterions to give
         * us a StringStack of tables from each criterion
         */
        private void addCriterionTable(Criterion c, UniqueList<String> s)
        {
            if (c != null)
            {
                s.add(c.getColumn().getTableName());
                for (int i = 0; i < c.getClauses().size(); i++)
                {
                    addCriterionTable((c.getClauses().get(i)), s);
                }
            }
        }

        /**
         * get an array of all criterion attached to this
         * recursing through all sub criterion
         */
        public Criterion[] getAttachedCriterion()
        {
            ArrayList<Criterion> crits = new ArrayList<Criterion>();
            traverseCriterion(this, crits);
            Criterion[] crita = new Criterion[crits.size()];
            for (int i = 0; i < crits.size(); i++)
            {
                crita[i] = crits.get(i);
            }

            return crita;
        }

        /**
         * method supporting recursion through all criterions to give
         * us an ArrayList of them
         */
        private void traverseCriterion(Criterion c, List<Criterion> a)
        {
            if (c != null)
            {
                a.add(c);
                for (int i = 0; i < c.getClauses().size(); i++)
                {
                    traverseCriterion(c.getClauses().get(i), a);
                }
            }
        }
    } // end of inner class Criterion
}
