/*
 * @(#)CountPrefixCodec.java    1.1  14 September 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 java.io.Serializable;
import java.text.ParseException;
import java.util.StringTokenizer;
import java.util.Vector;

/**
 * <P><CODE>{@link Codec CODEC}</CODE> implementation 
 * whose encoding scheme prefixes data 
 * with character and element counts.
 *
 * This CODEC does not make use of data compression,
 * and does not result in any data loss.
 * This CODEC produces encoded data 
 * that is easily readable at low levels of recursion,
 * but is often difficult to read at high levels
 * of recursion.</P>
 *
 * <P>The unique three letter identifier 
 * for this CODEC is "<CODE>CPC</CODE>".
 *
 * This CODEC is automatically installed by the JPT, 
 * and is the deafult CODEC for use with the JPT.</P>
 *
 * @author  Richard Rasala
 * @author  Jeff Raab
 * @version 2.2
 * @since   1.0
 * @see CodecUtilities
 */
public class CountPrefixCodec 
    implements Codec, Serializable 
{
    /**
     * Constructs an object capable of encoding and decoding data 
     * using the "Count-prefix" encoding scheme.
     */
    public CountPrefixCodec() {}
    
    ///////////
    // Codec //
    ///////////

    /**
     * Encodes the given array of data <CODE>String</CODE>s
     * into a single compound data <CODE>String</CODE> 
     * using the "Count-prefix" encoding scheme.
     *
     * @param data an array of data <CODE>String</CODE>s
     * @return the resulting encoded <CODE>String</CODE>
     * @see #decode(String)
     * @see CodecUtilities#encode(String[])
     * @see CodecUtilities#encode(Stringable[])
	 * @see Codec
     */
    public String encode(String[] data) {
    
    	// encode null array as #@
        if (data == null)
            return "#@";
            
        // encode non-null array a as a compound string
        // containing data.length elements
        else {
            String s = data.length + "@";
            
            for (int i = 0; i < data.length; i++)
                if (data[i] == null)
                    s += "#:";
                else
                    s += data[i].length() + ":" + data[i];
            
            return s;
        }
    }

    /**
     * Decodes the given compound data <CODE>String</CODE> 
     * into an array of data <CODE>String</CODE>s 
     * using the "Count-prefix" encoding scheme.
     *
     * @param data an encoded data <CODE>String</CODE>
     * @return the resulting array of data <CODE>String</CODE>s
     * @throws ParseException if the data was not encoded 
     *		using this scheme
     * @see #encode(String[])
     * @see CodecUtilities#decode(String)
	 * @see Codec
     */
    public String[] decode(String data) throws ParseException {
        // if there is no data to decode return null
        if (data == null)
            return null;
        
        int pos    = 0,
            colon  = 0,
            length = 0,
            strlen = 0;
        
        String[] element = null;
        
        String substring = null;
        
        // find @ character and expect
        //              #@ indicating a null array
        //   array_length@ indicating a non-null array of given length
        pos = data.indexOf("@");
        if (pos == -1)
            throw new ParseException
                ("Expected '#@' OR 'array_length@'", data.length());
        
        // check for null array
        substring = data.substring(0, pos);
        
        if (0 == substring.length())
            throw new ParseException
                ("Expected '#@' OR 'array_length@'", pos);
        
        if ('#' == substring.charAt(0))
            return element;
        
        // parse array length
        try {
            length = Integer.parseInt(data.substring(0, pos++));
        } catch (NumberFormatException ex) {
            throw new ParseException
                ("Expected 'array_length@'", pos);
        }
        
        if (length < 0)
            throw new ParseException
                ("Expected array_length >= 0", pos);
        
        // build array of proper size
        element = new String[length];
        
        // parse individual (possibly encoded) Strings
        for (int i = 0; i < length; i++) {
            
            // find colon character and expect
            //               #: indicating a null String
            //   string_length: indicating a non-null String of given length
            colon = data.indexOf(":", pos);
            
            if (colon == -1)
                throw new ParseException
                    ("Expected '#:' OR 'string_length:'", data.length());
            
            substring = data.substring(pos, colon);
            pos = colon + 1;
            
            if (0 == substring.length())
                throw new ParseException
                    ("Expected '#:' OR 'string_length:'", colon);
            
            // skip this cycle if element[i] should be null
            if ('#' == substring.charAt(0))
                continue;
            
            // parse string length
            try {
                strlen = Integer.parseInt(substring);
            } catch (NumberFormatException ex) {
                throw new ParseException
                    ("Expected 'string_length:'", colon);
            }
            
            if (strlen < 0)
                throw new ParseException
                    ("Expected string_length >= 0", colon);
            
            // compose string
            if ((pos + strlen) > data.length())
                throw new ParseException(
                    "Expected " + 
                     (pos + strlen - data.length()) +
                     " more characters in the String",
                     data.length());
            
            element[i] = data.substring(pos, pos + strlen);
            pos = pos + strlen;
        }

        // return the decomposed String array
        return element;
    }
    
    /**
     * Returns the unique identifier for this encoding scheme:
     * the <CODE>String</CODE> "<CODE>CPC</CODE>".
     *
     * @see CodecUtilities#installCodec(Codec)
     * @see CodecUtilities#getDefaultCodec()
     * @see Codec
     */
    public String getPrefix() {
        return "CPC";
    }
}
