/*
 * @(#)CodecUtilities.java    1.0  7 February 2001
 *
 * Copyright 2004
 * 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.
 *
 * 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.
 *
 * 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>
 *
 * @author  Jeff Raab
 * @version 2.2
 * @see Codec
 */
public class CodecUtilities {
    
    /** The table of installed codecs. */
    private static Hashtable codecs = null;

    /**
     * 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.
     *
     * @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());
    }
}
