/*
 *  PHEX - The pure-java Gnutella-servent.
 *  Copyright (C) 2001 - 2005 Phex Development Group
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package phex.utils;

/**
 * This class is able to convert byte arrays to hex strings and hex strings to
 * byte arrays.
 * You could do this with the standard J2SE calls Integer.toHexString( int ) and
 * Byte.valueOf( String, 16 ). But this code is highly optimized and outperforms
 * the standard J2SE 1.3 algorithem by the factor 4:1.
 */
public final class HexConverter
{
    /**
     * The shortcut array to convert from decimal to hex value.
     */
    private static final char[] hexValueArray;

    static
    {
        hexValueArray = new char[16];
        hexValueArray[0] = '0';
        hexValueArray[1] = '1';
        hexValueArray[2] = '2';
        hexValueArray[3] = '3';
        hexValueArray[4] = '4';
        hexValueArray[5] = '5';
        hexValueArray[6] = '6';
        hexValueArray[7] = '7';
        hexValueArray[8] = '8';
        hexValueArray[9] = '9';
        hexValueArray[10] = 'A';
        hexValueArray[11] = 'B';
        hexValueArray[12] = 'C';
        hexValueArray[13] = 'D';
        hexValueArray[14] = 'E';
        hexValueArray[15] = 'F';
    }

    /**
     * Converts the data byte array into a hex string.
     */
    public static final String toHexString( byte[] data )
    {
        return toHexString( data, 0, data.length );
    }

    /**
     * Converts length bytes from the data byte array starting from offset into
     * a hex string.
     */
    public static final String toHexString( byte[] data, int offset, int length )
    {
        if ( length < 0 )
        {
            throw new IllegalArgumentException( "Length " + length + " < 0 " );
        }
        int end = offset + length;
        if ( offset > data.length || end > data.length )
        {
            throw new IndexOutOfBoundsException( "Data length: " + data.length +
                " offset: " + offset + " length: " + length );
        }

        StringBuffer buffer = new StringBuffer( length * 2 );
        for ( int i = offset; i < end; i++ )
        {
            buffer.append( hexValueArray[ (data[i] >> 4) & 0x0F ] );
            buffer.append( hexValueArray[  data[i] & 0x0F] );
        }
        return buffer.toString();
    }

    /**
     * Converts a single byte into a hex string.
     */
    public static final String toHexString( byte data )
    {
        StringBuffer buffer = new StringBuffer( 2 );
        buffer.append( hexValueArray[ (data >> 4) & 0x0F ] );
        buffer.append( hexValueArray[  data & 0x0F] );
        return buffer.toString();
    }

    /**
     * Converts hexString into a byte array.
     */
    public static final byte[] toBytes( String hexString )
    {
        if ( hexString == null )
        {
            throw new NullPointerException( "HexString is null" );
        }
        int length = hexString.length();
        if ( length % 2 != 0 )
        {
            throw new NumberFormatException( "Hex string has odd characters: " + hexString );
        }

        byte[] data = new byte[ length / 2 ];
        char highChar, lowChar;
        byte highNibble, lowNibble;
        for (int i = 0, offset = 0; i < length; i += 2, offset ++ )
        {
            highChar = hexString.charAt( i );
            if ( highChar >= '0' && highChar <= '9')
            {
                highNibble = (byte)(highChar - '0');
            }
            else if (highChar >= 'A' && highChar <= 'F')
            {
                highNibble = (byte)(10 + highChar - 'A');
            }
            else if (highChar >= 'a' && highChar <= 'f')
            {
                highNibble = (byte)(10 + highChar - 'a');
            }
            else
            {
                throw new NumberFormatException( "Invalid hex char: " + highChar );
            }

            lowChar = hexString.charAt( i + 1 );
            if ( lowChar >= '0' && lowChar <= '9')
            {
                lowNibble = (byte)(lowChar - '0');
            }
            else if (lowChar >= 'A' && lowChar <= 'F')
            {
                lowNibble = (byte)(10 + lowChar - 'A');
            }
            else if (lowChar >= 'a' && lowChar <= 'f')
            {
                lowNibble = (byte)(10 + lowChar - 'a');
            }
            else
            {
                throw new NumberFormatException( "Invalid hex char: " + lowChar );
            }

            data[ offset ] = (byte)(highNibble << 4 | lowNibble);
        }
        return data;
    }
}