package org.maachang.util;

import java.io.IOException;
import java.io.Serializable;
import java.net.InetAddress;
import java.util.ArrayList;

/**
 * サブネットマスク.
 *  
 * @version 2007/10/18
 * @author  masahito suzuki
 * @since MaachangProxy 1.00
 */
public class NetMask implements Serializable {
    /**
     * シリアライズID.
     */
    private static final long serialVersionUID = -7686107137171859532L;
    
    /**
     * ネットワークアドレス.
     */
    private int network = 0 ;
    
    /**
     * サブネットマスク.
     */
    private int netmask = 0 ;
    
    /**
     * コンストラクタ.
     */
    private NetMask() {}
    
    /**
     * コンストラクタ.
     * <BR><BR>
     * 指定条件でサブネットマスクを構築します.
     * <BR>
     * @param addr 対象のアドレス情報を指定します.
     * @param mask 対象のサブネットマスクを設定します.
     * @exception Exception 例外.
     */
    public NetMask( InetAddress addr,String mask )
        throws Exception {
        if( addr == null || mask == null || mask.length() <= 0 ){
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        try{
            this.network = NetMask.convertAddrByInt( addr.getHostAddress() ) ;
            this.netmask = NetMask.convertAddrByInt( mask ) ;
            if( this.network == 0 || this.netmask == 0 ){
                throw new IOException(
                    "指定したアドレス[" + addr + "]は不正です"
                ) ;
            }
        }catch( IOException ae ){
            throw ae ;
        }catch( Exception e ){
            throw new IOException( e ) ;
        }
    }
    
    /**
     * コンストラクタ.
     * <BR><BR>
     * 指定条件でサブネットマスクを構築します.
     * <BR>
     * @param addr 対象のアドレス情報を指定します.
     * @param mask 対象のサブネットマスクを設定します.
     * @exception Exception 例外.
     */
    public NetMask( String addr,String mask )
        throws Exception {
        if(
            addr == null || addr.length() <= 0 ||
            mask == null || mask.length() <= 0
        ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        try{
            this.network = NetMask.convertAddrByInt( addr ) ;
            this.netmask = NetMask.convertAddrByInt( mask ) ;
            if( this.network == 0 || this.netmask == 0 ){
                throw new IOException(
                    "指定したアドレス[" + addr + "]は不正です"
                ) ;
            }
        }catch( IOException ae ){
            throw ae ;
        }catch( Exception e ){
            throw new IOException( e ) ;
        }
    }
    
    /**
     * コンストラクタ.
     * <BR><BR>
     * 指定条件でサブネットマスクを構築します.
     * <BR>
     * @param addr 対象のアドレス情報を指定します.<BR>
     *             また、指定アドレスは[192.168.1.1/24]のような形式で
     *             設定します.
     * @exception Exception 例外.
     */
    public NetMask( String addr )
        throws Exception {
        int msk ;
        String saddr = null ;
        if( addr == null || addr.length() <= 0 ){
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        try{
            msk = NetMask.getAddrToMaskLength( addr ) ;
            saddr = NetMask.getAddr( addr ) ;
            if( msk == 0 || addr == null || addr.length() <= 0 ){
                throw new IOException(
                    "指定したアドレス[" + addr + "]は不正です"
                ) ;
            }
            this.network = NetMask.convertAddrByInt( saddr ) ;
            this.netmask = NetMask.getSubnet( msk ) ;
            if( this.network == 0 || this.netmask == 0 ){
                throw new IOException(
                    "指定したアドレス[" + addr + "]は不正です"
                ) ;
            }
        }catch( IOException ae ){
            throw ae ;
        }catch( Exception e ){
            throw new IOException( e ) ;
        }finally{
            saddr = null ;
        }
    }
    
    /**
     * ネットワークアドレスを取得.
     * <BR><BR>
     * 対象のネットワークアドレスを取得します.
     * <BR>
     * @return String 対象のネットワークアドレスが返されます.
     */
    public String getNetworkAddress() {
        return NetMask.getIntToAddr( this.network ) ;
    }
    
    /**
     * 対象のサブネットマスクを取得.
     * <BR><BR>
     * 対象のサブネットマスクが返されます.
     * <BR>
     * @return String 対象のサブネットマスクが返されます.
     */
    public String getNetMaskNumber() {
        return String.valueOf( this.netmask ) ;
    }
    
    /**
     * 対象のサブネットマスクを取得.
     * <BR><BR>
     * 対象のサブネットマスクが返されます.
     * <BR>
     * @return String 対象のサブネットマスクが返されます.
     */
    public String getNetMask() {
        return NetMask.getIntToAddr( this.netmask ) ;
    }
    
    /**
     * 対象範囲内のネットワークアドレスを取得.
     * <BR><BR>
     * 対象ネットワーク範囲のアドレスを取得します.
     * <BR>
     * @param no 取得項番を設定します.
     * @return String 範囲内のネットワークアドレスが返されます.
     */
    public String getRangeAddress( int no ) {
        int len ;
        len = ( ~this.netmask ) - 1 ;
        if( no < 0 || no >= len ){
            return null ;
        }
        return NetMask.getIntToAddr(
            ( this.network & this.netmask ) + no + 1
        ) ;
    }
    
    /**
     * 対象範囲内のネットワークアドレス数を取得.
     * <BR><BR>
     * 対象範囲内のネットワークアドレス数を取得します.
     * <BR>
     * @return int 範囲内のネットワークアドレス数が返されます.
     */
    public int getRangeSize() {
        return ( ~this.netmask ) - 1 ;
    }
    
    /**
     * オブジェクトを文字列に変換.
     * <BR><BR>
     * オブジェクトを文字列に変換します.
     * <BR>
     * @return String 文字列が返されます.
     */
    public String toString() {
        StringBuilder buf = null ;
        buf = new StringBuilder() ;
        buf.append( "netaddress:" ) ;
        buf.append( NetMask.getIntToAddr( this.network ) ) ;
        buf.append( " NetMask:" ) ;
        buf.append( NetMask.getIntToAddr( this.netmask ) ) ;
        return buf.toString() ;
    }
    
    /**
     * 指定アドレスが対象条件の範囲内であるかチェック.
     * <BR><BR>
     * 指定アドレスが対象条件の範囲内であるかチェックします.
     * <BR>
     * @param addr 対象のアドレスを設定します.
     * @return boolean チェック結果が返されます.
     */
    public boolean isRange( InetAddress addr ) {
        if( addr == null ){
            return false ;
        }
        return this.isRange( addr.getHostAddress() ) ;
    }
    
    /**
     * 指定アドレスが対象条件の範囲内であるかチェック.
     * <BR><BR>
     * 指定アドレスが対象条件の範囲内であるかチェックします.
     * <BR>
     * @param addr 対象のアドレスを設定します.
     * @return boolean チェック結果が返されます.
     */
    public boolean isRange( String addr ) {
        int iaddr ;
        boolean ret = false ;
        if( addr == null || addr.length() <= 0 ){
            return false ;
        }
        try{
            iaddr = NetMask.convertAddrByInt( addr ) ;
            if( ( this.network & this.netmask ) == ( iaddr & this.netmask ) ){
                if( this.netmask == -1 ||
                    ( ( iaddr = ( iaddr & ( ~this.netmask ) ) ) != 0 &&
                    iaddr != ( ~this.netmask ) ) ) {
                    ret = true ;
                }
            }
        }catch( Exception e ){
            ret = false ;
        }
        return ret ;
    }
    
    /**
     * Intから、アドレス情報に変換.
     */
    private static final String getIntToAddr( int addr ) {
        StringBuilder buf = null ;
        buf = new StringBuilder() ;
        buf.append( ( ( ( addr & 0xff000000 ) >> 24 ) & 0x000000ff ) ) ;
        buf.append( "." ) ;
        buf.append( ( ( ( addr & 0x00ff0000 ) >> 16 ) & 0x000000ff ) ) ;
        buf.append( "." ) ;
        buf.append( ( ( ( addr & 0x0000ff00 ) >> 8 ) & 0x000000ff ) ) ;
        buf.append( "." ) ;
        buf.append( ( addr & 0x000000ff ) ) ;
        return buf.toString() ;
    }
    
    /**
     * 指定アドレスから、マスク長を取得.
     */
    private static final String getAddr( String addr )
        throws Exception {
        int pnt ;
        String ret = null ;
        if( ( pnt = addr.indexOf( "/" ) ) == -1 ){
            return addr ;
        }
        try{
            ret = addr.substring( 0,pnt ).trim() ;
        }catch( Exception e ){
            throw new IOException( e ) ;
        }
        return ret ;
    }
    
    /**
     * 指定アドレスから、アドレス情報を取得.
     */
    private static final int getAddrToMaskLength( String addr )
        throws Exception {
        int pnt ;
        int ret ;
        if( ( pnt = addr.indexOf( "/" ) ) == -1 ){
            return 0 ;
        }
        try{
            ret = Integer.parseInt( addr.substring( pnt+1 ).trim() ) ;
        }catch( Exception e ){
            throw new IOException( e ) ;
        }
        return ret ;
    }
    
    /**
     * 指定アドレスをintに変換.
     */
    private static final int convertAddrByInt( String addr )
        throws Exception {
        int ret ;
        ArrayList lst = null ;
        try{
            lst = StringUtil.cutString( addr,"." ) ;
            if( lst != null && lst.size() == 4 ){
                ret = 
                (
                    ( Integer.parseInt( ( String )lst.get( 3 ) ) & 0x000000ff ) |
                    ( ( Integer.parseInt( ( String )lst.get( 2 ) ) & 0x000000ff ) << 8 ) |
                    ( ( Integer.parseInt( ( String )lst.get( 1 ) ) & 0x000000ff ) << 16 ) |
                    ( ( Integer.parseInt( ( String )lst.get( 0 ) ) & 0x000000ff ) << 24 )
                ) ;
            }
            else{
                ret = 0 ;
            }
        }finally{
            if( lst != null ){
                lst.clear() ;
            }
            lst = null ;
        }
        
        return ret ;
    }
    
    /**
     * 指定サブネットマスク長から、サブネットマスク情報を取得.
     */
    private static final int getSubnet( int length )
        throws Exception {
        int ret ;
        switch( length ){
            case 0 :   ret = 0x00000000 ; break ;
            case 1 :   ret = 0x80000000 ; break ;
            case 2 :   ret = 0xc0000000 ; break ;
            case 3 :   ret = 0xe0000000 ; break ;
            case 4 :   ret = 0xf0000000 ; break ;
            case 5 :   ret = 0xf8000000 ; break ;
            case 6 :   ret = 0xfc000000 ; break ;
            case 7 :   ret = 0xfe000000 ; break ;
            case 8 :   ret = 0xff000000 ; break ;
            case 9 :   ret = 0xff800000 ; break ;
            case 10 :  ret = 0xffc00000 ; break ;
            case 11 :  ret = 0xffe00000 ; break ;
            case 12 :  ret = 0xfff00000 ; break ;
            case 13 :  ret = 0xfff80000 ; break ;
            case 14 :  ret = 0xfffc0000 ; break ;
            case 15 :  ret = 0xfffe0000 ; break ;
            case 16 :  ret = 0xffff0000 ; break ;
            case 17 :  ret = 0xffff8000 ; break ;
            case 18 :  ret = 0xffffc000 ; break ;
            case 19 :  ret = 0xffffe000 ; break ;
            case 20 :  ret = 0xfffff000 ; break ;
            case 21 :  ret = 0xfffff800 ; break ;
            case 22 :  ret = 0xfffffc00 ; break ;
            case 23 :  ret = 0xfffffe00 ; break ;
            case 24 :  ret = 0xffffff00 ; break ;
            case 25 :  ret = 0xffffff80 ; break ;
            case 26 :  ret = 0xffffffc0 ; break ;
            case 27 :  ret = 0xffffffe0 ; break ;
            case 28 :  ret = 0xfffffff0 ; break ;
            case 29 :  ret = 0xfffffff8 ; break ;
            case 30 :  ret = 0xfffffffc ; break ;
            case 31 :  ret = 0xfffffffe ; break ;
            case 32 :  ret = 0xffffffff ; break ;
            default :
                throw new IOException( "対象マスク長が範囲外です(length:" + length + ")" ) ;
        }
        return ret ;
    }
}

