/*
 * This software is distributed under following license based on modified BSD
 * style license.
 * ----------------------------------------------------------------------
 * 
 * Copyright 2009 The Nimbus2 Project. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer. 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE NIMBUS PROJECT ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
 * NO EVENT SHALL THE NIMBUS PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * The views and conclusions contained in the software and documentation are
 * those of the authors and should not be interpreted as representing official
 * policies, either expressed or implied, of the Nimbus2 Project.
 */
package jp.ossc.nimbus.service.sql;

import java.io.IOException;
import java.io.Reader;
import java.io.InputStream;
import java.io.ByteArrayInputStream;
import java.io.CharArrayReader;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.math.BigInteger;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Set;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ParameterMetaData;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
import java.sql.Blob;
import java.sql.Clob;

import jp.ossc.nimbus.beans.BooleanEditor;
import jp.ossc.nimbus.beans.Property;
import jp.ossc.nimbus.beans.PropertyAccess;
import jp.ossc.nimbus.beans.NestedProperty;
import jp.ossc.nimbus.beans.NoSuchPropertyException;
import jp.ossc.nimbus.beans.dataset.DataSet;
import jp.ossc.nimbus.beans.dataset.Record;
import jp.ossc.nimbus.beans.dataset.RecordList;
import jp.ossc.nimbus.beans.dataset.PropertyGetException;
import jp.ossc.nimbus.core.ServiceBase;

/**
 * 塱懕娗棟丅<p>
 *
 * @author M.Takata
 */
public class DefaultPersistentManagerService extends ServiceBase
 implements PersistentManager, DefaultPersistentManagerServiceMBean{
    
    private static final long serialVersionUID = 301756347991573032L;
    
    private PropertyAccess propertyAccess;
    private boolean isIgnoreNullProperty;
    private Map<Integer, Class<?>> resultSetJDBCTypeMap;
    
    @Override
    public void setIgnoreNullProperty(boolean isIgnore){
        isIgnoreNullProperty = isIgnore;
    }
    @Override
    public boolean isIgnoreNullProperty(){
        return isIgnoreNullProperty;
    }
    
    @Override
    public void setResultSetJDBCTypeMap(Map<Integer, Class<?>> mapping){
        resultSetJDBCTypeMap = mapping;
    }
    @Override
    public Map<Integer, Class<?>> getResultSetJDBCTypeMap(){
        return resultSetJDBCTypeMap;
    }
    
    @Override
    public void setResultSetJDBCType(String jdbcType, Class<?> javaType) throws IllegalArgumentException{
        Integer type = null;
        try{
            final Field field = Types.class.getField(jdbcType);
            type = (Integer)field.get(null);
        }catch(NoSuchFieldException e){
            throw new IllegalArgumentException(e);
        }catch(IllegalAccessException e){
            throw new IllegalArgumentException(e);
        }
        resultSetJDBCTypeMap.put(type, javaType);
    }
    
    @Override
    public void createService() throws Exception{
        resultSetJDBCTypeMap = new HashMap<Integer, Class<?>>();
        resultSetJDBCTypeMap.put(Types.CHAR, String.class);
        resultSetJDBCTypeMap.put(Types.VARCHAR, String.class);
        resultSetJDBCTypeMap.put(Types.LONGVARCHAR, String.class);
        resultSetJDBCTypeMap.put(Types.NUMERIC, java.math.BigDecimal.class);
        resultSetJDBCTypeMap.put(Types.DECIMAL, java.math.BigDecimal.class);
        resultSetJDBCTypeMap.put(Types.BIT, Boolean.TYPE);
        resultSetJDBCTypeMap.put(Types.TINYINT, Byte.TYPE);
        resultSetJDBCTypeMap.put(Types.SMALLINT, Short.TYPE);
        resultSetJDBCTypeMap.put(Types.INTEGER, Integer.TYPE);
        resultSetJDBCTypeMap.put(Types.BIGINT, Long.TYPE);
        resultSetJDBCTypeMap.put(Types.REAL, Float.TYPE);
        resultSetJDBCTypeMap.put(Types.DOUBLE, Double.TYPE);
        resultSetJDBCTypeMap.put(Types.BINARY, byte[].class);
        resultSetJDBCTypeMap.put(Types.VARBINARY, byte[].class);
        resultSetJDBCTypeMap.put(Types.LONGVARBINARY, byte[].class);
        resultSetJDBCTypeMap.put(Types.DATE, java.sql.Date.class);
        resultSetJDBCTypeMap.put(Types.TIME, java.sql.Time.class);
        resultSetJDBCTypeMap.put(Types.TIMESTAMP, java.sql.Timestamp.class);
        resultSetJDBCTypeMap.put(Types.CLOB, java.sql.Clob.class);
        resultSetJDBCTypeMap.put(Types.BLOB, java.sql.Blob.class);
        resultSetJDBCTypeMap.put(Types.ARRAY, java.sql.Array.class);
        resultSetJDBCTypeMap.put(Types.STRUCT, java.sql.Struct.class);
        resultSetJDBCTypeMap.put(Types.REF, java.sql.Ref.class);
    }
    
    @Override
    public void startService() throws Exception{
        propertyAccess = new PropertyAccess();
        propertyAccess.setIgnoreNullProperty(isIgnoreNullProperty);
    }
    
    @Override
    public Object loadQuery(Connection con, String query, Object input, Object output) throws PersistentException{
        return loadQuery(con, query, input, output, null, null);
    }
    
    @Override
    public Object loadQuery(Connection con, String query, Object input, Object output, Map<String, Object> statementProps, Map<String, Object> resultSetProps) throws PersistentException{
        final StringBuilder buf = new StringBuilder(query);
        final List<Object> inputProps = parseInput(buf);
        final List<Object> outputProps = parseOutput(buf);
        String sql = buf.toString();
        return load(con, sql, input, inputProps, output, outputProps, statementProps, resultSetProps);
    }
    
    private List<Object> parseInput(StringBuilder query) throws PersistentException{
        return parseQuery(query, "<-{", false);
    }
    
    private List<Object> parseOutput(StringBuilder query) throws PersistentException{
        return parseQuery(query, "->{", true);
    }
    
    private List<Object> parseQuery(StringBuilder query, String prefix, boolean isSet) throws PersistentException{
        Collection<Object> result = null;
        while(true){
            int startIndex = query.indexOf(prefix);
            if(startIndex == -1){
                break;
            }
            final int endIndex = query.indexOf("}", startIndex + 3);
            if(endIndex == -1){
                throw new PersistentException("Illegal query : " + query);
            }
            final String propStr = query.substring(startIndex + 3, endIndex).trim();
            if(propStr.length() == 0){
                throw new PersistentException("Illegal query : " + query);
            }
            if(result == null){
                result = isSet ? new LinkedHashSet<Object>() : new ArrayList<Object>();
            }
            result.add(propStr);
            query.delete(startIndex, endIndex + 1);
        }
        return result == null ? null : (isSet ? new ArrayList<Object>(result) : (List<Object>)result);
    }
    
    @Override
    public Object load(Connection con, String sql, Object input, Object inputProps, Object output, Object outputProps) throws PersistentException{
        return load(con, sql, input, inputProps, output, outputProps, null, null);
    }
    
    @SuppressWarnings("unchecked")
    @Override
    public Object load(Connection con, String sql, Object input, Object inputProps, Object output, Object outputProps, Map<String, Object> statementProps, Map<String, Object> resultSetProps) throws PersistentException{
        List<Object> inputPropList = null;
        if(inputProps != null){
            if(inputProps.getClass().isArray()){
                inputPropList = (List<Object>)Arrays.asList((Object[])inputProps);
            }else if(inputProps instanceof List){
                inputPropList = (List<Object>)inputProps;
            }else if(inputProps instanceof String){
                inputPropList = new ArrayList<Object>();
                inputPropList.add(inputProps);
            }else{
                throw new PersistentException("No supported inputProps type." + inputProps);
            }
        }else if(input != null && input instanceof Map){
            inputPropList = new ArrayList<Object>(((Map<Object, ?>)input).keySet());
        }
        List<Object> outputPropList = null;
        Map<String, String> outputPropMap = null;
        if(outputProps != null){
            if(outputProps.getClass().isArray()){
                outputPropList = (List<Object>)Arrays.asList((Object[])outputProps);
            }else if(outputProps instanceof List){
                outputPropList = (List<Object>)outputProps;
            }else if(outputProps instanceof Map){
                outputPropMap = (Map<String, String>)outputProps;
            }else if(outputProps instanceof String){
                outputPropList = new ArrayList<Object>();
                outputPropList.add(outputProps);
            }else{
                throw new PersistentException("No supported outputProps type." + outputProps);
            }
        }
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try{
            try{
                statement = con.prepareStatement(sql);
            }catch(SQLException e){
                throw new PersistentException("Illegal sql : " + sql, e);
            }
            setStatementProperties(statement, statementProps);
            try{
                final ParameterMetaData metadata
                    = statement.getParameterMetaData();
                if(inputPropList != null
                    && inputPropList.size() != metadata.getParameterCount()){
                    throw new PersistentException("Illegal sql : " + sql);
                }
                if(input != null){
                    if(inputPropList != null){
                        for(int i = 0, imax = inputPropList.size(); i < imax; i++){
                            Object param = propertyAccess.get(
                                input,
                                inputPropList.get(i).toString()
                            );
                            setObject(statement, i + 1, param);
                        }
                    }else if(input.getClass().isArray()){
                        for(int i = 0, imax = Array.getLength(input); i < imax; i++){
                            Object param = Array.get(input, i);
                            setObject(statement, i + 1, param);
                        }
                    }else if(input instanceof List){
                        List<Object> list = (List<Object>)input;
                        for(int i = 0, imax = list.size(); i < imax; i++){
                            Object param = list.get(i);
                            setObject(statement, i + 1, param);
                        }
                    }else{
                        setObject(statement, 1, input);
                    }
                }
            }catch(NoSuchPropertyException e){
                throw new PersistentException(
                    "Input bean get error.",
                    e
                );
            }catch(InvocationTargetException e){
                throw new PersistentException(
                    "Input bean get error.",
                    e.getTargetException()
                );
            }catch(SQLException e){
                throw new PersistentException(
                    "The parameter is not suitable for SQL.",
                    e
                );
            }
            try{
                resultSet = statement.executeQuery();
            }catch(SQLException e){
                throw new PersistentException("SQL execute error : " + sql, e);
            }
            setResultSetProperties(resultSet, resultSetProps);
            if(outputPropList != null){
                try{
                    ResultSetMetaData metadata = resultSet.getMetaData();
                    if((outputPropList.size() != metadata.getColumnCount())){
                        throw new PersistentException("Illegal sql : " + sql);
                    }
                    outputPropMap = new LinkedHashMap<String, String>();
                    for(int i = 0, imax = outputPropList.size(); i < imax; i++){
                        outputPropMap.put(
                            metadata.getColumnName(i + 1),
                            outputPropList.get(i).toString()
                        );
                    }
                }catch(SQLException e){
                    throw new PersistentException(
                        "The parameter is not suitable for SQL.",
                        e
                    );
                }
            }
            return fillOutput(resultSet, output, outputPropMap, false);
        }finally{
            if(statement != null){
                try{
                    statement.close();
                }catch(SQLException e){
                }
                statement = null;
            }
            if(resultSet != null){
                try{
                    resultSet.close();
                }catch(SQLException e){
                }
                resultSet = null;
            }
        }
    }
    
    @SuppressWarnings("unchecked")
    private Object fillOutput(
        ResultSet resultSet,
        Object output,
        Map<String, String> outputMapping,
        boolean isCursor
    ) throws PersistentException{
        if(output == null){
            if(isCursor){
                output = new LinkedHashMap<String, Object>();
            }else{
                output = new ArrayList<Object>();
            }
        }
        try{
            final ResultSetMetaData metadata = resultSet.getMetaData();
            final int colCount = metadata.getColumnCount();
            boolean isOutputMappingFromMetaData = false;
            if(outputMapping == null
                && (output instanceof RecordList
                        || output instanceof Record
                        || !(output instanceof List))
            ){
                outputMapping = new LinkedHashMap<String, String>();
                for(int i = 1; i <= colCount; i++){
                    outputMapping.put(
                        metadata.getColumnName(i),
                        metadata.getColumnName(i).toUpperCase()
                    );
                }
                isOutputMappingFromMetaData = true;
            }
            if(output instanceof DataSet){
                final Set<String> headerSet = new LinkedHashSet<String>();
                final Map<String, RecordList> recordListMap = new LinkedHashMap<String, RecordList>();
                final Map<String, Property> recordListPropMap = new LinkedHashMap<String, Property>();
                for(Map.Entry<String, String> entry : outputMapping.entrySet()){
                    final Property prop = propertyAccess.getProperty(
                        entry.getValue()
                    );
                    if(prop instanceof NestedProperty){
                        Object obj = ((NestedProperty)prop).getThisProperty()
                            .getProperty(output);
                        if(obj instanceof RecordList){
                            recordListMap.put(entry.getKey(), (RecordList)obj);
                            recordListPropMap.put(
                                entry.getKey(),
                                ((NestedProperty)prop).getNestedProperty()
                            );
                        }else{
                            headerSet.add(entry.getKey());
                        }
                    }else{
                        throw new PersistentException(
                            "Output bean fill error."
                        );
                    }
                }
                final Map<RecordList, Record> recordMap = new HashMap<RecordList, Record>();
                while(true){
                    if(!isCursor){
                        if(!resultSet.next()){
                            break;
                        }
                    }
                    if(headerSet.size() != 0){
                        for(String columnName : headerSet){
                            setValue(
                                output,
                                outputMapping.get(columnName),
                                resultSet,
                                columnName,
                                isOutputMappingFromMetaData
                            );
                        }
                        headerSet.clear();
                    }
                    recordMap.clear();
                    for(Map.Entry<String, RecordList> entry : recordListMap.entrySet()){
                        final RecordList list = entry.getValue();
                        Record record = recordMap.get(list);
                        if(record == null){
                            record = list.createRecord();
                            recordMap.put(list, record);
                            list.addRecord(record);
                        }
                        Property prop = recordListPropMap.get(entry.getKey());
                        prop.setProperty(
                            record,
                            getValue(record, prop, resultSet, entry.getKey())
                        );
                    }
                    if(isCursor){
                        break;
                    }
                }
                return output;
            }else if(output instanceof RecordList){
                RecordList list = (RecordList)output;
                if(list.getSchema() == null){
                    list.setSchema(createSchema(metadata));
                }
                while(true){
                    if(!isCursor){
                        if(!resultSet.next()){
                            break;
                        }
                    }
                    final Record record = list.createRecord();
                    for(Map.Entry<String, String> entry : outputMapping.entrySet()){
                        setValue(
                            record,
                            entry.getValue(),
                            resultSet,
                            entry.getKey(),
                            isOutputMappingFromMetaData
                        );
                    }
                    list.addRecord(record);
                    if(isCursor){
                        break;
                    }
                }
                return list;
            }else if(output instanceof List){
                List<Map<String, Object>> list = (List<Map<String, Object>>)output;
                while(true){
                    if(!isCursor){
                        if(!resultSet.next()){
                            break;
                        }
                    }
                    final Map<String, Object> record = new LinkedHashMap<String, Object>();
                    for(int i = 1; i <= colCount; i++){
                        record.put(
                            metadata.getColumnName(i),
                            resultSet.getObject(i)
                        );
                    }
                    list.add(record);
                    if(isCursor){
                        break;
                    }
                }
                return list;
            }else if(output instanceof Class){
                final Class<?> outputClass = (Class<?>)output;
                if(Record.class.isAssignableFrom(outputClass)){
                    if(!isCursor){
                        if(!resultSet.next()){
                            return null;
                        }
                    }
                    Record record = null;
                    try{
                        record = (Record)outputClass.newInstance();
                        if(record.getSchema() == null){
                            record.setSchema(createSchema(metadata));
                        }
                    }catch(InstantiationException e){
                        throw new PersistentException(
                            "Output bean instantiate error.",
                            e
                        );
                    }catch(IllegalAccessException e){
                        throw new PersistentException(
                            "Output bean instantiate error.",
                            e
                        );
                    }
                    for(Map.Entry<String, String> entry : outputMapping.entrySet()){
                        setValue(
                            record,
                            entry.getValue(),
                            resultSet,
                            entry.getKey(),
                            isOutputMappingFromMetaData
                        );
                    }
                    return record;
                }else if(RecordList.class.isAssignableFrom(outputClass)){
                    RecordList list = null;
                    try{
                        list = (RecordList)outputClass.newInstance();
                        if(list.getSchema() == null){
                            list.setSchema(createSchema(metadata));
                        }
                    }catch(InstantiationException e){
                        throw new PersistentException(
                            "Output bean instantiate error.",
                            e
                        );
                    }catch(IllegalAccessException e){
                        throw new PersistentException(
                            "Output bean instantiate error.",
                            e
                        );
                    }
                    while(true){
                        if(!isCursor){
                            if(!resultSet.next()){
                                break;
                            }
                        }
                        final Record record = list.createRecord();
                        for(Map.Entry<String, String> entry : outputMapping.entrySet()){
                            setValue(
                                record,
                                entry.getValue(),
                                resultSet,
                                entry.getKey(),
                                isOutputMappingFromMetaData
                            );
                        }
                        list.addRecord(record);
                        if(isCursor){
                            break;
                        }
                    }
                    return list;
                }else if(outputClass.isArray()){
                    List<Object> list = new ArrayList<Object>();
                    while(true){
                        if(!isCursor){
                            if(!resultSet.next()){
                                break;
                            }
                        }
                        Object bean = fillOutput(
                            resultSet,
                            outputClass.getComponentType(),
                            outputMapping,
                            isCursor
                        );
                        list.add(bean);
                        if(isCursor){
                            break;
                        }
                    }
                    return listToArray(list, outputClass.getComponentType());
                }else if(String.class.equals(outputClass) || outputClass.isPrimitive() || Number.class.isAssignableFrom(outputClass)){
                    if(!resultSet.next()){
                        return null;
                    }
                    return getValue(outputClass, resultSet, 1);
                }else{
                    if(isCursor){
                        Object bean = null;
                        try{
                            bean = outputClass.newInstance();
                        }catch(InstantiationException e){
                            throw new PersistentException(
                                "Output bean instantiate error.",
                                e
                            );
                        }catch(IllegalAccessException e){
                            throw new PersistentException(
                                "Output bean instantiate error.",
                                e
                            );
                        }
                        for(Map.Entry<String, String> entry : outputMapping.entrySet()){
                            setValue(
                                bean,
                                entry.getValue(),
                                resultSet,
                                entry.getKey(),
                                isOutputMappingFromMetaData
                            );
                        }
                        return bean;
                    }else{
                        final List<Object> list = new ArrayList<Object>();
                        while(resultSet.next()){
                            Object bean = null;
                            try{
                                bean = outputClass.newInstance();
                            }catch(InstantiationException e){
                                throw new PersistentException(
                                    "Output bean instantiate error.",
                                    e
                                );
                            }catch(IllegalAccessException e){
                                throw new PersistentException(
                                    "Output bean instantiate error.",
                                    e
                                );
                            }
                            for(Map.Entry<String, String> entry : outputMapping.entrySet()){
                                setValue(
                                    bean,
                                    entry.getValue(),
                                    resultSet,
                                    entry.getKey(),
                                    isOutputMappingFromMetaData
                                );
                            }
                            list.add(bean);
                        }
                        return list;
                    }
                }
            }else{
                if(!isCursor){
                    if(!resultSet.next()){
                        return output;
                    }
                }
                if(output instanceof Record){
                    Record record = (Record)output;
                    if(record.getSchema() == null){
                        record.setSchema(createSchema(metadata));
                    }
                }
                for(Map.Entry<String, String> entry : outputMapping.entrySet()){
                    setValue(
                        output,
                        entry.getValue(),
                        resultSet,
                        entry.getKey(),
                        isOutputMappingFromMetaData
                    );
                }
                return output;
            }
        }catch(IllegalArgumentException e){
            throw new PersistentException(
                "Output bean fill error.",
                e
            );
        }catch(NoSuchPropertyException e){
            throw new PersistentException(
                "Output bean fill error.",
                e
            );
        }catch(InvocationTargetException e){
            throw new PersistentException(
                "Output bean fill error.",
                e.getTargetException()
            );
        }catch(SQLException e){
            throw new PersistentException(
                "Output bean fill error.",
                e
            );
        }
    }
    
    private Object listToArray(List<Object> list, Class<?> componentType){
        if(componentType.isPrimitive()){
            Object array = Array.newInstance(componentType, list.size());
            if(Byte.TYPE.equals(componentType)){
                for(int i = 0, imax = list.size(); i < imax; i++){
                    Array.setByte(array, i, ((Number)list.get(i)).byteValue());
                }
            }else if(Short.TYPE.equals(componentType)){
                for(int i = 0, imax = list.size(); i < imax; i++){
                    Array.setShort(array, i, ((Number)list.get(i)).shortValue());
                }
            }else if(Integer.TYPE.equals(componentType)){
                for(int i = 0, imax = list.size(); i < imax; i++){
                    Array.setInt(array, i, ((Number)list.get(i)).intValue());
                }
            }else if(Long.TYPE.equals(componentType)){
                for(int i = 0, imax = list.size(); i < imax; i++){
                    Array.setLong(array, i, ((Number)list.get(i)).longValue());
                }
            }else if(Float.TYPE.equals(componentType)){
                for(int i = 0, imax = list.size(); i < imax; i++){
                    Array.setFloat(array, i, ((Number)list.get(i)).floatValue());
                }
            }else if(Double.TYPE.equals(componentType)){
                for(int i = 0, imax = list.size(); i < imax; i++){
                    Array.setDouble(array, i, ((Number)list.get(i)).doubleValue());
                }
            }else if(Boolean.TYPE.equals(componentType)){
                for(int i = 0, imax = list.size(); i < imax; i++){
                    Array.setBoolean(array, i, ((Boolean)list.get(i)).booleanValue());
                }
            }
            return array;
        }else{
            return list.toArray((Object[])Array.newInstance(componentType, list.size()));
        }
    }
    
    private void setValue(
        Object target,
        String propName,
        ResultSet rs,
        String cloumnName,
        boolean isIgnoreCase
    ) throws PersistentException{
        int index = 0;
        try{
            index = rs.findColumn(cloumnName);
        }catch(SQLException e){
            throw new PersistentException(
                "Output bean fill error.",
                e
            );
        }
        setValue(target, propName, rs, index, isIgnoreCase);
    }
    
    private void setValue(
        Object target,
        String propName,
        ResultSet rs,
        int index,
        boolean isIgnoreCase
    ) throws PersistentException{
        try{
            propertyAccess.set(
                target,
                propName,
                getValue(target, propName, rs, index)
            );
        }catch(IllegalArgumentException e){
            throw new PersistentException(
                "Output bean fill error.",
                e
            );
        }catch(NoSuchPropertyException e){
            if(isIgnoreCase){
                try{
                    propertyAccess.set(
                        target,
                        propName.toLowerCase(),
                        getValue(target, propName, rs, index)
                    );
                }catch(IllegalArgumentException e2){
                    throw new PersistentException(
                        "Output bean fill error.",
                        e2
                    );
                }catch(NoSuchPropertyException e2){
                    throw new PersistentException(
                        "Output bean fill error.",
                        e2
                    );
                }catch(InvocationTargetException e2){
                    throw new PersistentException(
                        "Output bean fill error.",
                        e2.getTargetException()
                    );
                }
            }else{
                throw new PersistentException(
                    "Output bean fill error.",
                    e
                );
            }
        }catch(InvocationTargetException e){
            Throwable targetException = e.getTargetException();
            if(isIgnoreCase && (targetException instanceof PropertyGetException)){
                try{
                    propertyAccess.set(
                        target,
                        propName.toLowerCase(),
                        getValue(target, propName, rs, index)
                    );
                }catch(IllegalArgumentException e2){
                    throw new PersistentException(
                        "Output bean fill error.",
                        e2
                    );
                }catch(NoSuchPropertyException e2){
                    throw new PersistentException(
                        "Output bean fill error.",
                        e2
                    );
                }catch(InvocationTargetException e2){
                    throw new PersistentException(
                        "Output bean fill error.",
                        e2.getTargetException()
                    );
                }
            }else{
                throw new PersistentException(
                    "Output bean fill error.",
                    e.getTargetException()
                );
            }
        }
    }
    
    private Object getValue(
        Object target,
        String propName,
        ResultSet rs,
        int index
    ) throws PersistentException{
        try{
            return getValue(target, propertyAccess.getProperty(propName), rs, index);
        }catch(IllegalArgumentException e){
            throw new PersistentException(
                "Output bean fill error.",
                e
            );
        }
    }
    
    private Object getValue(
        Object target,
        Property prop,
        ResultSet rs,
        String cloumnName
    ) throws PersistentException{
        int index = 0;
        try{
            index = rs.findColumn(cloumnName);
        }catch(SQLException e){
            throw new PersistentException(
                "Output bean fill error.",
                e
            );
        }
        return getValue(target, prop, rs, index);
    }
    
    private Object getValue(
        Object target,
        Property prop,
        ResultSet rs,
        int index
    ) throws PersistentException{
        try{
            return getValue(prop.getPropertyType(target), rs, index);
        }catch(NoSuchPropertyException e){
            throw new PersistentException(
                "Output bean fill error.",
                e
            );
        }catch(InvocationTargetException e){
            throw new PersistentException(
                "Output bean fill error.",
                e.getTargetException()
            );
        }
    }
    
    private Object getValue(
        Class<?> type,
        ResultSet rs,
        int index
    ) throws PersistentException{
        try{
            final ResultSetMetaData metadata = rs.getMetaData();
            final int jdbcType = metadata.getColumnType(index);
            Object value = null;
            switch(jdbcType){
            case Types.BLOB:
            case Types.CLOB:
                switch(jdbcType){
                case Types.BLOB:
                    Blob blob = null;
                    if(byte[].class.equals(type)){
                        blob = rs.getBlob(index);
                        if(blob != null){
                            value = blob.getBytes(1l, (int)blob.length());
                        }
                    }else if(InputStream.class.equals(type)){
                        value = rs.getBinaryStream(index);
                    }else if(String.class.equals(type)){
                        blob = rs.getBlob(index);
                        if(blob != null){
                            value = new String(blob.getBytes(1l, (int)blob.length()));
                        }
                    }else{
                        value = rs.getBlob(index);
                    }
                    break;
                case Types.CLOB:
                    Clob clob = null;
                    if(char[].class.equals(type)){
                        clob = rs.getClob(index);
                        if(clob != null){
                            Reader r = clob.getCharacterStream();
                            value = new char[(int)clob.length()];
                            r.read((char[])value);
                            r.close();
                        }
                    }else if(InputStream.class.equals(type)){
                        value = rs.getAsciiStream(index);
                    }else if(Reader.class.equals(type)){
                        clob = rs.getClob(index);
                        if(clob != null){
                            value = clob.getCharacterStream();
                        }
                    }else if(String.class.equals(type)){
                        clob = rs.getClob(index);
                        if(clob != null){
                            value = clob.getSubString(1l, (int)clob.length());
                        }
                    }else{
                        value = rs.getClob(index);
                    }
                    break;
                }
                break;
            case Types.DECIMAL:
            case Types.NUMERIC:
                if(Byte.TYPE.equals(type) || Byte.class.equals(type)){
                    value = new Byte(rs.getByte(index));
                }else if(Short.TYPE.equals(type) || Short.class.equals(type)){
                    value = new Short(rs.getShort(index));
                }else if(Integer.TYPE.equals(type) || Integer.class.equals(type)){
                    value = new Integer(rs.getInt(index));
                }else if(Long.TYPE.equals(type) || Long.class.equals(type)){
                    value = new Long(rs.getLong(index));
                }else if(Float.TYPE.equals(type) || Float.class.equals(type)){
                    value = new Float(rs.getFloat(index));
                }else if(Double.TYPE.equals(type) || Double.class.equals(type)){
                    value = new Double(rs.getDouble(index));
                }else if(BigInteger.class.equals(type)){
                    value = new BigInteger(rs.getString(index));
                }else if(BigDecimal.class.equals(type)){
                    value = rs.getBigDecimal(index);
                }else{
                    value = rs.getObject(index);
                }
                break;
            case Types.CHAR:
            case Types.VARCHAR:
                if(Boolean.TYPE.equals(type) || Boolean.class.equals(type)){
                    BooleanEditor editor = new BooleanEditor();
                    editor.setAsText(rs.getString(index));
                    value = editor.getValue();
                }else{
                    value = rs.getString(index);
                }
                break;
            default:
                value = rs.getObject(index);
                break;
            }
            return value;
        }catch(IOException e){
            throw new PersistentException(
                "Output bean fill error.",
                e
            );
        }catch(SQLException e){
            throw new PersistentException(
                "Output bean fill error.",
                e
            );
        }
    }
    
    private void setObject(
        PreparedStatement statement,
        int index,
        Object value
    ) throws PersistentException{
        try{
            if(value != null){
                if(value instanceof byte[]){
                    statement.setBinaryStream(
                        index,
                        new ByteArrayInputStream((byte[])value),
                        ((byte[])value).length
                    );
                    return;
                }else if(value instanceof InputStream){
                    statement.setBinaryStream(
                        index,
                        (InputStream)value
                    );
                    return;
                }else if(value instanceof char[]){
                    statement.setCharacterStream(
                        index,
                        new CharArrayReader((char[])value),
                        ((char[])value).length
                    );
                    return;
                }else if(value instanceof Reader){
                    statement.setCharacterStream(
                        index,
                        (Reader)value
                    );
                    return;
                }
            }
            statement.setObject(index, value);
        }catch(SQLException e){
            throw new PersistentException(
                "The parameter is not suitable for SQL.",
                e
            );
        }
    }
    
    private String createSchema(ResultSetMetaData metadata) throws SQLException{
        final int colCount = metadata.getColumnCount();
        final StringBuilder buf = new StringBuilder();
        for(int i = 1; i <= colCount; i++){
            final Class<?> type = resultSetJDBCTypeMap
                .get(metadata.getColumnType(i));
            buf.append(':')
               .append(metadata.getColumnName(i));
            if(type != null){
                buf.append(',')
                   .append(type.getName());
            }
            if(i != colCount){
                buf.append('\n');
            }
        }
        return buf.toString();
    }
    
    @Override
    public Cursor createQueryCursor(Connection con, String query, Object input) throws PersistentException{
        return createQueryCursor(con, query, input, null, null);
    }
    
    @Override
    public Cursor createQueryCursor(Connection con, String query, Object input, Map<String, Object> statementProps, Map<String, Object> resultSetProps) throws PersistentException{
        final StringBuilder buf = new StringBuilder(query);
        final List<Object> inputProps = parseInput(buf);
        final List<Object> outputProps = parseOutput(buf);
        String sql = buf.toString();
        return createCursor(con, sql, input, inputProps, outputProps, statementProps, resultSetProps);
    }
    
    @Override
    public Cursor createCursor(Connection con, String sql, Object input, Object inputProps, Object outputProps) throws PersistentException{
        return createCursor(con, sql, input, inputProps, outputProps, null, null);
    }
    
    @SuppressWarnings("unchecked")
    @Override
    public Cursor createCursor(Connection con, String sql, Object input, Object inputProps, Object outputProps, Map<String, Object> statementProps, Map<String, Object> resultSetProps) throws PersistentException{
        List<Object> inputPropList = null;
        if(inputProps != null){
            if(inputProps.getClass().isArray()){
                inputPropList = (List<Object>)Arrays.asList((Object[])inputProps);
            }else if(inputProps instanceof List){
                inputPropList = (List<Object>)inputProps;
            }else if(inputProps instanceof String){
                inputPropList = new ArrayList<Object>();
                inputPropList.add(inputProps);
            }else{
                throw new PersistentException("No supported inputProps type." + inputProps);
            }
        }else if(input != null && input instanceof Map){
            inputPropList = new ArrayList<Object>(((Map<Object,?>)input).keySet());
        }
        List<Object> outputPropList = null;
        Map<String, String> outputPropMap = null;
        if(outputProps != null){
            if(outputProps.getClass().isArray()){
                outputPropList = (List<Object>)Arrays.asList((Object[])outputProps);
            }else if(outputProps instanceof List){
                outputPropList = (List<Object>)outputProps;
            }else if(outputProps instanceof Map){
                outputPropMap = (Map<String, String>)outputProps;
            }else if(outputProps instanceof String){
                outputPropList = new ArrayList<Object>();
                outputPropList.add(outputProps);
            }else{
                throw new PersistentException("No supported outputProps type." + outputProps);
            }
        }
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try{
            statement = con.prepareStatement(sql);
        }catch(SQLException e){
            throw new PersistentException("Illegal sql : " + sql, e);
        }
        setStatementProperties(statement, statementProps);
        try{
            final ParameterMetaData metadata
                = statement.getParameterMetaData();
            if(inputPropList != null
                && inputPropList.size() != metadata.getParameterCount()){
                throw new PersistentException("Illegal sql : " + sql);
            }
            if(input != null){
                if(inputPropList != null){
                    for(int i = 0, imax = inputPropList.size(); i < imax; i++){
                        Object param = propertyAccess.get(
                            input,
                            inputPropList.get(i).toString()
                        );
                        setObject(statement, i + 1, param);
                    }
                }else if(input.getClass().isArray()){
                    for(int i = 0, imax = Array.getLength(input); i < imax; i++){
                        Object param = Array.get(input, i);
                        setObject(statement, i + 1, param);
                    }
                }else if(input instanceof List){
                    List<Object> list = (List<Object>)input;
                    for(int i = 0, imax = list.size(); i < imax; i++){
                        Object param = list.get(i);
                        setObject(statement, i + 1, param);
                    }
                }else{
                    setObject(statement, 1, input);
                }
            }
        }catch(NoSuchPropertyException e){
            throw new PersistentException(
                "Input bean get error.",
                e
            );
        }catch(InvocationTargetException e){
            throw new PersistentException(
                "Input bean get error.",
                e.getTargetException()
            );
        }catch(SQLException e){
            throw new PersistentException(
                "The parameter is not suitable for SQL.",
                e
            );
        }
        try{
            resultSet = statement.executeQuery();
        }catch(SQLException e){
            throw new PersistentException("SQL execute error : " + sql, e);
        }
        setResultSetProperties(resultSet, resultSetProps);
        if(outputPropList != null){
            try{
                ResultSetMetaData metadata = resultSet.getMetaData();
                if((outputPropList.size() != metadata.getColumnCount())){
                    throw new PersistentException("Illegal sql : " + sql);
                }
                outputPropMap = new LinkedHashMap<String, String>();
                for(int i = 0, imax = outputPropList.size(); i < imax; i++){
                    outputPropMap.put(
                        metadata.getColumnName(i + 1),
                        outputPropList.get(i).toString()
                    );
                }
            }catch(SQLException e){
                throw new PersistentException(
                    "The parameter is not suitable for SQL.",
                    e
                );
            }
        }
        return new CursorImpl(
            statement,
            resultSet,
            outputPropMap
        );
    }
    
    @Override
    public int persistQuery(Connection con, String query, Object input) throws PersistentException{
        return persistQuery(con, query, input, null);
    }
    
    @Override
    public int persistQuery(Connection con, String query, Object input, Map<String, Object> statementProps) throws PersistentException{
        final StringBuilder buf = new StringBuilder(query);
        final List<Object> inputProps = parseInput(buf);
        String sql = buf.toString();
        return persist(con, sql, input, inputProps, statementProps);
    }
    
    @Override
    public int persist(Connection con, String sql, Object input, Object inputProps) throws PersistentException{
        return persist(con, sql, input, inputProps, null);
    }
    
    @SuppressWarnings("unchecked")
    @Override
    public int persist(Connection con, String sql, Object input, Object inputProps, Map<String, Object> statementProps) throws PersistentException{
        List<Object> inputPropList = null;
        if(inputProps != null){
            if(inputProps.getClass().isArray()){
                inputPropList = (List<Object>)Arrays.asList((Object[])inputProps);
            }else if(inputProps instanceof List){
                inputPropList = (List<Object>)inputProps;
            }else if(inputProps instanceof String){
                inputPropList = new ArrayList<Object>();
                inputPropList.add(inputProps);
            }else{
                throw new PersistentException("No supported inputProps type." + inputProps);
            }
        }
        PreparedStatement statement = null;
        try{
            try{
                statement = con.prepareStatement(sql);
                final ParameterMetaData metadata
                    = statement.getParameterMetaData();
                if(inputPropList != null
                    && (inputPropList.size() != metadata.getParameterCount())){
                    throw new PersistentException("Illegal sql : " + sql);
                }
            }catch(SQLException e){
                throw new PersistentException("Illegal sql : " + sql, e);
            }
            setStatementProperties(statement, statementProps);
            return persistQueryInternal(sql, statement, input, inputPropList, false);
        }finally{
            if(statement != null){
                try{
                    statement.close();
                }catch(SQLException e){
                }
                statement = null;
            }
        }
    }
    
    @SuppressWarnings("unchecked")
    private int persistQueryInternal(
        String sql,
        PreparedStatement statement,
        Object input,
        List<Object> inputProps,
        boolean isBatch
    ) throws PersistentException{
        if(input instanceof DataSet){
            if(inputProps == null){
                throw new PersistentException(
                    "Input bean get error."
                );
            }
            final List<Object> beans = new ArrayList<Object>();
            final List<Property> properties = new ArrayList<Property>();
            int count = -1;
            try{
                for(Object propStr : inputProps){
                    final Property prop = propertyAccess.getProperty(propStr.toString());
                    if(prop instanceof NestedProperty){
                        Object obj = ((NestedProperty)prop).getThisProperty()
                            .getProperty(input);
                        if(obj instanceof RecordList){
                            final int size = ((RecordList)obj).size();
                            if(count == -1){
                                count = size;
                            }else if(count != size){
                                throw new PersistentException(
                                    "Input bean get error."
                                );
                            }
                        }
                        beans.add(obj);
                        properties.add(
                            ((NestedProperty)prop).getNestedProperty()
                        );
                    }else{
                        throw new PersistentException(
                            "Input bean get error."
                        );
                    }
                }
            }catch(NoSuchPropertyException e){
                throw new PersistentException(
                    "Input bean get error.",
                    e
                );
            }catch(InvocationTargetException e){
                throw new PersistentException(
                    "Input bean get error.",
                    e.getTargetException()
                );
            }
            if(count == 0){
                return 0;
            }
            if(count == -1){
                try{
                    for(int i = 0, imax = beans.size(); i < imax; i++){
                        Object param = properties.get(i).getProperty(
                            beans.get(i)
                        );
                        setObject(statement, i + 1, param);
                    }
                }catch(NoSuchPropertyException e){
                    throw new PersistentException(
                        "Input bean get error.",
                        e
                    );
                }catch(InvocationTargetException e){
                    throw new PersistentException(
                        "Input bean get error.",
                        e.getTargetException()
                    );
                }
                try{
                    if(isBatch){
                        statement.addBatch();
                        return -1;
                    }else{
                        return statement.executeUpdate();
                    }
                }catch(SQLException e){
                    throw new PersistentException("SQL execute error : " + sql, e);
                }
            }else{
                for(int i = 0; i < count; i++){
                    try{
                        for(int j = 0, jmax = beans.size(); j < jmax; j++){
                            Object bean = beans.get(j);
                            if(bean instanceof RecordList){
                                bean = ((RecordList)bean).get(i);
                            }
                            Object param = properties.get(j).getProperty(
                                bean
                            );
                            setObject(statement, j + 1, param);
                        }
                    }catch(NoSuchPropertyException e){
                        throw new PersistentException(
                            "Input bean get error.",
                            e
                        );
                    }catch(InvocationTargetException e){
                        throw new PersistentException(
                            "Input bean get error.",
                            e.getTargetException()
                        );
                    }
                    try{
                        statement.addBatch();
                    }catch(SQLException e){
                        throw new PersistentException("SQL add batch error : " + sql, e);
                    }
                }
                if(isBatch){
                    return -1;
                }else{
                    int[] updateCounts = null;
                    try{
                        updateCounts = statement.executeBatch();
                    }catch(SQLException e){
                        throw new PersistentException("SQL execute error : " + sql, e);
                    }
                    int result = 0;
                    for(int c : updateCounts){
                        if(c > 0){
                            result += c;
                        }
                    }
                    if(result == 0){
                        try{
                            result = statement.getUpdateCount();
                        }catch(SQLException e){
                        }
                    }
                    return result;
                }
            }
        }else if((input instanceof List)
            || (input != null && input.getClass().isArray())
        ){
            List<Object> list = null;
            if(input instanceof List){
                list = (List<Object>)input;
            }else{
                list = (List<Object>)Arrays.asList((Object[])input);
            }
            if(inputProps != null){
                if(list.size() == 0){
                    return 0;
                }
                for(Object bean : list){
                    try{
                        for(int i = 0, imax = inputProps.size(); i < imax; i++){
                            Object param = propertyAccess.get(
                                bean,
                                inputProps.get(i).toString()
                            );
                            setObject(statement, i + 1, param);
                        }
                    }catch(NoSuchPropertyException e){
                        throw new PersistentException(
                            "Input bean get error.",
                            e
                        );
                    }catch(InvocationTargetException e){
                        throw new PersistentException(
                            "Input bean get error.",
                            e.getTargetException()
                        );
                    }
                    try{
                        statement.addBatch();
                    }catch(SQLException e){
                        throw new PersistentException("SQL add batch error : " + sql, e);
                    }
                }
                if(isBatch){
                    return -1;
                }else{
                    int[] updateCounts = null;
                    try{
                        updateCounts = statement.executeBatch();
                    }catch(SQLException e){
                        throw new PersistentException("SQL execute error : " + sql, e);
                    }
                    int result = 0;
                    for(int count : updateCounts){
                        if(count > 0){
                            result += count;
                        }
                    }
                    if(result == 0){
                        try{
                            result = statement.getUpdateCount();
                        }catch(SQLException e){
                        }
                    }
                    return result;
                }
            }else{
                if(list.size() == 0){
                    return 0;
                }
                int result = 0;
                for(int i = 0, imax = list.size(); i < imax; i++){
                    Object bean = list.get(i);
                    if(bean instanceof Map){
                        try{
                            int j = 0;
                            for(Object propName : ((Map<Object,Object>)bean).keySet()){
                                Object param = propertyAccess.get(
                                    bean,
                                    propName.toString()
                                );
                                setObject(statement, ++j, param);
                            }
                        }catch(NoSuchPropertyException e){
                            throw new PersistentException(
                                "Input bean get error.",
                                e
                            );
                        }catch(InvocationTargetException e){
                            throw new PersistentException(
                                "Input bean get error.",
                                e.getTargetException()
                            );
                        }
                        try{
                            statement.addBatch();
                        }catch(SQLException e){
                            throw new PersistentException("SQL add batch error : " + sql, e);
                        }
                        if(i == imax - 1){
                            if(isBatch){
                                result = -1;
                            }else{
                                int updateCount = 0;
                                int[] updateCounts = null;
                                try{
                                    updateCounts = statement.executeBatch();
                                }catch(SQLException e){
                                    throw new PersistentException("SQL execute error : " + sql, e);
                                }
                                for(int count : updateCounts){
                                    if(count > 0){
                                        updateCount += count;
                                    }
                                }
                                if(updateCount == 0){
                                    try{
                                        updateCount = statement.getUpdateCount();
                                    }catch(SQLException e){
                                    }
                                }
                                result = updateCount;
                            }
                        }
                    }else{
                        setObject(statement, i + 1, bean);
                        if(i == imax - 1){
                            if(isBatch){
                                try{
                                    statement.addBatch();
                                }catch(SQLException e){
                                    throw new PersistentException("SQL add batch error : " + sql, e);
                                }
                                result = -1;
                            }else{
                                try{
                                    result = statement.executeUpdate();
                                }catch(SQLException e){
                                    throw new PersistentException("SQL execute error : " + sql, e);
                                }
                            }
                        }
                    }
                }
                return result;
            }
        }else{
            try{
                if(input != null){
                    if(inputProps != null){
                        for(int i = 0, imax = inputProps.size(); i < imax; i++){
                            Object param = propertyAccess.get(
                                input,
                                inputProps.get(i).toString()
                            );
                            setObject(statement, i + 1, param);
                        }
                    }else{
                        if(input instanceof Map){
                            int i = 0;
                            for(Object propName : ((Map<Object,Object>)input).keySet()){
                                Object param = propertyAccess.get(
                                    input,
                                    propName.toString()
                                );
                                setObject(statement, ++i, param);
                            }
                        }else{
                            setObject(statement, 1, input);
                        }
                    }
                }else{
                    if(inputProps != null){
                        for(int i = 0, imax = inputProps.size(); i < imax; i++){
                            setObject(statement, i + 1, null);
                        }
                    }else{
                        int parameterCount = 0;
                        try{
                            ParameterMetaData paramMetaData = statement.getParameterMetaData();
                            parameterCount = paramMetaData.getParameterCount();
                        }catch(SQLException e){
                            throw new PersistentException("Illegal sql : " + sql, e);
                        }
                        if(parameterCount != 0){
                            setObject(statement, 1, input);
                        }
                    }
                }
            }catch(NoSuchPropertyException e){
                throw new PersistentException(
                    "Input bean get error.",
                    e
                );
            }catch(InvocationTargetException e){
                throw new PersistentException(
                    "Input bean get error.",
                    e.getTargetException()
                );
            }
            if(isBatch){
                try{
                    statement.addBatch();
                }catch(SQLException e){
                    throw new PersistentException("SQL add batch error : " + sql, e);
                }
                return -1;
            }else{
                try{
                    return statement.executeUpdate();
                }catch(SQLException e){
                    throw new PersistentException("SQL execute error : " + sql, e);
                }
            }
        }
    }
    
    @Override
    public BatchExecutor createQueryBatchExecutor(Connection con, String query) throws PersistentException{
        return createQueryBatchExecutor(con, query, null);
    }
    
    @Override
    public BatchExecutor createQueryBatchExecutor(Connection con, String query, Map<String, Object> statementProps) throws PersistentException{
        final StringBuilder buf = new StringBuilder(query);
        final List<Object> inputProps = parseInput(buf);
        String sql = buf.toString();
        return new BatchExecutorImpl(con, sql, inputProps, statementProps);
    }
    
    @Override
    public BatchExecutor createBatchExecutor(Connection con, String sql, Object inputProps) throws PersistentException{
        return createBatchExecutor(con, sql, inputProps, null);
    }
    
    @SuppressWarnings("unchecked")
    @Override
    public BatchExecutor createBatchExecutor(Connection con, String sql, Object inputProps, Map<String, Object> statementProps) throws PersistentException{
        List<Object> inputPropList = null;
        if(inputProps != null){
            if(inputProps.getClass().isArray()){
                inputPropList = (List<Object>)Arrays.asList((Object[])inputProps);
            }else if(inputProps instanceof List){
                inputPropList = (List<Object>)inputProps;
            }else if(inputProps instanceof String){
                inputPropList = new ArrayList<Object>();
                inputPropList.add(inputProps);
            }else{
                throw new PersistentException("No supported inputProps type." + inputProps);
            }
        }
        return new BatchExecutorImpl(con, sql, inputPropList, statementProps);
    }
    
    private void setStatementProperties(PreparedStatement st, Map<String, Object> statementProps) throws PersistentException{
        if(statementProps == null || statementProps.size() == 0){
            return;
        }
        try{
            for(Map.Entry<String, Object> entry : statementProps.entrySet()){
                propertyAccess.set(
                    st,
                    entry.getKey(),
                    entry.getValue()
                );
            }
        }catch(NoSuchPropertyException e){
            throw new PersistentException(
                "Statement property set error.",
                e
            );
        }catch(InvocationTargetException e){
            throw new PersistentException(
                "Statement property set error.",
                e.getTargetException()
            );
        }
    }
    
    private void setResultSetProperties(ResultSet rs, Map<String, Object> resultSetProps) throws PersistentException{
        if(resultSetProps == null || resultSetProps.size() == 0){
            return;
        }
        try{
            for(Map.Entry<String, Object> entry : resultSetProps.entrySet()){
                propertyAccess.set(
                    rs,
                    entry.getKey(),
                    entry.getValue()
                );
            }
        }catch(NoSuchPropertyException e){
            throw new PersistentException(
                "ResultSet property set error.",
                e
            );
        }catch(InvocationTargetException e){
            throw new PersistentException(
                "ResultSet property set error.",
                e.getTargetException()
            );
        }
    }
    
    /**
     * 撉傒崬傒僇乕僜儖丅<p>
     *
     * @author M.Takata
     */
    private class CursorImpl implements Cursor{
        private PreparedStatement statement;
        private ResultSet resultSet;
        private Map<String, String> outputMapping;
        
        public CursorImpl(
            PreparedStatement statement,
            ResultSet resultSet,
            Map<String, String> outputMapping
        ){
            this.statement = statement;
            this.resultSet = resultSet;
            this.outputMapping = outputMapping;
        }
        
        @Override
        public boolean next() throws PersistentException{
            if(resultSet == null){
                throw new PersistentException("Closed");
            }
            try{
                return resultSet.next();
            }catch(SQLException e){
                throw new PersistentException(e);
            }
        }
        
        @Override
        public boolean previous() throws PersistentException{
            if(resultSet == null){
                throw new PersistentException("Closed");
            }
            try{
                return resultSet.previous();
            }catch(SQLException e){
                throw new PersistentException(e);
            }
        }
        
        @Override
        public boolean first() throws PersistentException{
            if(resultSet == null){
                throw new PersistentException("Closed");
            }
            try{
                return resultSet.first();
            }catch(SQLException e){
                throw new PersistentException(e);
            }
        }
        
        @Override
        public boolean last() throws PersistentException{
            if(resultSet == null){
                throw new PersistentException("Closed");
            }
            try{
                return resultSet.last();
            }catch(SQLException e){
                throw new PersistentException(e);
            }
        }
        
        @Override
        public void beforeFirst() throws PersistentException{
            if(resultSet == null){
                throw new PersistentException("Closed");
            }
            try{
                resultSet.beforeFirst();
            }catch(SQLException e){
                throw new PersistentException(e);
            }
        }
        
        @Override
        public void afterLast() throws PersistentException{
            if(resultSet == null){
                throw new PersistentException("Closed");
            }
            try{
                resultSet.afterLast();
            }catch(SQLException e){
                throw new PersistentException(e);
            }
        }
        
        @Override
        public boolean absolute(int row) throws PersistentException{
            if(resultSet == null){
                throw new PersistentException("Closed");
            }
            try{
                return resultSet.absolute(row);
            }catch(SQLException e){
                throw new PersistentException(e);
            }
        }
        
        @Override
        public boolean relative(int rows) throws PersistentException{
            if(resultSet == null){
                throw new PersistentException("Closed");
            }
            try{
                return resultSet.relative(rows);
            }catch(SQLException e){
                throw new PersistentException(e);
            }
        }
        
        @Override
        public boolean isFirst() throws PersistentException{
            if(resultSet == null){
                throw new PersistentException("Closed");
            }
            try{
                return resultSet.isFirst();
            }catch(SQLException e){
                throw new PersistentException(e);
            }
        }
        
        @Override
        public boolean isLast() throws PersistentException{
            if(resultSet == null){
                throw new PersistentException("Closed");
            }
            try{
                return resultSet.isLast();
            }catch(SQLException e){
                throw new PersistentException(e);
            }
        }
        
        @Override
        public boolean isBeforeFirst() throws PersistentException{
            if(resultSet == null){
                throw new PersistentException("Closed");
            }
            try{
                return resultSet.isBeforeFirst();
            }catch(SQLException e){
                throw new PersistentException(e);
            }
        }
        
        @Override
        public boolean isAfterLast() throws PersistentException{
            if(resultSet == null){
                throw new PersistentException("Closed");
            }
            try{
                return resultSet.isAfterLast();
            }catch(SQLException e){
                throw new PersistentException(e);
            }
        }
        
        @Override
        public void setFetchDirection(int direction) throws PersistentException{
            if(resultSet == null){
                throw new PersistentException("Closed");
            }
            try{
                resultSet.setFetchDirection(direction);
            }catch(SQLException e){
                throw new PersistentException(e);
            }
        }
        
        @Override
        public int getFetchDirection() throws PersistentException{
            if(resultSet == null){
                throw new PersistentException("Closed");
            }
            try{
                return resultSet.getFetchDirection();
            }catch(SQLException e){
                throw new PersistentException(e);
            }
        }
        
        @Override
        public void setFetchSize(int rows) throws PersistentException{
            if(resultSet == null){
                throw new PersistentException("Closed");
            }
            try{
                resultSet.setFetchSize(rows);
            }catch(SQLException e){
                throw new PersistentException(e);
            }
        }
        
        @Override
        public int getFetchSize() throws PersistentException{
            if(resultSet == null){
                throw new PersistentException("Closed");
            }
            try{
                return resultSet.getFetchSize();
            }catch(SQLException e){
                throw new PersistentException(e);
            }
        }
        
        @Override
        public int getRow() throws PersistentException{
            if(resultSet == null){
                throw new PersistentException("Closed");
            }
            try{
                return resultSet.getRow();
            }catch(SQLException e){
                throw new PersistentException(e);
            }
        }
        
        @Override
        public Object load(Object output) throws PersistentException{
            if(resultSet == null){
                throw new PersistentException("Closed");
            }
            return fillOutput(resultSet, output, outputMapping, true);
        }
        
        @Override
        public boolean isClosed(){
            return statement == null;
        }
        
        @Override
        public void close(){
            if(statement != null){
                try{
                    statement.close();
                }catch(SQLException e){
                }
                statement = null;
            }
            if(resultSet != null){
                try{
                    resultSet.close();
                }catch(SQLException e){
                }
                resultSet = null;
            }
            outputMapping = null;
        }
    }
    
    /**
     * 僶僢僠幚峴丅<p>
     *
     * @author M.Takata
     */
    private class BatchExecutorImpl implements BatchExecutor{
        
        private Connection connection;
        private String sql;
        private PreparedStatement statement;
        private List<Object> inputProps;
        private int currentBatchCount;
        private int autoBatchPersistCount;
        private boolean isAutoCommitOnPersist;
        
        BatchExecutorImpl(Connection con, String sql, List<Object> inputProps, Map<String, Object> statementProps) throws PersistentException{
            connection = con;
            this.sql = sql;
            this.inputProps = inputProps;
            try{
                statement = con.prepareStatement(sql);
                if(inputProps != null){
                    final ParameterMetaData metadata
                        = statement.getParameterMetaData();
                    if(inputProps.size() != metadata.getParameterCount()){
                        throw new PersistentException("Illegal sql : " + sql);
                    }
                }
            }catch(SQLException e){
                throw new PersistentException("Illegal sql : " + sql, e);
            }
            setStatementProperties(statement, statementProps);
        }
        
        @Override
        public void setAutoBatchPersistCount(int count){
            autoBatchPersistCount = count;
        }
        
        @Override
        public int getAutoBatchPersistCount(){
            return autoBatchPersistCount;
        }
        
        @Override
        public void setAutoCommitOnPersist(boolean isCommit){
            isAutoCommitOnPersist = isCommit;
        }
        
        @Override
        public boolean isAutoCommitOnPersist(){
            return isAutoCommitOnPersist;
        }
        
        @Override
        public int addBatch(Object input) throws PersistentException{
            if(statement == null){
                throw new PersistentException("Closed");
            }
            persistQueryInternal(sql, statement, input, inputProps, true);
            currentBatchCount++;
            if(autoBatchPersistCount > 0 && currentBatchCount >= autoBatchPersistCount){
                return persist();
            }else{
                return 0;
            }
        }
        
        @Override
        public int persist() throws PersistentException{
            if(statement == null){
                throw new PersistentException("Closed");
            }
            int[] updateCounts = null;
            try{
                updateCounts = statement.executeBatch();
            }catch(SQLException e){
                throw new PersistentException("Batch execute error.", e);
            }
            int result = 0;
            for(int count : updateCounts){
                if(count > 0){
                    result += count;
                }
            }
            if(result == 0){
                try{
                    result = statement.getUpdateCount();
                }catch(SQLException e){
                }
            }
            currentBatchCount = 0;
            try{
                if(isAutoCommitOnPersist && !connection.getAutoCommit()){
                    connection.commit();
                }
            }catch(SQLException e){
                throw new PersistentException(e);
            }
            return result;
        }
        
        @Override
        public void clearBatch() throws PersistentException{
            if(statement != null){
                try{
                    statement.clearBatch();
                }catch(SQLException e){
                    throw new PersistentException("Batch clear error.", e);
                }
            }
            currentBatchCount = 0;
        }
        
        @Override
        public void close(){
            if(statement != null){
                try{
                    statement.close();
                }catch(SQLException e){
                }
                statement = null;
            }
            if(inputProps != null){
                inputProps = null;
            }
            connection = null;
            currentBatchCount = 0;
        }
    }
}