/*-------------------------------------------------------------------------
*
*	Copyright (C) 2005, PostgreSQL Global Development Group
*
*--------------------------------------------------------------------------
*/
package org.postgresql.net;

import java.io.Serializable;
import java.sql.SQLException;

import org.postgresql.util.GT;
import org.postgresql.util.PGobject;
import org.postgresql.util.PGtokenizer;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;

/**
 *	This represents org.postgresql's inet datatype, which is used
 *	to hold IPV4 network addresses and ip addresses.
 *
 *	<p>
 *	This class wraps the postgresql specific INET datatype.  It supports
 *	values in the following format.
 *	<p>
 *	a.b.c.d[/netmask]
 *
 *	@author Russell Francis &lt; rfrancis@ev.net &gt;
 */
public class PGinet extends PGobject implements Serializable, Cloneable
{
	private int hashValue;

	/**
 	 *	This constructor takes a string in the inet format
	 *	a.b.c.d[/netmask] and creates a new PGinet to
	 *	represent it.
	 *
 	 *	@param s The string representation of the inet value.
	 *	@exception SQLException If the string is invalid.
	 */
	public PGinet( String s )
	throws SQLException
	{
		this();
		this.setValue( s );
	}

	/**
	 *	A simple constructor.
	 */
	public PGinet()
	{
		setType( "inet" );
	}

	/**
	 *	This method sets the value of this PGinet object.
	 *
 	 *	@param v A string representation of an inet address a.b.c.d[/netmask]
	 *	@exception SQLException If the parameter is not a valid inet address.
	 */
	public void setValue( String v )
	throws SQLException
	{
		if( v == null )
		{
			throw( new PSQLException( GT.tr( "Conversion to type {0} failed: {1}.",
				new Object[]{ type, v } ), PSQLState.DATA_TYPE_MISMATCH ) );
		}

		PGtokenizer t = new PGtokenizer( v, '/' );

		int size = t.getSize();
		if( ( size != 1 ) && ( size != 2 ) )
		{
			throw( new PSQLException( GT.tr( "Conversion to type {0} failed: {1}.",
				new Object[]{ type, v } ), PSQLState.DATA_TYPE_MISMATCH ) );
		}

		int a = 0;
		int b = 0;
		int c = 0;
		int d = 0;
		int netmask = 32;

		try
		{
			if( size == 2 )
			{
				netmask = Integer.valueOf( t.getToken( 1 ) ).intValue();
				if( ( netmask < 0 ) || ( netmask > 32 ) )
				{
					throw( new PSQLException( 
						GT.tr( "Conversion to typ {0} failed: {1}.", new Object[]{ type, v } ),
						PSQLState.DATA_TYPE_MISMATCH ) );
				}
			}

			t = new PGtokenizer( t.getToken( 0 ), '.' );
			if( t.getSize() != 4 )
			{
				throw( new PSQLException( 
					GT.tr( "Conversion to typ {0} failed: {1}.", new Object[]{ type, v } ),
					PSQLState.DATA_TYPE_MISMATCH ) );
			}

			a = Integer.valueOf( t.getToken( 0 ) ).intValue();
			b = Integer.valueOf( t.getToken( 1 ) ).intValue();
			c = Integer.valueOf( t.getToken( 2 ) ).intValue();
			d = Integer.valueOf( t.getToken( 3 ) ).intValue();
		}
		catch( NumberFormatException e )
		{
			throw( new PSQLException( GT.tr( "Conversion to type {0} failed: {1}.",
				new Object[]{ type, v } ), PSQLState.DATA_TYPE_MISMATCH, e ) );
		}

		// ensure that the values are within a valid range.
		if( ( a < 0 ) || ( a > 255 ) ||
			( b < 0 ) || ( b > 255 ) ||
			( c < 0 ) || ( c > 255 ) ||
			( d < 0 ) || ( d > 255 ) )
		{
			throw( new PSQLException( GT.tr( "Conversion to type {0} failed: {1}.",
				new Object[]{ type, v } ), PSQLState.DATA_TYPE_MISMATCH ) ); 
		}

		this.hashValue = ((a ^ c) << 24) | ((b ^ d) << 16) | ((b ^ c) << 8) | (d ^ netmask);
		this.value = "" + a + "." + b + "." + c + "." + d + ((netmask == 32) ? "" : ("/" + netmask));
	}

	/**
	 *	Get the hash code for this network address.
	 *
 	 *	@return The hash value for this object.
	 */
	public int hashCode()
	{
		return( this.hashValue );
	}

	/**
	 *	Compare two PGinet's for equality.
	 *
	 *	@param obj The object which we wish to compare.
	 *	@return true if it represents the same network or ip address 
	 *		as this PGinet, false otherwise.
	 */
	public boolean equals( Object obj )
	{
		if( obj instanceof PGinet )
		{
			PGinet inet = (PGinet)obj;
			if( this.toString().equals( inet.toString() ) )
			{
				return( true );
			}
		}
		return( false );
	}

	/**
 	 *	Make a duplicate of this PGinet object.
	 *
	 *	@return null on failure, or a new PGinet address
	 *		which is equal to this object.
	 */
	public Object clone()
	{
		try
		{
			return( new PGinet( this.getValue() ) );
		}
		catch( SQLException e )
		{
			return( null );
		}
	}
}
