JMU
Custom Streams
in Java


Prof. David Bernstein
James Madison University

Computer Science Department
bernstdh@jmu.edu


Motivation
One Approach
An Example
An Example (cont.)
javaexamples/io/LittleEndianInputStream.java
        package io;

import java.io.*;


/**
 *  For reading from binary streams that were written on a
 *  machine that uses a little-endian scheme.
 *
 *  For example, C under DOS/Windows uses a little-endian scheme 
 *  (i.e., LSB first).
 *
 *  @author  Prof. David Bernstein, James Madison University
 *  @version 1.0
 */
public class LittleEndianInputStream 
{
    protected byte[]          b1, b2, b4, b8;
    protected InputStream     in;


    /**
     * Construct a new LittleEndianInputStream
     *
     * @param in   The InputStream to use
     */

    public LittleEndianInputStream(InputStream in)
    {
       this.in = in;

       b1 = new byte[1];
       b2 = new byte[2];
       b4 = new byte[4];
       b8 = new byte[8];
    }


    /**
     * Returns the number of bytes available without blocking
     *
     */
    public int available() throws IOException
    {
       return in.available();
    }


    /**
     * Closes this input stream
     */
    public void close() throws IOException
    {
       in.close();
    }


    /**
     * Reads an array of bytes
     *
     * @param b   The buffer into which the data are read
     * @param off The start offset of the data
     * @param len The maximum number of bytes read
     *
     * @return    The number of bytes actually read or -1 if end-of-stream
     */
    public int read(byte[] b, int off, int len) throws IOException
    {
       return in.read(b, off, len);
    }


    /**
     * Reads an array of bytes
     *
     * @param b   The buffer into which the data are read
     *
     * @return    The number of bytes actually read or -1 if end-of-stream
     */
    public int read(byte[] b) throws IOException
    {
       return in.read(b);
    }


    /**
     * Reads an array of bytes
     *
     * Note: Unlike read(), this method keeps reading until all of the bytes
     *       are read or an end-of-stream is encountered
     *
     * @param b   The buffer into which the data are read
     */
    public void readFully(byte[] b) throws IOException
    {
       EOFException eofe;
       int          i, n;


       for (i=0; i < b.length; i+=n) 
       {
          n = read(b, i, b.length-i);

          if (n < 0)    // End of File Encountered
          {
             eofe = new EOFException("EOF in LittleEndianInputStream");
             throw(eofe);
          }
       }
    }


    /**
     * Reads an int in __big-endian__ format
     */
    public int readInt() throws IOException
    {
       readFully(b4);

       return (b4[0]&0xff) << 24 |
              (b4[1]&0xff) << 16 |
              (b4[2]&0xff) <<  8 |
              (b4[3]&0xff);
    }


    /**
     * Reads a 1-byte integer (-128 to 127)
     *
     * @return   An int with the appropriate value
     */
    public int readInteger1() throws IOException
    {
       readFully(b1);

       return b1[0];
    }


    /**
     * Reads a 2-byte integer (-32,768 to 32,767)
     *
     * @return   An int with the appropriate value
     */

    public int readInteger2() throws IOException
    {
       readFully(b2);

       return (int)( (short)( (b2[1] & 0xFF) << 8 |
                              (b2[0] & 0xFF)) );
    }


    /**
     * Reads a 4-byte integer (-2,147,483,648 to 2,147,483,647)
     *
     * @return   An int with the appropriate value
     */
    public int readInteger4() throws IOException
    {
       readFully(b4);

       return (b4[3]&0xff) << 24 |
              (b4[2]&0xff) << 16 |
              (b4[1]&0xff) <<  8 |
              (b4[0]&0xff);
    }


    /**
     * Reads an 8-byte integer (-9,223,372,036,854,775,808 to 
     *                           9,223,372,036,854,775,807)
     *
     * @return   A long with the appropriate value
     */

    public long readInteger8() throws IOException
    {
       readFully(b8);

       return (long)(b8[7]& 0xFF) << 56 |
              (long)(b8[6]& 0xFF) << 48 |
              (long)(b8[5]& 0xFF) << 40 |
              (long)(b8[4]& 0xFF) << 32 |
              (long)(b8[3]& 0xFF) << 24 |
              (long)(b8[2]& 0xFF) << 16 |
              (long)(b8[1]& 0xFF) <<  8 |
              (long)(b8[0]& 0xFF);
    }


    /**
     * Reads a 4-byte real number
     *
     * @return   A float with the appropriate value
     */

    public float readReal4() throws IOException
    {
       return Float.intBitsToFloat(readInteger4());
    }


    /**
     * Reads an 8-byte real number
     *
     * @return   A double with the appropriate value
     */

    public double readReal8() throws IOException
    {
       return Double.longBitsToDouble(readInteger8());
    }


    /**
     * Reads a String
     *
     * @param  n   The number of characters in the String
     */
    public String readString(int n) throws IOException
    {
       byte[]  b;


       b = new byte[n];
       readFully(b);

       return new String(b);
    }


    /**
     * Reads an unsigned 1-byte integer (0 to 255)
     *
     * @return   An int with the appropriate value
     */
    public int readUnsignedInteger1() throws IOException
    {
       readFully(b1);

       return (int)( (b1[0] & 0xFF) );
    }


    /**
     * Reads an unsigned 2-byte integer (0 to 65,536)
     *
     * @return   An int with the appropriate value
     */
    public int readUnsignedInteger2() throws IOException
    {
       readFully(b2);

       return ((b2[1] & 0xFF) << 8 |
               (b2[0] & 0xFF) );
    }


    /**
     * Reads an unsigned 4-byte integer (0 to 4,294,967,296)
     *
     * @return   A long with the appropriate value
     */

    public long readUnsignedInteger4() throws IOException
    {
       readFully(b4);

       return ( (b4[3]&0xff) << 24 |
                (b4[2]&0xff) << 16 |
                (b4[1]&0xff) <<  8 |
                (b4[0]&0xff) );
    }
}
        
An Example (cont.)
javaexamples/io/LittleEndianOutputStream.java
        package io;

import java.io.*;


/**
 *  For writing binary streams to be read on a
 *  machine that uses a little-endian scheme.
 *
 *  For example, C under DOS/Windows uses a little-endian scheme 
 *  (i.e., LSB first).
 *
 *  @author  Prof. David Bernstein, James Madison University
 *  @version 1.0
 */
public class LittleEndianOutputStream 
{
    protected byte[]       b1, b2, b4;
    protected OutputStream out;


    /**
     * Construct a new LittleEndianOutputStream
     *
     * @param in   The OutputStream to use
     */
    public LittleEndianOutputStream(OutputStream out)
    {
        this.out = out;

        b1 = new byte[1];
        b2 = new byte[2];
        b4 = new byte[4];
    }


    /**
     * Closes this output stream
     */
    public void close() throws IOException
    {
        out.close();
    }


    /**
     * Writes a 1-byte integer
     *
     * @param   i  The int to write
     */
    public void writeInteger1(int i) throws IOException
    {
        b1[0] = (byte)i;

        out.write(b1,0,1);
    }


    /**
     * Writes a 2-byte integer
     *
     * @param   i  The int to write
     */
    public void writeInteger2(int i) throws IOException
    {
        b2[0] = (byte)i;        // LSB
        b2[1] = (byte)(i >> 8); // MSB

        out.write(b2,0,2);
    }


    /**
     * Writes a 4-byte integer
     *
     * @param   i  The int to write
     */
    public void writeInteger4(int i) throws IOException
    {
        b4[0] = (byte)i;         // LSB
        b4[1] = (byte)(i >> 8);
        b4[2] = (byte)(i >> 16);
        b4[3] = (byte)(i >> 24); // MSB

        out.write(b4,0,4);
    }


    /**
     * Writes an 8-byte integer
     *
     * @param   l The long to write
     */
    public void writeInteger8(long l) throws IOException
    {
        b4[0] = (byte)l;         // LSB
        b4[1] = (byte)(l >> 8);
        b4[2] = (byte)(l >> 16);
        b4[3] = (byte)(l >> 24);
        b4[4] = (byte)(l >> 32);
        b4[5] = (byte)(l >> 40);
        b4[6] = (byte)(l >> 48);
        b4[7] = (byte)(l >> 56); // MSB

        out.write(b4,0,4);
    }


    /**
     * Writes a 4-byte real number
     *
     * @param   f  The float to write
     */
    public void writeReal4(float f) throws IOException
    {
        writeInteger4(Float.floatToIntBits(f));
    }


    /**
     * Writes an 8-byte real number
     *
     * @param   d The double to write
     */
    public void writeReal8(double d) throws IOException
    {
        writeInteger8(Double.doubleToLongBits(d));
    }

}
        
Another Example
Another Example (cont.)

Extending a DataInputStream

javaexamples/http/v0/HttpInputStream.java
        import java.io.*;


/**
 * An input stream for reading both byte-stream data and
 * character-stream that is terminated using the HTTP end-of-line
 * characters ('\r\n')
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 0.0
 */
public class HttpInputStream extends DataInputStream
{
    private static final int  INITIAL_BUFFER_SIZE =  256;
    public  static final byte CR   = (byte)'\r'; //13;
    public  static final byte LF   = (byte)'\n'; //10;
    public  static final byte NULL = (byte)'\0'; // 0;

    private byte[] buffer;
    private int    currentSize;    

    /**
     * Explicit Value Constructor
     *
     * @param is    The underlying InputStream
     */
    public HttpInputStream(InputStream is)
    {
       super(is);
    }


    /**
     * Add a byte to the current line buffer (increasing the
     * size of the line buffer if necessary)
     *
     * @Param b  The byte to add
     */
    private void addToLineBuffer(byte b)
    {
       byte[]   temp;       

       buffer[currentSize] = b;
       currentSize++;
       
       if (currentSize == buffer.length)
       {
          temp = new byte[buffer.length*2];
          System.arraycopy(buffer, 0, temp, 0, buffer.length);
          buffer = temp;
       }
    }
    

    /**
     * Reads the input stream, one line at a time. 
     * Lines are terminated either by '\r\n' or by end-of-stream.
     *
     * @return    A String containing the line
     */
    public String readHttpLine() throws IOException 
    {
       byte     previous;
       byte[]   b;       
       int      status, stringLength;
       String   result;

       // This method will read a byte at a time.  It would be much
       // more efficient to read multiple bytes at a time into a buffer
       // but this is much simpler.
       b           = new byte[1];       

       buffer      = new byte[INITIAL_BUFFER_SIZE];
       currentSize = 0; 
       
       status      = -1;       
       previous    = NULL;
       
       while (status < 0)
       {
          try
          {
             readFully(b); // Blocks until a byte is available
             if ((b[0] == LF) && (previous == CR))
             {
                status = 1;
             }
             else
             {
                previous = b[0];
                addToLineBuffer(b[0]);
             }
          }
          catch (EOFException eofe)
          {
             status = 0;
          }
       }

       if (status == 0) stringLength = currentSize;     // Terminated by EOS
       else             stringLength = currentSize - 1; // Remove the CR

       try
       {
          // Use the HTTP "standard" character set
          result = new String(buffer, 0, stringLength, "ISO-8859-1");
       }
       catch (UnsupportedEncodingException uee)
       {
          // Use the default character set
          result = new String(buffer, 0, stringLength);
       }
       
       return result;       
    }
}
        
Another Example (cont.)

Extending a PrintStream

javaexamples/http/v0/HttpOutputStream.java
        import java.io.*;

/**
 * A PrintStream that can be used for writing HTTP requests
 * and responses
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 0.0
 */
public class HttpOutputStream extends PrintStream
{
    /**
     * Explicit Value Constructor
     *
     * @param os  The underlying OutputStream to use
     */
    public HttpOutputStream(OutputStream os)
    {
       super(os, false);
    }

    /**
     * Print an End-Of-Line marker
     */
    public void printEOL()
    {
       print("\r\n");
    }

    /**
     * Print a line (using the HTTP End-Of-Line marker)
     *
     * @param line  The line to print
     */
    public void printHttpLine(String line)
    {
       print(line);       
       print("\r\n");
    }

    /**
     * Print an appropriately formatted and terminated
     * header line
     *
     * @param name   The name
     * @param value  The corresponding value
     */
    public void printHeaderLine(String name, String value)
    {
       print(name);
       print(":");
       print(value);
       printEOL();
    }

}