/*
 * 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.
 */
package org.apache.openjpa.kernel;

import java.io.Serializable;
import java.security.AccessController;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.enhance.PersistenceCapable;
import org.apache.openjpa.kernel.exps.AggregateListener;
import org.apache.openjpa.kernel.exps.Constant;
import org.apache.openjpa.kernel.exps.FilterListener;
import org.apache.openjpa.kernel.exps.Literal;
import org.apache.openjpa.kernel.exps.Path;
import org.apache.openjpa.kernel.exps.QueryExpressions;
import org.apache.openjpa.kernel.exps.Val;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.rop.BatchedResultObjectProvider;
import org.apache.openjpa.lib.rop.EagerResultList;
import org.apache.openjpa.lib.rop.MergedResultObjectProvider;
import org.apache.openjpa.lib.rop.RangeResultObjectProvider;
import org.apache.openjpa.lib.rop.ResultList;
import org.apache.openjpa.lib.rop.ResultObjectProvider;
import org.apache.openjpa.lib.util.ClassUtil;
import org.apache.openjpa.lib.util.J2DoPrivHelper;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.OrderedMap;
import org.apache.openjpa.lib.util.ReferenceHashSet;
import org.apache.openjpa.lib.util.StringUtil;
import org.apache.openjpa.lib.util.collections.AbstractReferenceMap;
import org.apache.openjpa.lib.util.collections.LinkedMap;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.meta.MetaDataRepository;
import org.apache.openjpa.util.GeneralException;
import org.apache.openjpa.util.ImplHelper;
import org.apache.openjpa.util.InvalidStateException;
import org.apache.openjpa.util.NoResultException;
import org.apache.openjpa.util.NonUniqueResultException;
import org.apache.openjpa.util.OpenJPAException;
import org.apache.openjpa.util.UnsupportedException;
import org.apache.openjpa.util.UserException;


/**
 * Implementation of the {@link Query} interface.
 *
 * @author Abe White
 */
public class QueryImpl implements Query {
    private static final long serialVersionUID = 1L;

    private static final Localizer _loc = Localizer.forPackage(QueryImpl.class);

    private final String _language;
    private final StoreQuery _storeQuery;
    private transient final BrokerImpl _broker;
    private transient final Log _log;
    private transient ClassLoader _loader = null;

    // query has its own internal lock
    private ReentrantLock _lock;

    // unparsed state
    private Class<?> _class = null;
    private boolean _subclasses = true;
    private boolean _readOnly = false;
    private String _query = null;
    private String _params = null;

    // parsed state
    private transient Compilation _compiled = null;
    private transient boolean _compiling = false;
    private transient ResultPacker _packer = null;

    // candidates
    private transient Collection<?> _collection = null;
    private transient Extent _extent = null;

    // listeners
    private Map<String,FilterListener> _filtListeners = null;
    private Map<String,AggregateListener> _aggListeners = null;

    // configuration for loading objects
    private FetchConfiguration _fc = null;
    private boolean _ignoreChanges = false;
    private Class<?> _resultMappingScope = null;
    private String _resultMappingName = null;

    // these fields should only be used directly after we have a compilation,
    // because their values may be encoded in the query string
    private Boolean _unique = null;
    private Class<?> _resultClass = null;
    private transient long _startIdx = 0;
    private transient long _endIdx = Long.MAX_VALUE;
    private transient boolean _rangeSet = false;

    // remember the list of all the results we have returned so we
    // can free their resources when close or closeAll is called
    private transient final Collection<RemoveOnCloseResultList> _resultLists =
        new ReferenceHashSet(AbstractReferenceMap.ReferenceStrength.WEAK);

    private boolean _printParameters = false;
    /**
     * Construct a query managed by the given broker.
     */
    public QueryImpl(Broker broker, String language, StoreQuery storeQuery) {
        _broker = (BrokerImpl) broker;
        _language = language;
        _storeQuery = storeQuery;
        _fc = (FetchConfiguration) broker.getFetchConfiguration().clone();
        _log = broker.getConfiguration().getLog(OpenJPAConfiguration.LOG_QUERY);
        _storeQuery.setContext(this);
        _printParameters = _broker.getPrintParameters();
        if (_broker != null && _broker.getMultithreaded())
            _lock = new ReentrantLock();
    }

    /**
     * Internal store query.
     */
    public StoreQuery getStoreQuery() {
        return _storeQuery;
    }

    @Override
    public Broker getBroker() {
        return _broker;
    }

    @Override
    public Query getQuery() {
        return this;
    }

    @Override
    public StoreContext getStoreContext() {
        return _broker;
    }

    @Override
    public String getLanguage() {
        return _language;
    }

    @Override
    public FetchConfiguration getFetchConfiguration() {
        return _fc;
    }

    @Override
    public String getQueryString() {
        return _query;
    }

    @Override
    public boolean getIgnoreChanges() {
        assertOpen();
        return _ignoreChanges;
    }

    @Override
    public void setIgnoreChanges(boolean flag) {
        lock();
        try {
            assertOpen();
            // allowed modification: no read-only check
            _ignoreChanges = flag;
        } finally {
            unlock();
        }
    }

    @Override
    public boolean isReadOnly() {
        assertOpen();
        return _readOnly;
    }

    @Override
    public void setReadOnly(boolean flag) {
        lock();
        try {
            assertOpen();
            _readOnly = flag;
        } finally {
            unlock();
        }
    }

    @Override
    public void addFilterListener(FilterListener listener) {
        lock();
        try {
            assertOpen();
            assertNotReadOnly();
            if (_filtListeners == null)
                _filtListeners = new HashMap<>(5);
            _filtListeners.put(listener.getTag(), listener);
        } finally {
            unlock();
        }
    }

    @Override
    public void removeFilterListener(FilterListener listener) {
        lock();
        try {
            assertOpen();
            assertNotReadOnly();
            if (_filtListeners != null)
                _filtListeners.remove(listener.getTag());
        } finally {
            unlock();
        }
    }

    @Override
    public Collection<FilterListener> getFilterListeners() {
        if (_filtListeners == null)
            return Collections.emptyList();
        else
            return _filtListeners.values();
    }

    @Override
    public FilterListener getFilterListener(String tag) {
        // first check listeners for this query
        if (_filtListeners != null) {
            FilterListener listen = _filtListeners.get(tag);
            if (listen != null)
                return listen;
        }

        // check user-defined listeners from configuration
        FilterListener[] confListeners = _broker.getConfiguration().
            getFilterListenerInstances();
        for (FilterListener confListener : confListeners)
            if (confListener.getTag().equals(tag))
                return confListener;

        // check store listeners
        return _storeQuery.getFilterListener(tag);
    }

    @Override
    public void addAggregateListener(AggregateListener listener) {
        lock();
        try {
            assertOpen();
            assertNotReadOnly();
            if (_aggListeners == null)
                _aggListeners = new HashMap<>(5);
            _aggListeners.put(listener.getTag(), listener);
        } finally {
            unlock();
        }
    }

    @Override
    public void removeAggregateListener(AggregateListener listener) {
        lock();
        try {
            assertOpen();
            assertNotReadOnly();
            if (_aggListeners != null)
                _aggListeners.remove(listener.getTag());
        } finally {
            unlock();
        }
    }

    @Override
    public Collection<AggregateListener> getAggregateListeners() {
        if (_aggListeners == null)
            return Collections.emptyList();
        else
            return _aggListeners.values();
    }

    @Override
    public AggregateListener getAggregateListener(String tag) {
        // first check listeners for this query
        if (_aggListeners != null) {
            AggregateListener listen = _aggListeners.
                get(tag);
            if (listen != null)
                return listen;
        }

        // check user-defined listeners from configuration
        AggregateListener[] confListeners = _broker.getConfiguration().
            getAggregateListenerInstances();
        for (AggregateListener confListener : confListeners)
            if (confListener.getTag().equals(tag))
                return confListener;

        // check store listeners
        return _storeQuery.getAggregateListener(tag);
    }

    @Override
    public Extent getCandidateExtent() {
        // if just the class is set, fetch the corresponding extent; if the
        // extent is already set but its ignore cache setting is wrong,
        // get a new extent with the correct setting (don't modify orig extent
        // in case the user has a reference to it and might use it)
        lock();
        try {
            Class<?> cls = getCandidateType();
            if (_extent == null && _collection == null && _broker != null
                && cls != null) {
                _extent = _broker.newExtent(cls, _subclasses);
                _extent.setIgnoreChanges(_ignoreChanges);
            } else if (_extent != null
                && _extent.getIgnoreChanges() != _ignoreChanges && cls != null){
                _extent = _broker.newExtent(cls, _extent.hasSubclasses());
                _extent.setIgnoreChanges(_ignoreChanges);
            }
            return _extent;
        } finally {
            unlock();
        }
    }

    @Override
    public void setCandidateExtent(Extent candidateExtent) {
        lock();
        try {
            assertOpen();
            assertNotReadOnly();

            if (candidateExtent == _extent)
                return;
            if (candidateExtent == null) {
                _extent = null;
                return;
            }

            // if extent then not collection
            _extent = candidateExtent;
            _collection = null;

            boolean invalidate = false;
            if (_extent.getElementType() != _class) {
                _class = _extent.getElementType();
                _loader = null;
                invalidate = true;
            }
            if (_extent.hasSubclasses() != _subclasses) {
                _subclasses = _extent.hasSubclasses();
                invalidate = true;
            }
            if (invalidate)
                invalidateCompilation();
        } finally {
            unlock();
        }
    }

    @Override
    public Collection<?> getCandidateCollection() {
        assertOpen();
        return _collection;
    }

    @Override
    public void setCandidateCollection(Collection<?> candidateCollection) {
        if (!_storeQuery.supportsInMemoryExecution())
            throw new UnsupportedException(_loc.get("query-nosupport",
                _language));

        lock();
        try {
            assertOpen();

            // if collection then not extent
            _collection = candidateCollection;
            if (_collection != null)
                _extent = null;
        } finally {
            unlock();
        }
    }

    @Override
    public Class getCandidateType() {
        lock();
        try {
            assertOpen();
            if (_class != null || _compiled != null || _query == null
                || _broker == null)
                return _class;

            // check again after compilation; maybe encoded in string
            compileForCompilation();
            return _class;
        } finally {
            unlock();
        }
    }

    @Override
    public void setCandidateType(Class candidateClass, boolean subs) {
        lock();
        try {
            assertOpen();
            assertNotReadOnly();
            _class = candidateClass;
            _subclasses = subs;
            _loader = null;
            invalidateCompilation();
        } finally {
            unlock();
        }
    }

    @Override
    public boolean hasSubclasses() {
        return _subclasses;
    }

    @Override
    public String getResultMappingName() {
        assertOpen();
        return _resultMappingName;
    }

    @Override
    public Class getResultMappingScope() {
        assertOpen();
        return _resultMappingScope;
    }

    @Override
    public void setResultMapping(Class<?> scope, String name) {
        lock();
        try {
            assertOpen();
            _resultMappingScope = scope;
            _resultMappingName = name;
            _packer = null;
        } finally {
            unlock();
        }
    }

    @Override
    public boolean isUnique() {
        lock();
        try {
            assertOpen();
            if (_unique != null)
                return _unique;
            if ((_query == null && _language.endsWith("JPQL")) || _compiling || _broker == null)
                return false;

            // check again after compilation; maybe encoded in string
            if (_compiled == null) {
                compileForCompilation();
                if (_unique != null)
                    return _unique;
            }

            // no explicit setting; default
            StoreQuery.Executor ex = compileForExecutor();
            if (!ex.isAggregate(_storeQuery))
                return false;
            return !ex.hasGrouping(_storeQuery);
        } finally {
            unlock();
        }
    }

    /**
     * Affirms if this query has originated by parsing a string-based query.
     */
    public boolean isParsedQuery() {
        return getQueryString() != null;
    }

    @Override
    public void setUnique(boolean unique) {
        lock();
        try {
            assertOpen();
            assertNotReadOnly();
            _unique = (unique) ? Boolean.TRUE : Boolean.FALSE;
        } finally {
            unlock();
        }
    }

    @Override
    public Class getResultType() {
        lock();
        try {
            assertOpen();
            if (_resultClass != null || _compiled != null || _query == null
                || _broker == null)
                return _resultClass;

            // check again after compilation; maybe encoded in string
            compileForCompilation();
            return _resultClass;
        } finally {
            unlock();
        }
    }

    @Override
    public void setResultType(Class cls) {
        lock();
        try {
            assertOpen();
            // allowed modification: no read-only check
            _resultClass = cls;
            _packer = null;
        } finally {
            unlock();
        }
    }

    @Override
    public long getStartRange() {
        assertOpen();
        return _startIdx;
    }

    @Override
    public long getEndRange() {
        assertOpen();
        return _endIdx;
    }

    @Override
    public void setRange(long start, long end) {
        if (start < 0 || end < 0)
            throw new UserException(_loc.get("invalid-range",
                String.valueOf(start), String.valueOf(end)));

        if (end - start > Integer.MAX_VALUE && end != Long.MAX_VALUE)
            throw new UserException(_loc.get("range-too-big",
                String.valueOf(start), String.valueOf(end)));

        lock();
        try {
            assertOpen();
            // allowed modification: no read-only check
            _startIdx = start;
            _endIdx = end;
            _rangeSet = true;
        } finally {
            unlock();
        }
    }

    @Override
    public String getParameterDeclaration() {
        lock();
        try {
            assertOpen();
            if (_params != null || _compiled != null || _compiling
                || _broker == null)
                return _params;

            // check again after compilation; maybe encoded in string
            compileForCompilation();
            return _params;
        } finally {
            unlock();
        }
    }

    @Override
    public void declareParameters(String params) {
        if (!_storeQuery.supportsParameterDeclarations())
            throw new UnsupportedException(_loc.get("query-nosupport",
                _language));

        lock();
        try {
            assertOpen();
            assertNotReadOnly();
            _params = StringUtil.trimToNull(params);
            invalidateCompilation();
        } finally {
            unlock();
        }
    }

    @Override
    public void compile() {
        lock();
        try {
            assertOpen();
            StoreQuery.Executor ex = compileForExecutor();
            getResultPacker(_storeQuery, ex);
            ex.validate(_storeQuery);
        } finally {
            unlock();
        }
    }

    @Override
    public Object getCompilation() {
        lock();
        try {
            return compileForCompilation().storeData;
        } finally {
            unlock();
        }
    }

    /**
     * Compile query properties.
     */
    private Compilation compileForCompilation() {
        if (_compiled != null || _compiling)
            return _compiled;

        assertNotSerialized();
        assertOpen();

        boolean readOnly = _readOnly;
        _readOnly = false;
        _compiling = true;
        try {
            _compiled = compilationFromCache();
            return _compiled;
        } catch (OpenJPAException ke) {
            throw ke;
        } catch (RuntimeException re) {
            throw new GeneralException(re);
        } finally {
            _compiling = false;
            _readOnly = readOnly;
        }
    }

    /**
     * Find the cached compilation for the current query, creating one if it
     * does not exist.
     */
    @SuppressWarnings("unchecked")
    protected Compilation compilationFromCache() {
        Map compCache = _broker.getConfiguration().getQueryCompilationCacheInstance();
        if (compCache == null || !isParsedQuery()) {
            return newCompilation();
        } else {
            CompilationKey key = new CompilationKey();
            key.queryType = _storeQuery.getClass();
            key.candidateType = getCandidateType();
            key.subclasses = hasSubclasses();
            key.query = getQueryString();
            key.language = getLanguage();
            key.storeKey = _storeQuery.newCompilationKey();
            Compilation comp = (Compilation) compCache.get(key);

            // parse declarations if needed
            if (comp == null) {
                comp = newCompilation();
                // only cache those queries that can be compiled
                if (comp.storeData != null) {

                    synchronized (compCache) {
                        Compilation existingComp = (Compilation) compCache.get(key);
                        if (existingComp == null) {
                            compCache.put(key, comp);
                        } else {
                            comp = existingComp;
                        }
                    }
                }
            } else {
                _storeQuery.populateFromCompilation(comp.storeData);
            }

            return comp;
        }
    }

    /**
     * Create and populate a new compilation.
     */
    private Compilation newCompilation() {
        Compilation comp = new Compilation();
        comp.storeData = _storeQuery.newCompilation();
        _storeQuery.populateFromCompilation(comp.storeData);
        return comp;
    }

    /**
     * Compile for execution, choosing between datastore and in-mem
     * compilation based on what we support and our settings.
     */
    private StoreQuery.Executor compileForExecutor() {
        Compilation comp = compileForCompilation();
        if (_collection == null) {
            if (comp.datastore != null)
                return comp.datastore;
            if (comp.memory != null)
                return comp.memory;
            if (_storeQuery.supportsDataStoreExecution())
                return compileForDataStore(comp);
            return compileForInMemory(comp);
        }

        if (comp.memory != null)
            return comp.memory;
        if (comp.datastore != null)
            return comp.datastore;
        if (_storeQuery.supportsInMemoryExecution())
            return compileForInMemory(comp);
        return compileForDataStore(comp);
    }

    /**
     * Create an expression tree for datastore execution.
     */
    private StoreQuery.Executor compileForDataStore(Compilation comp) {
        if (comp.datastore == null)
            comp.datastore = createExecutor(false);
        return comp.datastore;
    }

    /**
     * Create an expression tree for in-memory execution.
     */
    private StoreQuery.Executor compileForInMemory(Compilation comp) {
        if (comp.memory == null)
            comp.memory = createExecutor(true);
        return comp.memory;
    }

    /**
     * Return a query executor of the proper type.
     */
    private StoreQuery.Executor createExecutor(boolean inMem) {
        assertCandidateType();

        MetaDataRepository repos = _broker.getConfiguration().
            getMetaDataRepositoryInstance();
        ClassMetaData meta = repos.getMetaData(_class,
            _broker.getClassLoader(), false);

        ClassMetaData[] metas;
        if (_class == null || _storeQuery.supportsAbstractExecutors())
            metas = new ClassMetaData[]{ meta };
        else if (_subclasses && (meta == null || meta.isManagedInterface()))
            metas = repos.getImplementorMetaDatas(_class,
                _broker.getClassLoader(), true);
        else if (meta != null && (_subclasses || meta.isMapped()))
            metas = new ClassMetaData[]{ meta };
        else
            metas = StoreQuery.EMPTY_METAS;

        if (metas.length == 0)
            throw new UserException(_loc.get("no-impls", _class));
        try {
            if (metas.length == 1) {
                if (inMem)
                    return _storeQuery.newInMemoryExecutor(metas[0],
                        _subclasses);
                return _storeQuery.newDataStoreExecutor(metas[0], _subclasses);
            }

            // multiple implementors
            StoreQuery.Executor[] es = new StoreQuery.Executor[metas.length];
            for (int i = 0; i < es.length; i++) {
                if (inMem)
                    es[i] = _storeQuery.newInMemoryExecutor(metas[i], true);
                else
                    es[i] = _storeQuery.newDataStoreExecutor(metas[i], true);
            }
            return new MergedExecutor(es);
        } catch (OpenJPAException ke) {
            throw ke;
        } catch (RuntimeException re) {
            throw new GeneralException(re);
        }
    }

    /**
     * Clear any compilation, forcing this query to be recompiled
     * next time it's executed. This should be invoked whenever any
     * state changes that would cause the underlying query
     * representation to change.
     *
     * @since 0.3.0
     */
    private boolean invalidateCompilation() {
        if (_compiling)
            return false;
        _storeQuery.invalidateCompilation();
        _compiled = null;
        _packer = null;
        return true;
    }

    @Override
    public Object execute() {
        return execute((Object[]) null);
    }

    @Override
    public Object execute(Object[] params) {
        return execute(OP_SELECT, params);
    }

    @Override
    public Object execute(Map params) {
        return execute(OP_SELECT, params);
    }

    private Object execute(int operation, Object[] params) {
        if (params == null)
            params = StoreQuery.EMPTY_OBJECTS;

        lock();
        try {
            assertNotSerialized();
            _broker.beginOperation(true);
            try {
                assertOpen();
                _broker.assertNontransactionalRead();

                // get executor
                Compilation comp = compileForCompilation();
                StoreQuery.Executor ex = (isInMemory(operation))
                    ? compileForInMemory(comp) : compileForDataStore(comp);

                assertParameters(_storeQuery, ex, params);
                if (_log.isTraceEnabled())
                    logExecution(operation, ex.getOrderedParameterTypes(_storeQuery),
                        params);

                if (operation == OP_SELECT)
                    return execute(_storeQuery, ex, params);
                if (operation == OP_DELETE)
                    return delete(_storeQuery, ex, params);
                if (operation == OP_UPDATE)
                    return update(_storeQuery, ex, params);
                throw new UnsupportedException();
            } catch (OpenJPAException ke) {
                throw ke;
            } catch (Exception e) {
                throw new UserException(e);
            } finally {
                _broker.endOperation();
            }
        }
        finally {
            unlock();
        }
    }

    private Object execute(int operation, Map params) {
        if (params == null)
            params = Collections.EMPTY_MAP;

        lock();
        try {
            _broker.beginOperation(true);
            try {
                assertNotSerialized();
                assertOpen();
                _broker.assertNontransactionalRead();

                // get executor
                Compilation comp = compileForCompilation();
                StoreQuery.Executor ex = (isInMemory(operation))
                    ? compileForInMemory(comp) : compileForDataStore(comp);

                assertParameters(_storeQuery, ex, params);
                Object[] arr = (params.isEmpty()) ? StoreQuery.EMPTY_OBJECTS :
                    ex.toParameterArray(_storeQuery, params);
                if (_log.isTraceEnabled())
                    logExecution(operation, params);

                if (operation == OP_SELECT)
                    return execute(_storeQuery, ex, arr);
                if (operation == OP_DELETE)
                    return delete(_storeQuery, ex, arr);
                if (operation == OP_UPDATE)
                    return update(_storeQuery, ex, arr);
                throw new UnsupportedException();
            } catch (OpenJPAException ke) {
                throw ke;
            } catch (Exception e) {
                throw new UserException(_loc.get("query-execution-error",
                        _query), e);
            } finally {
                _broker.endOperation();
            }
        }
        finally {
            unlock();
        }
    }

    @Override
    public long deleteAll() {
        return deleteAll((Object[]) null);
    }

    @Override
    public long deleteAll(Object[] params) {
        return ((Number) execute(OP_DELETE, params)).longValue();
    }

    @Override
    public long deleteAll(Map params) {
        return ((Number) execute(OP_DELETE, params)).longValue();
    }

    @Override
    public long updateAll() {
        return updateAll((Object[]) null);
    }

    @Override
    public long updateAll(Object[] params) {
        return ((Number) execute(OP_UPDATE, params)).longValue();
    }

    @Override
    public long updateAll(Map params) {
        return ((Number) execute(OP_UPDATE, params)).longValue();
    }

    /**
     * Converts the values of given <code>params</code> Map into an array in
     * consultation of the <code>paramTypes</code> Map.
     *
     * The indexing of the resultant array is significant for following
     * interrelated but tacit assumptions:
     * The values in the returned Object[] is consumed by {@link Parameter}
     * expressions. Query parsing creates these Parameters and sets their
     * key and index. The index set on the Parameter by the parser is the
     * same index used to access the Object[] elements returned by this method.
     *
     * {@link JPQLExpressionBuilder} creates and populates parameters as
     * follows:
     * The parameter key is not the token encountered by the parser, but
     * converted to Integer or String based on the context in which the token
     * appeared.
     * The index for positional (Integer) parameter is the value of the key
     * minus 1.
     * The index for named (String) parameter is the order in which the
     * token appeared to parser during scanning.
     *
     *
     * The first LinkedMap argument to this method is the result of parsing.
     * This LinkedMap contains the parameter key and their expected
     * (if determinable) value types. That it is a LinkedMap points to the
     * fact that an ordering is implicit. The ordering of the keys in this Map
     * is the same as the order in which parser encountered the parameter
     * tokens.
     *
     * For example, parsing result of the following two JPQL queries
     *   a) UPDATE CompUser e SET e.name= ?1, e.age = ?2 WHERE e.userid = ?3
     *   b) UPDATE CompUser e SET e.name= :name, e.age = :age WHERE e.userid =
     *          :id
     * The parameter keys will appear in the order (3,2,1) or (:id, :name, :age)
     * in the given LinkedMap because WHERE clause is parsed before SET clause.
     * The corresponding Parameter Expressions created by the parser will have
     * following key and index:
     *    a) 1:0, 2:1, 3:2
     *    b) name:1, age:2, id:0
     *
     * The purpose of this method is to produce an Object[] with an indexing
     * scheme that matches the indexing of the Parameter Expression.
     * The user map (the second argument) should produce the following Object[]
     * in two above-mentioned cases
     *   a) {1:"Shannon",2:29,3:5032} --> ["Shannon", 29, 5032]
     *   b) {"name":"Shannon", "age":29, "id":5032} --> [5032, "Shannon", 29]
     *
     */

    /**
     * Return whether we should execute this query in memory.
     */
    private boolean isInMemory(int operation) {
        // if there are any dirty instances in the current trans that are
        // involved in this query, we have to execute in memory or flush
        boolean inMem = !_storeQuery.supportsDataStoreExecution()
            || _collection != null;
        if (!inMem && (!_ignoreChanges || operation != OP_SELECT)
            && _broker.isActive() && isAccessPathDirty()) {
            int flush = _fc.getFlushBeforeQueries();
            if ((flush == FLUSH_TRUE
                || (flush == FLUSH_WITH_CONNECTION && _broker.hasConnection())
                || operation != OP_SELECT
                || !_storeQuery.supportsInMemoryExecution())
                && _broker.getConfiguration().supportedOptions().
                contains(OpenJPAConfiguration.OPTION_INC_FLUSH)) {
                _broker.flush();
            } else {
                if (_log.isInfoEnabled())
                    _log.info(_loc.get("force-in-mem", _class));
                inMem = true;
            }
        }

        if (inMem && !_storeQuery.supportsInMemoryExecution())
