/*
 *	The contents of this file are subject to the Netscape Public License 
 *	Version 1.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.mozilla.org/NPL/. 
 *	
 *	Software distributed under the License is distributed on an "AS IS" basis, 
 *	WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 
 *	for the specific language governing rights and limitations under the License. 
 *	
 *	The Original Code is the Netscape Messaging Access SDK Version 3.5 code, 
 *	released on or about June 15, 1998. 
 *	
 *	The Initial Developer of the Original Code is Netscape Communications 
 *	Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape 
 *	Communications Corporation. All Rights Reserved. 
 *	
 *	Contributor(s): ______________________________________. 
*/
  
/*
* Copyright (c) 1997 and 1998 Netscape Communications Corporation
* (http://home.netscape.com/misc/trademarks.html)
*/


package netscape.messaging.mime;
import java.io.*;
import java.lang.*;
import java.util.*;
import netscape.messaging.mime.MIMEParser;

/**
 * The MIMEBasicPart class is a common class used for all Basic
 * MIME BodyPart types:
 * Text, Image, Audio, Video, and Application. It does not represent structured
 * parts such as MIMEMessagePart and MIMEMultiPart.
 * @author Prasad Yendluri
 */
public class MIMEBasicPart extends MIMEBodyPart implements Cloneable
{

	/**
         * Content (primary) type Text.
         */
        public static final int TEXT            = 0;
	/**
         * Content (primary) type Audio.
         */
        public static final int AUDIO           = 1;
	/**
         * Content (primary) type Image.
         */
        public static final int IMAGE           = 2;
	/**
         * Content (primary) type Video.
         */
        public static final int VIDEO           = 3;
	/**
         * Content (primary) type Application.
         */
        public static final int APPLICATION     = 4;



//===========================================================================
//
//              Internal members not visible at the API
//
//===========================================================================

	// Encodings
        private static final int BASE64          = MIMEBodyPart.BASE64;
        private static final int QP              = MIMEBodyPart.QP;
        private static final int BINARY          = MIMEBodyPart.BINARY;
        private static final int E7BIT           = MIMEBodyPart.E7BIT;
        private static final int E8BIT           = MIMEBodyPart.E8BIT;
        private static final int EIGNORE         = 99;
        public static final int UNINITIALIZED = -1;
        private static final int MAXDATABUFSIZE = 1111111;

	private static final String[] m_stringContentTypes = { "Text", "Audio", "Image",
							       "Video", "Application" };
	private static final String[] m_stringEncoding  = { "base64", "quoted-printable",
							    "binary", "7bit", "8bit" };
	private static final String[] m_stringDisposition  = { "Attachment", "Inline"};

	private int 		m_contentType;
	private String 		m_contentMD5;
	private int 		m_contentTransferEncoding;
	private int 		m_parsedPart;

          // The key to the hashTable below is the name of the header field.
	  // To make sure we handle case differences in the header-name, we
	  // must convert the header name to lower-case always prior to the
          // hash look-up. The entries added to the hash table are objects of
          // class Header.
	private Hashtable 	m_mimeHeadersTable;

	  // Buffer to hold body-data (not encoded). We always store un-encoded.
          // Buffer stores data as parsed from input message or as given by the user
          // when constructing this bodyPart.
	private ByteBuffer m_databuf;
	private byte 	   m_readbuf[];
	private InputStream m_dataStream;

	private int m_nStartMessageDataIndex;
	private int m_nEndMessageDataIndex;
	private int m_nMessageDataLen;
    	private boolean m_bDecodedData;
    	protected boolean m_endData;
    	protected MIMEMultiPart m_parentContainer;


//===========================================================================
//
// 				CONSTRUCTORS
//
//===========================================================================

	/**
         * Constructs a MIMEBasicPart object with the given content-type.
	 * @param contentType Content type. Values:
         * TEXT, AUDIO, IMAGE, VIDEO, APPLICATION.
         * @see MIMEBasicPart#TEXT
         * @see MIMEBasicPart#AUDIO
         * @see MIMEBasicPart#IMAGE
         * @see MIMEBasicPart#VIDEO
         * @see MIMEBasicPart#APPLICATION
	 * @exception MIMEException If ContentType passed is invalid.
         */
	public MIMEBasicPart (int contentType) throws MIMEException
        {
	    	switch (contentType)
	    	{
		    case MIMEBasicPart.TEXT:
			m_contentType = TEXT;
			break;
		    case MIMEBasicPart.AUDIO:
			m_contentType = AUDIO;
			break;
		    case MIMEBasicPart.IMAGE:
			m_contentType = IMAGE;
			break;
		    case MIMEBasicPart.VIDEO:
			m_contentType = VIDEO;
			break;
		    case MIMEBasicPart.APPLICATION:
			m_contentType = APPLICATION;
			break;
		    default:
			 throw new MIMEException ("Invalid  content-type: " + contentType);
		}

		m_parsedPart = 0;
		m_contentDisposition      =  UNINITIALIZED;
		m_contentTransferEncoding =  UNINITIALIZED;
		m_mimeHeadersTable = new Hashtable();
		m_databuf =  null;
		m_readbuf = null;
		m_dataStream =  null;

    		m_nStartMessageDataIndex  = UNINITIALIZED;
	    	m_nEndMessageDataIndex    = UNINITIALIZED;
	    	m_nMessageDataLen         = 0;
	    	m_UserObject              = null;
	    	m_bDecodedData             = false;
	}


	/**
	* Default constructor.
	* Constructs a MIMEBasicPart object with MIMEBasicPart#TEXT content-type.
	*/
	public MIMEBasicPart() throws MIMEException
	{
   		 this (MIMEBasicPart.TEXT);
	}


//===========================================================================
//
//                        Methods overriden from MIMEBodyPart
//
//===========================================================================

	/**
	* Returns Content-Type of this MIME Part.
	* @see MIMEBodyPart#getContentType
	*/
	public String getContentType ()
        {
		String ct = new String (m_stringContentTypes [m_contentType]);
                return (ct);
        }


//===========================================================================
//
//                        Methods Specific to this Class alone.
//
//===========================================================================

	/**
	* Returns Content-MD5 of this MIME Part or NULL if Content-MD5 is not present.
        * @see #setContentMD5
	*/
	public String getContentMD5 ()
        {
		if (m_contentMD5 == null)
			return (null);

                return (m_contentMD5);
        }

	/**
         * Sets Content-MD5 of this MIME Part.
         * @param cid String to use as Content-ID.
	 * @see #getContentMD5
         */
	public void setContentMD5 (String md5)
	{
	    m_contentMD5 = md5;
	}

	/**
         * Returns an InputStream to this Part's Data after decoding any
	 * transfer encoding.
	 * @exception IOException If an IO error occurs.
	 * @exception MIMEException If the BodyData is not present.
	 * or an error is detected during decoding.
         * @see #setBodyData
         */
	public InputStream getBodyData () throws IOException, MIMEException
        {
	     if (m_databuf == null && m_dataStream == null)
	     {
		  throw new MIMEException ("getBodyData(): No BodyData present." );
	     }

	     if (m_databuf != null)
	     {
	     	ByteArrayInputStream ins = new ByteArrayInputStream (m_databuf.getBytes());
             	return (ins);
	     }

	     if (m_dataStream.markSupported())
			m_dataStream.reset();

             return (m_dataStream);
        }

	/**
     * Returns an ByteBuffer object, return null if no data
     * carsonl, used by parser
     */
	protected ByteBuffer getDataBuf() { return m_databuf; }



	/**
         * Sets BodyData of this MIME Part.
         * @param is Stream to read input data from. This data must be un-encoded raw data.
	 * @exception MIMEException If already set (or) if is parameter is null.
	 * @exception IOException If an IO error occurs.
	 * @see #getDataBuf
         */
	public void setBodyData (InputStream is) throws MIMEException, IOException
        {
		if (m_databuf != null && m_dataStream != null)
		{
		      throw new MIMEException ("setBodyData(): BodyData already set." );
		}

		if (is == null)
		{
		      throw new MIMEException ("setBodyData(): null inputStream");
		}

        	setMessageDataLen( is.available() );

		if (m_nMessageDataLen <= 0)
		{
			m_nMessageDataLen = 0;
		        throw new MIMEException ("setBodyData(): no data in inputStream");
		}

		if (m_nMessageDataLen > MAXDATABUFSIZE)
		{
			m_dataStream = is;
			if (is.markSupported())
				is.mark (2*m_nMessageDataLen);
		}
		else
		{
			ByteBuffer l_inbuf = new ByteBuffer (DATABUFSZ);
			int l_read = is.read (l_inbuf.buffer, 0, DATABUFSZ);

			if (l_read > 0)
			{
		     		m_databuf  = new ByteBuffer(DATABUFSZ); // initial size
			}
			else
			{
		     		throw new MIMEException ("setBodyData(): InputStream Empty.");
			}

			while (l_read > 0)
			{
		    		l_inbuf.hi = l_read;
		   		m_databuf.append (l_inbuf);
		  		l_read = is.read (l_inbuf.buffer, 0, DATABUFSZ);
			}
		}
        }

	/**
        * Sets BodyData of this MIME Part.
        * @param s Source string for input data. Must be un-encoded raw data.
	* @exception MIMEException If already set or if is parameter is null.
        */
    	public void setBodyData (String s) throws MIMEException
    	{
		if (m_databuf != null && m_dataStream != null)
		{
		      throw new MIMEException ("setBodyData(): BodyData already set." );
		}

		if ( s != null )
		{
			m_databuf = new ByteBuffer( s.length() );
			m_databuf.append( s.getBytes() );
			setMessageDataLen( s.length() );
		}
    	}

    	// non clone version
    	// carsonl. Not Exposed at API
    	protected void setBodyData( ByteBuffer s ) throws MIMEException
    	{
		if (m_databuf != null && m_dataStream != null)
		{
		      throw new MIMEException ("setBodyData(): BodyData already set." );
		}

    	    	if ( s != null )
        	    m_databuf = s;
    	}

        /**
        * Sets BodyData of this MIME Part.
	* Saves a reference to the passed byte buffer.
	* Does not copy the data.
        * @param s  un-encoded raw data.
	* @exception MIMEException If already set or if s parameter is null.
        */
    	public void setBodyData( byte s[] ) throws MIMEException
    	{
		if (m_databuf != null && m_dataStream != null)
		{
		      throw new MIMEException ("setBodyData(): BodyData already set.");
		}

    	    if ( s != null )
    	    {
        	    m_databuf = new ByteBuffer( s );
                setMessageDataLen( s.length );
        	}
    	}


	/**
         * Deletes BodyData of this MIME Part.
	 * If no BodyData is present, this method has no effect.
         */
	public void deleteBodyData ()
        {
		m_databuf = null;
		m_dataStream = null;
        	m_nMessageDataLen = 0;
        }

	/**
         * Outputs a byte-stream for this part with its MIME part headers and encoded
         * body data.
         * @param fullfilename Filename including full path of where to write the byte-stream.
	 * @exception IOException If an IO error occurs.
	 * @exception MIMEException If any required fields in the bodyPart are not set-up.
         */
	public void putByteStream (String fullfilename) throws IOException, MIMEException
        {
		FileOutputStream fos;

		fos = new FileOutputStream (fullfilename);

		putByteStream (fos);
	}

	/**
         * Outputs a byte-stream for this part with its MIME part headers and encoded
         * body data.
         * @param os OutputStream to write to.
	 * @exception IOException If an IO error occurs.
	 * @exception MIMEException If any required fields in the bodyPart are not set-up.
         */
	public void putByteStream (OutputStream os) throws IOException, MIMEException
        {
	     if (m_databuf == null && m_dataStream == null)
	     {
		throw new MIMEException ("MIMEBasicPart.putByteStream(). No bodyData!");
	     }

	     // Write out the headers first
	     StringBuffer l_hdrbuf = new StringBuffer (HDRBUFSZ);
	     byte[]       l_bytebuf;

	     //  Headers in member variables

	     // content-type
             switch (m_contentType)
             {
                  case TEXT:
		      l_hdrbuf.append ("Content-Type: text/");

		      if (m_contentSubType != null)
		           l_hdrbuf.append (m_contentSubType);
		      else
		           l_hdrbuf.append ("plain");

		      if (m_contentTypeParams != null && m_contentTypeParams.length() > 0 )
		      {
		           l_hdrbuf.append ("; ");
		           l_hdrbuf.append (m_contentTypeParams);
		           l_hdrbuf.append ("\r\n");
		      }
		      else
		      {
		      //    l_hdrbuf.append ("; ");
		      //    l_hdrbuf.append ("charset=us-ascii\r\n");
		            l_hdrbuf.append ("\r\n");
		      }
                      break;
                  case AUDIO:
		      l_hdrbuf.append ("Content-Type: audio/");

		      if (m_contentSubType != null)
		           l_hdrbuf.append (m_contentSubType);
		      else
		           throw new MIMEException ("putByteStream: No content-subtype." );
		           //l_hdrbuf.append ("basic");

		      if (m_contentTypeParams != null && m_contentTypeParams.length() > 0 )
		      {
		           l_hdrbuf.append ("; ");
		           l_hdrbuf.append (m_contentTypeParams);
		           l_hdrbuf.append ("\r\n");
		      }
		      else
		           l_hdrbuf.append ("\r\n");
                      break;
                  case IMAGE:
		      l_hdrbuf.append ("Content-Type: image/");

		      if (m_contentSubType != null)
		           l_hdrbuf.append (m_contentSubType);
		      else
		           throw new MIMEException ("putByteStream: No content-subtype.");
		           //l_hdrbuf.append ("jpeg");

		      if (m_contentTypeParams != null && m_contentTypeParams.length() > 0 )
		      {
		           l_hdrbuf.append ("; ");
		           l_hdrbuf.append (m_contentTypeParams);
		           l_hdrbuf.append ("\r\n");
		      }
		      else
		           l_hdrbuf.append ("\r\n");
                      break;
                  case VIDEO:
		      l_hdrbuf.append ("Content-Type: video/");
		      if (m_contentSubType != null)
		           l_hdrbuf.append (m_contentSubType);
		      else
		           throw new MIMEException ("putByteStream: No content-subtype.");
		           //l_hdrbuf.append ("mpeg");

		      if (m_contentTypeParams != null && m_contentTypeParams.length() > 0 )
		      {
		           l_hdrbuf.append ("; ");
		           l_hdrbuf.append (m_contentTypeParams);
		           l_hdrbuf.append ("\r\n");
		      }
		      else
		           l_hdrbuf.append ("\r\n");
                      break;
                  case APPLICATION:
		      l_hdrbuf.append ("Content-Type: application/");
		      if (m_contentSubType != null)
		           l_hdrbuf.append (m_contentSubType);
		      else
		           l_hdrbuf.append ("octet-stream");

		      if (m_contentTypeParams != null && m_contentTypeParams.length() > 0 )
		      {
		           l_hdrbuf.append ("; ");
		           l_hdrbuf.append (m_contentTypeParams);
		           l_hdrbuf.append ("\r\n");
		      }
		      else
		           l_hdrbuf.append ("\r\n");
                      break;
                  default:
                       throw new MIMEException ("Invalid  content-type: " + m_contentType);
             }

	     // write the header out to os
	     try
	     {
	        //l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1");
	          l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString());
	          os.write (l_bytebuf);
	     }
	     catch (Exception e)
	     {
		  throw new MIMEException ("Content-Type: " + e.getMessage());
	     }

	     // contentID
	     if (m_contentID != null)
	     {
		  l_hdrbuf.setLength (0);
		  l_hdrbuf.append ("Content-ID: " + m_contentID);
		  l_hdrbuf.append ("\r\n");

	     	  try
	     	  {
	              //l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1");
	              l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString());
	              os.write (l_bytebuf);
	          }
	          catch (Exception e)
	          {
		      throw new MIMEException ("Content-ID: " + e.getMessage());
	          }
	     }

	     // contentMD5
	     if (m_contentMD5 != null)
	     {
		  l_hdrbuf.setLength (0);
		  l_hdrbuf.append ("Content-MD5: " + m_contentMD5);
		  l_hdrbuf.append ("\r\n");

	     	  try
	     	  {
	            //l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1");
	              l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString());
	              os.write (l_bytebuf);
	          }
	          catch (Exception e)
	          {
		      throw new MIMEException ("Content-MD5: " + e.getMessage());
	          }
	     }

	     // contentDisposition
	     if (m_contentDisposition != UNINITIALIZED)
	     {
		  l_hdrbuf.setLength (0);
		  l_hdrbuf.append ("Content-Disposition: " +
				    m_stringDisposition [m_contentDisposition]);

		  if (m_contentDispParams != null)
		  {
		           l_hdrbuf.append ("; ");
		           l_hdrbuf.append (m_contentDispParams);
		  }

		  l_hdrbuf.append ("\r\n");

	     	  try
	     	  {
	            //l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1");
	              l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString());
	              os.write (l_bytebuf);
	          }
	          catch (Exception e)
	          {
		      throw new MIMEException ("Content-Disposition: " + e.getMessage());
	          }
	     }

	     // contentDescription
	     if (m_contentDescription != null)
	     {
		  l_hdrbuf.setLength (0);
		  l_hdrbuf.append ("Content-Description: " + m_contentDescription);
		  l_hdrbuf.append ("\r\n");

	     	  try
	     	  {
	              //l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1");
	              l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString());
	              os.write (l_bytebuf);
	          }
	          catch (Exception e)
	          {
		      throw new MIMEException ("Content-Description: " + e.getMessage());
	          }
	     }

	     // Content-transfer-encoding
	//	if (m_contentTransferEncoding == UNINITIALIZED)
	//	{
	//	  if ((m_contentType == TEXT) && (m_contentTypeParams != null) && m_contentTypeParams.length() > 0)
	//	  {
	//	       String l_params = m_contentTypeParams.toLowerCase();
	//	       int idx = l_params.indexOf ("charset=us-ascii");
	//	       if (idx >= 0)
	//	           m_contentTransferEncoding = E7BIT;
	//	       else
	//	           //m_contentTransferEncoding = BASE64;
	//	           //m_contentTransferEncoding = E8BIT;
	//	           m_contentTransferEncoding = EIGNORE;
        //
	//	  }
	//	  else
	//	       m_contentTransferEncoding = E7BIT;
	 //    }

	     if (m_contentTransferEncoding != EIGNORE && m_contentTransferEncoding != UNINITIALIZED)
	     {
	     	 l_hdrbuf.setLength (0);
	     	 l_hdrbuf.append ("Content-Transfer-Encoding: " +
	                       m_stringEncoding [m_contentTransferEncoding]);
	     	 l_hdrbuf.append ("\r\n");

	     	 try
	     	 {
	           	 // l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1");
	           	 l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString());
	           	 os.write (l_bytebuf);
	     	 }
	     	 catch (Exception e)
	     	 {
		   	 throw new MIMEException ("Content-Transfer-Encoding: " + e.getMessage());
	     	 }
	     }

	     // Do all the other headers
	     Header[] hdrs = getAllHeaders ();
	     if (hdrs != null)
                for (int i = 0, len = hdrs.length; i < len; i++)
		{
	            try
	            {
	                l_bytebuf = MIMEHelper.unicodeToASCII (hdrs[i].getLine());
	                os.write (l_bytebuf);
	            }
	            catch (Exception e)
	            {
    		        throw new MIMEException (hdrs[i].getLine() + "> "  + e.getMessage());
	            }
		}

             // blank-line after headers
             //l_bytebuf = new String("\r\n").getBytes("iso-8859-1");
             //os.write (LF);
             os.write (CRLF);

	     int encoding = m_contentTransferEncoding;

	     if (encoding == UNINITIALIZED || encoding == EIGNORE)
		   encoding = E7BIT;

             // Write out the bodydata encoding if needed.
	     switch (encoding)  // we know it is not UNINITIALIZED
	     {
		    case BASE64:
    			if (m_dataStream == null)
    			{
    	                	ByteArrayInputStream isb =
    					new ByteArrayInputStream (m_databuf.getBytes());
    				MIMEHelper.encodeBase64 (isb, os);
    			}
    			else
    			{
    	     			if (m_dataStream.markSupported())
    					m_dataStream.reset();
    				MIMEHelper.encodeBase64 (m_dataStream, os);
    			}
    			break;

		    case QP:
    			if (m_dataStream == null)
    			{
    	                	ByteArrayInputStream isq =
    					new ByteArrayInputStream (m_databuf.getBytes());
    				MIMEHelper.encodeQP (isq, os);
    			}
    			else
    			{
    	     			if (m_dataStream.markSupported())
    					m_dataStream.reset();
    				MIMEHelper.encodeQP (m_dataStream, os);
    			}
    			break;

		    case BINARY:
		    case E7BIT:
		    case E8BIT:
		    case EIGNORE:
    			if (m_dataStream == null)
    			{
    				os.write (m_databuf.getBytes());
    			}
    			else
    			{
    	     			if (m_dataStream.markSupported())
    					m_dataStream.reset();

    				if (m_readbuf == null)
    					m_readbuf = new byte [DATABUFSZ];

    				int l_read = m_dataStream.read (m_readbuf, 0, DATABUFSZ);

    				while (l_read > 0)
    				{
    				     os.write (m_readbuf, 0, l_read);
    		   		     l_read = m_dataStream.read (m_readbuf, 0, DATABUFSZ);
    				}
    			}
			break;

		    default:
			 throw new MIMEException ("Invalid  Content Transfer Encoding : "
						  + m_contentTransferEncoding);
	     } // switch

             //os.write (CRLF);

        } // putByteStream()

	/**
         * Returns the size of this Part's BodyData.
	 * @return Size of BodyData or -1 if this part does not have bodyData.
         */
	public int getSize ()
        {
		if (m_databuf != null)
		{
		     return (m_databuf.size());
		}
		else if (m_dataStream != null)
		{
		     return (m_nMessageDataLen);
		}

		return (-1);
        }

	/**
         * Sets Content-Transfer-Encoding of this MIME Part.
         * @param encoding Value that represents the encoding.
         * @see MIMEBodyPart#BASE64
         * @see MIMEBodyPart#QP
         * @see MIMEBodyPart#BINARY
         * @see MIMEBodyPart#E7BIT
         * @see MIMEBodyPart#E8BIT
	 * @exception MIMEException If encoding is invalid.
         */
	public void setContentEncoding (int encoding) throws MIMEException
        {
	    	switch (encoding)
	    	{
		    case BASE64:
		    case QP:
		    case BINARY:
		    case E7BIT:
		    case E8BIT:
			m_contentTransferEncoding = encoding;
			break;
		    default:
			 throw new MIMEException ("Invalid  Content Transfer Encoding : " + encoding);
              }
        }

	/**
         * Returns value of Content-Transfer-Encoding of this MIME Part. -1 if none present.
         * @see MIMEBodyPart#BASE64
         * @see MIMEBodyPart#QP
         * @see MIMEBodyPart#BINARY
         * @see MIMEBodyPart#E7BIT
         * @see MIMEBodyPart#E8BIT
         */
	public int getContentEncoding ()
        {
		if (m_contentTransferEncoding ==  UNINITIALIZED || m_contentTransferEncoding == EIGNORE)
		    return (-1);

		return (m_contentTransferEncoding);
        }

	/**
         * Sets a header of this body-part given name and value.
         * To be used to set any header that is not explicitly supported with a
         * set method, such as X-headers. Multiple invocations on the same header
	 * replace the old value.
         * @param name Name of the header field. Should not include ':'
         * @param value Value of the header field to be added.
	 * @exception MIMEException If either of name or value is NULL.
         */
	public void setHeader (String name, String value) throws MIMEException
        {
	     if ((name == null) || (value == null))
		 throw new MIMEException ("Invalid  NULL Header name or value");

	     if ( name.indexOf (':') > -1)
		 throw new MIMEException ("Invalid  ':' in Header name");

	     // Check if the header is one of those they need to specify with other methods
	     String rslt = isOKtoset (name);
	     if (rslt != null)
		 throw new MIMEException ("Invalid setHeader(). Use method: " + rslt);

	     String l_hashKey = name.toLowerCase();
	     Header l_hdr = new Header (name, value);
	     m_mimeHeadersTable.put (l_hashKey, l_hdr);
        }

        /**
        * Appends the value to an existing header. Creates new header if one does
	* not exist already.
        * @param name Name of the header field.
        * @param value Value of the header field to be added.
        * @exception MIMEException If either name or value are NULL
        */
	public void addHeader (String name, String value) throws MIMEException
	{
	     if ((name == null) || (value == null))
		 throw new MIMEException ("Invalid  NULL Header name or value");

	     String l_hashKey = name.toLowerCase();

	     Header l_hdr = (Header) m_mimeHeadersTable.get (l_hashKey);

	     if (l_hdr == null)
		setHeader (name, value);
	     else
	     {
		  StringBuffer l_oldvalue = new StringBuffer (l_hdr.getValue());
		  l_oldvalue.append (value);
		  Header l_newhdr = new Header (name, l_oldvalue.toString());
		  m_mimeHeadersTable.put (l_hashKey, l_newhdr);
	     }
	}

	/**
         * Returns the value of the requested header.
	 * Returns NULL if the header is not present.
         * @param name Name of the header field.
	 * @exception MIMEException If name passed is a NULL.
         */
	public String getHeader (String name) throws MIMEException
        {
	     if (name == null)
		 throw new MIMEException ("Invalid  NULL Header name");

	     String l_hashKey = name.toLowerCase();
	     Header l_hdr = (Header) m_mimeHeadersTable.get (l_hashKey);

	     if (l_hdr != null)
		return (l_hdr.getValue());
	     else
		  return (null);
        }

	/**
         * Deletes the requested header from this Part.
         * @param name Name of the header field.
	 * @exception MIMEException if name is NULL.
         */
	public void deleteHeader (String name) throws MIMEException
        {
	     if (name == null)
		 throw new MIMEException ("Invalid  NULL Header name");

	     String l_hashKey = name.toLowerCase();
	     m_mimeHeadersTable.remove (l_hashKey);
        }

	/**
         * Returns all the headers in this Part as an array of Header objects.
	 * @exception MIMEException If no headers exist.
         */
	public Header[] getAllHeaders () throws MIMEException
        {
		int l_numElements = m_mimeHeadersTable.size();
		int i = 0;

		if (l_numElements <= 0)
		   return (null);

		Header[] hdrs = new Header [l_numElements];
		Enumeration he = m_mimeHeadersTable.elements();

		while (he.hasMoreElements())
		{
			hdrs[i++] = (Header) he.nextElement();
		}

                return (hdrs);
        }

	/**
         * Checks if it is OK to set the header with the setHeader().
	 * @return null if it is OK to set. Name of the method to use if it is NOT ok.
         */
	private String isOKtoset (String name)
	{
	     if (name.equalsIgnoreCase ("Content-Type"))
		 return ("setContentType()");

	     if (name.equalsIgnoreCase ("Content-ID"))
		 return ("setContentID()");

	     if (name.equalsIgnoreCase ("Content-MD5"))
		 return ("setContentMD5()");

	     if (name.equalsIgnoreCase ("Content-Disposition"))
		 return ("setContentDisposition()");

	     if (name.equalsIgnoreCase ("Content-Description"))
		 return ("setContentDescription()");

	     if (name.equalsIgnoreCase ("Content-Transfer-Encoding"))
		 return ("setContentEncoding()");

	     return (null);

	} // end isOKtoset ()

	private ByteBuffer getStreamData (InputStream is)
	{
		ByteBuffer l_retbuf, l_readbuf = new ByteBuffer (DATABUFSZ);

		try
		{
	     		if (m_dataStream.markSupported())
				m_dataStream.reset();
			int l_read = is.read (l_readbuf.buffer, 0, DATABUFSZ);

			if (l_read > 0)
			{
		     		l_retbuf  = new ByteBuffer(DATABUFSZ); // initial size
			}
			else
			      return null;

			while (l_read > 0)
			{
		    	    l_readbuf.hi = l_read;
		            l_retbuf.append (l_readbuf);
		            l_read = is.read (l_readbuf.buffer, 0, DATABUFSZ);
			}

	     		if (m_dataStream.markSupported())
				m_dataStream.reset();
			return l_retbuf;
		}
		catch (IOException e)
		{
			return null;
		}
	}

	/**
	* Clones an instance of this MIMEBasicPart object.
	* @exception CloneNotSupportedException If thrown by constituent components.
	*/
    	public Object clone () throws CloneNotSupportedException
    	{
		// Do the bit-wise copy first.
		MIMEBasicPart l_theClone = (MIMEBasicPart) super.clone();

		// Take care of all other "reference"s.
        	if (m_mimeHeadersTable != null)
		      l_theClone.m_mimeHeadersTable = (Hashtable) m_mimeHeadersTable.clone();

		if (m_databuf != null)
			l_theClone.m_databuf = (ByteBuffer) m_databuf.clone();
		else if (m_dataStream != null)
		{
			l_theClone.m_databuf = getStreamData (m_dataStream);
			l_theClone.m_dataStream = null;
		}
		if (m_contentSubType != null)
			l_theClone.m_contentSubType = m_contentSubType;
		if (m_contentTypeParams != null)
			l_theClone.m_contentTypeParams = m_contentTypeParams;

		if (m_contentID != null)
			l_theClone.m_contentID = m_contentID;

		if (m_contentMD5 != null)
			l_theClone.m_contentMD5 = m_contentMD5;

		if (m_contentDispParams != null)
			l_theClone.m_contentDispParams = m_contentDispParams;

		if (m_contentDescription != null)
			l_theClone.m_contentDescription = m_contentDescription;

		l_theClone.m_nStartMessageDataIndex = m_nStartMessageDataIndex;
		l_theClone.m_nEndMessageDataIndex = m_nEndMessageDataIndex;
		l_theClone.m_nMessageDataLen = m_nMessageDataLen;
		l_theClone.m_UserObject = m_UserObject;
		l_theClone.m_bDecodedData = m_bDecodedData;

		return (l_theClone);

	} // end clone()


    protected int getStartMessageDataIndex() { return m_nStartMessageDataIndex; }
    protected void setStartMessageDataIndex( int i ) { m_nStartMessageDataIndex = i; }

    protected int getEndMessageDataIndex() { return m_nEndMessageDataIndex; }
    protected void setEndMessageDataIndex( int i ) { m_nEndMessageDataIndex = i; }

      public int getMessageDataLen() { return getSize(); }

    protected void setMessageDataLen( int len ) { setSize( len ); }

    protected void setContentType( int contentType ) { m_contentType = contentType; }

    protected boolean getDecodedData() { return m_bDecodedData; }
    protected void setDecodedData( boolean decodedData ) { m_bDecodedData = decodedData; }

    protected void setSize( int len )
    {
    	m_nMessageDataLen = len;

    	if (m_databuf != null)
    	     m_databuf.setSize( len );
    	//else if (m_dataStream != null)
    	//     m_nMessageDataLen = len;
    }

} // end class MIMEBasicPart
