package org.maachang.dbm ;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

import org.maachang.conf.Config;
import org.maachang.conf.ReadIni;
import org.maachang.dbm.engine.MDbmDefine;
import org.maachang.dbm.engine.MDbmEngineUtil;
import org.maachang.dbm.service.MDbmServerService;
import org.maachang.util.FileUtil;

/**
 * MaachangDbmマネージャ.
 * <BR><BR>
 * MaachangDbmとは、javaで作成されたDBM[データベースマネージャ]で
 * Key,Value形式の内容を永続化させます.<BR>
 * データ管理最大数は、9500000件(950万件)です.<BR>
 * <BR>
 * MaachangDbmの利用方法について説明します.<BR>
 * MaachangDbmを利用するには、まず最初に、以下のように、MaachangDbmマネージャを初期化する
 * 必要があります.<BR>
 * <BR>
 * <div style="border:1px solid gray; padding:5px;">
 * <pre>
 * MDbmManager.getInstance().open( "directory" ) ;
 * 
 * </pre>
 * </div>
 * <BR>
 * 第一引数には、永続化内容を保持するディレクトリ名を指定します.<BR>
 * これで、MaachangDbmが利用可能になります.<BR>
 * また、MaachangDbmを操作するには、<BR>
 * <BR>
 * <div style="border:1px solid gray; padding:5px;">
 * <pre>
 * MDbm dbm = MDbmManager.getInstance().getMDbm() ;
 * dbm.put( "hoge".getBytes( "UTF8" ),"hogehoge".getBytes( "UTF8" ) ;
 * byte[] b = dbm.get( "hoge".getBytes( "UTF8" ) ) ;
 * System.out.println( new String( b,"UTF8" ) ) ;
 * dbm.remove( "hoge".getBytes( "UTF8" ) ) ;
 * 
 * </pre>
 * </div>
 * <BR>
 * などで、利用できますが、この場合["hoge".getBytes( "UTF8" )]などと記述するのは、面倒です.<BR>
 * このため、MaachangDbm操作用オブジェクトとして、別途[MDbmOp]が用意されています.<BR>
 * 利用方法としては、以下の通りです.<BR>
 * <BR>
 * <div style="border:1px solid gray; padding:5px;">
 * <pre>
 * MDbmOp op = MDbmManager.getInstance().getMDbmOp() ;
 * 
 * </pre>
 * </div>
 * <BR>
 * のようにMaachangDbmOpを取得します.そして、MaachangDbmOpの利用方法は、下記の通りです.<BR>
 * <BR>
 * <div style="border:1px solid gray; padding:5px;">
 * <pre>
 * op.put( "hoge","moge" ) ;
 * String a = op.getString( "hoge" ) ;
 * op.remove( "hoge" ) ;
 * 
 * </pre>
 * </div>
 * <BR>
 * また、MaachangDbmは、トランザクションをサポートしており、利用方法は以下のようになります。
 * <BR>
 * <div style="border:1px solid gray; padding:5px;">
 * <pre>
 * <font color="green">// MDbmをトランザクションモードで取得.</font>
 * MDbm dbm = MDbmManager.getInstance().getMDbm( true ) ;
 * 
 * <font color="green">// コミット.</font>
 * dbm.commit() ;
 * 
 * <font color="green">// ロールバック.</font>
 * dbm.rollback() ;
 * 
 * </pre>
 * </div>
 * <BR>
 * また、２フェイスコミットなどを実施したい場合は、下記のように、commitの前に、
 * <BR>
 * <div style="border:1px solid gray; padding:5px;">
 * <pre>
 * <font color="green">// MDbmをトランザクションモードで取得.</font>
 * MDbm dbm = MDbmManager.getInstance().getMDbm( true ) ;
 * 
 * <font color="green">// データ書き込み可能かチェック.</font>
 * dbm.check() ;
 * 
 * <font color="green">// コミット.</font>
 * dbm.commit() ;
 * 
 * </pre>
 * </div>
 * <BR>
 * このようにして書き込み可能かチェックできます.
 * 
 * @version 2008/01/18
 * @author masahito suzuki
 * @since MaachangDBM 1.02
 */
public class MDbmManager {
    
    /**
     * MDBMモード : 最小構成.
     */
    public static final int MIN = 0 ;
    
    /**
     * MDBMモード : 中間構成.
     */
    public static final int MID = 1 ;
    
    /**
     * MDBMモード : 最大構成.
     */
    public static final int MAX = 2 ;
    
    /**
     * MDbm実装.
     */
    private MDbmImpl impl = null ;
    
    /**
     * MDbmServer.
     */
    private MDbmServerService server = null ;
    
    /**
     * 設定MDBMモード.
     */
    private int mode = MID ;
    
    /**
     * シングルトン.
     */
    private static final MDbmManager SNGL = new MDbmManager() ;
    
    /**
     * コンストラクタ.
     */
    private MDbmManager() {
        
    }
    
    /**
     * MaachangDbmマネージャを取得.
     * <BR><BR>
     * MaachangDbmマネージャを取得します.
     * <BR>
     * @return MDbmManager MaachangDbmマネージャが返されます.
     */
    public static final MDbmManager getInstance() {
        return SNGL ;
    }
    
    /**
     * MaachangDbmモードを取得.
     * <BR><BR>
     * MaachangDbmモードを取得します.
     * <BR>
     * @return int MDBMモードが返されます.<BR>
     *             [0]の場合、最小構成です.<BR>
     *             [1]の場合、中間構成です.<BR>
     *             [2]の場合、最大構成です.
     */
    public synchronized int getMode() {
        return mode ;
    }
    
    /**
     * MaachangDbmマネージャをオープン.
     * <BR><BR>
     * MaachangDbmマネージャをオープンします.
     * <BR>
     * @param directory 対象のディレクトリ名を設定します.
     * @exception Exception 例外.
     */
    public synchronized void open( String directory ) throws Exception {
        this.open( true,directory ) ;
    }
    
    /**
     * MaachangDbmマネージャをオープン.
     * <BR><BR>
     * MaachangDbmマネージャをオープンします.
     * <BR>
     * @param shutdown [true]の場合、シャットダウンモードをＯＮにします.
     * @param directory 対象のディレクトリ名を設定します.
     * @exception Exception 例外.
     */
    public synchronized void open( boolean shutdown,String directory ) throws Exception {
        if( impl == null ) {
            readMDbmOpt() ;
            if( MDbmEngineUtil.isMDbmVersion( directory ) == true ) {
                throw new IOException( "MDBMの構成バージョンが一致しません" ) ;
            }
            impl = new MDbmImpl( shutdown,directory ) ;
        }
    }
    
    /**
     * MaachangDbmマネージャをクローズ.
     * <BR><BR>
     * MaachangDbmマネージャをクローズします.
     */
    public synchronized void close() {
        if( server != null ) {
            server.destroy() ;
        }
        server = null ;
        if( impl != null ) {
            impl.destroy() ;
        }
        impl = null ;
    }
    
    /**
     * MaachangDbmオブジェクトを取得.
     * <BR><BR>
     * MaachangDbmオブジェクトを取得します.
     * <BR>
     * @return MDbm MaachangDbmオブジェクトが返されます.
     */
    public synchronized MDbm getMDbm() {
        return getMDbm( false ) ;
    }
    
    /**
     * MaachangDbmオブジェクトを取得.
     * <BR><BR>
     * MaachangDbmオブジェクトを取得します.
     * <BR>
     * @param mode [true]の場合、トランザクションオブジェクトが返されます.
     * @return MDbm Maachangbmオブジェクトが返されます.
     */
    public synchronized MDbm getMDbm( boolean mode ) {
        if( impl != null ) {
            if( mode == true ) {
                return new MDbmTransaction( impl ) ;
            }
            return impl ;
        }
        return null ;
    }
    
    /**
     * MaachangDbmオブジェクトを取得.
     * <BR><BR>
     * MaachangDbmオブジェクトを取得します.
     * <BR>
     * @param mode [true]の場合、トランザクションオブジェクトが返されます.
     * @return MDbm Maachangbmオブジェクトが返されます.
     */
    public synchronized MDbm getTransaction( MDbm mdbm ) {
        if( mdbm != null ) {
            return new MDbmTransaction( impl ) ;
        }
        return null ;
    }
    
    /**
     * MaachangDbm操作用オブジェクトを取得.
     * <BR><BR>
     * MaachangDbm操作用オブジェクトを取得します.
     * <BR>
     * @param mdbm 対象のMDbmオブジェクトを設定します.
     * @return MDbmOp MaachangDbm操作用オブジェクトを取得します.
     */
    public synchronized MDbmOp getMDbmOp( MDbm mdbm ) {
        if( mdbm == null || mdbm.isUse() == false ) {
            throw new IllegalArgumentException( "指定MDBMは不正です" ) ;
        }
        return new MDbmOpImpl( mdbm ) ;
    }
    
    /**
     * MaachangDbm操作用オブジェクトを取得.
     * <BR><BR>
     * MaachangDbm操作用オブジェクトを取得します.
     * <BR>
     * @return MDbmOp MaachangDbm操作用オブジェクトを取得します.
     */
    public synchronized MDbmOp getMDbmOp() {
        return getMDbmOp( false ) ;
    }
    
    /**
     * MaachangDbm操作用オブジェクトを取得.
     * <BR><BR>
     * MaachangDbm操作用オブジェクトを取得します.
     * <BR>
     * @param mode [true]の場合、トランザクションオブジェクトが返されます.
     * @return MDbmOp MaachangDbm操作用オブジェクトを取得します.
     */
    public synchronized MDbmOp getMDbmOp( boolean mode ) {
        if( impl != null ) {
            if( mode == true ) {
                return new MDbmOpImpl( new MDbmTransaction( impl ) ) ;
            }
            return new MDbmOpImpl( impl ) ;
        }
        return null ;
    }
    
    /**
     * MaachangDbmマネージャが既にクローズされているかチェック.
     * <BR><BR>
     * MaachangDbmマネージャが既にクローズされているかチェックします.
     * <BR>
     * @return boolean [true]の場合、既にクローズしています.
     */
    public synchronized boolean isClose() {
        return impl == null ;
    }
    
    /**
     * オープンディレクトリを取得.
     * <BR><BR>
     * 現在オープン中のディレクトリを取得します.
     * <BR>
     * @return String オープン中のディレクトリが返されます.<BR>
     *                [null]が返された場合、マネージャはクローズしています.
     */
    public synchronized String getDirectory() {
        if( impl != null ) {
            return impl.getDirectory() ;
        }
        return null ;
    }
    
    /**
     * 格納データ数を取得.
     * <BR><BR>
     * 格納データ数を取得します.
     * <BR>
     * @return int 格納データ数が返されます.<BR>
     *             [-1]が返された場合、マネージャはクローズしています.
     */
    public synchronized int size() {
        if( impl != null ) {
            return impl.size() ;
        }
        return -1 ;
    }
    
    /**
     * セクター管理ファイル数を取得.
     * <BR><BR>
     * 現在オープン中のセクター管理ファイル数を取得します.
     * <BR>
     * @return int セクター管理ファイル数が返されます.<BR>
     *             [-1]が返された場合、マネージャはクローズしています.
     */
    public synchronized int sectorFile() {
        if( impl != null ) {
            return impl.sectorFile() ;
        }
        return -1 ;
    }
    
    /**
     * MDBMサーバオブジェクトを設定.
     * <BR><BR>
     * MDBMサーバオブジェクトを設定します.
     * <BR>
     * @param server 対象のサーバオブジェクトを設定します.
     */
    public synchronized void setServer( MDbmServerService server ) {
        this.server = server ;
    }
    
    /**
     * MDBMサーバオブジェクトを取得.
     * <BR><BR>
     * MDBMサーバオブジェクトを取得します.
     * <BR>
     * @return MDbmServerService 対象のサーバオブジェクトが返されます.
     */
    public synchronized MDbmServerService getServer() {
        return this.server ;
    }
    
    /**
     * MDBM起動オプション.
     */
    private static final String OPT = "mdbm-opt" ;
    
    /**
     * MaachangDbmモードを設定.
     */
    private void setMode( int mode )
        throws IOException {
        if( impl != null ) {
            throw new IOException( "起動後に設定することはできません" ) ;
        }
        switch( mode ) {
            case MIN :
                this.mode = mode ;
                if( System.getProperty( MDbmDefine.HASH_PROPERTY ) == null ) {
                    System.setProperty( MDbmDefine.HASH_PROPERTY,String.valueOf( MDbmDefine.MIN_HASH ) ) ;
                }
                if( System.getProperty( MDbmDefine.ONE_FILE_SECTOR_PROPERTY ) == null ) {
                    System.setProperty( MDbmDefine.ONE_FILE_SECTOR_PROPERTY,String.valueOf( MDbmDefine.MIN_ONE_FILE_SECTOR ) ) ;
                }
                break ;
            case MID :
                this.mode = mode ;
                if( System.getProperty( MDbmDefine.HASH_PROPERTY ) == null ) {
                    System.setProperty( MDbmDefine.HASH_PROPERTY,String.valueOf( MDbmDefine.DEF_HASH ) ) ;
                }
                if( System.getProperty( MDbmDefine.ONE_FILE_SECTOR_PROPERTY ) == null ) {
                    System.setProperty( MDbmDefine.ONE_FILE_SECTOR_PROPERTY,String.valueOf( MDbmDefine.DEF_ONE_FILE_SECTOR ) ) ;
                }
                break ;
            case MAX :
                this.mode = mode ;
                if( System.getProperty( MDbmDefine.HASH_PROPERTY ) == null ) {
                    System.setProperty( MDbmDefine.HASH_PROPERTY,String.valueOf( MDbmDefine.MAX_HASH ) ) ;
                }
                if( System.getProperty( MDbmDefine.ONE_FILE_SECTOR_PROPERTY ) == null ) {
                    System.setProperty( MDbmDefine.ONE_FILE_SECTOR_PROPERTY,String.valueOf( MDbmDefine.MAX_ONE_FILE_SECTOR ) ) ;
                }
                break ;
        }
    }
    
    /**
     * 最初のオープン時にmdbm-opt(mdbm.conf)を読み込み、
     * 実行モードで、生成する.
     */
    private boolean readMDbmOpt() throws Exception {
        if( impl != null ) {
            throw new IOException( "起動後に設定することはできません" ) ;
        }
        if( FileUtil.isFileExists( "./conf/mdbm.conf" ) == false ) {
            return false ;
        }
        BufferedReader br = null ;
        try {
            br = new BufferedReader( new InputStreamReader(
                new FileInputStream( "./conf/mdbm.conf" ),"UTF8" )  ) ;
            Config conf = new Config() ;
            ReadIni.analisys( conf,br ) ;
            br.close() ;
            br = null ;
            
            String s = conf.get( OPT,"mode",0 ) ;
            if( conf.isSection( OPT ) == false ) {
                return false ;
            }
            int c = conf.getInt( OPT,MDbmDefine.HASH_PROPERTY,0 ) ;
            if( c >= 0 ) {
                System.setProperty( MDbmDefine.HASH_PROPERTY,String.valueOf( c ) ) ;
            }
            c = conf.getInt( OPT,MDbmDefine.ONE_FILE_SECTOR_PROPERTY,0 ) ;
            if( c >= 0 ) {
                System.setProperty( MDbmDefine.ONE_FILE_SECTOR_PROPERTY,String.valueOf( c ) ) ;
            }
            int mode = MID ;
            if( s != null && ( s = s.trim() ).length() > 0 ) {
                s = s.toLowerCase() ;
                if( "min".equals( s ) ) {
                    mode = MIN ;
                }
                else if( "mid".equals( s ) ) {
                    mode = MID ;
                }
                else if( "max".equals( s ) ) {
                    mode = MAX ;
                }
            }
            setMode( mode ) ;
            return true ;
        } finally {
            if( br != null ) {
                try {
                    br.close() ;
                } catch( Exception e ) {
                }
            }
        }
    }
}
