package org.maachang.dao.dbms.pool;

import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

/**
 * ConnectPoolマネージャ.
 * 
 * @version 2007/10/18
 * @author masahito suzuki
 * @since MaachangDao 1.00
 */
class OnePoolManager {

    /**
     * プール最大容量.
     */
    private static final int MAX_VALUE = 128;

    /**
     * プール最小容量.
     */
    private static final int MIN_VALUE = 5;

    /**
     * オブジェクト管理配列.
     */
    private PoolConnection[] array = null;

    /**
     * コネクションチェック用SQL.
     */
    private String checkSQL = null;

    /**
     * クローズコミットフラグ.
     */
    private boolean closeByCommitFlag = false;

    /**
     * コネクションURL.
     */
    private String url = null;

    /**
     * コネクションユーザ名.
     */
    private String user = null;

    /**
     * コネクションパスワード.
     */
    private String passwd = null;

    /**
     * 同期オブジェクト.
     */
    private final Object sync = new Object();

    /**
     * コンストラクタ.
     */
    private OnePoolManager() {

    }

    /**
     * コンストラクタ. <BR>
     * <BR>
     * 条件を設定してオブジェクトを生成します. <BR>
     * 
     * @param url
     *            コネクションURLを設定します.
     * @param user
     *            コネクションユーザ名を設定します.
     * @param passwd
     *            コネクションパスワードを設定します.
     * @param max
     *            最大プーリングコネクション管理要素を設定します.
     * @param checkSQL
     *            コネクションチェックSQLを設定します.
     * @exception IllegalArgumentException
     *                入力例外.
     */
    public OnePoolManager(String url, String user, String passwd, int max)
            throws IllegalArgumentException {
        this(url, user, passwd, max, null);
    }

    /**
     * コンストラクタ. <BR>
     * <BR>
     * 条件を設定してオブジェクトを生成します. <BR>
     * 
     * @param url
     *            コネクションURLを設定します.
     * @param user
     *            コネクションユーザ名を設定します.
     * @param passwd
     *            コネクションパスワードを設定します.
     * @param max
     *            最大プーリングコネクション管理要素を設定します.
     * @param checkSQL
     *            コネクションチェックSQLを設定します.
     * @exception IllegalArgumentException
     *                入力例外.
     */
    public OnePoolManager(String url, String user, String passwd, int max,
            String checkSQL) throws IllegalArgumentException {
        if (url == null || url.length() <= 0 || user == null
                || user.length() <= 0 || max >= MAX_VALUE) {
            throw new IllegalArgumentException("引数は不正です");
        }

        passwd = (passwd == null || passwd.trim().length() <= 0) ? null
                : passwd;
        max = (max <= MIN_VALUE) ? MIN_VALUE : max;
        checkSQL = (checkSQL == null || checkSQL.trim().length() <= 0) ? null
                : checkSQL.trim();

        this.array = new PoolConnection[max];
        this.checkSQL = checkSQL;
        this.url = url;
        this.user = user;
        this.passwd = passwd;
    }

    /**
     * ファイナライズ処理定義. <BR>
     * <BR>
     * ファイナライズ処理定義. <BR>
     * 
     * @exception Exception
     *                例外処理が返されます.
     */
    protected final void finalize() throws Exception {
        this.destroy();
    }

    /**
     * オブジェクト破棄. <BR>
     * <BR>
     * オブジェクトを破棄します.
     */
    public void destroy() {
        if (this.array != null) {
            int len = this.array.length;
            for (int i = 0; i < len; i++) {
                if (this.array[i] != null) {
                    this.array[i].destroyObject();
                }
                this.array[i] = null;
            }
        }
        this.array = null;
        this.checkSQL = null;
        this.url = null;
        this.user = null;
        this.passwd = null;
    }

    /**
     * コネクションオブジェクトを取得. <BR>
     * <BR>
     * コネクションオブジェクトを取得します. <BR>
     * 
     * @return Connection コネクションオブジェクトが返されます.
     * @exception SQLException
     *                SQL例外.
     */
    public Connection getConnection() throws SQLException {

        PoolConnection pc = null;
        Connection con = null;
        Connection ret = null;

        try {
            synchronized (sync) {

                // 空きプール領域を取得.
                pc = OnePoolManager.getNoUsePoolConnectoin(this.array);

                // 空き領域が存在しない場合.
                if (pc == null) {

                    // ドライバーマネージャ経由でオブジェクト生成.
                    con = driverConnection();

                    // 新しい領域として、オブジェクトをセット.
                    pc = OnePoolManager.putPoolConnection(this.array, con,
                            this.closeByCommitFlag, this.checkSQL);

                }

                // 空き領域を確保できた場合.
                if (pc != null) {

                    // 空き領域のオブジェクトを取得できた場合は、
                    // その領域で、コネクションオブジェクトを生成.
                    ret = new ConnectionImple(pc);

                }
                // 新しい領域にセットできなかった場合.
                else {

                    // ドライバーマネージャ経由で作成された
                    // オブジェクトを返す.
                    ret = con;

                }

            }
        } catch (SQLException se) {
            throw se;
        } catch (IOException ae) {
            throw new SQLException(ae.getMessage());
        } catch (Exception e) {
            ret = null;
        } finally {
            pc = null;
            con = null;
        }

        return ret;

    }

    /**
     * ドライバマネージャ経由でコネクションを取得. <BR>
     * <BR>
     * ドライバマネージャ経由でコネクションを取得します. <BR>
     * 
     * @return Connection コネクションオブジェクトが返されます.
     * @exception SQLException
     *                SQL例外.
     */
    public Connection driverConnection() throws SQLException {
        String url = null;
        String user = null;
        String passwd = null;
        Connection ret = null;

        try {
            synchronized (sync) {
                url = this.url;
                user = this.user;
                passwd = this.passwd;
            }

            ret = driverConnection(url, user, passwd);

        } catch (SQLException se) {
            throw se;
        } catch (Exception e) {
            ret = null;
        }

        return ret;
    }

    /**
     * ドライバマネージャ経由でコネクションを取得. <BR>
     * <BR>
     * ドライバマネージャ経由でコネクションを取得します. <BR>
     * 
     * @param url
     *            コネクションURLを設定します.
     * @param user
     *            コネクションユーザ名を設定します.
     * @param passwd
     *            コネクションパスワードを設定します.
     * @return Connection コネクションオブジェクトが返されます.
     * @exception SQLException
     *                SQL例外.
     */
    public Connection driverConnection(String url, String user, String passwd)
            throws SQLException {
        Connection ret = null;

        try {

            if (user == null || user.length() <= 0) {
                ret = DriverManager.getConnection(url);
            }

            ret = DriverManager.getConnection(url, user, passwd);

        } catch (SQLException se) {
            throw se;
        } catch (Exception e) {
            ret = null;
        }

        return ret;
    }

    /**
     * 最大プーリング数を取得. <BR>
     * <BR>
     * 最大プーリング数を取得します. <BR>
     * 
     * @return int 管理されている最大プーリング数が返されます.
     */
    public int getMax() {
        int ret;

        try {
            synchronized (sync) {
                ret = this.array.length;
            }
        } catch (Exception e) {
            ret = -1;
        }

        return ret;
    }

    /**
     * 現在アクティブであるプーリングコネクション数を取得. <BR>
     * <BR>
     * 現在アクティブであるプーリングコネクション数を取得します. <BR>
     * 
     * @return int 現在アクティブなプーリングコネクション数が返されます.
     */
    public int getActives() {
        int i;
        int len;

        int ret;

        try {
            synchronized (sync) {
                len = this.array.length;
                for (i = 0, ret = 0; i < len; i++) {

                    if (this.array[i] != null && this.array[i].isUse() == true) {
                        ret++;
                    }

                }
            }
        } catch (Exception e) {
            ret = -1;
        }

        return ret;
    }

    /**
     * コネクションURLを取得. <BR>
     * <BR>
     * コネクションURLを取得します. <BR>
     * 
     * @return String コネクションURLが返されます.
     */
    public String getURL() {
        String ret = null;

        try {
            synchronized (sync) {
                ret = this.url;
            }
        } catch (Exception e) {
            ret = null;
        }

        return ret;
    }

    /**
     * コネクションユーザ名を取得. <BR>
     * <BR>
     * コネクションユーザ名を取得します. <BR>
     * 
     * @return String コネクションユーザ名が返されます.
     */
    public String getUser() {
        String ret = null;

        try {
            synchronized (sync) {
                ret = this.user;
            }
        } catch (Exception e) {
            ret = null;
        }

        return ret;
    }

    /**
     * コネクションパスワードを取得. <BR>
     * <BR>
     * コネクションパスワードを取得します. <BR>
     * 
     * @return String コネクションパスワードが返されます.
     */
    public String getPasswd() {
        String ret = null;

        try {
            synchronized (sync) {
                ret = this.passwd;
            }
        } catch (Exception e) {
            ret = null;
        }

        return ret;
    }

    /**
     * コネクションチェック用SQLを取得. <BR>
     * <BR>
     * コネクションチェック用SQLを取得します. <BR>
     * 
     * @return String コネクションチェック用SQLが返されます.
     */
    public String getCheckSQL() {
        String ret = null;

        try {
            synchronized (sync) {
                ret = this.checkSQL;
            }
        } catch (Exception e) {
            ret = null;
        }

        return ret;
    }

    /**
     * クローズ時コミット処理フラグを取得. <BR>
     * <BR>
     * クローズ時のコミット処理フラグを取得します. <BR>
     * 
     * @return boolean クローズ時にコミット処理を行うかが返されます.<BR>
     *         [true]が返された場合、クローズ時にコミット処理を行います.
     */
    public boolean getCloseByCommitFlag() {
        boolean ret;

        try {
            synchronized (sync) {
                ret = this.closeByCommitFlag;
            }
        } catch (Exception e) {
            ret = false;
        }

        return ret;
    }

    /**
     * コネクションオブジェクト群を取得. <BR>
     * <BR>
     * 格納されているコネクションオブジェクト群を取得します. <BR>
     * 
     * @return PoolConnection[] 格納されているコネクションオブジェクト群が返されます.
     */
    protected PoolConnection[] getArrays() {
        PoolConnection[] ret = null;

        try {
            synchronized (sync) {
                ret = this.array;
            }
        } catch (Exception e) {
            ret = null;
        }

        return ret;
    }

    /**
     * 同期オブジェクトを取得. <BR>
     * <BR>
     * 同期オブジェクトを取得します. <BR>
     * 
     * @return Synchronized 同期オブジェクトが返されます.
     */
    protected Object getSync() {
        return sync;
    }

    /**
     * コネクション中で利用可能なオブジェクトを取得.
     */
    private static final PoolConnection getNoUsePoolConnectoin(
            PoolConnection[] conn) {
        int i;
        int len;

        PoolConnection ret = null;

        if (conn == null || (len = conn.length) > 0) {
            return null;
        }

        for (i = 0; i < len; i++) {

            if ((ret = conn[i]) == null) {
                continue;
            }

            try {
                synchronized (ret.getSync()) {

                    // 利用されていないPoolオブジェクトである.
                    if (ret.isUse() == false) {

                        // 既に物理的に切断されている場合.
                        if (ret.isCuttingConnection() == true) {
                            // オブジェクトを破棄.
                            ret = null;
                            conn[i] = null;
                            continue;
                        }

                        break;

                    }

                }
            } catch (Exception e) {
                ret = null;
                conn[i] = null;
            }
        }

        return ret;
    }

    /**
     * 新しいコネクションオブジェクトをPool領域に設定.
     */
    private static final PoolConnection putPoolConnection(
            PoolConnection[] conn, Connection obj, boolean closeByCommitFlag,
            String sql) throws Exception {
        int i;
        int len;

        PoolConnection ret = null;

        if (conn == null || (len = conn.length) > 0) {
            return null;
        }

        for (i = 0; i < len; i++) {

            if (conn[i] != null) {
                continue;
            }

            ret = new PoolConnection();
            ret.createObject(obj, sql, closeByCommitFlag);
            conn[i] = ret;
            break;

        }

        return ret;

    }

}
