/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * 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 com.alibaba.druid.filter.encoding;

import com.alibaba.druid.filter.FilterAdapter;
import com.alibaba.druid.filter.FilterChain;
import com.alibaba.druid.proxy.jdbc.*;
import com.alibaba.druid.util.Utils;

import java.io.Reader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Properties;

/**
 * @author wenshao [szujobs@hotmail.com]
 */
public class EncodingConvertFilter extends FilterAdapter {
    public static final String ATTR_CHARSET_CONVERTER = "ali.charset.converter";

    public static final String CLIENT_ENCODING_KEY = "clientEncoding";

    public static final String SERVER_ENCODING_KEY = "serverEncoding";

    @Override
    public ConnectionProxy connection_connect(FilterChain chain, Properties info) throws SQLException {
        ConnectionProxy conn = chain.connection_connect(info);

        String clientEncoding = info.getProperty(CLIENT_ENCODING_KEY);
        String serverEncoding = info.getProperty(SERVER_ENCODING_KEY);

        conn.putAttribute(ATTR_CHARSET_CONVERTER, new CharsetConvert(clientEncoding, serverEncoding));

        return conn;
    }

    @Override
    public String resultSet_getString(FilterChain chain, ResultSetProxy result, int columnIndex) throws SQLException {
        String value = super.resultSet_getString(chain, result, columnIndex);
        return decode(result.getStatementProxy().getConnectionProxy(), value);
    }

    @Override
    public String resultSet_getString(FilterChain chain,
                                      ResultSetProxy result,
                                      String columnLabel) throws SQLException {
        String value = super.resultSet_getString(chain, result, columnLabel);
        return decode(result.getStatementProxy().getConnectionProxy(), value);
    }

    @Override
    public Object resultSet_getObject(FilterChain chain, ResultSetProxy result, int columnIndex) throws SQLException {
        ResultSet rawResultSet = result.getResultSetRaw();
        ResultSetMetaData metadata = rawResultSet.getMetaData();
        int columnType = metadata.getColumnType(columnIndex);

        Object value = null;
        switch (columnType) {
            case Types.CHAR:
            case Types.CLOB:
            case Types.LONGVARCHAR:
            case Types.VARCHAR:
                value = super.resultSet_getString(chain, result, columnIndex);
                break;
            default:
                value = super.resultSet_getObject(chain, result, columnIndex);
        }

        return decodeObject(result.getStatementProxy().getConnectionProxy(), value);
    }

    @Override
    public <T> T resultSet_getObject(FilterChain chain,
                                     ResultSetProxy result,
                                     int columnIndex,
                                     Class<T> type) throws SQLException {
        ResultSet rawResultSet = result.getResultSetRaw();
        ResultSetMetaData metadata = rawResultSet.getMetaData();
        int columnType = metadata.getColumnType(columnIndex);

        Object value = null;
        switch (columnType) {
            case Types.CHAR:
            case Types.CLOB:
            case Types.LONGVARCHAR:
            case Types.VARCHAR:
                value = super.resultSet_getString(chain, result, columnIndex);
                break;
            default:
                value = super.resultSet_getObject(chain, result, columnIndex, type);
        }

        return (T) decodeObject(result.getStatementProxy().getConnectionProxy(), value);
    }

    @Override
    public Object resultSet_getObject(FilterChain chain, ResultSetProxy result, int columnIndex,
                                      java.util.Map<String, Class<?>> map) throws SQLException {
        ResultSet rawResultSet = result.getResultSetRaw();
        ResultSetMetaData metadata = rawResultSet.getMetaData();
        int columnType = metadata.getColumnType(columnIndex);

        Object value = null;
        switch (columnType) {
            case Types.CHAR:
            case Types.CLOB:
            case Types.LONGVARCHAR:
            case Types.VARCHAR:
                value = super.resultSet_getString(chain, result, columnIndex);
                break;
            default:
                value = super.resultSet_getObject(chain, result, columnIndex, map);
        }

        return decodeObject(result.getStatementProxy().getConnectionProxy(), value);
    }

    @Override
    public Object resultSet_getObject(FilterChain chain,
                                      ResultSetProxy result,
                                      String columnLabel) throws SQLException {
        ResultSet rawResultSet = result.getResultSetRaw();
        ResultSetMetaData metadata = rawResultSet.getMetaData();
        int columnIndex = rawResultSet.findColumn(columnLabel);
        int columnType = metadata.getColumnType(columnIndex);

        Object value = null;
        switch (columnType) {
            case Types.CHAR:
            case Types.CLOB:
            case Types.LONGVARCHAR:
            case Types.VARCHAR:
                value = super.resultSet_getString(chain, result, columnLabel);
                break;
            default:
                value = super.resultSet_getObject(chain, result, columnLabel);
        }
        return decodeObject(result.getStatementProxy().getConnectionProxy(), value);
    }

    @Override
    public <T> T resultSet_getObject(FilterChain chain,
                                     ResultSetProxy result,
                                     String columnLabel,
                                     Class<T> type) throws SQLException {
        ResultSet rawResultSet = result.getResultSetRaw();
        ResultSetMetaData metadata = rawResultSet.getMetaData();
        int columnIndex = rawResultSet.findColumn(columnLabel);
        int columnType = metadata.getColumnType(columnIndex);

        Object value = null;
        switch (columnType) {
            case Types.CHAR:
            case Types.CLOB:
            case Types.LONGVARCHAR:
            case Types.VARCHAR:
                value = super.resultSet_getString(chain, result, columnLabel);
                break;
            default:
                value = super.resultSet_getObject(chain, result, columnLabel, type);
        }
        return (T) decodeObject(result.getStatementProxy().getConnectionProxy(), value);
    }

    @Override
    public Object resultSet_getObject(FilterChain chain, ResultSetProxy result, String columnLabel,
                                      java.util.Map<String, Class<?>> map) throws SQLException {
        ResultSet rawResultSet = result.getResultSetRaw();
        ResultSetMetaData metadata = rawResultSet.getMetaData();
        int columnIndex = rawResultSet.findColumn(columnLabel);
        int columnType = metadata.getColumnType(columnIndex);

        Object value = null;
        switch (columnType) {
            case Types.CHAR:
                value = super.resultSet_getString(chain, result, columnLabel);
                break;
            case Types.CLOB:
                value = super.resultSet_getString(chain, result, columnLabel);
                break;
            case Types.LONGVARCHAR:
                value = super.resultSet_getString(chain, result, columnLabel);
                break;
            case Types.VARCHAR:
                value = super.resultSet_getString(chain, result, columnLabel);
                break;
            default:
                value = super.resultSet_getObject(chain, result, columnLabel, map);
        }

        return decodeObject(result.getStatementProxy().getConnectionProxy(), value);
    }

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

    public Object decodeObject(ConnectionProxy connection, Object object) throws SQLException {
        if (object instanceof String) {
            return decode(connection, (String) object);
        }

        if (object instanceof Reader) {
            Reader reader = (Reader) object;
            String text = Utils.read(reader);
            return new StringReader(decode(connection, text));
        }

        return object;
    }

    public Object decodeObject(CallableStatementProxy stmt, Object object) throws SQLException {
        if (object instanceof String) {
            return decode(stmt.getConnectionProxy(), (String) object);
        }

        if (object instanceof Reader) {
            Reader reader = (Reader) object;
            String text = Utils.read(reader);
            return new StringReader(decode(stmt.getConnectionProxy(), text));
        }

        return object;
    }

    public String encode(ConnectionProxy connection, String s) throws SQLException {
        try {
            CharsetConvert charsetConvert = (CharsetConvert) connection.getAttribute(ATTR_CHARSET_CONVERTER);

            return charsetConvert.encode(s);
        } catch (UnsupportedEncodingException e) {
            throw new SQLException(e.getMessage(), e);
        }
    }

    public String decode(ConnectionProxy connection, String s) throws SQLException {
        try {
            CharsetConvert charsetConvert = (CharsetConvert) connection.getAttribute(ATTR_CHARSET_CONVERTER);
            return charsetConvert.decode(s);
        } catch (UnsupportedEncodingException e) {
            throw new SQLException(e.getMessage(), e);
        }
    }

    // //////////////// Connection

    @Override
    public PreparedStatementProxy connection_prepareStatement(FilterChain chain, ConnectionProxy connection, String sql)
            throws SQLException {
        return super.connection_prepareStatement(chain, connection, encode(connection, sql));
    }

    @Override
    public PreparedStatementProxy connection_prepareStatement(FilterChain chain, ConnectionProxy connection,
                                                              String sql, int autoGeneratedKeys) throws SQLException {
        return super.connection_prepareStatement(chain, connection, encode(connection, sql), autoGeneratedKeys);
    }

    @Override
    public PreparedStatementProxy connection_prepareStatement(FilterChain chain, ConnectionProxy connection,
                                                              String sql, int resultSetType, int resultSetConcurrency)
            throws SQLException {
        return super.connection_prepareStatement(chain, connection, encode(connection, sql), resultSetType,
                resultSetConcurrency);
    }

    @Override
    public PreparedStatementProxy connection_prepareStatement(FilterChain chain, ConnectionProxy connection,
                                                              String sql, int resultSetType, int resultSetConcurrency,
                                                              int resultSetHoldability) throws SQLException {
        return super.connection_prepareStatement(chain, connection, encode(connection, sql), resultSetType,
                resultSetConcurrency, resultSetHoldability);
    }

    @Override
    public PreparedStatementProxy connection_prepareStatement(FilterChain chain, ConnectionProxy connection,
                                                              String sql, int[] columnIndexes) throws SQLException {
        return super.connection_prepareStatement(chain, connection, encode(connection, sql), columnIndexes);
    }

    @Override
    public PreparedStatementProxy connection_prepareStatement(FilterChain chain, ConnectionProxy connection,
                                                              String sql, String[] columnNames) throws SQLException {
        return super.connection_prepareStatement(chain, connection, encode(connection, sql), columnNames);
    }

    // / precall

    @Override
    public CallableStatementProxy connection_prepareCall(FilterChain chain, ConnectionProxy connection, String sql)
            throws SQLException {
        return super.connection_prepareCall(chain, connection, encode(connection, sql));
    }

    @Override
    public CallableStatementProxy connection_prepareCall(FilterChain chain, ConnectionProxy connection, String sql,
                                                         int resultSetType, int resultSetConcurrency)
            throws SQLException {
        return super.connection_prepareCall(chain, connection, encode(connection, sql), resultSetType,
                resultSetConcurrency);
    }

    @Override
    public CallableStatementProxy connection_prepareCall(FilterChain chain, ConnectionProxy connection, String sql,
                                                         int resultSetType, int resultSetConcurrency,
                                                         int resultSetHoldability) throws SQLException {
        return super.connection_prepareCall(chain, connection, encode(connection, sql), resultSetType,
                resultSetConcurrency, resultSetHoldability);
    }

    // nativeSQL

    @Override
    public String connection_nativeSQL(FilterChain chain, ConnectionProxy connection, String sql) throws SQLException {
        String encodedSql = encode(connection, sql);
        return super.connection_nativeSQL(chain, connection, encodedSql);
    }

    // ////////////// statement

    @Override
    public void statement_addBatch(FilterChain chain, StatementProxy statement, String sql) throws SQLException {
        super.statement_addBatch(chain, statement, encode(statement.getConnectionProxy(), sql));
    }

    @Override
    public boolean statement_execute(FilterChain chain, StatementProxy statement, String sql) throws SQLException {
        return super.statement_execute(chain, statement, encode(statement.getConnectionProxy(), sql));
    }

    @Override
    public boolean statement_execute(FilterChain chain, StatementProxy statement, String sql, int autoGeneratedKeys)
            throws SQLException {
        return super.statement_execute(chain, statement, encode(statement.getConnectionProxy(), sql), autoGeneratedKeys);
    }

    @Override
    public boolean statement_execute(FilterChain chain, StatementProxy statement, String sql, int[] columnIndexes)
            throws SQLException {
        return super.statement_execute(chain, statement, encode(statement.getConnectionProxy(), sql), columnIndexes);
    }

    @Override
    public boolean statement_execute(FilterChain chain, StatementProxy statement, String sql, String[] columnNames)
            throws SQLException {
        return super.statement_execute(chain, statement, encode(statement.getConnectionProxy(), sql), columnNames);
    }

    @Override
    public ResultSetProxy statement_executeQuery(FilterChain chain, StatementProxy statement, String sql)
            throws SQLException {
        return super.statement_executeQuery(chain, statement, encode(statement.getConnectionProxy(), sql));
    }

    @Override
    public int statement_executeUpdate(FilterChain chain, StatementProxy statement, String sql) throws SQLException {
        return super.statement_executeUpdate(chain, statement, encode(statement.getConnectionProxy(), sql));
    }

    @Override
    public int statement_executeUpdate(FilterChain chain, StatementProxy statement, String sql, int autoGeneratedKeys)
            throws SQLException {
        return super.statement_executeUpdate(chain, statement, encode(statement.getConnectionProxy(), sql),
                autoGeneratedKeys);
    }

    @Override
    public int statement_executeUpdate(FilterChain chain, StatementProxy statement, String sql, int[] columnIndexes)
            throws SQLException {
        return super.statement_executeUpdate(chain, statement, encode(statement.getConnectionProxy(), sql),
                columnIndexes);
    }

    @Override
    public int statement_executeUpdate(FilterChain chain, StatementProxy statement, String sql, String[] columnNames)
            throws SQLException {
        return super.statement_executeUpdate(chain, statement, encode(statement.getConnectionProxy(), sql), columnNames);
    }

    // ========== preparedStatement

    @Override
    public void preparedStatement_setString(FilterChain chain, PreparedStatementProxy statement, int parameterIndex,
                                            String x) throws SQLException {
        super.preparedStatement_setString(chain, statement, parameterIndex, encode(statement.getConnectionProxy(), x));
    }

    @Override
    public void preparedStatement_setCharacterStream(FilterChain chain, PreparedStatementProxy statement,
                                                     int parameterIndex, java.io.Reader reader) throws SQLException {
        String text = Utils.read(reader);
        String encodedText = encode(statement.getConnectionProxy(), text);
        super.preparedStatement_setCharacterStream(chain, statement, parameterIndex, new StringReader(encodedText));
    }

    @Override
    public void preparedStatement_setCharacterStream(FilterChain chain, PreparedStatementProxy statement,
                                                     int parameterIndex, java.io.Reader reader, int length)
            throws SQLException {
        String text = Utils.read(reader, length);
        String encodedText = encode(statement.getConnectionProxy(), text);
        super.preparedStatement_setCharacterStream(chain, statement, parameterIndex, new StringReader(encodedText),
                encodedText.length());
    }

    @Override
    public void preparedStatement_setCharacterStream(FilterChain chain, PreparedStatementProxy statement,
                                                     int parameterIndex, java.io.Reader reader, long length)
            throws SQLException {
        String text = Utils.read(reader, (int) length);
        String encodedText = encode(statement.getConnectionProxy(), text);
        super.preparedStatement_setCharacterStream(chain, statement, parameterIndex, new StringReader(encodedText),
                encodedText.length());
    }

    @Override
    public void preparedStatement_setObject(FilterChain chain, PreparedStatementProxy statement, int parameterIndex,
                                            Object x) throws SQLException {
        if (x instanceof String) {
            String encodedText = encode(statement.getConnectionProxy(), (String) x);
            super.preparedStatement_setObject(chain, statement, parameterIndex, encodedText);
        } else if (x instanceof Reader) {
            String text = Utils.read((Reader) x);
            String encodedText = encode(statement.getConnectionProxy(), text);
            super.preparedStatement_setObject(chain, statement, parameterIndex, new StringReader(encodedText));
        } else {
            super.preparedStatement_setObject(chain, statement, parameterIndex, x);
        }
    }

    @Override
    public void preparedStatement_setObject(FilterChain chain, PreparedStatementProxy statement, int parameterIndex,
                                            Object x, int targetSqlType) throws SQLException {
        if (x instanceof String) {
            String encodedText = encode(statement.getConnectionProxy(), (String) x);
            super.preparedStatement_setObject(chain, statement, parameterIndex, encodedText, targetSqlType);
        } else if (x instanceof Reader) {
            String text = Utils.read((Reader) x);
            String encodedText = encode(statement.getConnectionProxy(), text);
            super.preparedStatement_setObject(chain, statement, parameterIndex, new StringReader(encodedText),
                    targetSqlType);
        } else {
            super.preparedStatement_setObject(chain, statement, parameterIndex, x, targetSqlType);
        }
    }

    @Override
    public void preparedStatement_setObject(FilterChain chain, PreparedStatementProxy statement, int parameterIndex,
                                            Object x, int targetSqlType, int scaleOrLength) throws SQLException {
        if (x instanceof String) {
            String encodedText = encode(statement.getConnectionProxy(), (String) x);
            super.preparedStatement_setObject(chain, statement, parameterIndex, encodedText, targetSqlType,
                    scaleOrLength);
        } else if (x instanceof Reader) {
            String text = Utils.read((Reader) x);
            String encodedText = encode(statement.getConnectionProxy(), text);
            super.preparedStatement_setObject(chain, statement, parameterIndex, new StringReader(encodedText),
                    targetSqlType, scaleOrLength);
        } else {
            super.preparedStatement_setObject(chain, statement, parameterIndex, x, targetSqlType, scaleOrLength);
        }
    }

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

    @Override
    public long clob_position(FilterChain chain, ClobProxy wrapper, String searchstr, long start) throws SQLException {
        return chain.clob_position(wrapper, encode(wrapper.getConnectionWrapper(), searchstr), start);
    }

    @Override
    public String clob_getSubString(FilterChain chain, ClobProxy wrapper, long pos, int length) throws SQLException {
        String text = super.clob_getSubString(chain, wrapper, pos, length);
        return decode(wrapper.getConnectionWrapper(), text);
    }

    @Override
    public java.io.Reader clob_getCharacterStream(FilterChain chain, ClobProxy wrapper) throws SQLException {
        Reader reader = super.clob_getCharacterStream(chain, wrapper);
        String text = Utils.read(reader);
        return new StringReader(decode(wrapper.getConnectionWrapper(), text));
    }

    @Override
    public Reader clob_getCharacterStream(FilterChain chain, ClobProxy wrapper, long pos, long length)
            throws SQLException {
        Reader reader = super.clob_getCharacterStream(chain, wrapper, pos, length);
        String text = Utils.read(reader);
        return new StringReader(decode(wrapper.getConnectionWrapper(), text));
    }

    @Override
    public int clob_setString(FilterChain chain, ClobProxy wrapper, long pos, String str) throws SQLException {
        return chain.clob_setString(wrapper, pos, encode(wrapper.getConnectionWrapper(), str));
    }

    @Override
    public int clob_setString(FilterChain chain, ClobProxy wrapper, long pos, String str, int offset, int len)
            throws SQLException {
        return chain.clob_setString(wrapper, pos, encode(wrapper.getConnectionWrapper(), str), offset, len);
    }

    // ///////////// callableStatement_

    @Override
    public void callableStatement_setCharacterStream(FilterChain chain, CallableStatementProxy statement,
                                                     String parameterName, java.io.Reader reader) throws SQLException {
        String text = Utils.read(reader);
        Reader encodeReader = new StringReader(encode(statement.getConnectionProxy(), text));
        super.callableStatement_setCharacterStream(chain, statement, parameterName, encodeReader);
    }

    @Override
    public void callableStatement_setCharacterStream(FilterChain chain, CallableStatementProxy statement,
                                                     String parameterName, java.io.Reader reader, int length)
            throws SQLException {
        String text = Utils.read(reader, length);
        String encodeText = encode(statement.getConnectionProxy(), text);
        Reader encodeReader = new StringReader(encodeText);
        super.callableStatement_setCharacterStream(chain, statement, parameterName, encodeReader, encodeText.length());
    }

    @Override
    public void callableStatement_setCharacterStream(FilterChain chain, CallableStatementProxy statement,
                                                     String parameterName, java.io.Reader reader, long length)
            throws SQLException {
        String text = Utils.read(reader, (int) length);
        String encodeText = encode(statement.getConnectionProxy(), text);
        Reader encodeReader = new StringReader(encodeText);
        super.callableStatement_setCharacterStream(chain, statement, parameterName, encodeReader,
                (long) encodeText.length());
    }

    @Override
    public void callableStatement_setString(FilterChain chain, CallableStatementProxy statement, String parameterName,
                                            String x) throws SQLException {
        super.callableStatement_setString(chain, statement, parameterName, encode(statement.getConnectionProxy(), x));
    }

    @Override
    public void callableStatement_setObject(FilterChain chain, CallableStatementProxy statement, String parameterName,
                                            Object x) throws SQLException {
        if (x instanceof String) {
            String encodedText = encode(statement.getConnectionProxy(), (String) x);
            super.callableStatement_setObject(chain, statement, parameterName, encodedText);
        } else if (x instanceof Reader) {
            String text = Utils.read((Reader) x);
            String encodedText = encode(statement.getConnectionProxy(), text);
            super.callableStatement_setObject(chain, statement, parameterName, new StringReader(encodedText));
        } else {
            super.callableStatement_setObject(chain, statement, parameterName, x);
        }
    }

    @Override
    public void callableStatement_setObject(FilterChain chain, CallableStatementProxy statement, String parameterName,
                                            Object x, int targetSqlType) throws SQLException {
        if (x instanceof String) {
            String encodedText = encode(statement.getConnectionProxy(), (String) x);
            super.callableStatement_setObject(chain, statement, parameterName, encodedText, targetSqlType);
        } else if (x instanceof Reader) {
            String text = Utils.read((Reader) x);
            String encodedText = encode(statement.getConnectionProxy(), text);
            super.callableStatement_setObject(chain, statement, parameterName, new StringReader(encodedText),
                    targetSqlType);
        } else {
            super.callableStatement_setObject(chain, statement, parameterName, x, targetSqlType);
        }
    }

    @Override
    public void callableStatement_setObject(FilterChain chain, CallableStatementProxy statement, String parameterName,
                                            Object x, int targetSqlType, int scale) throws SQLException {
        if (x instanceof String) {
            String encodedText = encode(statement.getConnectionProxy(), (String) x);
            super.callableStatement_setObject(chain, statement, parameterName, encodedText, targetSqlType, scale);
        } else if (x instanceof Reader) {
            String text = Utils.read((Reader) x);
            String encodedText = encode(statement.getConnectionProxy(), text);
            super.callableStatement_setObject(chain, statement, parameterName, new StringReader(encodedText),
                    targetSqlType, scale);
        } else {
            super.callableStatement_setObject(chain, statement, parameterName, x, targetSqlType, scale);
        }
    }

    @Override
    public String callableStatement_getString(FilterChain chain, CallableStatementProxy statement, int parameterIndex)
            throws SQLException {
        String value = super.callableStatement_getString(chain, statement, parameterIndex);
        return decode(statement.getConnectionProxy(), value);
    }

    @Override
    public String callableStatement_getString(FilterChain chain, CallableStatementProxy statement, String parameterName)
            throws SQLException {
        String value = super.callableStatement_getString(chain, statement, parameterName);
        return decode(statement.getConnectionProxy(), value);
    }

    @Override
    public Object callableStatement_getObject(FilterChain chain, CallableStatementProxy statement, int parameterIndex)
            throws SQLException {
        Object value = chain.callableStatement_getObject(statement, parameterIndex);
        return decodeObject(statement, value);
    }

    @Override
    public Object callableStatement_getObject(FilterChain chain, CallableStatementProxy statement, int parameterIndex,
                                              java.util.Map<String, Class<?>> map) throws SQLException {
        Object value = chain.callableStatement_getObject(statement, parameterIndex, map);
        return decodeObject(statement, value);
    }

    @Override
    public Object callableStatement_getObject(FilterChain chain, CallableStatementProxy statement, String parameterName)
            throws SQLException {
        Object value = chain.callableStatement_getObject(statement, parameterName);
        return decodeObject(statement, value);
    }

    @Override
    public Object callableStatement_getObject(FilterChain chain, CallableStatementProxy statement,
                                              String parameterName, java.util.Map<String, Class<?>> map)
            throws SQLException {
        Object value = chain.callableStatement_getObject(statement, parameterName, map);
        return decodeObject(statement, value);
    }

}
