/*
 *	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.pop3;

import java.io.IOException;
import java.io.InputStream;
import java.util.Vector;
import netscape.messaging.*;

/**
*The POP3Client class represents the POP3 client.
*The client uses this class for communicating with a
*server using the POP3 protocol.
*The POP3 client conforms to the client specifications
*described in RFC 1939. For the URL,
*see "Where to Find More Information" in "About This Book."
*@author derekt@netscape.com
*@version 1.0
*/
public class POP3Client
{
    ///////////////////////////////////////////////////////////////////////////
    // Internal class used for socket I/O.
    ///////////////////////////////////////////////////////////////////////////
    private IO m_io;

    ///////////////////////////////////////////////////////////////////////////
    // Data member used for C-runtime equivalent routines.
    ///////////////////////////////////////////////////////////////////////////
    private Common m_common;

    ///////////////////////////////////////////////////////////////////////////
    // Data member used to store the timeout value.
    ///////////////////////////////////////////////////////////////////////////
    private int m_timeout;

    ///////////////////////////////////////////////////////////////////////////
    // Boolean variable to determine if we can send another command.
    ///////////////////////////////////////////////////////////////////////////
    private boolean m_mustProcess;

    ///////////////////////////////////////////////////////////////////////////
    // Callback data members.
    ///////////////////////////////////////////////////////////////////////////
    private Vector m_pendingList;
    private IPOP3Sink m_notifySink;

    ///////////////////////////////////////////////////////////////////////////
    // Reused response objects.
    ///////////////////////////////////////////////////////////////////////////
	private StringBuffer m_responseLine;
    private StringBuffer m_responseMessage;
    private int m_currentMessageNumber;

    ///////////////////////////////////////////////////////////////////////////
    // Byte arrays that are reused for commands and responses.
    ///////////////////////////////////////////////////////////////////////////
    private byte[] m_response;
    private byte[] m_messageData;
    private int m_messageDataSize;
    private int m_overFlowSize;
    private byte[] m_itoaBuffer;
    private byte[] m_tokenBuffer;
    private boolean m_multiLineState;

    ///////////////////////////////////////////////////////////////////////////
    // Constants.
    ///////////////////////////////////////////////////////////////////////////

    private final static int m_defaultPort = 110;
    private final static int m_minChunkSize = 1024;
    private final static int m_maxReplyLine = 512;

    private final static byte[] m_dele = { 'D', 'E', 'L', 'E', ' ' };
    private final static byte[] m_list = { 'L', 'I', 'S', 'T', ' ' };
    private final static byte[] m_noop = { 'N', 'O', 'O', 'P' };
    private final static byte[] m_pass = { 'P', 'A', 'S', 'S', ' ' };
    private final static byte[] m_quit = { 'Q', 'U', 'I', 'T' };
    private final static byte[] m_rset = { 'R', 'S', 'E', 'T' };
    private final static byte[] m_retr = { 'R', 'E', 'T', 'R', ' ' };
    private final static byte[] m_stat = { 'S', 'T', 'A', 'T' };
    private final static byte[] m_top = { 'T', 'O', 'P', ' ' };
    private final static byte[] m_uidl = { 'U', 'I', 'D', 'L', ' ' };
    private final static byte[] m_user = { 'U', 'S', 'E', 'R', ' ' };
    private final static byte[] m_xauthlist = { 'X', 'A', 'U', 'T', 'H', 'L', 'I', 'S', 'T', ' ' };
    private final static byte[] m_xsender = { 'X', 'S', 'E', 'N', 'D', 'E', 'R', ' ' };
    private final static byte[] m_space = { ' ' };

    ///////////////////////////////////////////////////////////////////////////
    // Command types for POP3.
    ///////////////////////////////////////////////////////////////////////////

    private final static int CONN = 0;
    private final static int DELE = 1;
    private final static int LISTA = 2;
    private final static int LIST = 3;
    private final static int NOOP = 4;
    private final static int PASS = 5;
    private final static int QUIT = 6;
    private final static int RSET = 7;
    private final static int RETR = 8;
    private final static int SENDCOMMANDA = 9;
    private final static int SENDCOMMAND = 10;
    private final static int STAT = 11;
    private final static int TOP = 12;
    private final static int UIDLA = 13;
    private final static int UIDL = 14;
    private final static int USER = 15;
    private final static int XAUTHLISTA = 16;
    private final static int XAUTHLIST = 17;
    private final static int XSENDER = 18;

    private final static Integer CONN_OBJ = new Integer(CONN);
    private final static Integer DELE_OBJ = new Integer(DELE);
    private final static Integer LISTA_OBJ = new Integer(LISTA);
    private final static Integer LIST_OBJ = new Integer(LIST);
    private final static Integer NOOP_OBJ = new Integer(NOOP);
    private final static Integer PASS_OBJ = new Integer(PASS);
    private final static Integer QUIT_OBJ = new Integer(QUIT);
    private final static Integer RSET_OBJ = new Integer(RSET);
    private final static Integer RETR_OBJ = new Integer(RETR);
    private final static Integer SENDCOMMANDA_OBJ = new Integer(SENDCOMMANDA);
    private final static Integer SENDCOMMAND_OBJ = new Integer(SENDCOMMAND);
    private final static Integer STAT_OBJ = new Integer(STAT);
    private final static Integer TOP_OBJ = new Integer(TOP);
    private final static Integer UIDLA_OBJ = new Integer(UIDLA);
    private final static Integer UIDL_OBJ = new Integer(UIDL);
    private final static Integer USER_OBJ = new Integer(USER);
    private final static Integer XAUTHLISTA_OBJ = new Integer(XAUTHLISTA);
    private final static Integer XAUTHLIST_OBJ = new Integer(XAUTHLIST);
    private final static Integer XSENDER_OBJ = new Integer(XSENDER);

    ///////////////////////////////////////////////////////////////////////////
    // Error messages for POP3.
    ///////////////////////////////////////////////////////////////////////////

    private final static String errProcessResponses = new String("Must call processResponses()");
    private final static String errParse = new String("Error while parsing response");

    /**
    *Constructor for POP3Client that takes a POP3Sink as a parameter.
	*@param in_sink Response sink that implements the IPOP3Sink interface.
	*@see #IPOP3Sink
    */
    public POP3Client( IPOP3Sink in_sink )
    {
        ///////////////////////////////////////////////////////////////////////
        // Variables that get initialized once in the constructor.
        ///////////////////////////////////////////////////////////////////////

        m_io = new IO();
        m_common = new Common();
        m_timeout = 0;
        m_pendingList = new Vector();
        m_notifySink = in_sink;

        m_response = new byte[m_maxReplyLine];
        m_itoaBuffer = new byte[m_maxReplyLine];
        m_tokenBuffer = new byte[m_maxReplyLine];
        m_messageData = new byte[m_minChunkSize];
        m_responseLine = new StringBuffer( m_maxReplyLine );
        m_responseMessage = new StringBuffer( m_maxReplyLine );

        ///////////////////////////////////////////////////////////////////////
        // Variables that get initialized in the constructor and on every new
        // connection.
        ///////////////////////////////////////////////////////////////////////

        m_multiLineState = false;
    }

    ///////////////////////////////////////////////////////////////////////////
    // Protocol commands
    ///////////////////////////////////////////////////////////////////////////

    /**
    *Connects to the server using the default port.
    *<p>To specify the connection port, use the
    *form of connect that takes a port number.
	*@param in_server Name of the host server to connect to.
    *@exception IOException If an I/O error occurs.
    *@see IPOP3Sink#connect
    *@see IPOP3Sink#error
    *@see #quit
    *@see #connect
    */
    public synchronized void connect( String in_server ) throws IOException
    {
        connect(  in_server, m_defaultPort );
    }

    /**
    *Connects to the server using the specified port.
    *<p>To use the default connection port, use the
    *form of connect that takes only the server name.
	*@param in_server Name of the host server to connect to.
	*@param in_port Number of the port to connect to.
    *@exception IOException If an I/O error occurs.
    *@see IPOP3Sink#connect
    *@see IPOP3Sink#error
    *@see #quit
    *@see #connect
    */
    public synchronized void connect( String in_server, int in_port ) throws IOException
    {
        ///////////////////////////////////////////////////////////////////////
        // Error checking before proceeding with command.
        ///////////////////////////////////////////////////////////////////////

        if ( in_server == null || in_port < 0 )
        {
            throw new IllegalArgumentException();
        }

        m_multiLineState = false;

        m_pendingList.removeAllElements();

        m_io.connect( in_server, in_port, m_messageData.length, 1024 );

        m_io.setTimeout( m_timeout );

        m_pendingList.addElement( CONN_OBJ );

        m_mustProcess = true;
    }

    /**
    *Deletes a message.
    *Marks a message for deletion on the server;
    *actually deleted when quit() is called.
	*@param in_messageNumber Number of the message to delete.
    *@exception IOException If an IO error occurs.
    *@see IPOP3Sink#delete
    *@see IPOP3Sink#error
    *@see #quit
    */
    public synchronized void delete( int in_messageNumber ) throws IOException
    {
        ///////////////////////////////////////////////////////////////////////
        // Error checking before proceeding with command.
        ///////////////////////////////////////////////////////////////////////

        if ( m_mustProcess )
        {
            throw new POP3Exception(errProcessResponses);
        }

        m_io.write( m_dele );
        m_io.send( m_itoaBuffer, 0, m_common.itoa(in_messageNumber, m_itoaBuffer), false );

        m_pendingList.addElement( DELE_OBJ );

        m_mustProcess = true;
    }

    /**
    *Closes the socket connection with the server.
    *Can be used to perform a high level "Cancel" while
    *retrieving a message.
    *NOTE: Do not call processResponses() after this method.
    *@exception IOException If an I/O error occurs.
    *@see IPOP3Sink#delete
    *@see #quit
    */
    public synchronized void disconnect() throws IOException
    {
        m_io.disconnect();
    }

    /**
    *Lists all messages.
    *POP3 includes two forms of LIST. This form goes through all
    *the messages in the mailbox and generates a list of messages.
    *The other form takes the message number of the message to
    *list as an argument.
    *@exception IOException If an I/O error occurs.
    *@see IPOP3Sink#listStart
    *@see IPOP3Sink#list
    *@see IPOP3Sink#listComplete
    *@see IPOP3Sink#error
    *@see #list
    */
    public synchronized void list() throws IOException
    {
        ///////////////////////////////////////////////////////////////////////
        // Error checking before proceeding with command.
        ///////////////////////////////////////////////////////////////////////

        if ( m_mustProcess )
        {
            throw new POP3Exception(errProcessResponses);
        }

        m_io.send( m_list, false );
        m_pendingList.addElement( LIST_OBJ );
        m_mustProcess = true;
    }

    /**
    *List a message.
    *POP3 includes two forms of LIST.
    *This form takes the message number of the message to list
    *as an argument. The other form goes through all the messages
    *in the mailbox and generates a list of messages.
	*@param in_messageNumber The message number to list.
    *@exception IOException If an I/O error occurs.
    *@see IPOP3Sink#listStart
    *@see IPOP3Sink#list
    *@see IPOP3Sink#listComplete
    *@see IPOP3Sink#error
    *@see #list
    */
    public synchronized void list( int in_messageNumber ) throws IOException
    {
        ///////////////////////////////////////////////////////////////////////
        // Error checking before proceeding with command.
        ///////////////////////////////////////////////////////////////////////

        if ( m_mustProcess )
        {
            throw new POP3Exception(errProcessResponses);
        }

        m_io.write( m_list );
        m_io.send( m_itoaBuffer, 0, m_common.itoa(in_messageNumber, m_itoaBuffer), false );
        m_pendingList.addElement( LISTA_OBJ );
        m_mustProcess = true;
    }

    /**
    *Gets positive server response; does not affect POP3 session.
    *Issues Noop command.
    *<P>Server responds to commands with a "still here" response.
    *Sending the noop method does nothing except force this server response.
    *Can be used to maintain server connection, perhaps being issued
    *at timed intervals to make sure that the server is still active.
    *Not needed by applications that do not need to maintain the connection.
    *@exception IOException If an I/O error occurs.
    *@see IPOP3Sink#noop
    *@see IPOP3Sink#error
    */
    public synchronized void noop() throws IOException
    {
        ///////////////////////////////////////////////////////////////////////
        // Error checking before proceeding with command.
        ///////////////////////////////////////////////////////////////////////

        if ( m_mustProcess )
        {
            throw new POP3Exception(errProcessResponses);
        }

        m_io.send( m_noop, false );
        m_pendingList.addElement( NOOP_OBJ );
        m_mustProcess = true;
    }

    /**
    *Enters a password.
    *Identifies the user password; on success, the POP3
    *session enters the Transaction state.
	*@param in_password User password.
    *@exception IOException If an I/O error occurs.
    *@see IPOP3Sink#pass
    *@see IPOP3Sink#error
    *@see #user
    */
    public synchronized void pass( String in_password ) throws IOException
    {
        ///////////////////////////////////////////////////////////////////////
        // Error checking before proceeding with command.
        ///////////////////////////////////////////////////////////////////////

        if ( in_password == null )
        {
            throw new IllegalArgumentException();
        }

        if ( m_mustProcess )
        {
            throw new POP3Exception(errProcessResponses);
        }

        m_io.write( m_pass );
        m_io.send( in_password, false );
        m_pendingList.addElement( PASS_OBJ );
        m_mustProcess = true;
    }

    /**
    *Closes the connection with the server and expunges any deleted messages.
    *Ends the session, purging deleted messages and logging the user
    *out from the POP3 server. If issued in Authentication state,
    *server closes connections. If issued in Transaction state,
    *server goes into Update state and deletes marked messages,
    *then quits.
    *@exception IOException If an I/O error occurs.
    *@see IPO3Sink#quit
    *@see IPOP3Sink#error
    *@see #delete
    */
    public synchronized void quit() throws IOException
    {
        ///////////////////////////////////////////////////////////////////////
        // Error checking before proceeding with command.
        ///////////////////////////////////////////////////////////////////////

        if ( m_mustProcess )
        {
            throw new POP3Exception(errProcessResponses);
        }

        m_io.send( m_quit, false );
        m_pendingList.addElement( QUIT_OBJ );
        m_mustProcess = true;
    }

    /**
    *Resets the state of the server by undeleting any deleted messages.
    *Cancels the current mail transfer and all current processes,
    *discards data, and clears all states.
    *@exception IOException If an I/O error occurs.
    *@see IPOP3Sink#reset
    *@see IPOP3Sink#error
    */
    public synchronized void reset() throws IOException
    {
        ///////////////////////////////////////////////////////////////////////
        // Error checking before proceeding with command.
        ///////////////////////////////////////////////////////////////////////

        if ( m_mustProcess )
        {
            throw new POP3Exception(errProcessResponses);
        }

        m_io.send( m_rset, false );
        m_pendingList.addElement( RSET_OBJ );
        m_mustProcess = true;
    }

    /**
    *Retrieves a message.
	*@param in_messageNumber Number of message to retrieve.
    *@exception IOException If an I/O error occurs.
    *@see IPOP3Sink#retrieve
    *@see IPOP3Sink#retrieveComplete
    *@see IPOP3Sink#retrieveStart
    *@see IPOP3Sink#error
    */
    public synchronized void retrieve( int in_messageNumber ) throws IOException
    {
        ///////////////////////////////////////////////////////////////////////
        // Error checking before proceeding with command.
        ///////////////////////////////////////////////////////////////////////

        if ( m_mustProcess )
        {
            throw new POP3Exception(errProcessResponses);
        }

        m_io.write( m_retr );
        m_io.send(m_itoaBuffer, 0, m_common.itoa(in_messageNumber, m_itoaBuffer), false);
        m_pendingList.addElement( RETR_OBJ );
        m_currentMessageNumber = in_messageNumber;
        m_mustProcess = true;
    }

    /**
    *Sends an unsupported command to the server.
    *Sends commands that are not supported by the Messaging Access SDK
    *implementation of POP3.
    *NOTE: This method is primarily intended to support extensions
    *to the protocol to meet client application needs.
	*@param in_command Raw command to send to the server.
	*@param in_multiLine Identifies whether response is multiline or not.
    *@exception IOException If an I/O error occurs.
    *@see IPOP3Sink#sendCommand
    *@see IPOP3Sink#sendCommandStart
    *@see IPOP3Sink#sendCommandComplete
    *@see IPOP3Sink#error
    */
    public synchronized void sendCommand( String in_command, boolean in_multiLine ) throws IOException
    {
        ///////////////////////////////////////////////////////////////////////
        // Error checking before proceeding with command.
        ///////////////////////////////////////////////////////////////////////

        if ( in_command == null )
        {
            throw new IllegalArgumentException();
        }

        if ( m_mustProcess )
        {
            throw new POP3Exception(errProcessResponses);
        }

        m_io.send( in_command, false );

        if ( in_multiLine )
        {
            m_pendingList.addElement( SENDCOMMAND_OBJ );
        }
        else
        {
            m_pendingList.addElement( SENDCOMMANDA_OBJ );
        }

        m_mustProcess = true;
    }

    /**
    *Gets the status of the mail drop.
    *Returns the number of messages and octet size of the mail drop.
    *Always returns message octet count.
    *@exception IOException If an I/O error occurs.
    *@see IPOP3Sink#stat
    *@see IPOP3Sink#error
    */
    public synchronized void stat() throws IOException
    {
        ///////////////////////////////////////////////////////////////////////
        // Error checking before proceeding with command.
        ///////////////////////////////////////////////////////////////////////

        if ( m_mustProcess )
        {
            throw new POP3Exception(errProcessResponses);
        }

        m_io.send( m_stat, false );
        m_pendingList.addElement( STAT_OBJ );
        m_mustProcess = true;
    }

    /**
    *Retrieves headers plus the specified number of lines from the body.
	*@param in_messageNumber Number of message to retrieve.
	*@param in_lines Number of lines of the message body to return.
    *@exception IOException If an I/O error occurs.
    *@see IPOP3Sink#topStart
    *@see IPOP3Sink#top
    *@see IPOP3Sink#topComplete
    *@see IPOP3Sink#error
    */
    public synchronized void top( int in_messageNumber, int in_lines ) throws IOException
    {
        ///////////////////////////////////////////////////////////////////////
        // Error checking before proceeding with command.
        ///////////////////////////////////////////////////////////////////////

        if ( m_mustProcess )
        {
            throw new POP3Exception(errProcessResponses);
        }

        m_io.write( m_top );
        m_io.write( m_itoaBuffer, 0, m_common.itoa(in_messageNumber, m_itoaBuffer) );
        m_io.write( m_space );
        m_io.send( m_itoaBuffer, 0, m_common.itoa(in_lines, m_itoaBuffer), false );
        m_pendingList.addElement( TOP_OBJ );
        m_currentMessageNumber = in_messageNumber;
        m_mustProcess = true;
    }

    /**
    *Lists all messages with their unique-ids.
    *POP3 includes two forms of uidList(). This form goes through all
    *the messages in the mailbox and generates a list of messages
    *with their unique ids.
    *The other form takes the message number of the message to
    *list as an argument.
    *@exception IOException If an I/O error occurs.
    *@see IPOP3Sink#uidList
    *@see IPOP3Sink#uidListStart
    *@see IPOP3Sink#uidListComplete
    *@see IPOP3Sink#error
    */
    public synchronized void uidList() throws IOException
    {
        ///////////////////////////////////////////////////////////////////////
        // Error checking before proceeding with command.
        ///////////////////////////////////////////////////////////////////////

        if ( m_mustProcess )
        {
            throw new POP3Exception(errProcessResponses);
        }

        m_io.send( m_uidl, false );

        m_pendingList.addElement( UIDL_OBJ );

        m_mustProcess = true;
    }

    /**
    *Lists a message with its unique id.
    *POP3 includes two forms of uidList(). This form takes the message number of the message to
    *list with its unique id as an argument.
    *The other form goes through all
    *the messages in the mailbox and generates a list of messages.
	*@param in_messageNumber Number of message for which to return a unique id. .
    *@exception IOException If an I/O error occurs.
    *@see IPOP3Sink#uidList
    *@see IPOP3Sink#uidListStart
    *@see IPOP3Sink#uidListComplete
    *@see IPOP3Sink#error
    */
    public synchronized void uidList( int in_messageNumber ) throws IOException
    {
        ///////////////////////////////////////////////////////////////////////
        // Error checking before proceeding with command.
        ///////////////////////////////////////////////////////////////////////

        if ( m_mustProcess )
        {
            throw new POP3Exception(errProcessResponses);
        }

        m_io.write( m_uidl );
        m_io.send( m_itoaBuffer, 0, m_common.itoa(in_messageNumber, m_itoaBuffer), false );
        m_pendingList.addElement( UIDLA_OBJ );
        m_mustProcess = true;
    }

    /**
    *Enters a username.
    *Identifies the user or mail drop by name to the server;
    *the server returns a known or unknown response.
	*@param in_user Name of the user's maildrop.
    *@exception IOException If an I/O error occurs.
    *@see IPOP3Sink#user
    *@see IPOP3Sink#error
    *@see #pass
    */
    public synchronized void user( String in_user ) throws IOException
    {
        ///////////////////////////////////////////////////////////////////////
        // Error checking before proceeding with command.
        ///////////////////////////////////////////////////////////////////////

        if ( in_user == null )
        {
            throw new IllegalArgumentException();
        }

        if ( m_mustProcess )
        {
            throw new POP3Exception(errProcessResponses);
        }

        m_io.write( m_user );
        m_io.send( in_user, false );
        m_pendingList.addElement( USER_OBJ );
        m_mustProcess = true;
    }

    /**
    *Returns a list of authenticated users.
    *@exception IOException If an I/O error occurs.
    *@see IPOP3Sink#xAuthList
    *@see IPOP3Sink#xAuthListStart
    *@see IPOP3Sink#xAuthListComplete
    *@see IPOP3Sink#error
    */
    public synchronized void xAuthList() throws IOException
    {
        ///////////////////////////////////////////////////////////////////////
        // Error checking before proceeding with command.
        ///////////////////////////////////////////////////////////////////////

        if ( m_mustProcess )
        {
            throw new POP3Exception(errProcessResponses);
        }

        m_io.send( m_xauthlist, false );
        m_pendingList.addElement( XAUTHLIST_OBJ );
        m_mustProcess = true;
   }

    /**
    *Returns a list of authenticated users for a specified message.
    *@param in_messageNumber The message number.
    *@exception IOException If an I/O error occurs.
    *@see IPOP3Sink#xAuthList
    *@see IPOP3Sink#xAuthListStart
    *@see IPOP3Sink#xAuthListComplete
    *@see IPOP3Sink#error
    */
    public synchronized void xAuthList( int in_messageNumber ) throws IOException
    {
        ///////////////////////////////////////////////////////////////////////
        // Error checking before proceeding with command.
        ///////////////////////////////////////////////////////////////////////

        if ( m_mustProcess )
        {
            throw new POP3Exception(errProcessResponses);
        }

        m_io.write( m_xauthlist );
        m_io.send(m_itoaBuffer, 0, m_common.itoa(in_messageNumber, m_itoaBuffer), false);
        m_pendingList.addElement( XAUTHLISTA_OBJ );
        m_mustProcess = true;
    }

    /**
    *Gets the email address of the sender of the specified message.
    *Gets the email address of the sender of the specified message.
    *Sends the XSENDER [arg] POP3 protocol command.
	*@param in_messageNumber Number of the message.
    *@exception IOException If an I/O error occurs.
    *@see IPOP3Sink#xSender
    *@see IPOP3Sink#error
    */
    public synchronized void xSender( int in_messageNumber ) throws IOException
    {
        ///////////////////////////////////////////////////////////////////////
        // Error checking before proceeding with command.
        ///////////////////////////////////////////////////////////////////////

        if ( m_mustProcess )
        {
            throw new POP3Exception(errProcessResponses);
        }

        m_io.write( m_xsender );
        m_io.send(m_itoaBuffer, 0, m_common.itoa(in_messageNumber, m_itoaBuffer), false);
        m_pendingList.addElement( XSENDER_OBJ );
        m_mustProcess = true;
    }

    /**
    *Reads in responses from the server and invokes the appropriate sink methods.
    *Processes the server responses for API commands. Invokes the
    *callback methods provided by the user for all responses that
    *are available at the time of execution.
    *NOTE: If a timeout occurs, the user can continue by calling processResponses() again.
	*@return int The number of responses processed.
    *@exception POP3ServerException If a server response error occurs.
    *@exception InterruptedIOException If a time-out occurs.
    *@exception IOException If an I/O error occurs.
    *@see IPOP3Sink#error
    */
    public void processResponses() throws IOException
    {
        synchronized(m_responseMessage)
        {
            try
            {
                Integer l_commandType;

                m_io.flush();

                ///////////////////////////////////////////////////////////////////
                // Prcoess as many of the responses as possible.
                ///////////////////////////////////////////////////////////////////

                while ( !m_pendingList.isEmpty() )
                {
                    l_commandType = (Integer)m_pendingList.firstElement();

                    switch( l_commandType.intValue() )
                    {
                        case CONN:
                            parseConnect();
                            break;
                        case DELE:
                            parseDelete();
                            break;
                        case LISTA:
                            parseListA();
                            break;
                        case LIST:
                            parseList();
                            break;
                        case NOOP:
                            parseNoop();
                            break;
                        case PASS:
                            parsePass();
                            break;
                        case QUIT:
                            parseQuit();
                            m_io.disconnect();
                            break;
                        case RSET:
                            parseRset();
                            break;
                        case RETR:
                            parseRetr();
                            break;
                        case SENDCOMMANDA:
                            parseSendCommandA();
                            break;
                        case SENDCOMMAND:
                            parseSendCommand();
                            break;
                        case STAT:
                            parseStat();
                            break;
                        case TOP:
                            parseTop();
                            break;
                        case UIDLA:
                            parseUidlA();
                            break;
                        case UIDL:
                            parseUidl();
                            break;
                        case USER:
                            parseUser();
                            break;
                        case XAUTHLISTA:
                            parseXAuthListA();
                            break;
                        case XAUTHLIST:
                            parseXAuthList();
                            break;
                        case XSENDER:
                            parseXSender();
                            break;
                    }

                    m_pendingList.removeElementAt( 0 );
                }

                m_mustProcess = false;
            }
            catch( POP3ServerException e )
            {
                m_mustProcess = false;
                m_pendingList.removeElementAt( 0 );
                throw e;
            }
        }
    }

    ///////////////////////////////////////////////////////////////////////////
    // Preferences
    ///////////////////////////////////////////////////////////////////////////

    /**
    *Sets the size of the data chunks that are passed to the response sink
    *for the retrieve() callback.  The chunk size must be >= 1024.
	*@param in_chunkSize Size of chunk used for sending messages using
	*the send method. Minimum chunk size: 1024. Default = 1K.
    *@exception IOException If an I/O error occurs.
    *@see #retrieve
    */
    public synchronized void setChunkSize( int in_chunkSize )
    {
        synchronized(m_responseMessage)
        {
            if ( in_chunkSize < m_minChunkSize )
            {
                throw new IllegalArgumentException();
            }

            m_messageData = new byte[in_chunkSize];
        }
    }

    /**
    *Registers a new response sink, overriding the one passed into the
    *constructor or any set afterwards.
    *Server reponses are routed to this new response sink.
	*@param in_responseSink New POP3 response sink to use.
    *@exception IOException If an I/O error occurs.
    */
    public synchronized void setResponseSink( IPOP3Sink in_responseSink  )
    {
        synchronized(m_responseMessage)
        {
            if ( in_responseSink == null )
            {
                throw new NullPointerException();
            }

            m_notifySink = in_responseSink;
        }
    }

    /**
    *Sets the amount of time allowed to wait before
    *returning control to the user.
	*@param in_timeout Time-out period to set in milliseconds.
	*Values, in milliseconds:
	*<ul>
	*<li>0 = infinite time-out (default)
	*<li>-1 = no waiting
	*<li>> 0 = length of time-out period
	</ul>
    *@exception IOException If an I/O error occurs.
    *@see #processResponses
    */
    public synchronized void setTimeout( int in_timeout ) throws IOException
    {
        synchronized(m_responseMessage)
        {
            if ( in_timeout < -1 )
            {
                throw new IllegalArgumentException();
            }

            m_timeout = in_timeout;

            if ( m_io != null )
            {
                m_io.setTimeout( m_timeout );
            }
        }
    }

    ///////////////////////////////////////////////////////////////////////////
    // Internal functions
    ///////////////////////////////////////////////////////////////////////////

    /**
    *Separates the response code from the message part of the response.
    */
    private final boolean setStatusInfo(
                        byte[] in_response,
                        int in_byteCount,
                        StringBuffer out_responseMessage ) throws IOException
    {
        boolean l_responseStatus;

        if ( in_byteCount < 3 )
        {
            throw new POP3Exception(errParse);
        }

        if ( '+' == (char)in_response[0] &&
                'O' == (char)in_response[1] &&
                'K' == (char)in_response[2] )
        {
            l_responseStatus = true;
        }
        else
        {
            l_responseStatus = false;
        }

        if ( in_byteCount > 4 )
        {
            m_responseMessage.setLength( 0 );

            for ( int count = 4; count < in_byteCount; count++ )
            {
                m_responseMessage.append( (char)m_response[count] );
            }
        }
        else
        {
            m_responseMessage.setLength( 0 );
        }

        return l_responseStatus;
    }

    /**
    *Copies the next token into the specified byte array.
    */
    private final int getNextToken(
                        byte[] in_parseLine,
                        int in_offset,
                        byte[] out_token ) throws IOException
    {
        int i;
        int n = 0;

        for ( i = in_offset; Character.isSpaceChar((char)in_parseLine[i]); i++ )
        {
        }

        while ( i < in_parseLine.length &&
                Character.isDigit((char)in_parseLine[i]) )
        {
            out_token[n++] = in_parseLine[i++];
        }

        out_token[n] = '\0';

        return i;
    }

    /**
    *Copies the bytes in a byte array into a StringBuffer.
    */
    private final void toStringBuffer( StringBuffer out_buffer,
                                    byte[] in_buffer,
                                    int offset,
                                    int in_length )
    {
        out_buffer.setLength( in_length );

        for ( int i = 0; i < in_length; i++ )
        {
            out_buffer.setCharAt( i, (char)in_buffer[offset+i] );
        }
    }

    ///////////////////////////////////////////////////////////////////////////
    // Methods for parsing the responses and invoking sink notifications.
    ///////////////////////////////////////////////////////////////////////////

    private final void parseConnect() throws IOException
    {
        int l_byteCount = m_io.readLine( m_response );

        if ( setStatusInfo( m_response, l_byteCount, m_responseMessage ) )
        {
            m_notifySink.connect( m_responseMessage );
        }
        else
        {
            m_notifySink.error( m_responseMessage );
        }
    }

    private final void parseDelete() throws IOException
    {
        int l_byteCount = m_io.readLine( m_response );

        if ( setStatusInfo( m_response, l_byteCount, m_responseMessage ) )
        {
            m_notifySink.delete( m_responseMessage );
        }
        else
        {
            m_notifySink.error( m_responseMessage );
        }
    }

    private final void parseListA() throws IOException
    {
        int l_offset = 3;
        int l_messageNumber;
        int l_octetCount;
        int l_byteCount = m_io.readLine( m_response );

        if ( setStatusInfo( m_response, l_byteCount, m_responseMessage ) )
        {
            m_notifySink.listStart();

            l_offset = getNextToken( m_response, l_offset, m_tokenBuffer );
            l_messageNumber = m_common.atoi( m_tokenBuffer );
            l_offset = getNextToken( m_response, l_offset, m_tokenBuffer );
            l_octetCount = m_common.atoi( m_tokenBuffer );

            m_notifySink.list( l_messageNumber, l_octetCount );
            m_notifySink.listComplete();
        }
        else
        {
            m_notifySink.error( m_responseMessage );
        }
    }

    private final void parseList() throws IOException
    {
        int l_offset = 0;
        int l_messageNumber;
        int l_octetCount;
        int l_byteCount;
        boolean l_responseStatus;

        if ( m_multiLineState == false )
        {
            l_byteCount = m_io.readLine( m_response );

            if ( setStatusInfo(m_response, l_byteCount, m_responseMessage) )
            {
                m_notifySink.listStart();
            }
            else
            {
                m_notifySink.error( m_responseMessage );
                return;
            }

            m_multiLineState = true;
        }

        l_byteCount = m_io.readLine( m_response );

        while( ('.' != (char)m_response[0]) || ( l_byteCount != 1 ) )
        {
            l_offset = getNextToken( m_response, l_offset, m_tokenBuffer );
            l_messageNumber = m_common.atoi( m_tokenBuffer );
            l_offset = getNextToken( m_response, l_offset, m_tokenBuffer );
            l_octetCount = m_common.atoi( m_tokenBuffer );
            l_offset = 0;

            m_notifySink.list( l_messageNumber, l_octetCount );

            l_byteCount = m_io.readLine( m_response );
        }

        m_multiLineState = false;
        m_notifySink.listComplete();
    }

    private final void parseNoop() throws IOException
    {
        int l_byteCount = m_io.readLine( m_response );

        if ( setStatusInfo( m_response, l_byteCount, m_responseMessage ) )
        {
            m_notifySink.noop();
        }
        else
        {
            m_notifySink.error( m_responseMessage );
        }
    }

    private final void parsePass() throws IOException
    {
        int l_byteCount = m_io.readLine( m_response );

        if ( setStatusInfo( m_response, l_byteCount, m_responseMessage ) )
        {
            m_notifySink.pass( m_responseMessage );
        }
        else
        {
            m_notifySink.error( m_responseMessage );
        }
    }

    private final void parseQuit() throws IOException
    {
        int l_byteCount = m_io.readLine( m_response );

        if ( setStatusInfo( m_response, l_byteCount, m_responseMessage ) )
        {
            m_notifySink.quit( m_responseMessage );
        }
        else
        {
            m_notifySink.error( m_responseMessage );
        }
    }

    private final void parseRset() throws IOException
    {
        int l_byteCount = m_io.readLine( m_response );

        if ( setStatusInfo( m_response, l_byteCount, m_responseMessage ) )
        {
            m_notifySink.reset( m_responseMessage );
        }
        else
        {
            m_notifySink.error( m_responseMessage );
        }
    }

    private final void parseRetr() throws IOException
    {
        int l_nextByte;
        int l_byteCount;
        int l_curByteCount;
        int l_octetCount;
        boolean l_responseStatus;

        if ( m_multiLineState == false )
        {
            l_byteCount = m_io.readLine( m_response );

            if ( setStatusInfo(m_response, l_byteCount, m_responseMessage) )
            {
                int ret_offset = getNextToken( m_response, 3, m_tokenBuffer );
		if (ret_offset != 3) // there was an octetCount after +OK
		{
                	l_octetCount = m_common.atoi (m_tokenBuffer);
                	m_notifySink.retrieveStart (m_currentMessageNumber, l_octetCount);
		}
		else
		{
                	m_notifySink.retrieveStart (m_currentMessageNumber, 0);
		}
            }
            else
            {
                m_notifySink.error( m_responseMessage );
                return;
            }

            m_multiLineState = true;
            m_messageDataSize = 0;
        }

        m_io.readLine( m_responseLine );
        m_responseLine.append( '\r' );
        m_responseLine.append( '\n' );
        l_byteCount = m_responseLine.length();

        while( ( m_responseLine.charAt(0) != '.') || ( l_byteCount != 3 ) )
        {
            l_curByteCount = 0;

            if ( m_responseLine.charAt(0) == '.' )
            {
                l_curByteCount++;
            }

            while ( l_curByteCount < l_byteCount )
            {
                if ( m_messageDataSize == m_messageData.length )
                {
                    m_notifySink.retrieve( m_messageData );
                    m_messageDataSize = 0;
                }

                m_messageData[m_messageDataSize++] = (byte)m_responseLine.charAt(l_curByteCount++);
            }

            m_io.readLine( m_responseLine );
            m_responseLine.append( '\r' );
            m_responseLine.append( '\n' );
            l_byteCount = m_responseLine.length();
        }

        if ( m_messageDataSize > 0 )
        {
            byte[] l_buffer = new byte[m_messageDataSize];

            for ( int i = 0; i < m_messageDataSize; i++ )
            {
                l_buffer[i] = m_messageData[i];
            }
            m_notifySink.retrieve( l_buffer );
        }

        m_multiLineState = false;
        m_notifySink.retrieveComplete();
    }

    private final void parseSendCommandA() throws IOException
    {
        int l_byteCount = m_io.readLine( m_response );

        if ( setStatusInfo( m_response, l_byteCount, m_responseMessage ) )
        {
            m_notifySink.sendCommandStart();
            m_notifySink.sendCommand( m_responseMessage );
            m_notifySink.sendCommandComplete();
        }
        else
        {
            m_notifySink.error( m_responseMessage );
        }
    }

    private final void parseSendCommand() throws IOException
    {
        int l_byteCount;
        boolean l_responseStatus;

        if ( m_multiLineState == false )
        {
            l_byteCount = m_io.readLine( m_response );

            if ( setStatusInfo(m_response, l_byteCount, m_responseMessage) )
            {
                m_notifySink.listStart();
            }
            else
            {
                m_notifySink.error( m_responseMessage );
                return;
            }

            m_multiLineState = true;
        }

        l_byteCount = m_io.readLine( m_response );

        while( ('.' != (char)m_response[0]) && ( l_byteCount != 1 ) )
        {
            toStringBuffer( m_responseMessage, m_response, 0, l_byteCount );

            m_notifySink.sendCommand( m_responseMessage );

            m_io.readLine( m_response );
        }

        m_multiLineState = false;
        m_notifySink.sendCommandComplete();
    }

    private final void parseStat() throws IOException
    {
        int l_offset = 3;
        int l_messageCount;
        int l_octetCount;
        int l_byteCount = m_io.readLine( m_response );

        if ( setStatusInfo(m_response, l_byteCount, m_responseMessage) )
        {
            l_offset = getNextToken( m_response, l_offset, m_tokenBuffer );
            l_messageCount = m_common.atoi( m_tokenBuffer );
            l_offset = getNextToken( m_response, l_offset, m_tokenBuffer );
            l_octetCount = m_common.atoi( m_tokenBuffer );

            m_notifySink.stat( l_messageCount, l_octetCount );
        }
        else
        {
            m_notifySink.error( m_responseMessage );
        }
    }

    private final void parseTop() throws IOException
    {
        int l_byteCount;
        boolean l_responseStatus;

        if ( m_multiLineState == false )
        {
            l_byteCount = m_io.readLine( m_response );

            if ( setStatusInfo(m_response, l_byteCount, m_responseMessage) )
            {
                m_notifySink.topStart( m_currentMessageNumber );
            }
            else
            {
                m_notifySink.error( m_responseMessage );
                return;
            }

            m_multiLineState = true;
        }

        l_byteCount = m_io.readLine( m_response );

        while( ('.' != (char)m_response[0]) && ( l_byteCount != 1 ) )
        {
            if ( '.' != (char)m_response[0] )
            {
                toStringBuffer( m_responseMessage, m_response, 0, l_byteCount );
            }
            else
            {
                toStringBuffer( m_responseMessage, m_response, 1, l_byteCount - 1 );
            }

            m_notifySink.top( m_responseMessage );

            l_byteCount = m_io.readLine( m_response );
        }

        m_multiLineState = false;
        m_notifySink.topComplete();
    }

    private final void parseUidlA() throws IOException
    {
        int l_offset = 3;
        int l_messageNumber;
        int l_byteCount = m_io.readLine( m_response );

        if ( setStatusInfo(m_response, l_byteCount, m_responseMessage) )
        {
            m_notifySink.uidListStart();

            l_offset = getNextToken( m_response, l_offset, m_tokenBuffer );
            l_messageNumber = m_common.atoi( m_tokenBuffer );

            getNextToken( m_response, l_offset, m_tokenBuffer );
            toStringBuffer( m_responseMessage, m_tokenBuffer, 0, l_byteCount - l_offset );

            m_notifySink.uidList( l_messageNumber, m_responseMessage );
            m_notifySink.uidListComplete();
        }
        else
        {
            m_notifySink.error( m_responseMessage );
        }
    }

    private final void parseUidl() throws IOException
    {
        int l_offset = 0;
        int l_byteCount;
        int l_messageNumber;
        boolean l_responseStatus;

        if ( m_multiLineState == false )
        {
            l_byteCount = m_io.readLine( m_response );

            if ( setStatusInfo(m_response, l_byteCount, m_responseMessage) )
            {
                m_notifySink.uidListStart();
            }
            else
            {
                m_notifySink.error( m_responseMessage );
                return;
            }

            m_multiLineState = true;
        }

        l_byteCount = m_io.readLine( m_response );

        while( ('.' != (char)m_response[0]) && ( l_byteCount != 1 ) )
        {
            l_offset = getNextToken( m_response, l_offset, m_tokenBuffer );
            l_messageNumber = m_common.atoi( m_tokenBuffer );

            getNextToken( m_response, l_offset, m_tokenBuffer );
            toStringBuffer( m_responseMessage, m_tokenBuffer, 0, l_byteCount - l_offset );
            l_offset = 0;

            m_notifySink.uidList( l_messageNumber, m_responseMessage );

            l_byteCount = m_io.readLine( m_response );
        }

        m_multiLineState = false;
        m_notifySink.uidListComplete();
    }

    private final void parseUser() throws IOException
    {
        int l_byteCount = m_io.readLine( m_response );

        if ( setStatusInfo(m_response, l_byteCount, m_responseMessage) )
        {
            m_notifySink.user( m_responseMessage );
        }
        else
        {
            m_notifySink.error( m_responseMessage );
        }
    }

    private final void parseXAuthListA() throws IOException
    {
        int l_offset = 3;
        int l_octetCount;
        int l_messageNumber;
        int l_byteCount = m_io.readLine( m_response );

        if ( setStatusInfo(m_response, l_byteCount, m_responseMessage) )
        {
            m_notifySink.xAuthListStart();

            l_offset = getNextToken( m_response, l_offset, m_tokenBuffer );
            l_messageNumber = m_common.atoi( m_tokenBuffer );

            l_offset = getNextToken( m_response, l_offset, m_tokenBuffer );
            l_octetCount = m_common.atoi( m_tokenBuffer );

            getNextToken( m_response, l_offset, m_tokenBuffer );
            toStringBuffer( m_responseMessage, m_tokenBuffer, 0, l_byteCount - l_offset );

            m_notifySink.xAuthList( l_messageNumber, l_octetCount, m_responseMessage );
            m_notifySink.xAuthListComplete();
        }
        else
        {
            m_notifySink.error( m_responseMessage );
        }
    }

    private final void parseXAuthList() throws IOException
    {
        int l_offset = 0;
        int l_byteCount;
        int l_octetCount;
        int l_messageNumber;
        boolean l_responseStatus;

        if ( m_multiLineState == false )
        {
            l_byteCount = m_io.readLine( m_response );

            if ( setStatusInfo(m_response, l_byteCount, m_responseMessage) )
            {
                m_notifySink.xAuthListStart();
            }
            else
            {
                m_notifySink.error( m_responseMessage );
                return;
            }

            m_multiLineState = true;
        }

        l_byteCount = m_io.readLine( m_response );

        while( ('.' != (char)m_response[0]) && ( l_byteCount != 1 ) )
        {
            l_offset = getNextToken( m_response, l_offset, m_tokenBuffer );
            l_messageNumber = m_common.atoi( m_tokenBuffer );

            l_offset = getNextToken( m_response, l_offset, m_tokenBuffer );
            l_octetCount = m_common.atoi( m_tokenBuffer );

            getNextToken( m_response, l_offset, m_tokenBuffer );
            toStringBuffer( m_responseMessage, m_tokenBuffer, 0, l_byteCount - l_offset );
            l_offset = 0;

            m_notifySink.xAuthList( l_messageNumber, l_octetCount, m_responseMessage );

            l_byteCount = m_io.readLine( m_response );
        }

        m_multiLineState = false;
        m_notifySink.xAuthListComplete();
    }

    private final void parseXSender() throws IOException
    {
        int l_byteCount = m_io.readLine( m_response );

        if ( setStatusInfo(m_response, l_byteCount, m_responseMessage) )
        {
            m_notifySink.xSender( m_responseMessage );
        }
        else
        {
            m_notifySink.error( m_responseMessage );
        }
    }
}
