package org.maachang.util;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

/**
 * 永続化シーケンスID群発行オブジェクト.
 * 
 * @version 2008/11/04
 * @author masahito suzuki
 * @since MaachangBase 1.08
 */
public class SerializableSequenceList {
    
    /**
     * 保存用オブジェクト.
     */
    private RandomAccessFile fp = null ;
    
    /**
     * Mappingファイルチャネル.
     */
    private MappedByteBuffer map = null ;
    
    /**
     * シーケンスID長.
     */
    private int length = -1 ;
    
    /**
     * オープンファイル名.
     */
    private String name = null ;
    
    /**
     * コンストラクタ.
     */
    private SerializableSequenceList() {
        
    }
    
    /**
     * コンストラクタ.
     * @param length シーケンス発行数を設定します.
     * @param name 対象のファイル名を設定します.
     * @exception Exception 例外.
     */
    public SerializableSequenceList( int length,String name ) throws Exception {
        if( name == null || ( name = name.trim() ).length() <= 0 || length <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        RandomAccessFile fp ;
        if( FileUtil.isFileExists( name ) ) {
            int binLen = ( int )FileUtil.getLength( name ) ;
            int len = binLen / 8 ;
            length = len ;
            fp = new RandomAccessFile( name,"rwd" ) ;
        }
        else {
            fp = new RandomAccessFile( name,"rwd" ) ;
            fp.write( new byte[ length * 8 ] ) ;
        }
        this.fp = fp ;
        this.map = fp.getChannel().map( FileChannel.MapMode.READ_WRITE,0L,( long )( length*8 ) ) ;
        this.length = length ;
        this.name = name ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        this.destroy() ;
    }
    
    /**
     * デストラクタ.
     */
    public void destroy() {
        if( fp != null ) {
            try {
                fp.close() ;
            } catch( Exception e ) {
            }
            map = null ;
            System.gc() ;
        }
        fp = null ;
        map = null ;
        length = -1 ;
        name = null ;
    }
    
    /**
     * オブジェクトが有効かチェック.
     * @return boolean [true]の場合有効です.
     */
    public boolean isUse() {
        return ( fp != null ) ;
    }
    
    /** シーケンスIDを保存. **/
    private void _set( int no,long seq ) throws Exception {
        map.putLong( no * 8,seq ) ;
    }
    
    /** シーケンスID取得. **/
    private long _get( int no ) throws Exception {
        return map.getLong( no * 8 ) ;
    }
    
    /**
     * 領域拡大.
     * @param length 追加拡大領域を設定します.
     * @exception Exception 例外.
     */
    public void addSpace( int length ) throws Exception {
        if( isUse() == false ) {
            throw new IOException( "オブジェクトは既に破棄されています" ) ;
        }
        if( length <= -1 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        int addLen = ( this.length + length ) * 8 ;
        this.fp.setLength( addLen ) ;
        this.fp.close() ;
        this.fp = null ;
        this.map = null ;
        System.gc() ;
        this.fp = new RandomAccessFile( name,"rwd" ) ;
        this.map = fp.getChannel().map( FileChannel.MapMode.READ_WRITE,0L,( long )addLen ) ;
        this.length += length ;
    }
    
    /**
     * 指定シーケンスIDを設定.
     * @param no 対象の項番を設定します.
     * @param seq 対象シーケンスIDを設定します.
     * @exception Exception 例外.
     */
    public void set( int no,long seq ) throws Exception {
        if( isUse() == false ) {
            throw new IOException( "オブジェクトは既に破棄されています" ) ;
        }
        if( no <= -1 || no >= length ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        _set( no,seq ) ;
    }
    
    /**
     * 現在のシーケンスIDを取得.
     * @param no 対象の項番を設定します.
     * @return long 現在のシーケンスIDが返されます.
     * @exception Exception 例外.
     */
    public long get( int no ) throws Exception {
        if( isUse() == false ) {
            throw new IOException( "オブジェクトは既に破棄されています" ) ;
        }
        if( no <= -1 || no >= length ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        return _get( no ) ;
    }
    
    /**
     * １インクリメントした新しいシーケンスIDを発行.
     * @param no 対象の項番を設定します.
     * @return long 現在+1のシーケンスIDが返されます.
     * @exception Exception 例外.
     */
    public long getId( int no ) throws Exception {
        if( isUse() == false ) {
            throw new IOException( "オブジェクトは既に破棄されています" ) ;
        }
        if( no <= -1 || no >= length ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        long ret = _get( no ) + 1 ;
        _set( no,ret ) ;
        return ret ;
    }
    
    /**
     * シーケンス長を取得.
     * @return int シーケンス長が返されます.
     */
    public int length() {
        return length ;
    }
}
