// Copyright (c) 1998, 2006, Oracle. All rights reserved.  
package oracle.toplink.essentials.queryframework;

import java.util.*;
import oracle.toplink.essentials.internal.helper.*;
import oracle.toplink.essentials.internal.queryframework.*;
import oracle.toplink.essentials.exceptions.*;
import oracle.toplink.essentials.expressions.*;
import oracle.toplink.essentials.internal.sessions.AbstractRecord;
import oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl;
import oracle.toplink.essentials.internal.sessions.AbstractSession;

/**set
 * <p><b>Purpose</b>:
 * Concrete class for all read queries involving a collection of objects.
 * <p>
 * <p><b>Responsibilities</b>:
 * Return a container of the objects generated by the query.
 * Implements the inheritance feature when dealing with abstract descriptors
 *
 * @author Yvon Lavoie
 * @since TOPLink/Java 1.0
 */
public class ReadAllQuery extends ObjectLevelReadQuery {

    /** Used for ordering support. */
    protected Vector orderByExpressions;

    /** Used for collection and stream support. */
    protected ContainerPolicy containerPolicy;

    /**
     * PUBLIC:
     * Return a new read all query.
     * A reference class must be specified before execution.
     * It is better to provide the class and expression builder on construction to esnure a single expression builder is used.
     * If no selection criteria is specified this will read all objects of the class from the database.
     */
    public ReadAllQuery() {
        super();
        this.useCollectionClass(ClassConstants.Vector_class);
    }

    /**
     * PUBLIC:
     * Return a new read all query.
     * It is better to provide the class and expression builder on construction to esnure a single expression builder is used.
     * If no selection criteria is specified this will read all objects of the class from the database.
     */
    public ReadAllQuery(Class classToRead) {
        this();
        setReferenceClass(classToRead);
    }

    /**
     * PUBLIC:
     * Return a new read all query for the class and the selection criteria.
     */
    public ReadAllQuery(Class classToRead, Expression selectionCriteria) {
        this();
        setReferenceClass(classToRead);
        setSelectionCriteria(selectionCriteria);
    }

    /**
     * PUBLIC:
     * Return a new read all query for the class.
     * The expression builder must be used for all associated expressions used with the query.
     */
    public ReadAllQuery(Class classToRead, ExpressionBuilder builder) {
        this();
        this.defaultBuilder = builder;
        setReferenceClass(classToRead);
    }

    /**
     * PUBLIC:
     * Return a new read all query.
     * The call represents a database interaction such as SQL, Stored Procedure.
     */
    public ReadAllQuery(Class classToRead, Call call) {
        this();
        setReferenceClass(classToRead);
        setCall(call);
    }

    /**
     * PUBLIC:
     * The expression builder should be provide on creation to ensure only one is used.
     */
    public ReadAllQuery(ExpressionBuilder builder) {
        this();
        this.defaultBuilder = builder;
    }

    /**
     * PUBLIC:
     * Create a read all query with the database call.
     */
    public ReadAllQuery(Call call) {
        this();
        setCall(call);
    }

    /**
     * PUBLIC:
     * Order the query results by the object's attribute or query key name.
     */
    public void addAscendingOrdering(String queryKeyName) {
        addOrdering(getExpressionBuilder().get(queryKeyName).ascending());
    }

    /**
     * PUBLIC:
     * Order the query results by the object's attribute or query key name.
     */
    public void addDescendingOrdering(String queryKeyName) {
        addOrdering(getExpressionBuilder().get(queryKeyName).descending());
    }

    /**
     * PUBLIC:
     * Add the ordering expression.  This allows for ordering across relationships or functions.
     * Example: readAllQuery.addOrdering(expBuilder.get("address").get("city").toUpperCase().descending())
     */
    public void addOrdering(Expression orderingExpression) {
        getOrderByExpressions().addElement(orderingExpression);
        //Bug2804042 Must un-prepare if prepared as the SQL may change.
        setIsPrepared(false);
    }

    /**
     * INTERNAL:
     * The cache check is done before the prepare as a hit will not require the work to be done.
     */
    protected Object checkEarlyReturnImpl(AbstractSession session, AbstractRecord translationRow) {
        // Check for in-memory only query.
        if (shouldCheckCacheOnly()) {
            // assert !isReportQuery();
            if (shouldUseWrapperPolicy()) {
                getContainerPolicy().setElementDescriptor(getDescriptor());
            }

            // PERF: Fixed to not query each unit of work cache (is not conforming),
            // avoid hashtable and primary key indexing.
            // At some point we may need to support some kind of in-memory with conforming option,
            // but we do not currently allow this.
            AbstractSession rootSession = session;
            while (rootSession.isUnitOfWork()) {
                rootSession = ((UnitOfWorkImpl)rootSession).getParent();
            }
            Vector allCachedVector = rootSession.getIdentityMapAccessor().getAllFromIdentityMap(getSelectionCriteria(), getReferenceClass(), translationRow, getInMemoryQueryIndirectionPolicy(), false);

            // Must ensure that all of the objects returned are correctly registered in the unit of work.
            if (session.isUnitOfWork()) {
                allCachedVector = ((UnitOfWorkImpl)session).registerAllObjects(allCachedVector);
            }

            return getContainerPolicy().buildContainerFromVector(allCachedVector, session);
        } else {
            return null;
        }
    }

    /**
     * INTERNAL:
     * Check to see if a custom query should be used for this query.
     * This is done before the query is copied and prepared/executed.
     * null means there is none.
     */
    protected DatabaseQuery checkForCustomQuery(AbstractSession session, AbstractRecord translationRow) {
        checkDescriptor(session);

        // check if user defined a custom query
        if ((!isUserDefined()) && isExpressionQuery() && (getSelectionCriteria() == null) && (!hasOrderByExpressions()) && (getDescriptor().getQueryManager().hasReadAllQuery())) {
            return getDescriptor().getQueryManager().getReadAllQuery();
        } else {
            return null;
        }
    }

    /**
     * INTERNAL:
     * Clone the query.
     */
    public Object clone() {
        ReadAllQuery cloneQuery = (ReadAllQuery)super.clone();

        // Don't use setters as that will trigger unprepare
        if (hasOrderByExpressions()) {
            cloneQuery.orderByExpressions = (Vector)getOrderByExpressions().clone();
        }
        cloneQuery.containerPolicy = getContainerPolicy().clone(cloneQuery);

        return cloneQuery;
    }

    /**
     * INTERNAL:
     * Conform the result if specified.
     */
    protected Object conformResult(Object result, UnitOfWorkImpl unitOfWork, AbstractRecord arguments, boolean buildDirectlyFromRows) {
        ContainerPolicy cp;
        IdentityHashtable indexedInterimResult = null;
        Expression selectionCriteriaClone = null;

        if (getSelectionCriteria() != null) {
            selectionCriteriaClone = (Expression)getSelectionCriteria().clone();
            selectionCriteriaClone.getBuilder().setSession(unitOfWork);
            selectionCriteriaClone.getBuilder().setQueryClass(getReferenceClass());
        }
        cp = getContainerPolicy();

        // This code is now a great deal different...  For one, registration is done
        // as part of conforming.  Also, this should only be called if one actually
        // is conforming.
        // First scan the UnitOfWork for conforming instances.
        // This will walk through the entire cache of registered objects.
        // Let p be objects from result not in the cache.
        // Let c be objects from cache.
        // Presently p intersect c = empty set, but later p subset c.
        // By checking cache now doesConform will be called p fewer times.
        indexedInterimResult = unitOfWork.scanForConformingInstances(selectionCriteriaClone, getReferenceClass(), arguments, this);

        // Now conform the result from the database.
        // Remove any deleted or changed objects that no longer conform.
        // Deletes will only work for simple queries, queries with or's or anyof's may not return
        // correct results when untriggered indirection is in the model.		
        Vector fromDatabase = null;

        // When building directly from rows, one of the performance benefits
        // is that we no longer have to wrap and then unwrap the originals.
        // result is just a vector, not a container of wrapped originals.
        if (buildDirectlyFromRows) {
            Vector rows = (Vector)result;
            Set identitySet = null;
            fromDatabase = new Vector(rows.size());
            for (int i = 0; i < rows.size(); i++) {
                Object object = rows.elementAt(i);

                // null is placed in the row collection for 1-m joining to filter duplicate rows.
                if (object != null) {
                    Object clone = conformIndividualResult(object, unitOfWork, arguments, selectionCriteriaClone, indexedInterimResult, buildDirectlyFromRows);
                    if (clone != null) {
                        // Avoid duplicates if -m joining was used and a cache hit occured.
                        if (getJoinedAttributeManager().isToManyJoin()) {
                            if (identitySet == null) {
                                identitySet = new TopLinkIdentityHashSet(rows.size());
                            }
                            if (!identitySet.contains(clone)) {
                                identitySet.add(clone);
                                fromDatabase.addElement(clone);
                            }
                        } else {
                            fromDatabase.addElement(clone);
                        }
                    }
                }
            }
        } else {
            fromDatabase = new Vector(cp.sizeFor(result));
            AbstractSession sessionToUse = unitOfWork.getParent();
            for (Object iter = cp.iteratorFor(result); cp.hasNext(iter);) {
                Object object = cp.next(iter, sessionToUse);
                Object clone = conformIndividualResult(object, unitOfWork, arguments, selectionCriteriaClone, indexedInterimResult, buildDirectlyFromRows);
                if (clone != null) {
                    fromDatabase.addElement(clone);
                }
            }
        }

        // Now add the unwrapped conforming instances into an appropriate container.  
        // Wrapping is done automatically.
        // Make sure a vector of exactly the right size is returned.
        //
        Object conformedResult = cp.containerInstance(indexedInterimResult.size() + fromDatabase.size());
        Object eachClone;
        for (Enumeration enumtr = indexedInterimResult.elements(); enumtr.hasMoreElements();) {
            eachClone = enumtr.nextElement();
            cp.addInto(eachClone, conformedResult, unitOfWork);
        }
        for (Enumeration enumtr = fromDatabase.elements(); enumtr.hasMoreElements();) {
            eachClone = enumtr.nextElement();
            cp.addInto(eachClone, conformedResult, unitOfWork);
        }
        return conformedResult;
    }

    /**
     * INTERNAL:
     * Execute the query.
     * Get the rows and build the object from the rows.
     * @exception  DatabaseException - an error has occurred on the database
     * @return java.lang.Object collection of objects resulting from execution of query.
     */
    protected Object executeObjectLevelReadQuery() throws DatabaseException {
        Object result = null;
        if (getContainerPolicy().overridesRead()) {
            return getContainerPolicy().execute();
        }

        Vector rows = getQueryMechanism().selectAllRows();
        setExecutionTime(System.currentTimeMillis());
        // If using -m joins, must set all rows.
        if (getJoinedAttributeManager().isToManyJoin()) {
            getJoinedAttributeManager().setDataResults(rows, getSession());
        }

        if (getSession().isUnitOfWork()) {
            result = registerResultInUnitOfWork(rows, (UnitOfWorkImpl)getSession(), getTranslationRow(), true);// 
        } else {
            result = getQueryMechanism().buildObjectsFromRows(rows);
        }

        if (shouldIncludeData()) {
            ComplexQueryResult complexResult = new ComplexQueryResult();
            complexResult.setResult(result);
            complexResult.setData(rows);
            return complexResult;
        }

        return result;
    }

    /**
     * INTERNAL:
     * Return the query's container policy.
     * @return oracle.toplink.essentials.internal.queryframework.ContainerPolicy
     */
    public ContainerPolicy getContainerPolicy() {
        return containerPolicy;
    }

    /**
     * INTERNAL:
     * Return the order expressions for the query.
     */
    public Vector getOrderByExpressions() {
        if (orderByExpressions == null) {
            orderByExpressions = new Vector();
        }
        return orderByExpressions;
    }

    /**
     * INTERNAL:
     * The order bys are lazy initialized to conserv space.
     */
    public boolean hasOrderByExpressions() {
        return orderByExpressions != null;
    }

    /**
     * INTERNAL:
     * Verify that we have hierarchical query expressions
     */
    public boolean hasHierarchicalExpressions() {
        return false;
    }

    /**
     * INTERNAL:
     * Return true is this query has batching
     */
    public boolean hasBatchReadAttributes() {
        return false;
    }

    /**
     * INTERNAL:
     * Return if the attribute is specified for batch reading.
     */
    public boolean isAttributeBatchRead(String attributeName) {
        return false;
    }

    /**
     * PUBLIC:
     * Return if this is a read all query.
     */
    public boolean isReadAllQuery() {
        return true;
    }

    /**
     * INTERNAL:
     * Prepare the receiver for execution in a session.
     */
    protected void prepare() throws QueryException {
        super.prepare();

        getContainerPolicy().prepare(this, getSession());

        if (getContainerPolicy().overridesRead()) {
            return;
        }

        prepareSelectAllRows();
    }

    /**
     * INTERNAL:
     * Set the properties needed to be cascaded into the custom query.
     */
    protected void prepareCustomQuery(DatabaseQuery customQuery) {
        ReadAllQuery customReadQuery = (ReadAllQuery)customQuery;
        customReadQuery.setContainerPolicy(getContainerPolicy());
        customReadQuery.setCascadePolicy(getCascadePolicy());
        customReadQuery.setShouldRefreshIdentityMapResult(shouldRefreshIdentityMapResult());
        customReadQuery.setShouldMaintainCache(shouldMaintainCache());
        customReadQuery.setShouldUseWrapperPolicy(shouldUseWrapperPolicy());
    }

    /**
     * INTERNAL:
     * Prepare the receiver for execution in a session.
     */
    public void prepareForExecution() throws QueryException {
        super.prepareForExecution();

        getContainerPolicy().prepareForExecution();

    }

    /**
     * INTERNAL:
     * Prepare the mechanism.
     */
    protected void prepareSelectAllRows() {
        getQueryMechanism().prepareSelectAllRows();
    }

    /**
     * INTERNAL:
     * All objects queried via a UnitOfWork get registered here.  If the query
     * went to the database.
     * <p>
     * Involves registering the query result individually and in totality, and
     * hence refreshing / conforming is done here.
     *
     * @param result may be collection (read all) or an object (read one),
     * or even a cursor.  If in transaction the shared cache will
     * be bypassed, meaning the result may not be originals from the parent
     * but raw database rows.
     * @param unitOfWork the unitOfWork the result is being registered in.
     * @param arguments the original arguments/parameters passed to the query
     * execution.  Used by conforming
     * @param buildDirectlyFromRows If in transaction must construct
     * a registered result from raw database rows.
     *
     * @return the final (conformed, refreshed, wrapped) UnitOfWork query result
     */
    public Object registerResultInUnitOfWork(Object result, UnitOfWorkImpl unitOfWork, AbstractRecord arguments, boolean buildDirectlyFromRows) {
        // For bug 2612366: Conforming results in UOW extremely slow.
        // Replacing results with registered versions in the UOW is a part of 
        // conforming and is now done while conforming to maximize performance.
        if (shouldConformResultsInUnitOfWork() || getDescriptor().shouldAlwaysConformResultsInUnitOfWork()) {
            return conformResult(result, unitOfWork, arguments, buildDirectlyFromRows);
        }

        // When building directly from rows, one of the performance benefits
        // is that we no longer have to wrap and then unwrap the originals.
        // result is just a vector, not a collection of wrapped originals.
        // Also for cursors the initial connection is automatically registered.
        if (buildDirectlyFromRows) {
            Vector rows = (Vector)result;
            Set identitySet = null;
            ContainerPolicy cp = getContainerPolicy();
            Object clones = cp.containerInstance(rows.size());
            for (Enumeration enumtr = rows.elements(); enumtr.hasMoreElements();) {
                Object row = enumtr.nextElement();

                // null is placed in the row collection for 1-m joining to filter duplicate rows.
                if (row != null) {
                    Object clone = registerIndividualResult(row, unitOfWork, buildDirectlyFromRows);

                    // Avoid duplicates if -m joining was used and a cache hit occured.
                    if (getJoinedAttributeManager().isToManyJoin()) {
                        if (identitySet == null) {
                            identitySet = new TopLinkIdentityHashSet(rows.size());
                        }
                        if (!identitySet.contains(clone)) {
                            identitySet.add(clone);
                            cp.addInto(clone, clones, unitOfWork);
                        }
                    } else {
                        cp.addInto(clone, clones, unitOfWork);
                    }
                }
            }
            return clones;
        }

        ContainerPolicy cp;
        cp = getContainerPolicy();

        Object clones = cp.containerInstance(cp.sizeFor(result));
        AbstractSession sessionToUse = unitOfWork.getParent();
        for (Object iter = cp.iteratorFor(result); cp.hasNext(iter);) {
            Object object = cp.next(iter, sessionToUse);
            Object clone = registerIndividualResult(object, unitOfWork, buildDirectlyFromRows);
            cp.addInto(clone, clones, unitOfWork);
        }
        return clones;
    }

    /**
     * PUBLIC:
     * Set the container policy. Used to support different containers
     * (e.g. Collections, Maps).
     */
    public void setContainerPolicy(ContainerPolicy containerPolicy) {
        // CR#... a container policy is always required, default is vector,
        // required for deployment XML.
        if (containerPolicy == null) {
            return;
        }
        this.containerPolicy = containerPolicy;
        setIsPrepared(false);
    }

    /**
     * INTERNAL:
     * Set the order expressions for the query.
     */
    public void setOrderByExpressions(Vector orderByExpressions) {
        this.orderByExpressions = orderByExpressions;
    }

    /**
     * PUBLIC:
     * Configure the mapping to use an instance of the specified container class
     * to hold the target objects.
     * <p>jdk1.2.x: The container class must implement (directly or indirectly) the Collection interface.
     * <p>jdk1.1.x: The container class must be a subclass of Vector.
     */
    public void useCollectionClass(Class concreteClass) {
        // Set container policy.
        setContainerPolicy(ContainerPolicy.buildPolicyFor(concreteClass));

    }

    /**
     * PUBLIC:
     * Configure the query to use an instance of the specified container class
     * to hold the result objects. The key used to index the value in the Map
     * is the value returned by a call to the specified zero-argument method.
     * The method must be implemented by the class (or a superclass) of the
     * value to be inserted into the Map.
     * <p>jdk1.2.x: The container class must implement (directly or indirectly) the Map interface.
     * <p>jdk1.1.x: The container class must be a subclass of Hashtable.
     * <p>The referenceClass must set before calling this method.
     */
    public void useMapClass(Class concreteClass, String methodName) {
        // the reference class has to be specified before coming here
        if (getReferenceClass() == null) {
            throw QueryException.referenceClassMissing(this);
        }
        ContainerPolicy policy = ContainerPolicy.buildPolicyFor(concreteClass);
        policy.setKeyName(methodName, getReferenceClass().getName());
        setContainerPolicy(policy);
    }
}
