//******************************************************************************
//
// File:    RmiInputStream.java
// Package: org.jpwg.jini.print.data
// Unit:    Class org.jpwg.jini.print.data.RmiInputStream
//
//******************************************************************************

package org.jpwg.jini.print.data;

import java.io.InputStream;
import java.io.IOException;

import java.rmi.Remote;
import java.rmi.RemoteException;

import java.rmi.server.UnicastRemoteObject;

import net.jini.print.data.RemoteInputStream;

/**
 * Class RmiInputStream encapsulates an input stream object that can be passed 
 * to a remote object as an argument or return value of a remote method call. 
 * The RmiInputStream uses remote method invocation (RMI) under the hood to 
 * transport the stream to the remote object. 
 * <P>
 * An RmiInputStream object is a wrapper for a regular input stream object 
 * (class {@link java.io.InputStream InputStream}). You construct an 
 * RmiInputStream on top of a plain InputStream. You can then pass the 
 * RmiInputStream to a remote object as an argument or return value of a remote 
 * method call. When the remote object calls input stream methods on the 
 * RmiInputStream, these get translated across the remote connection as method 
 * calls on the original InputStream. 
 * <P>
 * Class RmiInputStream provides a concrete implementation of the abstract base 
 * class {@link net.jini.print.data.RemoteInputStream RemoteInputStream} and 
 * implements the contract for a remote input stream. Specifically, the client 
 * can attempt to recover from a RemoteException thrown by the 
 * <CODE>read()</CODE>, <CODE>skip()</CODE>, or <CODE>close()</CODE> methods by 
 * retrying the method call. For further information, see class {@link 
 * net.jini.print.data.RemoteInputStream RemoteInputStream}. 
 * <P>
 * The RmiInputStream does not support the <CODE>mark()</CODE> and 
 * <CODE>reset()</CODE> operations. 
 * <P>
 * When using an input stream, it's generally more efficient to read many bytes 
 * at a time rather than one byte at a time -- that is, to call {@link 
 * #read(byte[]) <CODE>read(byte[])</CODE>} or {@link #read(byte[],int,int) 
 * <CODE>read(byte[],int,int)</CODE>} rather than {@link #read() 
 * <CODE>read()</CODE>}. Reading many bytes at a time is even more important 
 * with a remote connection in the middle. Whenever you use an RmiInputStream, 
 * consider layering a {@link java.io.BufferedInputStream BufferedInputStream} 
 * on top of the RmiInputStream. Additionally, the {@link 
 * java.io.BufferedInputStream BufferedInputStream} supports the 
 * <CODE>mark()</CODE> and <CODE>reset()</CODE> operations. 
 * <P>
 * <B>Design</B>
 * <P>
 * <CENTER>
 * <IMG SRC="../doc-files/RmiInputStreamFig1.gif" WIDTH=473 HEIGHT=295>
 * </CENTER>
 * <P>
 * An RmiInputStream object, which extends the abstract base class 
 * RemoteInputStream, contains a "backend" object. The backend object implements 
 * private interface RmiInputStream.Backend, which is a Remote interface. The 
 * backend object is an instance of private class RmiInputStream.BackendImpl, 
 * which is a UnicastRemoteObject. When an RmiInputStream object is created in 
 * some Java virtual machine (called the "local JVM" in the diagram), the 
 * RmiInputStream object in turn creates a backend object, passing it a 
 * reference to the original input stream. 
 * <P>
 * When the RmiInputStream object is sent to some other JVM (called the "remote 
 * JVM" in the diagram) as an argument or return value of a remote method call, 
 * the RmiInputStream object's fields are serialized. Since the backend object 
 * is an exported remote object, a remote reference to the backend object gets 
 * serialized. When deserialized in the remote JVM, the RmiInputStream object 
 * therefore contains this remote reference (an RMI stub for the backend object 
 * instead of the backend object itself). 
 * <P>
 * The backend object supports two operations:
 * <OL TYPE=1>
 * <LI>
 * Read a given number of bytes from the original input stream, starting from a 
 * given byte offset relative to the start of the original input stream. 
 * <LI>
 * Close the original input stream.
 * </OL>
 * The backend object's read operation includes a byte offset so the backend 
 * object can repeat a previous read operation and get the same result (so the 
 * read operation can be idempotent). The backend object keeps the bytes it 
 * reads from the original input stream in a buffer, in case it gets a request 
 * to re-read them. 
 * <P>
 * <B>DRAFT STANDARD VERSION 1.0 (23-MAY-2000)</B>
 *
 * @author  Alan Kaminsky
 * @version 13-Mar-2000
 */
public class RmiInputStream
	extends RemoteInputStream
	{

// Hidden helper interfaces and classes.

	/**
	 * Interface RmiInputStream.Backend defines the remote interface for the 
	 * backend object contained inside an RmiInputStream object. 
	 * <P>
	 * <B>DRAFT STANDARD VERSION 1.0 (23-MAY-2000)</B>
	 *
	 * @author  Alan Kaminsky
	 * @version 13-Mar-2000
	 */
	static interface Backend
		extends Remote
		{

	// Exported operations.

		/**
		 * Read a block of bytes from the original input stream. This method 
		 * blocks until input data is available, end of stream is detected, or 
		 * an exception is thrown. 
		 * <P>
		 * The <CODE>pos</CODE> parameter lets the caller re-read a block of 
		 * bytes in case the previous <CODE>read()</CODE> call failed with a 
		 * RemoteException. The value of <CODE>pos</CODE> must be the position 
		 * of some byte returned by the previous <CODE>read()</CODE> call or the 
		 * position of the next byte after the last byte returned by the 
		 * previous <CODE>read()</CODE> call. Another way to look at it is that 
		 * a <CODE>read()</CODE> call acknowledges correct receipt of all bytes 
		 * up to and including position <CODE>pos-1</CODE>, and until such 
		 * acknowledgment is received the backend must be prepared to re-read 
		 * the bytes from the previous call. If the value of <CODE>pos</CODE> is 
		 * invalid, an IOException is thrown. 
		 *
		 * @param  pos  Position in the input stream from which to start reading
		 *              bytes. Bytes are numbered in sequence starting with 0.
		 * @param  n    Maximum number of bytes to read. If < 0, 0 is used.
		 *
		 * @return  An array of bytes read from the original input stream. The
		 *          length of the array indicates the number of bytes actually 
		 *          read; this may be anywhere from 0 to <I>n</I>. If 
		 *          end-of-stream is encountered, null is returned. 
		 *
		 * @exception  IOException
		 *     Thrown if an I/O error occurred or if the value of 
		 *     <CODE>pos</CODE> is invalid. 
		 * @exception  RemoteException
		 *     Thrown if a remote error occurred.
		 */
		public byte[] read
			(long pos,
			 int n)
			throws IOException, RemoteException;

		/**
		 * Close the original input stream.
		 *
		 * @exception  IOException
		 *     Thrown if an I/O error occurred.
		 * @exception  RemoteException
		 *     Thrown if a remote error occurred.
		 */
		public void close()
			throws IOException, RemoteException;

		}

	/**
	 * Class RmiInputStream.BackendImpl encapsulates the remote implementation 
	 * for the backend object contained inside an RmiInputStream object. 
	 * <P>
	 * <B>DRAFT STANDARD VERSION 1.0 (23-MAY-2000)</B>
	 *
	 * @author  Alan Kaminsky
	 * @version 13-Mar-2000
	 */
	static class BackendImpl
		extends UnicastRemoteObject
		implements Backend
		{

	// Hidden data members.

		/**
		 * The original input stream, or null if <CODE>close()</CODE> was 
		 * called. 
		 */
		private transient InputStream myInputStream;
		/**
		 * Array of bytes previously read from the original input stream. 
		 * Indexes 0..myCount-1 contain valid bytes. If null, EOF was 
		 * encountered. 
		 */
		private transient byte[] myBytes = new byte [0];
		/**
		 * Number of valid bytes in <CODE>myBytes</CODE>.
		 */
		private transient int myCount = 0;
		/**
		 * Position of the first byte in <CODE>myBytes</CODE>.
		 */
		private transient long myPosition = 0L;

	// Exported constructors.

		/**
		 * Construct and export a new backend implementation object layered on 
		 * top of the given input stream. 
		 *
		 * @param  theInputStream  Original input stream.
		 *
		 * @exception  RemoteException
		 *     Thrown if a remote error occurred.
		 */
		public BackendImpl
			(InputStream theInputStream)
			throws RemoteException
			{
			super();
			myInputStream = theInputStream;
			}

	// Exported operations inherited and implemented from interface Backend.

		/**
		 * Read a block of bytes from the original input stream. This method 
		 * blocks until input data is available, end of stream is detected, or 
		 * an exception is thrown. 
		 * <P>
		 * The <CODE>pos</CODE> parameter lets the caller re-read a block of 
		 * bytes in case the previous <CODE>read()</CODE> call failed with a 
		 * RemoteException. The value of <CODE>pos</CODE> must be the position 
		 * of some byte returned by the previous <CODE>read()</CODE> call or the 
		 * position of the next byte after the last byte returned by the 
		 * previous <CODE>read()</CODE> call. Another way to look at it is that 
		 * a <CODE>read()</CODE> call acknowledges correct receipt of all bytes 
		 * up to and including position <CODE>pos-1</CODE>, and until such 
		 * acknowledgment is received the backend must be prepared to re-read 
		 * the bytes from the previous call. If the value of <CODE>pos</CODE> is 
		 * invalid, an IOException is thrown. 
		 *
		 * @param  pos  Position in the input stream from which to start reading
		 *              bytes. Bytes are numbered in sequence starting with 0.
		 * @param  n    Maximum number of bytes to read. If < 0, 0 is used.
		 *
		 * @return  An array of bytes read from the original input stream. The
		 *          length of the array indicates the number of bytes actually 
		 *          read; this may be anywhere from 0 to <I>n</I>. If 
		 *          end-of-stream is encountered, null is returned. 
		 *
		 * @exception  IOException
		 *     Thrown if an I/O error occurred or if the value of 
		 *     <CODE>pos</CODE> is invalid. 
		 * @exception  RemoteException
		 *     Thrown if a remote error occurred.
		 */
		public byte[] read
			(long pos,
			 int n)
			throws IOException, RemoteException
			{
			// Adjust n if necessary.
			n = n < 0 ? 0 : n;

			// Verify original input stream is open.
			if (myInputStream == null)
				{
				throw new IOException
					("org.jpwg.jini.print.data.RmiInputStream.BackendImpl.read(): stream closed");
				}

			// Early return if EOF already encountered.
			else if (myBytes == null)
				{
				return null;
				}

			// Verify precondition on pos.
			else if (myPosition > pos || pos > myPosition + myCount)
				{
				throw new IOException
					("org.jpwg.jini.print.data.RmiInputStream.BackendImpl.read(): invalid position");
				}

			// Process a request to read the next block of bytes.
			else if (pos == myPosition + myCount)
				{
				// Reallocate byte buffer if necessary.
				if (n > myBytes.length)
					{
					myBytes = new byte [n];
					}

				// Read the next block of up to n bytes.
				myPosition = pos;
				myCount = myInputStream.read (myBytes, 0, n);

				// EOF encountered. Return EOF indication.
				if (myCount == -1)
					{
					myBytes = null;
					myCount = 0;
					return null;
					}

				// EOF not encountered. Return array of bytes actually read.
				else
					{
					byte[] result = new byte [myCount];
					System.arraycopy (myBytes, 0, result, 0, myCount);
					return result;
					}
				}

			// Process a request to re-read a previous block of bytes.
			else
				{
				// Determine number of bytes available starting at position pos.
				int availableCount = (int)(myPosition + myCount - pos);

				// Determine number of bytes to return.
				int returnCount = Math.min (n, availableCount);

				// Return array of that many bytes starting at position pos.
				byte[] result = new byte [returnCount];
				System.arraycopy
					(myBytes, (int)(pos-myPosition), result, 0, returnCount);
				return result;
				}
			}

		/**
		 * Close the original input stream.
		 *
		 * @exception  IOException
		 *     Thrown if an I/O error occurred.
		 * @exception  RemoteException
		 *     Thrown if a remote error occurred.
		 */
		public void close()
			throws IOException, RemoteException
			{
			if (myInputStream != null)
				{
				myInputStream.close();
				myInputStream = null;
				}
			}

		}

// Hidden data members.

	/**
	 * Backend object. (In the remote JVM, this field has a remote reference to 
	 * the backend object in the local JVM.) If null, the stream is closed.
	 * @serial
	 */
	private Backend myBackend;
	/**
	 * Position in the original input stream of the next byte after the last 
	 * byte successfully read from the backend. 
	 * @serial
	 */
	private long myPosition = 0L;
	/**
	 * One-byte array for use by the zero-argument <CODE>read()</CODE> method.
	 */
	private transient byte[] oneByteBuffer = new byte [1];

// Exported constructors.

	/**
	 * Construct a new RMI input stream object that provides remote access to 
	 * the given input stream. 
	 *
	 * @param  theInputStream  Original input stream.
	 *
	 * @exception  RemoteException
	 *     Thrown if a remote error occurred.
	 */
	public RmiInputStream
		(InputStream theInputStream)
		throws RemoteException
		{
		super();
		myBackend = new BackendImpl (theInputStream);
		}

// Exported operations inherited and overridden from class InputStream.

	/**
	 * Reads the next byte of data from this remote input stream. The value byte 
	 * is returned as an <CODE>int</CODE> in the range <CODE>0</CODE> to 
	 * <CODE>255</CODE>. If no byte is available because the end of the stream 
	 * has been reached, the value <CODE>-1</CODE> is returned. This method 
	 * blocks until input data is available, the end of the stream is detected, 
	 * or an exception is thrown. 
	 *
	 * @return  The next byte of data, or <CODE>-1</CODE> if the end of the
	 *          stream is reached.
	 *
	 * @exception  IOException
	 *     Thrown if an I/O error occurred. The IOException may be a 
	 *     RemoteException indicating a remote error occurred. 
	 */
	public synchronized int read()
		throws IOException
		{
		int result = read (oneByteBuffer, 0, 1);
		return result == -1 ? -1 : ((int) oneByteBuffer[0]) & 0xFF;
		}

	/**
	 * Reads some number of bytes from this remote input stream and stores them 
	 * into the buffer array <CODE>b</CODE>. The number of bytes actually read 
	 * is returned as an integer. This method blocks until input data is 
	 * available, end of file is detected, or an exception is thrown. 
	 * <P> 
	 * If <CODE>b</CODE> is null, a {@link java.lang.NullPointerException 
	 * NullPointerException} is thrown. If the length of <CODE>b</CODE> is zero, 
	 * then no bytes are read and <CODE>0</CODE> is returned; otherwise, there 
	 * is an attempt to read at least one byte. If no byte is available because 
	 * the stream is at end of file, the value <CODE>-1</CODE> is returned; 
	 * otherwise, at least one byte is read and stored into <CODE>b</CODE>. 
	 * <P>
	 * The first byte read is stored into element <CODE>b[0]</CODE>, the next 
	 * one into <CODE>b[1]</CODE>, and so on. The number of bytes read is, at 
	 * most, equal to the length of <CODE>b</CODE>. Let <I>k</I> be the number 
	 * of bytes actually read; these bytes will be stored in elements 
	 * <CODE>b[0]</CODE> through <CODE>b[</CODE><I>k</I><CODE>-1]</CODE>, 
	 * leaving elements <CODE>b[</CODE><I>k</I><CODE>]</CODE> through 
	 * <CODE>b[b.length-1]</CODE> unaffected. 
	 * <P>
	 * If the first byte cannot be read for any reason other than end of file, 
	 * then an {@link java.io.IOException IOException} is thrown. In particular, 
	 * an {@link java.io.IOException IOException} is thrown if the input stream 
	 * has been closed.
	 * <P>
	 * The <CODE>read(b)</CODE> method for class RemoteInputStream has the same 
	 * effect as: 
	 * <PRE>
	 *     read (b, 0, b.length) 
	 * </PRE>
	 * <I>Note:</I> It is best to avoid reading a very large number of bytes in 
	 * a single <CODE>read()</CODE> call (i.e., giving a very large array for 
	 * <CODE>b</CODE>). This is because the backend object must retain in its 
	 * internal buffer all the bytes read, in order to guarantee the 
	 * RmiInputStream's behavior after a RemoteException as described above. 
	 * Thus, calling <CODE>read(b)</CODE> causes the backend object to allocate 
	 * an internal buffer of <CODE>b.length</CODE> bytes. 
	 *
	 * @param  b  The buffer into which the data is read.
	 *
	 * @return  The total number of bytes read into the buffer, or
	 *          <CODE>-1</CODE> if there is no more data because the end of the 
	 *          stream has been reached. 
	 *
	 * @exception  IOException
	 *     Thrown if an I/O error occurred. The IOException may be a 
	 *     RemoteException indicating a remote error occurred. 
	 *
	 * @see  #read(byte[], int, int)
	 */
	public synchronized int read
		(byte[] b)
		throws IOException
		{
		return read (b, 0, b.length);
		}

	/**
	 * Reads up to <CODE>len</CODE> bytes of data from this remote input stream 
	 * into an array of bytes. An attempt is made to read as many as 
	 * <CODE>len</CODE> bytes, but a smaller number may be read, possibly zero. 
	 * The number of bytes actually read is returned as an integer. 
	 * <P>
	 * This method blocks until input data is available, end of file is 
	 * detected, or an exception is thrown. 
	 * <P>
	 * If <CODE>b</CODE> is null, a {@link java.lang.NullPointerException 
	 * NullPointerException} is thrown. 
	 * <P>
	 * If <CODE>off</CODE> is negative, or <CODE>len</CODE> is negative, or 
	 * <CODE>off+len</CODE> is greater than the length of the array 
	 * <CODE>b</CODE>, then an {@link java.lang.IndexOutOfBoundsException 
	 * IndexOutOfBoundsException} is thrown. 
	 * <P>
	 * If <CODE>len</CODE> is zero, then no bytes are read and <CODE>0</CODE> is 
	 * returned; otherwise, there is an attempt to read at least one byte. If no 
	 * byte is available because the stream is at end of file, the value 
	 * <CODE>-1</CODE> is returned; otherwise, at least one byte is read and 
	 * stored into <CODE>b</CODE>. 
	 * <P>
	 * The first byte read is stored into element <CODE>b[off]</CODE>, the next 
	 * one into <CODE>b[off+1]</CODE>, and so on. The number of bytes read is, 
	 * at most, equal to <CODE>len</CODE>. Let <I>k</I> be the number of bytes 
	 * actually read; these bytes will be stored in elements <CODE>b[off]</CODE> 
	 * through <CODE>b[off+</CODE><I>k</I><CODE>-1]</CODE>, leaving elements 
	 * <CODE>b[off+</CODE><I>k</I><CODE>]</CODE> through 
	 * <CODE>b[off+len-1]</CODE> unaffected. 
	 * <P>
	 * In every case, elements <CODE>b[0]</CODE> through <CODE>b[off-1]</CODE> 
	 * and elements <CODE>b[off+len]</CODE> through <CODE>b[b.length-1]</CODE> 
	 * are unaffected.
	 * <P>
	 * If the first byte cannot be read for any reason other than end of file, 
	 * then an {@link java.io.IOException IOException} is thrown. In particular, 
	 * an {@link java.io.IOException IOException} is thrown if the input stream 
	 * has been closed. 
	 * <P>
	 * <I>Note:</I> It is best to avoid reading a very large number of bytes in 
	 * a single <CODE>read()</CODE> call (i.e., giving a very large value for 
	 * <CODE>len</CODE>). This is because the backend object must retain in its 
	 * internal buffer all the bytes read, in order to guarantee the 
	 * RmiInputStream's behavior after a RemoteException as described above. 
	 * Thus, calling <CODE>read(b,off,len)</CODE> causes the backend object to 
	 * allocate an internal buffer of <CODE>len</CODE> bytes. 
	 *
	 * @param  b    The buffer into which the data is read.
	 * @param  off  The start offset in array <CODE>b</CODE> at which the data
	 *              is written.
	 * @param  len  The maximum number of bytes to read.
	 *
	 * @return  The total number of bytes read into the buffer, or
	 *          <CODE>-1</CODE> if there is no more data because the end of the 
	 *          stream has been reached. 
	 *
	 * @exception  IOException
	 *     Thrown if an I/O error occurred. The IOException may be a 
	 *     RemoteException indicating a remote error occurred. 
	 */
	public synchronized int read
		(byte[] b,
		 int off,
		 int len)
		throws IOException
		{
		if (b == null)
			{
			throw new NullPointerException();
			}
		if (off < 0 || len < 0 || off + len > b.length)
			{
			throw new IndexOutOfBoundsException();
			}
		if (myBackend == null)
			{
			throw new IOException
				("org.jpwg.jini.print.data.RmiInputStream.read(): stream closed");
			}
		return doRead (b, off, len);
		}

	/**
	 * This hidden method is used by both <CODE>read(b,off,len)</CODE> and 
	 * <CODE>skip(n)</CODE>. It assumes that <CODE>len</CODE> >= 0. If the 
	 * <CODE>b</CODE> parameter is not null, then this method has exactly the 
	 * same contract as <CODE>read(b,off,len)</CODE>. If the <CODE>b</CODE> 
	 * parameter is null, then this method has the same contract as 
	 * <CODE>read(b,off,len)</CODE> except that the bytes read, if any, are not 
	 * stored in <CODE>b</CODE>. 
	 */
	private int doRead
		(byte[] b,
		 int off,
		 int len)
		throws IOException
		{
		// Read up to len bytes from the backend. 
		byte[] newBytes = myBackend.read (myPosition, len);

		// Early return if EOF encountered.
		if (newBytes == null)
			{
			return -1;
			}

		// Determine actual number of new bytes read.
		int newCount = newBytes.length;

		// Update position.
		myPosition += newCount;

		// Return new bytes.
		if (b != null)
			{
			System.arraycopy (newBytes, 0, b, off, newCount);
			}
		return newCount;
		}

	/**
	 * Skips over and discards <CODE>n</CODE> bytes of data from this remote 
	 * input stream. The <CODE>skip()</CODE> method may, for a variety of 
	 * reasons, end up skipping over some smaller number of bytes, possibly 0. 
	 * This may result from any of a number of conditions; reaching end of file 
	 * before <CODE>n</CODE> bytes have been skipped is only one possibility. 
	 * The actual number of bytes skipped is returned. If <CODE>n</CODE> is 
	 * negative, no bytes are skipped. 
	 * <P>
	 * <I>Note:</I> It is best to avoid skipping a very large number of bytes in 
	 * a single <CODE>skip(n)</CODE> call. This is because the backend object 
	 * must retain in its internal buffer all the bytes skipped over, in order 
	 * to guarantee the RmiInputStream's behavior after a RemoteException as 
	 * described above. Thus, calling <CODE>skip(n)</CODE> causes the backend 
	 * object to allocate an internal buffer of <CODE>n</CODE> bytes. 
	 *
	 * @param  n  The number of bytes to be skipped.
	 *
	 * @return  The actual number of bytes skipped.
	 *
	 * @exception  IOException
	 *     Thrown if an I/O error occurred. The IOException may be a 
	 *     RemoteException indicating a remote error occurred. 
	 */
	public synchronized long skip
		(long n)
		throws IOException
		{
		// Verify stream is open.
		if (myBackend == null)
			{
			throw new IOException
				("org.jpwg.jini.print.data.RmiInputStream.skip(): stream closed");
			}

		// Skip at most Integer.MAX_VALUE bytes.
		return doRead (null, 0, (int) Math.min (n, Integer.MAX_VALUE));
		}

	/**
	 * Closes this remote input stream and releases any system resources 
	 * associated with the stream. 
	 *
	 * @exception  IOException
	 *     Thrown if an I/O error occurred. The IOException may be a 
	 *     RemoteException indicating a remote error occurred. 
	 */
	public synchronized void close()
		throws IOException
		{
		if (myBackend != null)
			{
			myBackend.close();
			myBackend = null;
			}
		}

	}