/*
 * 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.sql;

import java.io.Serializable;
import java.sql.*;
import java.util.*;
import java.lang.reflect.*;

import jp.ossc.nimbus.beans.*;

/**
 * Statementラッパー。<p>
 *
 * @author M.Takata
 */
public class StatementWrapper implements Statement, Serializable {
    
    private static final long serialVersionUID = -3678006522511003450L;
    
    protected Connection connection;
    
    protected Statement statement;
    
    protected Class<? extends ResultSet> resultSetWrapperClass;
    
    protected Map<Property, Object> resultSetProperties;
    
    /**
     * 指定したStatementをラップするインスタンスを生成する。<p>
     *
     * @param st ラップするStatement
     */
    public StatementWrapper(Statement st){
        this(null, st);
    }
    
    /**
     * 指定したStatementをラップするインスタンスを生成する。<p>
     *
     * @param con このStatementを生成したConnection
     * @param st ラップするStatement
     */
    public StatementWrapper(Connection con, Statement st){
        connection = con;
        statement = st;
    }
    
    /**
     * ラップするStatementを設定する。<p>
     *
     * @param st ラップするStatement
     */
    public void setStatement(Statement st){
        statement = st;
    }
    
    /**
     * ラップしているStatementを取得する。<p>
     *
     * @return ラップしているStatement
     */
    public Statement getStatement(){
        return statement;
    }
    
    /**
     * ラップする{@link ResultSetWrapper}の実装クラスを設定する。<p>
     *
     * @param clazz ラップするResultSetWrapperの実装クラス
     * @exception IllegalArgumentException 指定したクラスがResultSetのサブクラスでない場合
     */
    public void setResultSetWrapperClass(Class<? extends ResultSet> clazz)
     throws IllegalArgumentException{
        
        if(clazz != null
             && !ResultSet.class.isAssignableFrom(clazz)){
            throw new IllegalArgumentException(
                "Illegal class : " + clazz.getName()
            );
        }
        resultSetWrapperClass = clazz;
    }
    
    /**
     * ラップする{@link ResultSetWrapper}の実装クラスを取得する。<p>
     *
     * @return ラップするResultSetWrapperの実装クラス
     */
    public Class<? extends ResultSet> getResultSetWrapperClass(){
        return resultSetWrapperClass;
    }
    
    /**
     * {@link ResultSetWrapper}にプロパティを設定する。<p>
     *
     * @param name プロパティ名
     * @param value 値
     */
    public void setResultSetProperty(String name, Object value){
        if(resultSetProperties == null){
            resultSetProperties = new LinkedHashMap<Property, Object>();
        }
        final Property prop = PropertyFactory.createProperty(name);
        resultSetProperties.put(prop, value);
    }
    
    /**
     * {@link ResultSetWrapper}のプロパティを取得する。<p>
     *
     * @param name プロパティ名
     * @return 値
     */
    public Object getResultSetProperty(String name){
        if(resultSetProperties == null){
            return null;
        }
        for(Map.Entry<Property, Object> entry : resultSetProperties.entrySet()){
            if(entry.getKey().getPropertyName().equals(name)){
                return entry.getValue();
            }
        }
        return null;
    }
    
    protected ResultSet createResultSetWrapper(ResultSet rs)
     throws SQLException{
        if(resultSetWrapperClass == null){
            return rs;
        }
        ResultSet result = null;
        try{
            final Constructor<? extends ResultSet> constructor
                 = resultSetWrapperClass.getConstructor(
                    new Class[]{ResultSet.class}
                );
            result = constructor.newInstance(
                new Object[]{rs}
            );
            if(result instanceof ResultSetWrapper){
                applyResultSetProperties((ResultSetWrapper)result);
            }
        }catch(InvocationTargetException e){
            throw new SQLException(e.getTargetException().getMessage());
        }catch(Exception e){
            throw new SQLException(e.getMessage());
        }
        return result;
    }
    
    protected void applyResultSetProperties(ResultSetWrapper rsw)
     throws Exception{
        if(resultSetProperties == null || resultSetProperties.size() == 0){
            return;
        }
        for(Map.Entry<Property, Object> entry : resultSetProperties.entrySet()){
            entry.getKey().setProperty(rsw, entry.getValue());
        }
    }
    
    @Override
    public int getFetchDirection() throws SQLException {
        return statement.getFetchDirection();
    }
    
    @Override
    public int getFetchSize() throws SQLException {
        return statement.getFetchSize();
    }
    
    @Override
    public int getMaxFieldSize() throws SQLException {
        return statement.getMaxFieldSize();
    }
    
    @Override
    public int getMaxRows() throws SQLException {
        return statement.getMaxRows();
    }
    
    @Override
    public int getQueryTimeout() throws SQLException {
        return statement.getQueryTimeout();
    }
    
    @Override
    public int getResultSetConcurrency() throws SQLException {
        return statement.getResultSetConcurrency();
    }
    
    @Override
    public int getResultSetHoldability() throws SQLException {
        return statement.getResultSetHoldability();
    }
    
    @Override
    public int getResultSetType() throws SQLException {
        return statement.getResultSetType();
    }
    
    @Override
    public int getUpdateCount() throws SQLException {
        return statement.getUpdateCount();
    }
    
    @Override
    public void cancel() throws SQLException {
        statement.cancel();
    }
    
    @Override
    public void clearBatch() throws SQLException {
        statement.clearBatch();
    }
    
    @Override
    public void clearWarnings() throws SQLException {
        statement.clearWarnings();
    }
    
    @Override
    public void close() throws SQLException {
        statement.close();
    }
    
    @Override
    public boolean getMoreResults() throws SQLException {
        return statement.getMoreResults();
    }
    
    @Override
    public int[] executeBatch() throws SQLException {
        return statement.executeBatch();
    }
    
    @Override
    public void setFetchDirection(int arg0) throws SQLException {
        statement.setFetchDirection(arg0);
    }
    
    @Override
    public void setFetchSize(int arg0) throws SQLException {
        statement.setFetchSize(arg0);
    }
    
    @Override
    public void setMaxFieldSize(int arg0) throws SQLException {
        statement.setMaxFieldSize(arg0);
    }
    
    @Override
    public void setMaxRows(int arg0) throws SQLException {
        statement.setMaxRows(arg0);
    }
    
    @Override
    public void setQueryTimeout(int arg0) throws SQLException {
        statement.setQueryTimeout(arg0);
    }
    
    @Override
    public boolean getMoreResults(int arg0) throws SQLException {
        return statement.getMoreResults(arg0);
    }
    
    @Override
    public void setEscapeProcessing(boolean arg0) throws SQLException {
        statement.setEscapeProcessing(arg0);
    }
    
    @Override
    public int executeUpdate(String arg0) throws SQLException {
        return statement.executeUpdate(arg0);
    }
    
    @Override
    public void addBatch(String arg0) throws SQLException {
        statement.addBatch(arg0);
    }
    
    @Override
    public void setCursorName(String arg0) throws SQLException {
        statement.setCursorName(arg0);
    }
    
    @Override
    public boolean execute(String arg0) throws SQLException {
        return statement.execute(arg0);
    }
    
    @Override
    public int executeUpdate(String arg0, int arg1) throws SQLException {
        return statement.executeUpdate(arg0, arg1);
    }
    
    @Override
    public boolean execute(String arg0, int arg1) throws SQLException {
        return statement.execute(arg0, arg1);
    }
    
    @Override
    public int executeUpdate(String arg0, int[] arg1) throws SQLException {
        return statement.executeUpdate(arg0, arg1);
    }
    
    @Override
    public boolean execute(String arg0, int[] arg1) throws SQLException {
        return statement.execute(arg0, arg1);
    }
    
    @Override
    public Connection getConnection() throws SQLException {
        if(connection == null){
            return statement.getConnection();
        }else{
            return connection;
        }
    }
    
    @Override
    public ResultSet getGeneratedKeys() throws SQLException {
        return createResultSetWrapper(statement.getGeneratedKeys());
    }
    
    @Override
    public ResultSet getResultSet() throws SQLException {
        return createResultSetWrapper(statement.getResultSet());
    }
    
    @Override
    public SQLWarning getWarnings() throws SQLException {
        return statement.getWarnings();
    }
    
    @Override
    public int executeUpdate(String arg0, String[] arg1) throws SQLException {
        return statement.executeUpdate(arg0, arg1);
    }
    
    @Override
    public boolean execute(String arg0, String[] arg1) throws SQLException {
        return statement.execute(arg0, arg1);
    }
    
    @Override
    public ResultSet executeQuery(String arg0) throws SQLException {
        return createResultSetWrapper(statement.executeQuery(arg0));
    }
    
    @Override
    public boolean isPoolable() throws SQLException{
        return statement.isPoolable();
    }
    
    @Override
    public void setPoolable(boolean isPoolable) throws SQLException{
        statement.setPoolable(isPoolable);
    }
    
    @Override
    public boolean isClosed() throws SQLException{
        return statement.isClosed();
    }
    
    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException{
        return statement.isWrapperFor(iface);
    }
    
    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException{
        return statement.unwrap(iface);
    }

    @Override
    public void closeOnCompletion() throws SQLException {
        statement.closeOnCompletion();
    }

    @Override
    public boolean isCloseOnCompletion() throws SQLException {
        return statement.isCloseOnCompletion();
    }
}
