package org.maachang.mimdb.server ;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;

import org.maachang.mimdb.MimdbException;
import org.maachang.mimdb.core.QueryCompileInfo;
import org.maachang.mimdb.core.RemoteTable;
import org.maachang.mimdb.core.RemoteTableManager;
import org.maachang.mimdb.core.util.ByteArrayIO;
import org.maachang.mimdb.core.util.ObjectBinary;
import org.maachang.mimdb.core.util.jsnappy.JSnappy;
import org.maachang.mimdb.core.util.jsnappy.JSnappyBuffer;

/**
 * Mimdbクライアント.
 * 
 * @version 2014/01/18
 * @author  masahito suzuki
 * @since MasterInMemDB 1.02
 */
public final class MimdbClient {
    
    /** 受信バッファ長. **/
    protected static final int RECV_LENGTH = MimdbIOThread.BUFFER_LENGTH ;
    
    /** 送信バッファ長. **/
    protected static final int SEND_LENGTH = MimdbIOThread.BUFFER_LENGTH >> 1 ;
    
    /** データバッファ長. **/
    protected static final int BUFFER_LENGTH = MimdbIOThread.BUFFER_LENGTH >> 1 ;
    
    /** 通信ソケット. **/
    protected Socket socket = null ;
    protected InputStream input = null ;
    protected OutputStream output = null ;
    
    /** クローズ状態. **/
    private boolean closeFlag = true ;
    
    /** 接続条件. **/
    private String addr ;
    private String bindAddr ;
    private int port ;
    private int bindPort ;
    private int timeout ;
    
    /** バッファ情報. **/
    private final ByteArrayIO buffer = new ByteArrayIO( BUFFER_LENGTH ) ;
    
    /** 受信バイナリ. **/
    private byte[] work = new byte[ BUFFER_LENGTH ] ;
    
    /** 受信オフセット. **/
    private int[] recvOff = new int[ 1 ] ;
    
    /**
     * コンストラクタ.
     * @param addr 接続先のIPアドレスを設定します.
     *             [null]を設定した場合は、ローカルIPで接続します.
     * @param port 接続先のポート番号を設定します.
     *             [-1]の場合は、規定のポート番号で設定します.
     * @exception Exception 例外.
     */
    public MimdbClient( String addr,int port ) throws Exception {
        this( addr,port,null,-1,-1 ) ;
    }
    
    /**
     * コンストラクタ.
     * @param addr 接続先のIPアドレスを設定します.
     *             [null]を設定した場合は、ローカルIPで接続します.
     * @param port 接続先のポート番号を設定します.
     *             [-1]の場合は、規定のポート番号で設定します.
     * @param timeout 受信タイムアウト値を設定します.
     * @exception Exception 例外.
     */
    public MimdbClient( String addr,int port,int timeout ) throws Exception {
        this( addr,port,null,-1,timeout ) ;
    }
    
    /**
     * コンストラクタ.
     * @param addr 接続先のIPアドレスを設定します.
     *             [null]を設定した場合は、ローカルIPで接続します.
     * @param port 接続先のポート番号を設定します.
     *             [-1]の場合は、規定のポート番号で設定します.
     * @param bindAddr バインド先のIPアドレスを設定します.
     *             [null]を設定した場合は、バインドIPアドレスは設定されません
     * @param bindPort バインド先のポート番号を設定します.
     *             [-1]の場合は、バインドポートは設定されません.
     * @param timeout 受信タイムアウト値を設定します.
     * @exception Exception 例外.
     */
    public MimdbClient( String addr,int port,String bindAddr,int bindPort,int timeout ) throws Exception {
        if( addr == null || ( addr = addr.trim() ).length() <= 0 || "null".equals( addr ) ) {
            addr = "127.0.0.1" ;
        }
        if( port > 65535 || port < 0 ) {
            port = ConnectionDefine.DEF_PORT ;
        }
        if( timeout <= -1 ) {
            timeout = 30000 ; // デフォルトタイムアウトは30秒.
        }
        if( bindPort > 65535 || bindPort < 0 ) {
            bindPort = -1 ;
            bindAddr = null ;
        }
        // 接続条件をセット.
        this.addr = addr ;
        this.port = port ;
        this.bindAddr = bindAddr ;
        this.bindPort = bindPort ;
        this.timeout = timeout ;
        
        // 接続処理.
        _connect() ;
    }
    
    /** 通信再接続. **/
    private final void _connect() throws Exception {
        close() ;
        
        Socket s ;
        System.setProperty( "networkaddress.cache.ttl","300" ) ;
        System.setProperty( "networkaddress.cache.negative.ttl","0" ) ;
        
        // バインド用.
        if( bindPort != -1 ) {
            s = new Socket() ;
            if( bindAddr != null ) {
                s.bind( new InetSocketAddress( InetAddress.getByName( bindAddr ),bindPort ) ) ;
            }
            else {
                s.bind( new InetSocketAddress( bindPort ) ) ;
            }
            s.connect( new InetSocketAddress( InetAddress.getByName( addr ),port ) ) ;
        }
        // 非バインド用.
        else {
            s = new Socket() ;
            s.connect( new InetSocketAddress( InetAddress.getByName( addr ),port ) ) ;
        }
        
        s.setReuseAddress( true ) ;
        s.setSoLinger( true,MimdbIOThread.LINGER ) ;
        s.setSendBufferSize( SEND_LENGTH ) ;
        s.setReceiveBufferSize( RECV_LENGTH ) ;
        s.setTcpNoDelay( MimdbIOThread.TCP_NO_DELAY ) ;
        s.setKeepAlive( MimdbIOThread.KEEP_ALIVE ) ;
        s.setOOBInline( MimdbIOThread.OOB_INLINE ) ;
        s.setSoTimeout( timeout ) ;
        socket = s ;
        closeFlag = false ;
        
        //input = new BufferedInputStream( s.getInputStream() ) ;
        input = s.getInputStream() ;
        output = new BufferedOutputStream( s.getOutputStream() ) ;
        //output = s.getOutputStream() ;
    }
    
    /**
     * オブジェクトの破棄.
     */
    public void close() {
        closeFlag = true ;
        if( socket != null ) {
            try {
                input.close() ;
            } catch( Exception e ) {}
            try {
                output.close() ;
            } catch( Exception e ) {}
            try {
                socket.close() ;
            } catch( Exception e ) {}
            socket = null ;
            input = null ;
            output = null ;
        }
    }
    
    /**
     * 接続状態を取得.
     * @return boolean [true]の場合、接続されていません
     */
    public boolean isClose() {
        return closeFlag ;
    }
    
    /** チェック. **/
    protected final void check() {
        if( closeFlag ) {
            throw new MimdbException( "オブジェクトが切断されています" ) ;
        }
    }
    
    /**
     * 接続先IPアドレスを取得.
     * @return String 接続先のIPアドレスが返却されます.
     */
    public String getAddress() {
        check() ;
        return addr ;
    }
    
    /**
     * 接続先ポート番号を取得.
     * @return int 接続先ポート番号をが返却されます.
     */
    public int getPort() {
        check() ;
        return port ;
    }
    
    /**
     * バインドIPアドレスを取得.
     * @return String 接続先のIPアドレスが返却されます.
     */
    public String getBindAddress() {
        check() ;
        return bindAddr ;
    }
    
    /**
     * バインドポート番号を取得.
     * @return int 接続先ポート番号をが返却されます.
     */
    public int getBindPort() {
        check() ;
        return bindPort ;
    }
    
    /**
     * コネクションタイムアウト値を取得.
     * @return int コネクションタイムアウト値が返却されます.
     */
    public int getTimeout() {
        check() ;
        return timeout ;
    }
    
    /**
     * コネクションタイムアウト値を設定.
     * @param time タイムアウト値を設定します.
     */
    public void setTimeout( int time ) {
        check() ;
        timeout = time ;
        try {
            socket.setSoTimeout( time ) ;
        } catch( Exception e ) {
            throw new MimdbException( e ) ;
        }
    }
    
    /**
     * テーブル名に対するリモートテーブル要素を取得.
     * @param table 対象のテーブル名を設定します.
     * @return RemoteTable テーブル定義オブジェクトが返却されます.
     * @exception Exception 例外.
     */
    public RemoteTable getTable( final String table )
        throws Exception {
        check() ;
        
        long dbId = -1L ;
        RemoteTable ret = null ;
        RemoteTableManager man = RemoteTableManager.getInstance() ;
        
        // 指定テーブルが存在する場合、チェックタイムが経過した場合に
        // 再問い合わせする.
        if( man.isTable( table ) ) {
            ret = (RemoteTable)man.get( table ) ;
            if( ret.getLastUpdateTime() + ConnectionDefine.TABLE_TIMING >= System.currentTimeMillis() ) {
                // チェックタイム内の場合は、問い合わせしない.
                return ret ;
            }
            dbId = ret.getDbId() ;
        }
        
        try {
            
            // バッファクリア.
            buffer.clear() ;
            
            // 更新IDをセット.
            ObjectBinary.encode( buffer,dbId ) ;
            
            // テーブル名をセット.
            ObjectBinary.encode( buffer,table ) ;
            
            // 送信処理.
            send( ConnectionDefine.TABLE_INFO ) ;
            
            // 受信処理.
            byte[] recv = recv( ConnectionDefine.TABLE_INFO ) ;
            recvOff[ 0 ] = 0 ;
            int len = recv.length ;
            
            // 処理結果がtrueの場合.
            if( ( Boolean )ObjectBinary.decodeBinary( recvOff,recv,len ) ) {
                // リモートテーブルオブジェクトから取得.
                ret = (RemoteTable)man.get( table ) ;
                ret.update() ;
            }
            // 処理結果がfalseの場合.
            else {
                if( ret == null ) {
                    ret = new RemoteTable() ;
                }
                RemoteTable.toObject( ret,recv,recvOff[0],len ) ;
                man.put( ret ) ;
            }
            
            return ret ;
            
        } catch( IOException e ) {
            close() ;
            throw e ;
        }
    }
    
    /**
     * statement実行.
     * @param out int[0]に実行結果のResultID、int[1]に長さが、
     *            int[2]に最大長が返却されます.
     * @param sql 対象のプリコンパイル済みSQLを設定します.
     * @param offset 対象のオフセット値を設定します.
     * @param limit 対象のリミット値を設定します.
     * @exception Exception 例外.
     */
    public void statement( final int[] out,final QueryCompileInfo sql,final int offset,final int limit )
        throws Exception {
        check() ;
        
        try {
            buffer.clear() ;
            
            // ステートメントアクセス.
            ObjectBinary.encode( buffer,false ) ;
            
            // オフセット値をセット.
            ObjectBinary.encode( buffer,offset ) ;
            
            // リミット値をセット.
            ObjectBinary.encode( buffer,limit ) ;
            
            // プリコンパイル条件をバイナリ化.
            sql.getBinary( buffer ) ;
            
            // 送信処理.
            send( ConnectionDefine.EXECUTION_SQL ) ;
            
            // 受信処理.
            byte[] recv = recv( ConnectionDefine.EXECUTION_SQL ) ;
            recvOff[ 0 ] = 0 ;
            int len = recv.length ;
            
            // 処理結果が[false]の場合.
            if( !( Boolean )ObjectBinary.decodeBinary( recvOff,recv,len ) ) {
                throw new IOException( "statement処理失敗" ) ;
            }
            
            // 結果情報IDをセット.
            out[ 0 ] = ( Integer )ObjectBinary.decodeBinary( recvOff,recv,len ) ;
            
            // データ件数を返却.
            out[ 1 ] = ( Integer )ObjectBinary.decodeBinary( recvOff,recv,len ) ;
            
            // 最大データ件数を返却.
            out[ 2 ] = ( Integer )ObjectBinary.decodeBinary( recvOff,recv,len ) ;
            
        } catch( IOException e ) {
            close() ;
            throw e ;
        }
    }
    
    /**
     * プリコンパイル済みstatement実行.
     * @param out int[0]に実行結果のResultID、int[1]に長さが、
     *            int[2]に最大長が返却されます.
     * @param preparedId 対象のプリコンパイル済みIDを設定します.
     * @param sql 対象のプリコンパイル済みSQLを設定します.
     * @param offset 対象のオフセット値を設定します.
     * @param limit 対象のリミット値を設定します.
     * @param params 対象のパラメータ群を設定します.
     * @return int プリコンパイル済みIDが返却されます.
     * @exception Exception 例外.
     */
    public int prepared( final int[] out,final int preparedId,QueryCompileInfo sql,
        final int offset,final int limit,final Object... params )
        throws Exception {
        check() ;
        
        try {
            buffer.clear() ;
            
            // プリコンパイル済みIDがセットされている場合.
            // プリコンパイル済みIDのみを送信して、通信の軽量化を図る.
            if( preparedId >= 0L ) {
                
                // プリコンパイル済みアクセス.
                ObjectBinary.encode( buffer,true ) ;
                
                // 新規プリコンパイル済みアクセスでない.
                ObjectBinary.encode( buffer,false ) ;
                
                // オフセット値をセット.
                ObjectBinary.encode( buffer,offset ) ;
                
                // リミット値をセット.
                ObjectBinary.encode( buffer,limit ) ;
                
                // プリコンパイル済みIDをセット.
                ObjectBinary.encode( buffer,preparedId ) ;
                
                // パラメータ群をセット.
                setParams( buffer,params ) ;
                
                // 送信処理.
                send( ConnectionDefine.EXECUTION_SQL ) ;
                
                // 受信処理.
                byte[] recv = recv( ConnectionDefine.EXECUTION_SQL ) ;
                recvOff[ 0 ] = 0 ;
                int len = recv.length ;
                
                // 処理結果が[true]の場合.
                if( ( Boolean )ObjectBinary.decodeBinary( recvOff,recv,len ) ) {
                    
                    // プリコンパイル済みIDをセット.
                    int preId = ( Integer )ObjectBinary.decodeBinary( recvOff,recv,len ) ;
                    
                    // 結果情報IDをセット.
                    out[ 0 ] = ( Integer )ObjectBinary.decodeBinary( recvOff,recv,len ) ;
                    
                    // データ件数を返却.
                    out[ 1 ] = ( Integer )ObjectBinary.decodeBinary( recvOff,recv,len ) ;
                    
                    // 最大データ件数を返却.
                    out[ 2 ] = ( Integer )ObjectBinary.decodeBinary( recvOff,recv,len ) ;
                    
                    return preId ;
                }
            }
            
            // プリコンパイル済みIDがセットされていないか、プリコンパイル済み処理で
            // [false]が返却された場合.
            buffer.clear() ;
            
            // プリコンパイル済みアクセス.
            ObjectBinary.encode( buffer,true ) ;
            
            // 新規プリコンパイル済みアクセス.
            ObjectBinary.encode( buffer,true ) ;
            
            // オフセット値をセット.
            ObjectBinary.encode( buffer,offset ) ;
            
            // リミット値をセット.
            ObjectBinary.encode( buffer,limit ) ;
            
            // プリコンパイル条件をバイナリ化.
            sql.getBinary( buffer ) ;
            
            // パラメータ群をセット.
            setParams( buffer,params ) ;
            
            // 送信処理.
            send( ConnectionDefine.EXECUTION_SQL ) ;
            
            // 受信処理.
            byte[] recv = recv( ConnectionDefine.EXECUTION_SQL ) ;
            recvOff[ 0 ] = 0 ;
            int len = recv.length ;
            
            // 処理結果が[false]の場合.
            if( !( Boolean )ObjectBinary.decodeBinary( recvOff,recv,len ) ) {
                throw new IOException( "preparedStatement処理失敗" ) ;
            }
            
            // プリコンパイル済みIDをセット.
            int preId = ( Integer )ObjectBinary.decodeBinary( recvOff,recv,len ) ;
            
            // 結果情報IDをセット.
            out[ 0 ] = ( Integer )ObjectBinary.decodeBinary( recvOff,recv,len ) ;
            
            // データ件数を返却.
            out[ 1 ] = ( Integer )ObjectBinary.decodeBinary( recvOff,recv,len ) ;
            
            // 最大データ件数を返却.
            out[ 2 ] = ( Integer )ObjectBinary.decodeBinary( recvOff,recv,len ) ;
            
            // プリコンパイル済みIDをセット.
            sql.setPreparedId( preId ) ;
            
            return preId ;
            
        } catch( IOException e ) {
            close() ;
            throw e ;
        }
    }
    
    /**
     * 結果情報を取得.
     * @param out int[0]には、結果の件数が格納されます.
     * @param id ResultIDを設定します.
     * @param offset オフセット値を設定します.
     * @param fetch フェッチサイズを設定します.
     * @return Object[][] 結果情報が返却されます.
     * @exception Exception 例外.
     */
    public Object[][] resultSet( final int[] out,final int id,final int offset,final int fetch )
        throws Exception {
        check() ;
        
        try {
            buffer.clear() ;
            
            // ResultIdをセット.
            ObjectBinary.encode( buffer,id ) ;
            
            // オフセット値をセット.
            ObjectBinary.encode( buffer,offset ) ;
            
            // フェッチサイズをセット.
            ObjectBinary.encode( buffer,fetch ) ;
            
            // 送信処理.
            send( ConnectionDefine.RESULT_GET ) ;
            
            // 受信処理.
            byte[] recv = recv( ConnectionDefine.RESULT_GET ) ;
            recvOff[ 0 ] = 0 ;
            int len = recv.length ;
            
            // 処理結果が[false]の場合.
            if( !( Boolean )ObjectBinary.decodeBinary( recvOff,recv,len ) ) {
                throw new MimdbException( "ResultSetは既にクローズされています" ) ;
            }
            
            // データ返却数が返されます.
            int length = ( Integer )ObjectBinary.decodeBinary( recvOff,recv,len ) ;
            out[ 0 ] = length ;
            
            // データが存在する場合.
            if( length > 0 ) {
                
                int j ;
                Object[] row ;
                Object[][] ret = new Object[ length ][] ;
                
                // 取得行数を取得.
                int rowLen = ( Integer )ObjectBinary.decodeBinary( recvOff,recv,len ) ;
                
                // 結果情報一覧を取得.
                for( int i = 0 ; i < length ; i ++ ) {
                    
                    row = new Object[ rowLen ] ;
                    ret[ i ] = row ;
                    for( j = 0 ; j < rowLen ; j ++ ) {
                        row[ j ] = ObjectBinary.decodeBinary( recvOff,recv,len ) ;
                    }
                    
                }
                
                return ret ;
            }
            
            // データが存在しない場合.
            return null ;
            
        } catch( IOException e ) {
            close() ;
            throw e ;
        }
    }
    
    /**
     * クローズ命令を送信.
     * @param type クローズタイプを設定します.
     *             [ConnectionDefine.CLOSE_CONNECTION]を設定すると、コネクションクローズします.
     *             [ConnectionDefine.CLOSE_RESULT_SET]を設定すると、ResultSetクローズします.
     * @param id ResultSetをクローズする場合は設定します.
     * @return boolean [true]の場合はクローズが成功しました.
     * @exception Exception 例外.
     */
    public boolean sendClose( final int type,final int id )
        throws Exception {
        check() ;
        
        try {
            
            buffer.clear() ;
            
            // typeをセット.
            ObjectBinary.encode( buffer,type ) ;
            
            // ResultIdをセット.
            if( type == ConnectionDefine.CLOSE_CONNECTION ) {
                ObjectBinary.encode( buffer,-1 ) ;
            }
            else {
                ObjectBinary.encode( buffer,id ) ;
            }
            
            // 送信処理.
            send( ConnectionDefine.CLOSE ) ;
            
            // 受信処理.
            byte[] recv = recv( ConnectionDefine.CLOSE ) ;
            recvOff[ 0 ] = 0 ;
            int len = recv.length ;
            
            // 処理結果を取得.
            return ( Boolean )ObjectBinary.decodeBinary( recvOff,recv,len ) ;
            
        } catch( IOException e ) {
            close() ;
            throw e ;
        }
    }
    
    /** パラメータ群をセット. **/
    private static final void setParams( final ByteArrayIO buf,final Object[] params )
        throws Exception {
        if( params != null ) {
            int len = params.length ;
            for( int i = 0 ; i < len ; i ++ ) {
                ObjectBinary.encode( buf,params[ i ] ) ;
            }
        }
    }
    
    /**
     * 送信処理.
     * @param head 送信ヘッダ情報を設定します.
     * @param body 送信body情報を設定します.
     * @exception Exception 例外.
     */
    private final void send( int type )
        throws Exception {
        byte[] w = work ;
        byte[] b = buffer.toByteArray() ;
        buffer.clear() ;
        
        // 圧縮条件の場合.
        if( b.length >= ConnectionDefine.COMPRESS_LENGTH ) {
            // データを圧縮.
            JSnappyBuffer buf = JSnappy.compress( b,0,b.length ) ;
            b = buf.toByteArray() ;
            type |= 0x80 ;
        }
        
        // ヘッダ条件を作成.
        int off = ConnectionDefine.HEAD_LENGTH ;
        System.arraycopy( ConnectionDefine.HEAD,0,w,0,off ) ;
        w[ off ] = (byte)type ;
        
        int len = b.length + ConnectionDefine.HEAD_PLUS ;
        w[ off+1 ] = (byte)( len & 0xff ) ;
        w[ off+2 ] = (byte)( ( len & 0xff00 ) >> 8 ) ;
        w[ off+3 ] = (byte)( ( len & 0xff0000 ) >> 16 ) ;
        w[ off+4 ] = (byte)( ( len & 0xff000000 ) >> 24 ) ;
        
        // ヘッダ送信.
        output.write( w,0,ConnectionDefine.HEAD_PLUS ) ;
        
        // body送信.
        output.write( b,0,b.length ) ;
        
        // 更新.
        output.flush() ;
    }
    
    /**
     * 受信処理.
     * @return byte[] 受信された情報が返却されます.
     * @exception Exception 例外.
     */
    private final byte[] recv( final int type ) throws Exception {
        int len ;
        int allLen = -1 ;
        int recvType = -1 ;
        byte[] w = work ;
        
        // バッファクリア.
        buffer.clear() ;
        
        // 受信完了までループ処理.
        while( true ) {
            
            // 受信処理.
            if( ( len = input.read( w ) ) <= 0 ) {
                if( len <= -1 ) {
                    // 切断された.
                    close() ;
                    throw new IOException( "コネクションが切断されました" ) ;
                }
                continue ;
            }
            
            // バッファセット.
            buffer.write( w,0,len ) ;
            
            // 初期受信の場合.
            if( allLen == -1 ) {
                
                // 受信長がヘッダを越えていない場合.
                if( buffer.writeLength() < ConnectionDefine.HEAD_PLUS ) {
                    continue ;
                }
                
                // 参照取得.
                buffer.peek( w,0,ConnectionDefine.HEAD_PLUS ) ;
                
                byte[] h = ConnectionDefine.HEAD ;
                int off = ConnectionDefine.HEAD_LENGTH ;
                
                // 先頭３バイトがmimかチェック.
                for( int i = 0 ; i < off ; i ++ ) {
                    if( w[ i ] != h[ i ] ) {
                        // 一致しない場合は、エラー.
                        throw new IOException( "通信ヘッダが一致しません" ) ;
                    }
                }
                
                recvType = w[ off ] & 0xff ;
                allLen = ( ( w[ off + 1 ] & 0xff ) |
                    ( ( w[ off + 2 ] & 0xff ) << 8 ) |
                    ( ( w[ off + 3 ] & 0xff ) << 16 ) |
                    ( ( w[ off + 4 ] & 0xff ) << 24 ) ) ;
                
                // データ受信量が不正な場合.
                if( allLen <= 0 || allLen >= ConnectionDefine.MAX_RECV_LENGTH ) {
                    throw new IOException( "受信データ長が不正です:" + allLen ) ;
                }
                
            }
            
            // 受信完了.
            if( buffer.writeLength() >= allLen ) {
                break ;
            }
            
        }
        
        // Bodyを取得.
        byte[] body = new byte[ allLen - ConnectionDefine.HEAD_PLUS ] ;
        buffer.skip( ConnectionDefine.HEAD_PLUS ) ;
        buffer.read( body ) ;
        buffer.clear() ;
        
        // 圧縮されている場合.
        if( ( recvType & 0x80 ) == 0x80 ) {
            // タイプを復元.
            recvType = recvType & 0x7f ;
            
            // 圧縮を解凍.
            JSnappyBuffer buf = JSnappy.decompress( body,0,body.length ) ;
            body = buf.toByteArray() ;
            
        }
        
        // 処理タイプを元に情報を返信.
        switch( recvType ) {
            // テーブル情報要求.
            case ConnectionDefine.TABLE_INFO : 
            // SQL文受信.
            case ConnectionDefine.EXECUTION_SQL :
            // 結果情報を要求.
            case ConnectionDefine.RESULT_GET :
            // クローズ条件を返却.
            case ConnectionDefine.CLOSE :
                if( recvType != type ) {
                    throw new MimdbException( "要求タイプ[" + type +
                        "]に対して、受信タイプ[" + recvType + "]は一致しません" ) ;
                }
                return body ;
            
            // エラー返却された場合.
            case ConnectionDefine.ERROR :
                recvError( recvOff,body ) ;
                return body ;
        }
        
        // それ以外の条件が受信された場合はエラー.
        throw new IOException( "不明な受信タイプ:" + type ) ;
    }
    
    /**
     * 受信エラーを処理.
     */
    private static final void recvError( final int[] off,final byte[] recv )
        throws Exception {
        off[ 0 ] = 0 ;
        int len = recv.length ;
        String err = (String)ObjectBinary.decodeBinary( off,recv,len ) ;
        throw new MimdbException( err ) ;
    }
}
