org.opoo.oqs.core.AbstractQuery.java Source code

Java tutorial

Introduction

Here is the source code for org.opoo.oqs.core.AbstractQuery.java

Source

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

import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.opoo.oqs.Mapper;
import org.opoo.oqs.Query;
import org.opoo.oqs.QueryException;
import org.opoo.oqs.QueryFactory;
import org.opoo.oqs.QueryIterator;
import org.opoo.oqs.TypedValue;
import org.opoo.oqs.dialect.Dialect;
import org.opoo.oqs.jdbc.ConnectionManager;
import org.opoo.oqs.jdbc.ResultSetHandler;
import org.opoo.oqs.type.Type;
import org.opoo.oqs.type.TypeFactory;
import org.opoo.util.Assert;
import org.opoo.util.ClassUtils;
import org.opoo.util.CollectionUtils;
import org.opoo.util.StringUtils;

/**
 * Query
 *
 * @author Alex Lin(alex@opoo.org)
 * @version 1.0
 * @since OQS1.0
 */
public abstract class AbstractQuery implements Query {
    private static final Log log = LogFactory.getLog(Query.class);

    private static final Object UNSET_PARAMETER = "<unset parameter>";
    private static final Object UNSET_TYPE = "<unset type>";

    //
    private final int debugLevel;
    private final AbstractQueryFactory queryFactory;
    private final Dialect dialect;
    private final String queryString;

    //
    private List values = new ArrayList(4);
    private List types = new ArrayList(4);
    private int positionalParameterCount = 0;
    private Set actualNamedParameters = new HashSet(4);
    private Map namedParameters = new HashMap(4);
    private Map namedParameterLists = new HashMap(4);
    private boolean readOnly;
    private String comment;

    //
    private int maxResults = -1, firstResult = 0;

    private String operation = null;
    private boolean isSelect = false;
    private boolean isCall = false;
    private boolean isUpdate = false;

    private boolean isComplexMapper = false;
    private boolean isTranslated = false;
    private Mapper mapper = null;
    private String sql = null;

    //
    String msql;
    int maxRows = 0, fetchSize = 0, queryTimeout = 0;

    public AbstractQuery(AbstractQueryFactory queryFactory, String queryString) {
        this.dialect = queryFactory.dialect;
        this.debugLevel = queryFactory.debugLevel;
        this.queryFactory = queryFactory;
        this.queryString = queryString;
        this.msql = queryString;

        if (debugLevel > 1) {
            System.out.println("[SQS]: " + queryString);
        }
        initialize();
    }

    private void initialize() {
        QueryStringHelper.verifyQueryString(msql);
        initParameterBookKeeping();
    }

    protected void translateMsqlIfNecessary() {
        if (isSelect && mapper == null && !isTranslated) {
            QueryTranslator translator = new DefaultQueryTranslator(msql, queryFactory.beanClassLoader);
            msql = translator.getSQLString();
            mapper = translator.getMapper();
            isTranslated = true;
        }
    }

    /**
     * 
     */
    protected void before() {
        translateMsqlIfNecessary();
        //verifyParameters();
        verifyPositionalParameters(false);
        verifyNamedParameters();
        processSQL(false);
        processLimitSQL(); //
        verifyPositionalParametersAfterProcessSQL();
    }

    /**
     * 
     */
    protected void after() {
        setMaxRows(0);
        setQueryTimeout(0);
        setFetchSize(0);
    }

    protected abstract List doList() throws QueryException;

    //protected abstract Iterator doIterate() throws QueryException;

    protected abstract int doUpdate() throws QueryException;

    protected abstract Object doCall() throws QueryException;

    protected void check(boolean b) {
        if (b) {
            throw new QueryException("The called method cannot handle '" + operation + "'", msql);
        }
    }

    public List list() throws QueryException {
        check(!isSelect);
        before();
        try {
            return doList();
        } finally {
            after();
        }
    }

    public QueryIterator iterate() throws QueryException {
        check(!isSelect);
        before();
        try {
            //return doIterate();
            return iterate(getSql(), valueArray(), typeArray());
        } finally {
            after();
        }
    }

    public int executeUpdate() throws QueryException {
        check(!isUpdate);
        before();
        return doUpdate();
    }

    public Serializable insert() throws QueryException {
        checkDialect();
        check(!isUpdate);
        if (!queryString.trim().toUpperCase().startsWith("INSERT")) {
            throw new QueryException("Insert() use for insert new row and get generated id only.", sql);
        }

        before();
        //return doInsert();

        String qs = getSql();
        if (dialect.supportsInsertSelectIdentity()) {
            qs = dialect.appendIdentitySelectToInsert(qs);
            if (debugLevel > 1) {
                System.out.println("[IQS]: " + qs);
            }
        }

        //if(true) return null;
        return getInsertSelectIdentity(qs);
    }

    public Object call() throws QueryException {
        check(!isCall);
        before();
        try {
            return doCall();
        } finally {
            after();
        }
    }

    public int[] batch() {
        throw new UnsupportedOperationException();
    }

    private QueryIterator iterate(String sql, Object[] params, Type[] types) throws QueryException {
        try {
            final ConnectionManager manager = getQueryFactory().getConnectionManager();
            final Connection conn = manager.getConnection();
            final PreparedStatement ps = conn.prepareStatement(sql);

            if (params != null) {
                for (int i = 0; i < params.length; i++) {
                    types[i].safeSet(ps, params[i], i + 1);
                }
            }
            applyStatementSettings(ps);

            final ResultSet rs = ps.executeQuery();

            return createIterator(rs, ps, conn, manager);
        } catch (Exception ex) {
            throw new QueryException("iterate error:" + sql, ex);
        }
    }

    private QueryIterator createIterator(ResultSet rs, PreparedStatement ps, Connection conn, ConnectionManager cm)
            throws SQLException {
        mapper.initialize(rs.getMetaData());
        advance(rs);
        return new QueryIterator(rs, ps, conn, cm, mapper);
    }

    protected QueryFactory getQueryFactory() {
        return queryFactory;
    }

    /////////////
    protected void checkDialect() {
        Assert.notNull(dialect, "Dialect");
        //if(this.dialect == null)
        //{
        //throw new QueryException("dialectnull");
        //}
    }

    public final String getQueryString() {
        return queryString;
    }

    public final String getSql() {
        if (debugLevel > 0) {
            System.out.println("[SQL]: " + sql);
        }
        return sql;
    }

    /**
     * 
     *
     * @return Map
     */
    protected Map getNamedParams() {
        return new HashMap(namedParameters);
    }

    /**
     * SQL
     * Query StringSQLNamed parameters
     * <p>:namedparam ==> ?
     *
     * @param reserveFirstParameter boolean
     * @throws QueryException
     */
    private void processSQL(boolean reserveFirstParameter) throws QueryException {
        if (actualNamedParameters.size() == 0) {
            sql = msql;
            return;
        }

        Map namedParams = getNamedParams();
        String qs = bindParameterLists(namedParams);

        if (debugLevel > 1) {
            System.out.println("[TQS]: " + qs);
        }

        List theValues = new ArrayList();
        List theTypes = new ArrayList();
        int startIndex = reserveFirstParameter ? 1 : 0;
        int valuesIndex = startIndex;
        int typesIndex = startIndex;

        StringTokenizer st = new StringTokenizer(qs, QueryStringHelper.QS_SEPARATORS);
        while (st.hasMoreTokens()) {
            String str = st.nextToken();
            if (str.startsWith("?")) {
                theValues.add(values.get(valuesIndex++));
                theTypes.add(types.get(typesIndex++));
            } else if (str.startsWith(QueryStringHelper.QS_VARIABLE_PREFIX)) {
                qs = qs.replaceAll(str, "?");
                TypedValue vt = (TypedValue) namedParams.get(str.substring(1));
                if (vt == null) {
                    throw new QueryException("Process named parameter exception.", str.substring(1));
                }
                theValues.add(vt.getValue());
                theTypes.add(vt.getType());
            }
        }

        values = theValues;
        types = theTypes;
        sql = qs;
    }

    /**
     * SQLPOSIONAL PARAMETERS
     * 
     */
    private void verifyPositionalParametersAfterProcessSQL() {
        if (sql == null) {
            throw new QueryException("Build sql exception, query string", msql);
        }
        int positionCount = StringUtils.countUnquoted(sql, '?');

        if (positionCount == 0) {
            return;
        }
        int i = types.size();
        i = values.size();

        if (positionCount != types.size() || positionCount != values.size() || types.size() != values.size()) {
            throw new QueryException(
                    "Expected positional parameter count: " + (positionCount) + ", actual parameters: " + values,
                    sql);
        }
    }

    /**
     * positional parameter
     *
     * @param reserveFirstParameter boolean index10?
     * @throws QueryException
     */
    private void verifyPositionalParameters(boolean reserveFirstParameter) throws QueryException {
        if (positionalParameterCount == 0) {
            return;
        }

        int positionalValueCount = 0;
        for (int i = 0; i < values.size(); i++) {
            Object object = types.get(i);
            if (values.get(i) == UNSET_PARAMETER || object == UNSET_TYPE) {
                if (reserveFirstParameter && i == 0) {
                    continue;
                } else {
                    throw new QueryException("Unset positional parameter at position: " + i, getQueryString());
                }
            }
            positionalValueCount++;
        }

        if (positionalParameterCount != positionalValueCount) {
            if (reserveFirstParameter && positionalParameterCount - 1 != positionalValueCount) {
                throw new QueryException("Expected positional parameter count: " + (positionalParameterCount - 1)
                        + ", actual parameters: " + values, getQueryString());
            } else if (!reserveFirstParameter) {
                throw new QueryException("Expected positional parameter count: " + positionalParameterCount
                        + ", actual parameters: " + values, getQueryString());
            }
        }
    }

    /**
     * 
     * @throws QueryException
     */
    private void verifyNamedParameters() throws QueryException {
        if (actualNamedParameters.size() == 0) {
            return;
        }

        if (actualNamedParameters.size() != namedParameters.size() + namedParameterLists.size()) {
            Set missingParams = new HashSet(actualNamedParameters);
            missingParams.removeAll(namedParameterLists.keySet());
            missingParams.removeAll(namedParameters.keySet());
            throw new QueryException("Not all named parameters have been set: " + missingParams, getQueryString());
        }
    }

    /////////////////////////////////////////
    //Page Limit, timeout, fetchsize eg.
    /**
     * SQL
     */
    private void processLimitSQL() {
        //Dialect dialect = getDialect();
        //
        if (dialect == null || !isSelect) { //
            log.debug("do not process limit sql:dialect=" + dialect + ", isSelect=" + isSelect);
            return;
        }

        boolean useLimit = dialect.supportsLimit() && maxResults > 0;
        boolean hasFirstRow = firstResult > 0;
        boolean useOffset = hasFirstRow && useLimit && dialect.supportsLimitOffset();

        if (useLimit) {
            //process SQL
            sql = dialect.getLimitString(sql.trim(), //use of trim() here is ugly?
                    useOffset ? firstResult : 0, dialect.useMaxForLimit() ? maxResults + firstResult : maxResults);
            //process Parameters
            appendLimitParameters();
            //appendLimitParameters(dialect);
        } else {
            if (maxResults > 0) {
                setMaxRows(maxResults + firstResult);
            }
        }
        //st.setQueryTimeout( getTimeout());
        //st.setFetchSize( getFetchSize());
    }

    private void appendLimitParameters() {
        if (!dialect.supportsVariableLimit()) {
            return;
        }
        if (maxResults == -1) {
            throw new QueryException("no max results set");
        }
        int firstRow = firstResult;
        int lastRow = dialect.useMaxForLimit() ? maxResults + firstResult : maxResults;
        boolean hasFirstRow = firstRow > 0 && dialect.supportsLimitOffset();
        boolean reverse = dialect.bindLimitParametersInReverseOrder();
        /**
         * boolean hasFirstRow = firstRow > 0 && dialect.supportsLimitOffset();
         boolean reverse = dialect.bindLimitParametersInReverseOrder();
         if ( hasFirstRow ) st.setInt( index + ( reverse ? 1 : 0 ), firstRow );
         st.setInt( index + ( reverse || !hasFirstRow ? 0 : 1 ), lastRow );
                return hasFirstRow ? 2 : 1;
            
         */
        /*
         List limitValues = new ArrayList(1);
         List limitTypes = new ArrayList(1);
         if (hasFirstRow && !reverse)
         {
          limitValues.add(new Integer(firstRow));
          limitTypes.add(Type.INTEGER);
         }
         limitValues.add(new Integer(lastRow));
         limitTypes.add(Type.INTEGER);
         if (hasFirstRow && reverse)
         {
          limitValues.add(new Integer(firstRow));
          limitTypes.add(Type.INTEGER);
         }*/
        /*
         Integer[] rows = new Integer[2];
         if ( hasFirstRow ) rows[reverse ? 1 : 0]= new Integer(firstRow);
         rows[(reverse || !hasFirstRow) ? 0 : 1] = new Integer(lastRow);
         //int size = hasFirstRow ? 2 : 1;
         List limitValues = new ArrayList(1);
         List limitTypes = new ArrayList(1);
         if(hasFirstRow)
         {
          limitValues.addAll(Arrays.asList(rows));
          CollectionUtils.fill(limitTypes, Type.INTEGER, 2);
         }
         else
         {
          limitValues.add(rows[0]);
          limitTypes.add(Type.INTEGER);
         }
         */

        List limitValues = new ArrayList(1);
        List limitTypes = new ArrayList(1);
        Integer first = new Integer(firstRow);
        Integer last = new Integer(lastRow);
        if (hasFirstRow && !reverse) {
            limitValues.add(first);
        }
        limitValues.add(last);
        if (hasFirstRow && reverse) {
            limitValues.add(first);
        }
        CollectionUtils.fill(limitTypes, Type.INTEGER, hasFirstRow ? 2 : 1);

        //
        if (dialect.bindLimitParametersFirst()) {
            limitValues.addAll(values);
            limitTypes.addAll(types);
            values = limitValues;
            types = limitTypes;
        } else {
            values.addAll(limitValues);
            types.addAll(limitTypes);
        }
    }

    /**
     * 
     * @return Map
     */
    protected Map getNamedParameterLists() {
        return namedParameterLists;
    }

    /**
     * List
     * @return List
     */
    protected List getValues() {
        return values;
    }

    /**
     * List
     * @return List
     */
    protected List getTypes() {
        return types;
    }

    public Query setMaxResults(int maxResults) {
        checkDialect();
        this.maxResults = maxResults;
        return this;
    }

    public Query setFirstResult(int firstResult) {
        checkDialect();
        this.firstResult = firstResult;
        return this;
    }

    /**
     * 
     * @param maxRows int
     */
    protected void setMaxRows(int maxRows) {
        // preparedStatment.setMaxRows(10);
        //return this;
        this.maxRows = maxRows;
    }

    public Query setQueryTimeout(int timeout) {
        // preparedStatment.setQueryTimeout(10);
        this.queryTimeout = timeout;
        return this;
    }

    public Query setFetchSize(int fetchSize) {
        this.fetchSize = fetchSize;
        //preparedStatment.setFetchSize(10);
        return this;
    }

    protected int getFetchSize() {
        return fetchSize;
    }

    protected int getMaxRows() {
        return maxRows;
    }

    protected int getQueryTimeout() {
        return queryTimeout;
    }

    protected void applyStatementSettings(Statement stmt) throws SQLException {
        int fetchSize = getFetchSize();
        if (fetchSize > 0) {
            stmt.setFetchSize(fetchSize);
        }
        int maxRows = getMaxRows();
        if (maxRows > 0) {
            stmt.setMaxRows(maxRows);
        }
        int timeout = getQueryTimeout();
        if (timeout > 0) {
            stmt.setQueryTimeout(timeout);
        }
    }

    public Query setParameter(int position, Object val, Type type) {
        if (positionalParameterCount == 0) {
            throw new IllegalArgumentException("No positional parameters in query: " + getQueryString());
        }
        if (position < 0 || position > positionalParameterCount - 1) {
            throw new IllegalArgumentException(
                    "Positional parameter does not exist: " + position + " in query: " + getQueryString());
        }
        int size = values.size();
        if (position < size) {
            values.set(position, val);
            types.set(position, type);
        } else {
            // prepend value and type list with null for any positions before the wanted position.
            for (int i = 0; i < position - size; i++) {
                values.add(UNSET_PARAMETER);
                types.add(UNSET_TYPE);
            }
            values.add(val);
            types.add(type);
        }
        return this;
    }

    public Query setString(int position, String val) {
        setParameter(position, val, Type.STRING);
        return this;
    }

    public Query setCharacter(int position, char val) {
        setParameter(position, new Character(val), Type.CHARACTER);
        return this;
    }

    public Query setBoolean(int position, boolean val) {
        setParameter(position, val ? Boolean.TRUE : Boolean.FALSE, Type.BOOLEAN);
        return this;
    }

    public Query setByte(int position, byte val) {
        setParameter(position, new Byte(val), Type.BYTE);
        return this;
    }

    public Query setShort(int position, short val) {
        setParameter(position, new Short(val), Type.SHORT);
        return this;
    }

    public Query setInteger(int position, int val) {
        setParameter(position, new Integer(val), Type.INTEGER);
        return this;
    }

    public Query setLong(int position, long val) {
        setParameter(position, new Long(val), Type.LONG);
        return this;
    }

    public Query setFloat(int position, float val) {
        setParameter(position, new Float(val), Type.FLOAT);
        return this;
    }

    public Query setDouble(int position, double val) {
        setParameter(position, new Double(val), Type.DOUBLE);
        return this;
    }

    public Query setBinary(int position, byte[] val) {
        setParameter(position, val, Type.BINARY);
        return this;
    }

    public Query setText(int position, String val) {
        setParameter(position, val, Type.TEXT);
        return this;
    }

    public Query setSerializable(int position, Serializable val) {
        setParameter(position, val, Type.SERIALIZABLE);
        return this;
    }

    public Query setDate(int position, Date date) {
        setParameter(position, date, Type.DATE);
        return this;
    }

    public Query setTime(int position, Date date) {
        setParameter(position, date, Type.TIME);
        return this;
    }

    public Query setTimestamp(int position, Date date) {
        setParameter(position, date, Type.TIMESTAMP);
        return this;
    }

    public Query setLocale(int position, Locale locale) {
        setParameter(position, locale, Type.LOCALE);
        return this;
    }

    public Query setCalendar(int position, Calendar calendar) {
        setParameter(position, calendar, Type.CALENDAR);
        return this;
    }

    public Query setCalendarDate(int position, Calendar calendar) {
        setParameter(position, calendar, Type.CALENDAR_DATE);
        return this;
    }

    public Query setBinary(String name, byte[] val) {
        setParameter(name, val, Type.BINARY);
        return this;
    }

    public Query setText(String name, String val) {
        setParameter(name, val, Type.TEXT);
        return this;
    }

    public Query setBoolean(String name, boolean val) {
        setParameter(name, val ? Boolean.TRUE : Boolean.FALSE, Type.BOOLEAN);
        return this;
    }

    public Query setByte(String name, byte val) {
        setParameter(name, new Byte(val), Type.BYTE);
        return this;
    }

    public Query setCharacter(String name, char val) {
        setParameter(name, new Character(val), Type.CHARACTER);
        return this;
    }

    public Query setDate(String name, Date date) {
        setParameter(name, date, Type.DATE);
        return this;
    }

    public Query setDouble(String name, double val) {
        setParameter(name, new Double(val), Type.DOUBLE);
        return this;
    }

    public Query setFloat(String name, float val) {
        setParameter(name, new Float(val), Type.FLOAT);
        return this;
    }

    public Query setInteger(String name, int val) {
        setParameter(name, new Integer(val), Type.INTEGER);
        return this;
    }

    public Query setLocale(String name, Locale locale) {
        setParameter(name, locale, Type.LOCALE);
        return this;
    }

    public Query setCalendar(String name, Calendar calendar) {
        setParameter(name, calendar, Type.CALENDAR);
        return this;
    }

    public Query setCalendarDate(String name, Calendar calendar) {
        setParameter(name, calendar, Type.CALENDAR_DATE);
        return this;
    }

    public Query setLong(String name, long val) {
        setParameter(name, new Long(val), Type.LONG);
        return this;
    }

    public Query setParameter(String name, Object val, Type type) {
        if (!actualNamedParameters.contains(name)) {
            throw new IllegalArgumentException(
                    "Parameter " + name + " does not exist as a named parameter in [" + getQueryString() + "]");
        } else {
            namedParameters.put(name, new TypedValue(val, type));
            return this;
        }
    }

    public Query setSerializable(String name, Serializable val) {
        setParameter(name, val, Type.SERIALIZABLE);
        return this;
    }

    public Query setShort(String name, short val) {
        setParameter(name, new Short(val), Type.SHORT);
        return this;
    }

    public Query setString(String name, String val) {
        setParameter(name, val, Type.STRING);
        return this;
    }

    public Query setTime(String name, Date date) {
        setParameter(name, date, Type.TIME);
        return this;
    }

    public Query setTimestamp(String name, Date date) {
        setParameter(name, date, Type.TIMESTAMP);
        return this;
    }

    public Query setBigDecimal(int position, BigDecimal number) {
        setParameter(position, number, Type.BIG_DECIMAL);
        return this;
    }

    public Query setBigDecimal(String name, BigDecimal number) {
        setParameter(name, number, Type.BIG_DECIMAL);
        return this;
    }

    public Query setBigInteger(int position, BigInteger number) {
        setParameter(position, number, Type.BIG_DECIMAL);
        return this;
    }

    public Query setBigInteger(String name, BigInteger number) {
        setParameter(name, number, Type.BIG_DECIMAL);
        return this;
    }

    public Query setParameter(int position, Object val) throws QueryException {
        if (val == null) {
            setParameter(position, val, Type.SERIALIZABLE);
        } else {
            setParameter(position, val, guessType(val));
        }
        return this;
    }

    public Query setParameter(String name, Object val) throws QueryException {
        if (val == null) {
            setParameter(name, val, Type.SERIALIZABLE);
        } else {
            setParameter(name, val, guessType(val));
        }
        return this;
    }

    private static Type guessType(Object param) throws QueryException {
        //Class clazz = param.getClass();
        //DataTypeProxyHelper.getClassWithoutInitializingProxy(param);
        return TypeFactory.guessType(param);
    }

    public Type[] getReturnTypes() throws QueryException {
        return null;
    }

    public String[] getReturnAliases() throws QueryException {
        return null;
    }

    public Query setParameterList(String name, Collection vals, Type type) throws QueryException {
        if (vals.size() == 0) {
            throw new QueryException("Collection must not be empty!", "Parameter named " + name);
        }
        if (!actualNamedParameters.contains(name)) {
            throw new IllegalArgumentException(
                    "Parameter " + name + " does not exist as a named parameter in [" + getQueryString() + "]");
        }
        namedParameterLists.put(name, new TypedValue(vals, type));
        return this;
    }

    /**
     * Collection
     * <p>:
     * <pre>
     * qs: select id from table1 where name in (:names)
     * query.setParameterList("names", new String[]{"d", "g", "h"});
     * 
     * qs: select id from table1 where name in (:names0_, :names1_, :names2_);
     * </pre>
     *
     * @param namedParamsCopy Map
     * @return String
     */
    protected String bindParameterLists(Map namedParamsCopy) {
        String query = this.msql;
        Iterator iter = namedParameterLists.entrySet().iterator();
        while (iter.hasNext()) {
            Map.Entry me = (Map.Entry) iter.next();
            query = bindParameterList(query, (String) me.getKey(), (TypedValue) me.getValue(), namedParamsCopy);
        }
        return query;
    }

    /**
     * Collection
     * @param query String
     * @param name String
     * @param typedList TypedValue
     * @param namedParamsCopy Map
     * @return String
     */
    private static String bindParameterList(String query, String name, TypedValue typedList, Map namedParamsCopy) {
        Collection vals = (Collection) typedList.getValue();
        Type type = typedList.getType();
        StringBuffer list = new StringBuffer(16);
        Iterator iter = vals.iterator();
        int i = 0;
        while (iter.hasNext()) {
            String alias = name + i++ + '_';
            namedParamsCopy.put(alias, new TypedValue(iter.next(), type));
            list.append(QueryStringHelper.QS_VARIABLE_PREFIX + alias);
            if (iter.hasNext()) {
                list.append(", ");
            }
        }
        //return StringHelper.replace(query, ParserHelper.HQL_VARIABLE_PREFIX + name,
        //                            list.toString(), true);
        return query.replaceAll(QueryStringHelper.QS_VARIABLE_PREFIX + name, list.toString());
    }

    public Query setParameterList(String name, Collection vals) throws QueryException {
        if (vals == null) {
            throw new QueryException("Collection must be not null!", "parameter named " + name);
        }

        if (vals.size() == 0) {
            setParameterList(name, vals, null);
        } else {
            setParameterList(name, vals, guessType(vals.iterator().next()));
        }

        return this;
    }

    public String[] getNamedParameters() throws QueryException {
        return (String[]) actualNamedParameters.toArray(new String[actualNamedParameters.size()]);
    }

    public Query setProperties(Object bean) throws QueryException {
        Class clazz = bean.getClass();
        String[] params = getNamedParameters();
        for (int i = 0; i < params.length; i++) {
            String namedParam = params[i];
            final Object object = ClassUtils.getProperty(bean, namedParam);
            if (object == null) {
                //        throw new QueryException(
                //            "Set bean's properties for named parameters error."
                //            + "can not find property:" + namedParam);
                setParameter(namedParam, null, Type.SERIALIZABLE);
            } else if (object instanceof Collection) {
                setParameterList(namedParam, (Collection) object);
            } else if (object.getClass().isArray()) {
                setParameterList(namedParam, (Object[]) object);
            } else {
                setParameter(namedParam, object, guessType(object));
            }
        }
        return this;
    }

    public Query setParameterList(String name, Object[] vals, Type type) throws QueryException {
        return setParameterList(name, Arrays.asList(vals), type);
    }

    public Query setParameterList(String name, Object[] vals) throws QueryException {
        return setParameterList(name, Arrays.asList(vals));
    }

    public Object uniqueResult() throws QueryException {
        return uniqueElement(list());
    }

    static Object uniqueElement(List list) throws QueryException {
        int size = list.size();
        if (size == 0) {
            return null;
        }
        Object first = list.get(0);
        for (int i = 1; i < size; i++) {
            if (list.get(i) != first) {
                throw new QueryException("Result not unique. size is " + list.size());
            }
        }
        return first;
    }

    /**
     * 
     * @return Type[]
     */
    public Type[] typeArray() {
        return (Type[]) getTypes().toArray(new Type[getTypes().size()]);
    }

    /**
     * SQL
     * @return int[]
     * @see java.sql.Types
     */
    public int[] sqlType() {
        Type[] types = typeArray();
        int[] a = new int[types.length];
        for (int i = 0; i < types.length; i++) {
            a[i] = types[i].sqlType();
        }
        return a;
    }

    /**
     * 
     * @return Object[]
     */
    public Object[] valueArray() {
        return getValues().toArray();
    }

    public Query setComment(String comment) {
        this.comment = comment;
        return this;
    }

    public String getComment() {
        return comment;
    }

    public String toString() {
        return StringUtils.unqualify(getClass().getName()) + '(' + msql + ')';
    }

    public Query setParameters(Object[] values, Type[] types) {
        /**
         * Arrays.asListlistaddAll
         */
        this.values = new ArrayList(Arrays.asList(values));
        this.types = new ArrayList(Arrays.asList(types));
        return this;
    }

    public Query setParameters(Object[] values) {
        this.values = new ArrayList(Arrays.asList(values));
        this.types.clear();
        for (int i = 0; i < values.length; i++) {
            if (values[i] == null) {
                this.types.add(Type.SERIALIZABLE);
            } else {
                this.types.add(guessType(values[i]));
            }
        }
        return this;
    }

    public Query setParameters(String[] names, Object[] values) {
        if (names.length != values.length) {
            throw new IllegalArgumentException("named parameters count must match values count.");
        }
        for (int i = 0; i < names.length; i++) {
            if (values[i] != null) {
                if (values[i] instanceof Collection) {
                    setParameterList(names[i], (Collection) values[i]);
                    continue;
                } else if (values[i].getClass().isArray()) {
                    setParameterList(names[i], (Object[]) values[i]);
                    continue;
                }
            }
            setParameter(names[i], values[i]);
        }
        return this;
    }

    /**
     * Read only?
     * @return boolean
     */
    public boolean isReadOnly() {
        return readOnly;
    }

    public Query setReadOnly(boolean readOnly) {
        this.readOnly = readOnly;
        return this;
    }

    /**
     * queryqueryStringnamed parameternamepositional parameter
     * 4queryStringQuerist
     */
    private void initParameterBookKeeping() {
        StringTokenizer st = new StringTokenizer(msql, QueryStringHelper.QS_SEPARATORS);
        Set result = new HashSet();

        //add start
        operation = st.nextToken();
        if ("from".equalsIgnoreCase(operation)) {
            msql = "select * " + msql;
            operation = "select";
            isSelect = true;
        } else if ("select".equalsIgnoreCase(operation)) {
            isSelect = true;
        } else if ("insert".equalsIgnoreCase(operation) || "update".equalsIgnoreCase(operation)
                || "delete".equalsIgnoreCase(operation)) {
            isUpdate = true;
        } else if (operation.startsWith("{")) {
            isCall = true;
        } else if (dialect != null && dialect.getIdentitySelectString() != null
                && dialect.getIdentitySelectString().startsWith(operation)) {
            isSelect = true;
        } else {
            throw new QueryException("Syntax error, unsupport manipulate symbol '" + operation + "'", msql);
        }
        //add end

        while (st.hasMoreTokens()) {
            String string = st.nextToken();
            if (string.startsWith(QueryStringHelper.QS_VARIABLE_PREFIX)) {
                result.add(string.substring(1));
            }

            ///add
            if (!isComplexMapper && "new".equalsIgnoreCase(string)) {
                isComplexMapper = true;
            }
        }

        //
        if (isComplexMapper && !isSelect) {
            throw new QueryException("ComplexMapper query for select only," + " can not use for execute update.",
                    msql);
        }

        //
        actualNamedParameters = result;
        positionalParameterCount = StringUtils.countUnquoted(msql, '?');
    }

    protected ResultSetHandler createListResultSetHandler() {
        ResultSetHandler lrsh = new ListResultSetHandler(getMapper());
        return createResultSetHandler(lrsh);
    }

    protected ResultSetHandler createIteratorResultSetHandler() {
        ResultSetHandler lrsh = new ListResultSetHandler(getMapper());
        return createResultSetHandler(lrsh);
    }

    protected ResultSetHandler createResultSetHandler(final ResultSetHandler rsh) {
        return new ResultSetHandler() {
            public Object handle(ResultSet rs) throws SQLException {
                mapper.initialize(rs.getMetaData());
                advance(rs);
                return rsh.handle(rs);
            }
        };
    }

    /**
     * 
     *
     * @param rs ResultSet
     * @throws SQLException
     */
    private void advance(ResultSet rs) throws SQLException {
        if (dialect == null) {
            return;
        }

        boolean useLimit = dialect.supportsLimit() && maxResults > 0;
        if (!dialect.supportsLimitOffset() || !useLimit) {
            int firstRow = firstResult;
            if (firstRow > 0) {
                try {
                    // we can go straight to the first required row
                    rs.absolute(firstRow);
                    log.debug("using absolute");
                } catch (Exception e) {
                    // we need to step through the rows one row at a time (slow)
                    for (int m = 0; m < firstRow; m++) {
                        rs.next();
                    }
                }
            }
        }
    }

    protected Mapper getMapper() {
        Assert.notNull(mapper, "mapper is required");
        return mapper;
    }

    public Query setMapper(Mapper mapper) throws QueryException {
        this.mapper = mapper;
        return this;
    }

    //////////////////////////////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////

    /**
     * ResultSetID
     *
     * @param sql String
     * @return Serializable
     */
    private Serializable getInsertSelectIdentity(String sql) {
        try {
            ConnectionManager manager = queryFactory.getConnectionManager();
            Connection conn = queryFactory.getConnectionManager().getConnection();
            PreparedStatement ps = conn.prepareStatement(sql);
            try {
                Object values[] = valueArray();
                for (int i = 0; i < values.length; i++) {
                    Object value = values[i];
                    if (value == null) {
                        Type.SERIALIZABLE.safeSet(ps, value, i + 1);
                    } else {
                        TypeFactory.guessType(value).safeSet(ps, value, i + 1);
                    }
                }
                if (dialect.supportsInsertSelectIdentity()) {
                    if (!ps.execute()) {
                        while (!ps.getMoreResults() && ps.getUpdateCount() != -1) {
                            continue; // Do nothing (but stop checkstyle from complaining).
                        }
                    }
                    //note early exit!
                    ResultSet rs = ps.getResultSet();
                    try {
                        return getGeneratedIdentity(rs);
                    } finally {
                        //JdbcUtils.closeResultSet(rs);
                        close(rs, null);
                    }
                }
                //else if (Settings.isGetGeneratedKeysEnabled())
                //{
                //  ps.executeUpdate();
                //  //note early exit!
                //  return getGeneratedIdentity(ps.getGeneratedKeys());
                //}
                //else
                //{
                //  ps.executeUpdate();
                //need post insert then select identity
                //postInsert.append("NEED");
                //}

                //else if else
                else {
                    ps.executeUpdate();
                    ResultSet rs = ps.getGeneratedKeys();

                    if (rs != null) {
                        //getGeneratedKeys()key
                        try {
                            log.debug("Using getGeneratedKeys() to get keys.");
                            return getGeneratedIdentity(rs);
                        } finally {
                            close(rs, null);
                        }
                    }
                    //elsegetPostInsertGeneratedIndentity
                }
            } finally {
                //JdbcUtils.closeStatement(ps);
                close(null, ps);
                //JdbcUtils.closeConnection(conn);
                //PostInsertGeneratedIndentiry
                manager.releaseConnection(conn);
            }
        } catch (SQLException ex) {
            //throw new QueryException("could not insert: ", ex);
            log.debug("could not get id for insert, using getPostInsertGeneratedIndentity(): " + ex.getMessage());
        }
        return getPostInsertGeneratedIndentity();
    }

    private void close(ResultSet rs, Statement stmt) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException ex) {
                log.debug("Could not close JDBC ResultSet", ex);
            } catch (Throwable ex) {
                // We don't trust the JDBC driver: It might throw RuntimeException or Error.
                log.debug("Unexpected exception on closing JDBC ResultSet", ex);
            }
        }

        if (stmt != null) {
            try {
                stmt.close();
            } catch (Exception ex) {
                log.debug("Could not close JDBC Statement", ex);
            } catch (Throwable ex) {
                // We don't trust the JDBC driver: It might throw RuntimeException or Error.
                log.debug("Unexpected exception on closing JDBC Statement", ex);
            }
        }
    }

    /**
     * ResultSetID
     * @param rs ResultSet
     * @return Serializable
     * @throws QueryException
     * @throws SQLException
     */
    private Serializable getGeneratedIdentity(ResultSet rs) throws QueryException, SQLException {
        if (!rs.next()) {
            throw new QueryException("The database returned no natively generated identity value");
        }
        //final Serializable id = IdentifierGeneratorFactory.get( rs, type );
        Serializable id = (Serializable) rs.getObject(1);
        if (id != null) {
            if (id instanceof String) {
                return id.toString();
            }
            if (id instanceof Number) {
                return id;
            }
            //Number
            return null;
        }

        if (log.isDebugEnabled()) {
            log.debug("Natively generated identity: " + id);
        }
        return id;
    }

    /**
     * getPostInsertGeneratedIndentiry
     *
     * @return Serializable
     */
    private Serializable getPostInsertGeneratedIndentity() {
        final String sql = dialect.getIdentitySelectString();
        //return (Serializable) queryFactory.createQuery(sql).uniqueResult();
        return (Serializable) queryForObject(sql, Serializable.class);
    }

    protected abstract Object queryForObject(String sql, Class clazz);
}

class ListResultSetHandler implements ResultSetHandler {
    private final Mapper mapper;
    private final int rowsExpected;

    public ListResultSetHandler(Mapper mapper) {
        this(mapper, 0);
    }

    public ListResultSetHandler(Mapper mapper, int rowsExpected) {
        Assert.notNull(mapper, "mapper is required");
        this.mapper = mapper;
        this.rowsExpected = rowsExpected;
    }

    public Object handle(ResultSet rs) throws SQLException {
        List results = (this.rowsExpected > 0 ? new ArrayList(this.rowsExpected) : new ArrayList());
        int rowNum = 0;
        while (rs.next()) {
            results.add(mapper.map(rs, rowNum++));
        }
        return results;
    }
}

class IteratorResultSetHandler implements ResultSetHandler {
    private final Mapper mapper;

    public IteratorResultSetHandler(Mapper mapper) {
        Assert.notNull(mapper, "mapper is required");
        this.mapper = mapper;
    }

    public Object handle(ResultSet rs) throws SQLException {
        return null;
    }
}