Project 3 - ThreadedInputStream

Your tasks is to implement a ThreadedInputStream, which takes another input stream (typically an InflaterInputStream) as an argument. When start() is called on a ThreadedInputStream, you should create a background thread that copies the argument input stream into buffers. When a user invokes a read operation on the ThreadedInputStream, it should read from a filled buffer if one is available.

If no filled buffer is available, and the background thread is not currently filling a buffer from the argument input stream, then the read request can be fulfilled directly from the argument input stream. However, it is important to note that while this is happening, the background thread must not make a simultaneous request to fill a buffer from the argument input stream.

You do not have to worry about concurrent read requests on the ThreadedInputStream.

The following shows a skeleton for what your class should look like.

public class ThreadedInputStream extends InputStream implements Runnable {

   // Stream to read data from
   InputStream in;

   // If non-null, print debugging information to it
   PrintWriter debug;
	
   // Maximum amount to buffer
   static final int maxBytesBuffered  = Integer.getInteger("maxBytesBuffered",10000).intValue();

  ThreadedInputStream(InputStream i, PrintWriter d) {
                in = i;
                debug = d;
                }
  ThreadedInputStream(InputStream i) {
                this(i,Boolean.getBoolean("debug")?
                        new PrintWriter(System.err):null);
                }

  public void close() throws IOException {
	in.close();
	}

  public void start() {
	Thread t = new Thread(this, "Background reader");
	t.setPriority(Thread.MIN_PRIORITY);
	t.setDaemon(true);
	t.start();
	}
  byte [] oneByte = new byte[1];
  public int read() throws IOException {
	int i = read(oneByte,0,1);
	if (i < 0) return -1;
	return oneByte[0];
	}

  public int read(byte[] b, int off, int len)  throws IOException {
	???
	}

  public void run() {
	???
	}

}

Details

You should use a queue to hold filled buffers. A buffer should contain a count and up to 1024 bytes of buffered data. A count is needed because a buffer will not always be full.

The background thread should move buffer to the queue as soon as the first read result returns, even if that doesn't fill up the buffer.

There is a variable maxBytesBuffered that gives the limit on the number of bytes that can be buffered by the system. Don't buffer more than that.

The read function

If a filled buffer is available, the read request should be fulfilled out of that. You must keep track of the portion of a filled buffer that has already been consumed.

If no filled buffer is available, and the background thread is not currently filling a buffer, then the read request can be fulfilled directly from the argument input stream. It is OK to hold a lock during this entire process, because there is nothing useful that the background thread can do at this point.

End of File

Note that either the read function (when doing a direct read) or the background thread might encounter EOF in the argument input thread. Make sure the appropriate things happen in this case (e.g., if there are filled buffers, the read() function won't return EOF until all of the filled buffers are consumed).

Test drivier

A sample test driver is provided in the file Test.java. It takes a list of files as an argument and expands them. If you invoke it with

java -Ddebug=true -Dsleep=5 Test test.gz

it will print debugging information and the reader with sleep to give the background thread a chance to run. Try values of 0 through 100 for the sleep value (0 if not specified).