
/* ====================================================================
   Copyright 2002-2004   Apache Software Foundation

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
==================================================================== */
        

package org.apache.poi.contrib.poibrowser;

import java.io.*;
import java.util.*;
import org.apache.poi.hpsf.ClassID;



/**
 * <p>Provides utility methods for encoding and decoding hexadecimal
 * data.</p>
 *
 * @author Rainer Klute (klute@rainer-klute.de) - with portions from Tomcat
 * @version $Id$
 * @since 2002-01-24
 */
public class Codec
{

    /**
     * <p>The nibbles' hexadecimal values. A nibble is a half byte.</p>
     */
    protected static final byte hexval[] =
        {(byte) '0', (byte) '1', (byte) '2', (byte) '3',
         (byte) '4', (byte) '5', (byte) '6', (byte) '7',
         (byte) '8', (byte) '9', (byte) 'A', (byte) 'B',
         (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F'};



    /**
     * <p>Converts a string into its hexadecimal notation.</p>
     *
     * <p><strong>FIXME:</strong> If this method is called frequently,
     * it should directly implement the algorithm in the called method
     * in order to avoid creating a string instance.</p>
     */
    public static String hexEncode(final String s)
    {
        return hexEncode(s.getBytes());
    }



    /**
     * <p>Converts a byte array into its hexadecimal notation.</p>
     */
    public static String hexEncode(final byte[] s)
    {
        return hexEncode(s, 0, s.length);
    }



    /**
     * <p>Converts a part of a byte array into its hexadecimal
     * notation.</p>
     */
    public static String hexEncode(final byte[] s, final int offset,
                                   final int length)
    {
        StringBuffer b = new StringBuffer(length * 2);
        for (int i = offset; i < offset + length; i++)
        {
            int c = s[i];
            b.append((char) hexval[(c & 0xF0) >> 4]);
            b.append((char) hexval[(c & 0x0F) >> 0]);
        }
        return b.toString();
    }



    /**
     * <p>Converts a single byte into its hexadecimal notation.</p>
     */
    public static String hexEncode(final byte b)
    {
        StringBuffer sb = new StringBuffer(2);
        sb.append((char) hexval[(b & 0xF0) >> 4]);
        sb.append((char) hexval[(b & 0x0F) >> 0]);
        return sb.toString();
    }



    /**
     * <p>Converts a short value (16-bit) into its hexadecimal
     * notation.</p>
     */
    public static String hexEncode(final short s)
    {
        StringBuffer sb = new StringBuffer(4);
        sb.append((char) hexval[(s & 0xF000) >> 12]);
        sb.append((char) hexval[(s & 0x0F00) >>  8]);
        sb.append((char) hexval[(s & 0x00F0) >>  4]);
        sb.append((char) hexval[(s & 0x000F) >>  0]);
        return sb.toString();
    }



    /**
     * <p>Converts an int value (32-bit) into its hexadecimal
     * notation.</p>
     */
    public static String hexEncode(final int i)
    {
        StringBuffer sb = new StringBuffer(8);
        sb.append((char) hexval[(i & 0xF0000000) >> 28]);
        sb.append((char) hexval[(i & 0x0F000000) >> 24]);
        sb.append((char) hexval[(i & 0x00F00000) >> 20]);
        sb.append((char) hexval[(i & 0x000F0000) >> 16]);
        sb.append((char) hexval[(i & 0x0000F000) >> 12]);
        sb.append((char) hexval[(i & 0x00000F00) >>  8]);
        sb.append((char) hexval[(i & 0x000000F0) >>  4]);
        sb.append((char) hexval[(i & 0x0000000F) >>  0]);
        return sb.toString();
    }



    /**
     * <p>Converts a long value (64-bit) into its hexadecimal
     * notation.</p>
     */
    public static String hexEncode(final long l)
    {
        StringBuffer sb = new StringBuffer(16);
        sb.append(hexEncode((int) (l & 0xFFFFFFFF00000000L) >> 32));
        sb.append(hexEncode((int) (l & 0x00000000FFFFFFFFL) >>  0));
        return sb.toString();
    }



    /**
     * <p>Converts a class ID into its hexadecimal notation.</p>
     */
    public static String hexEncode(final ClassID classID)
    {
        return hexEncode(classID.getBytes());
    }



    /**
     * <p>Decodes the hexadecimal representation of a sequence of
     * bytes into a byte array. Each character in the string
     * represents a nibble (half byte) and must be one of the
     * characters '0'-'9', 'A'-'F' or 'a'-'f'.</p>
     *
     * @param s The string to be decoded
     *
     * @return The bytes
     *
     * @throw IllegalArgumentException if the string does not contain
     * a valid representation of a byte sequence.
     */
    public static byte[] hexDecode(final String s)
    {
        final int length = s.length();

        /* The string to be converted must have an even number of
           characters. */
        if (length % 2 == 1)
            throw new IllegalArgumentException
                ("String has odd length " + length);
        byte[] b = new byte[length / 2];
        char[] c = new char[length];
        s.toUpperCase().getChars(0, length, c, 0);
        for (int i = 0; i < length; i += 2)
            b[i/2] = (byte) (decodeNibble(c[i]) << 4 & 0xF0 |
                             decodeNibble(c[i+1])    & 0x0F);
        return b;
    }



    /**
     * <p>Decodes a nibble.</p>
     *
     * @param c A character in the range '0'-'9' or 'A'-'F'. Lower
     * case is not supported here.
     *
     * @return The decoded nibble in the range 0-15
     *
     * @throws IllegalArgumentException if <em>c</em> is not a
     * permitted character
     */
    protected static byte decodeNibble(final char c)
    {
        for (byte i = 0; i < hexval.length; i++)
            if ((byte) c == hexval[i])
                return i;
        throw new IllegalArgumentException("\"" + c + "\"" +
                                           " does not represent a nibble.");
    }



    /**
     * <p>For testing.</p>
     */
    public static void main(final String args[])
        throws IOException
    {
        final BufferedReader in =
            new BufferedReader(new InputStreamReader(System.in));
        String s;
        do
        {
            s = in.readLine();
            if (s != null)
            {
                String bytes = hexEncode(s);
                System.out.print("Hex encoded (String): ");
                System.out.println(bytes);
                System.out.print("Hex encoded (byte[]): ");
                System.out.println(hexEncode(s.getBytes()));
                System.out.print("Re-decoded (byte[]):  ");
                System.out.println(new String(hexDecode(bytes)));
            }
        }
        while (s != null);
    }

}
