Code architecture for switching between character and binary I/O
By Jeremy

The IMAP server has to be able to switch between reading a line of ASCII
and reading the plain bytes of a literal. This is made easier in IMAP
because the client ALWAYS waits for a command continuation response before
sending a literal. On the server, you can do this switch by using a
both an BufferedReader and an InputStream object wrapped around the
socket's InputStream:

//In the server:
Socket sock;
//Assume that socket is connected to a client:

//Create a BufferedReader for reading ASCII:
//tempIsr is not used, it's just for readability
InputStreamReader tempIsr = new InputStreamReader( sock.getInputStream(),
                                                   "US-ASCII" );
BufferedReader inChars = new BufferedReader( tempIsr );

//Grab the socket's input stream for reading bytes:
InputStream inBytes = sock.getInputStream();

while( true ) {
   //read until the CRLF
   String command = inChars.readLine();

   //parseCommand will make needLiteral() true and set literalLength() if
   //we need to read a literal
   parseCommand( command );

   //if the command is complete, then run it
   executeCommand( );

   if ( needLiteral( ) ) {
      //tell the client it's ok to send the literal now
      sendCommandContinuationRequestResponse();
      byte[] literal = new byte[literalLength()];
      //read the literal
      inBytes.read( literal, 0, literalLength() );
   }
}

Because the client ALWAYS waits for a command continuation request
response before sending a literal, the BufferedReader will not read too
far and consume any bytes beyond the CRLF. Because inChars is NOT
buffered, it will only read exactly literalLength() bytes. When the
literal has been filled, the loop can wrap around and read the rest of the
command.

If the client does not report the length of the literal correctly, then the server may read bytes that should be chars, or vice versa.  However, the server will survive.  If the reported length is too short then bytes that do not belong in the message will be put into it and following commands will be lost.  If the reported length is too long then bytes that belong in the message will not be put into it and the server will try o interpret message text as commands.  

Writing character and binary
----------------------------

A simple way to handle this is to only use a BufferedOutputStream, that
only accepts bytes to write, which it will not do any conversions on. To
write ASCII to the stream, you can construct the text in a String and call
String.getBytes("US-ASCII") to turn it into an array of bytes using the
right conversion.

Alternatively, you could use two objects as in the reading case. This
would require a BufferedOutputStream object for writing literals, and a
BufferedWriter for writing characters. Since both objects are buffered for
efficiency, it is REQUIRED that you call flush()  on either object after
you have written something.

I think both ways of writing will work the same.

For example:

//Method 1: Using only one stream

Socket sock;
BufferedOutputStream outBytes;
outBytes = new BufferedOutputStream( sock.getOutputStream() );
String textCharset = "US-ASCII";

//assume we're writing a fetch response:
byte[] message = selectedMailbox.getMessage( messageNumber );
String responseHead = "* " + messageNumber + " FETCH (RFC822 {" +
                      message.length() +
                      "}" + CRLF;
String responseTail = CRLF;
outBytes.write( responseHead.getBytes( textCharset ), 0,
                responseHead.length() );
outBytes.write( message, 0, message.length );
outBytes.write( responseTail.getBytes( textCharset ), 0,
                responseTail.length() );
outBytes.flush();

//Method 2: Using a stream and a writer

Socket sock;

//create a BufferedOutputStream for writing bytes
BufferedOutputStream outBytes;
outBytes = new BufferedOutputStream( sock.getOutputStream() );

//create a BufferedWriter for writing ascii
OutputStreamWriter tempOsr;
tempOsr = new OutputStreamWriter( sock.getOutputStream(), "US-ASCII" );
BufferedWriter outChars = new BufferedWriter( tempOsr );

//assume we're writing a fetch response:
byte[] message = selectedMailbox.getMessage( messageNumber );
String responseHead = "* " + messageNumber + " FETCH (RFC822 {" +
                      message.length() +
                      "}" + CRLF;
String responseTail = CRLF;

outChars.write( responseHead, 0, responseHead.length() );
outChars.flush();
outBytes.write( message, 0, message.length );
outBytes.flush();
outChars.write( responseTail, 0, responseTail.length() );
outChars.flush();

The above is NOT good code. You would wrap the paired write() and flush()
into a single method.  Literal writing and reading should be encapsulated in methods.