package org.maachang.proxy.engine.net.http;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/**
 * HttpSocket.
 * 
 * @version 2007/11/17
 * @author  masahito suzuki
 * @since MaachangProxy 1.00
 */
public class HttpSocket {
    
    /**
     * LOG.
     */
    //private static final Log LOG = LogFactory.getLog( HttpSocket.class ) ;
    
    /**
     * 基本受信タイムアウト(500ミリ秒).
     */
    public static final int RECEIVE_TIMEOUT = 500 ;
    
    private static final long DELETE_TIMEOUT = 30000L ;
    private static final int BUFFER = 131072 ;
    private static final int LINGER = 15 ;
    private Socket socket = null ;
    private InputStream inputStream = null ;
    private OutputStream outputStream = null ;
    private ConnectionInfo connectionInfo = null ;
    private long time = -1L ;
    private boolean isCloseSocket = false ;
    private Object ioSync = new Object() ;
    
    /**
     * コンストラクタ.
     */
    private HttpSocket() { }
    
    /**
     * コンストラクタ.
     * <BR><BR>
     * ソケット条件を設定してオブジェクトを設定します.
     * <BR>
     * @param socket 対象のソケット情報を設定します.
     * @exception Exception 例外.
     */
    protected HttpSocket( Socket socket ) throws Exception {
        if( socket == null || socket.isClosed() == true ) {
            throw new IllegalArgumentException( "コネクションは不正です" ) ;
        }
        this.socket = socket ;
        this.inputStream = null ;
        this.outputStream = null ;
        this.socket.setSendBufferSize( BUFFER ) ;
        this.socket.setReceiveBufferSize( BUFFER ) ;
        this.socket.setKeepAlive( true ) ;
        this.socket.setTcpNoDelay( true ) ;
        this.socket.setReuseAddress( true ) ;
        this.socket.setSoLinger( true,LINGER ) ;
        this.socket.setSoTimeout( RECEIVE_TIMEOUT ) ;
        this.time = System.currentTimeMillis() ;
        this.connectionInfo = null ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        this.destroy() ;
    }
    
    /**
     * オブジェクト破棄.
     */
    public void destroy() {
        if( this.inputStream != null ) {
            try {
                this.inputStream.close() ;
            } catch( Exception e ) {
            }
        }
        if( this.outputStream != null ) {
            try {
                this.outputStream.flush() ;
            } catch( Exception e ) {
            }
            try {
                this.outputStream.close() ;
            } catch( Exception e ) {
            }
        }
        if( this.socket != null ) {
            try {
                this.socket.close() ;
            } catch( Exception e ) {
            }
        }
        if( connectionInfo != null ) {
            connectionInfo.destroy() ;
        }
        this.socket = null ;
        this.inputStream = null ;
        this.outputStream = null ;
        this.time = -1L ;
    }
    
    /**
     * コネクションオブジェクトを設定.
     * <BR><BR>
     * コネクションオブジェクトを設定します.
     * <BR>
     * @param connectionInfo コネクションオブジェクトを設定します.
     */
    public synchronized void setConnectionInfo( ConnectionInfo connectionInfo ) {
        if( connectionInfo == null ) {
            return ;
        }
        this.connectionInfo = connectionInfo ;
    }
    
    /**
     * コネクションオブジェクトを取得.
     * <BR><BR>
     * コネクションオブジェクトを設定します.
     * <BR>
     * @return ConnectionInfo コネクションオブジェクトが返されます.
     */
    public synchronized ConnectionInfo getConnectionInfo() {
        return this.connectionInfo ;
    }
    
    /**
     * 更新処理.
     * <BR><BR>
     * このオブジェクトを再利用するための更新処理を実施します.
     * <BR>
     * @exception Exception 例外.
     */
    public void update() throws Exception {
        if( isClosed() == true ) {
            this.time = -1L ;
        }
        else {
            this.time = System.currentTimeMillis() ;
        }
    }
    
    /**
     * 受信情報が存在するかチェック.
     * <BR><BR>
     * 受信情報が存在するかチェックします.
     * <BR>
     * @return boolean [true]の場合、受信情報が存在します.
     * @exception Exception 例外.
     */
    public boolean isReceive() throws Exception {
        if( isClosed() == true ) {
            throw new IOException( "既にクローズされています" ) ;
        }
        else if( this.time + DELETE_TIMEOUT <= System.currentTimeMillis() ) {
            throw new IOException( "指定オブジェクトは一定期間利用されていません" ) ;
        }
        boolean ret = false ;
        if( this.inputStream().available() > 0 ) {
            ret = true ;
        }
        return ret ;
    }
    
    /**
     * ソケットオブジェクトを取得.
     * <BR><BR>
     * ソケットオブジェクトを取得します.
     * <BR>
     * @return Socket ソケットオブジェクトが返されます.
     */
    public Socket socket() {
        if( isClosed() == true ) {
            return null ;
        }
        return this.socket ;
    }
    
    /**
     * ソケット受信ストリームを取得.
     * <BR><BR>
     * ソケット受信ストリームを取得します.
     * <BR>
     * @return InputStream ソケット受信ストリームが返されます.
     * @exception Exception 例外.
     */
    protected InputStream inputStream()
        throws Exception {
        if( isClosed() == true ) {
            return null ;
        }
        InputStream ret = null ;
        synchronized( ioSync ) {
            if( this.inputStream == null ) {
                this.inputStream = this.socket.getInputStream() ;
            }
            ret = this.inputStream ;
        }
        return ret ;
    }
    
    /**
     * ソケット送信ストリームを取得.
     * <BR><BR>
     * ソケット送信ストリームを取得します.
     * <BR>
     * @return OutputStream ソケット送信ストリームが返されます.
     */
    protected OutputStream outputStream()
        throws Exception {
        if( isClosed() == true ) {
            return null ;
        }
        OutputStream ret = null ;
        synchronized( ioSync ) {
            if( this.outputStream == null ) {
                this.outputStream = this.socket.getOutputStream() ;
            }
            ret = this.outputStream ;
        }
        return ret ;
    }
    
    /**
     * 最終更新時間を取得.
     * <BR><BR>
     * 最終更新時間を取得します.
     * <BR>
     * @return long 最終更新時間が返されます.
     */
    public long time() {
        if( isClosed() == true ) {
            return -1L ;
        }
        return this.time ;
    }
    
    /**
     * このソケットを利用後にクローズするか設定.
     * <BR><BR>
     * このソケットを利用後にクローズする場合は[true]を設定します.
     * <BR>
     * @param flag [true]の場合、利用終了後にクローズします.
     */
    public void setCloseFlag( boolean flag ) {
        this.isCloseSocket = flag ;
    }
    
    /**
     * このソケットを利用後にクローズする場合は[true]が返されます.
     * <BR>
     * @return boolean [true]の場合、クローズされます.
     */
    protected boolean isCloseFlag() {
        return isCloseSocket ;
    }
    
    /**
     * クローズされているかチェック.
     * <BR>
     * @return boolean [true]の場合、このオブジェクトはクローズされています.
     */
    public boolean isClosed() {
        boolean ret = false ;
        try {
            if( this.socket == null || this.socket.isClosed() == true ) {
                ret = true ;
            }
        } catch( Exception e ) {
            ret = true ;
        }
        return ret ;
    }
}

