/*
 * @(#)CodecUtilities.java    1.0  26 April 2005
 *
 * Copyright 2005
 * College of Computer and Information Science
 * Northeastern University
 * Boston, MA  02115
 *
 * The Java Power Tools software may be used for educational
 * purposes as long as this copyright notice is retained intact
 * at the top of all source files.
 *
 * To discuss possible commercial use of this software, 
 * contact Richard Rasala at Northeastern University, 
 * College of Computer and Information Science,
 * 617-373-2462 or rasala@ccs.neu.edu.
 *
 * The Java Power Tools software has been designed and built
 * in collaboration with Viera Proulx and Jeff Raab.
 *
 * Should this software be modified, the words "Modified from 
 * Original" must be included as a comment below this notice.
 *
 * All publication rights are retained.  This software or its 
 * documentation may not be published in any media either
 * in whole or in part without explicit permission.
 *
 * This software was created with support from Northeastern 
 * University and from NSF grant DUE-9950829.
 */

package edu.neu.ccs.codec;

import edu.neu.ccs.*;
import edu.neu.ccs.util.*;
import java.text.ParseException;
import java.util.Hashtable;

/**
 * <p>Contains utility methods providing 
 * <CODE>{@link Codec CODEC}</CODE> operations.</p>
 *
 * <p>Nonstandard CODECs, that is, CODECs that are not included
 * in the JPT, must be installed using 
 * the <CODE>{@link #installCodec(Codec) installCodec}</CODE>
 * method of this class.</p>
 *
 * <p>Encoding and decoding operations must be performed
 * through the methods of this class,
 * and not directly through the methods
 * of an instance of any particular CODEC.</p>
 *
 * <p>The only change in 2.4.0 is to the documentation of the
 * method <code>decode</code>.  All code in this class is the
 * same as before.</p>
 *
 * @author  Jeff Raab
 * @author  Richard Rasala
 * @version 2.4.0
 * @see Codec
 */
public class CodecUtilities {
    
    /** The table of installed codecs. */
    private static Hashtable codecs = null;

    /**
     * <p>Decodes the given encoded data <CODE>String</CODE> 
     * into its (possibly encoded) component parts, 
     * using the CODEC identified within the data 
     * by its unique prefix.</p>
     *
     * <p>Throws <code>JPTError</code> if an error occurs.
     * Class <code>JPTError</code> extends the Java class
     * <code>Error</code> which implements the interface
     * <code>Throwable</code>.</p>
     *
     * <p>As of 2.4.0, this method is called by the method
     * <code>Strings.decode</code> which rather than throw
     * an error, returns a <code>null</code> array in case
     * of an error.  It is recommended that this method be
     * called directly only if the caller specifically
     * wants to know what specific <code>JPTError</code>
     * may have occured in an error situation.  The method
     * <code>Strings.decode</code> is definitely easier to
     * use.</p>
     *
     * @param data a data <CODE>String</CODE> to be decoded
     * @return the resulting array of <CODE>String</CODE>s
     * @see #encode(String[])
     * @see #encode(String[], String)
     * @see #encode(Stringable[])
     * @see #encode(Stringable[], String)
     * @since   1.0
     */
    public static String[] decode(String data) {

        // install default codecs if necessary
        if (!areCodecsInstalled())
            installStandardCodecs();
        
        // check for valid encoded data
        if (data.length() < 3) {
            throw new JPTError(
            	"Data is not valid encoded data: " + data);
        }
        
        // trim codec id off of data
        String id = data.substring(0, 3);
        data = data.substring(3);
        
        // get appropriate codec
        Codec c = (Codec)codecs.get(id);
        if (c == null) {
            throw new JPTError(
            	"Codec identifier not recognized: " + id);
        }

        // return the decoded data
        try {
            return c.decode(data);
        } catch (ParseException ex) {
            throw new JPTError(
            	"Parse exception while decoding data: " + 
            	ex.getMessage());
        }
    }

    /**
     * Encodes the given array of (possibly encoded) 
     * data <CODE>String</CODE>s 
     * into a single encoded data <CODE>String</CODE> 
     * using the default CODEC.
     *
     * @param data an array of data <CODE>String</CODE>s 
     *		to be encoded
     * @return the encoded data <CODE>String</CODE>
     * @see #decode(String)
     * @see #encode(String[], String)
     * @see #encode(Stringable[])
     * @see #encode(Stringable[], String)
     * @since   1.0
     */
    public static String encode(String[] data) {
        return encode(data, getDefaultCodec());
    }
    
    /**
     * Encodes the given array of (possibly encoded) 
     * data <CODE>String</CODE>s 
     * into a single encoded data <CODE>String</CODE> 
     * using the installed CODEC 
     * with the given unique identifier.
     *
     * @param data an array of data <CODE>String</CODE>s 
     *		to be encoded
     * @param codecID the unique identifier for the desired CODEC
     * @return the resulting encoded data <CODE>String</CODE>
     * @see #decode(String)
     * @see #encode(String[])
     * @see #encode(Stringable[])
     * @see #encode(Stringable[], String)
     * @since   1.0
     */
    public static String encode(String[] data, String codecID) {

        // install default codecs if necessary
        if (!areCodecsInstalled())
            installStandardCodecs();
        
        // ensure codec id is valid
        Codec c = (Codec)codecs.get(codecID);
        if (c == null)
            throw new JPTError(
                "Codec identifier not recognized: " + codecID);
            
        // return the encoded data
        return c.getPrefix() + c.encode(data);
    }
    
    /**
     * Encodes the given array of <CODE>Stringable</CODE> objects
     * into an encoded data <CODE>String</CODE> 
     * using the default CODEC.
     *
     * @param data an array of objects to be encoded
     * @return the resulting encoded data <CODE>String</CODE>
     * @see #decode(String)
     * @see #encode(String[])
     * @see #encode(String[], String)
     * @see #encode(Stringable[], String)
     * @since   1.0
     */
    public static String encode(Stringable[] data) {
        return encode(data, getDefaultCodec());
    }
    
    /**
     * Encodes the given array of <CODE>Stringable</CODE> objects
     * into a single encoded data <CODE>String</CODE> 
     * using the installed CODEC 
     * with the given unique identifier.
     *
     * @param data an array of objects to be encoded
     * @param codecID the unique identifier for the desired CODEC
     * @return the resulting encoded data <CODE>String</CODE>
     * @see #decode(String)
     * @see #encode(String[])
     * @see #encode(String[], String)
     * @see #encode(Stringable[])
     * @since   1.0
     */
    public static String encode(Stringable[] data, String codecID) {
        return encode(XObject.toStringArray(data), codecID);
    }

    /**
     * Returns the unique identifier 
     * for the default installed CODEC.
     *
     * @see #encode(String[], String)
     * @see #encode(Stringable[], String)
     * @since   1.0
     */
    public static String getDefaultCodec() {
        
        // install default CODECs if necessary
        if (!areCodecsInstalled())
            installStandardCodecs();
        
        // return the id for the "Count-prefix" CODEC
        return "CPC";
    }

    /**
     * Installs the given CODEC 
     * in the table of available CODECs.
     *
     * @param c the desired CODEC object to install
     * @since   1.0
     */
    public static void installCodec(Codec c) {
        codecs.put(c.getPrefix(), c);
    }

    /////////////////////
    // Private methods //
    /////////////////////
    
    /**
     * Returns whether or not 
     * the standard CODECs have been installed.
     *
     * @see #installStandardCodecs()
     * @since   1.0
     */
    private static boolean areCodecsInstalled() {
        return codecs != null;
    }
    
    /**
     * Installs the standard CODECs for the JPT.
     *
     * @see #areCodecsInstalled()
     * @since   1.0
     */
    private static void installStandardCodecs() {
        codecs = new Hashtable();
        installCodec(new CountPrefixCodec());
        installCodec(new EscapedCodec());
    }
}
