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

package org.jpwg.jini.print.data;

import java.io.Reader;
import java.io.IOException;

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

import java.rmi.server.UnicastRemoteObject;

import net.jini.print.data.RemoteReader;

/**
 * Class RmiReader encapsulates a reader object that can be passed to a remote 
 * object as an argument or return value of a remote method call. The RmiReader 
 * uses remote method invocation (RMI) under the hood to transport the stream to 
 * the remote object. 
 * <P>
 * An RmiReader object is a wrapper for a regular reader object (class {@link 
 * java.io.Reader Reader}). You construct an RmiReader on top of a plain Reader. 
 * You can then pass the RmiReader to a remote object as an argument or return 
 * value of a remote method call. When the remote object calls reader methods on 
 * the RmiReader, these get translated across the remote connection as method 
 * calls on the original Reader. 
 * <P>
 * Class RmiReader provides a concrete implementation of the abstract base class 
 * {@link net.jini.print.data.RemoteReader RemoteReader} and implements the 
 * contract for a remote reader. 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.RemoteReader RemoteReader}. 
 * <P>
 * The RmiReader does not support the <CODE>mark()</CODE> and 
 * <CODE>reset()</CODE> operations. 
 * <P>
 * When using a reader, it's generally more efficient to read many characters at 
 * a time rather than one character at a time -- that is, to call {@link 
 * #read(char[]) <CODE>read(char[])</CODE>} or {@link #read(char[],int,int) 
 * <CODE>read(char[],int,int)</CODE>} rather than {@link #read() 
 * <CODE>read()</CODE>}. Reading many characters at a time is even more 
 * important with a remote connection in the middle. Whenever you use an 
 * RmiReader, consider layering a {@link java.io.BufferedReader BufferedReader} 
 * on top of the RmiReader. Additionally, the {@link java.io.BufferedReader 
 * BufferedReader} supports the <CODE>mark()</CODE> and <CODE>reset()</CODE> 
 * operations. 
 * <P>
 * <B>Design</B>
 * <P>
 * <CENTER>
 * <IMG SRC="../doc-files/RmiReaderFig1.gif" WIDTH=473 HEIGHT=295>
 * </CENTER>
 * <P>
 * An RmiReader object, which extends the abstract base class RemoteReader, 
 * contains a "backend" object. The backend object implements private interface 
 * RmiReader.Backend, which is a Remote interface. The backend object is an 
 * instance of private class RmiReader.BackendImpl, which is a 
 * UnicastRemoteObject. When an RmiReader object is created in some Java virtual 
 * machine (called the "local JVM" in the diagram), the RmiReader object in turn 
 * creates a backend object, passing it a reference to the original reader. 
 * <P>
 * When the RmiReader 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 
 * RmiReader 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 RmiReader 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 characters from the original reader, starting from a 
 * given character offset relative to the start of the original reader. 
 * <LI>
 * Close the original reader.
 * </OL>
 * The backend object's read operation includes a character 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 
 * characters it reads from the original reader 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 RmiReader
	extends RemoteReader
	{

// Hidden helper interfaces and classes.

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

	// Exported operations.

		/**
		 * Read a block of characters from the original reader. 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 
		 * characters in case the previous <CODE>read()</CODE> call failed with 
		 * a RemoteException. The value of <CODE>pos</CODE> must be the position 
		 * of some character returned by the previous <CODE>read()</CODE> call 
		 * or the position of the next character after the last character 
		 * 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 characters up to and including position 
		 * <CODE>pos-1</CODE>, and until such acknowledgment is received the 
		 * backend must be prepared to re-read the characters from the previous 
		 * call. If the value of <CODE>pos</CODE> is invalid, an IOException is 
		 * thrown. 
		 *
		 * @param  pos  Position in the reader from which to start reading
		 *              characters. Characters are numbered in sequence starting 
		 *              with 0. 
		 * @param  n    Maximum number of characters to read. If < 0, 0 is used.
		 *
		 * @return  An array of characters read from the original reader. The
		 *          length of the array indicates the number of characters 
		 *          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 char[] read
			(long pos,
			 int n)
			throws IOException, RemoteException;

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

		}

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

	// Hidden data members.

		/**
		 * The original reader, or null if <CODE>close()</CODE> was called. 
		 */
		private transient Reader myReader;
		/**
		 * Array of characters previously read from the original reader. Indexes 
		 * 0..myCount-1 contain valid characters. If null, EOF was encountered. 
		 */
		private transient char[] myChars = new char [0];
		/**
		 * Number of valid characters in <CODE>myChars</CODE>.
		 */
		private transient int myCount = 0;
		/**
		 * Position of the first character in <CODE>myChars</CODE>.
		 */
		private transient long myPosition = 0L;

	// Exported constructors.

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

	// Exported operations inherited and implemented from interface Backend.

		/**
		 * Read a block of characters from the original reader. 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 
		 * characters in case the previous <CODE>read()</CODE> call failed with 
		 * a RemoteException. The value of <CODE>pos</CODE> must be the position 
		 * of some character returned by the previous <CODE>read()</CODE> call 
		 * or the position of the next character after the last character 
		 * 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 characters up to and including position 
		 * <CODE>pos-1</CODE>, and until such acknowledgment is received the 
		 * backend must be prepared to re-read the characters from the previous 
		 * call. If the value of <CODE>pos</CODE> is invalid, an IOException is 
		 * thrown. 
		 *
		 * @param  pos  Position in the reader from which to start reading
		 *              characters. Characters are numbered in sequence starting 
		 *              with 0. 
		 * @param  n    Maximum number of characters to read. If < 0, 0 is used.
		 *
		 * @return  An array of characters read from the original reader. The
		 *          length of the array indicates the number of characters 
		 *          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 char[] read
			(long pos,
			 int n)
			throws IOException, RemoteException
			{
			// Adjust n if necessary.
			n = n < 0 ? 0 : n;

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

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

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

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

				// Read the next block of up to n characters.
				myPosition = pos;
				myCount = myReader.read (myChars, 0, n);

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

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

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

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

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

		/**
		 * Close the original reader.
		 *
		 * @exception  IOException
		 *     Thrown if an I/O error occurred.
		 * @exception  RemoteException
		 *     Thrown if a remote error occurred.
		 */
		public void close()
			throws IOException, RemoteException
			{
			if (myReader != null)
				{
				myReader.close();
				myReader = 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 reader of the next character after the last 
	 * character successfully read from the backend. 
	 * @serial
	 */
	private long myPosition = 0L;
	/**
	 * One-character array for use by the zero-argument <CODE>read()</CODE> 
	 * method. 
	 */
	private transient char[] oneCharBuffer = new char [1];

// Exported constructors.

	/**
	 * Construct a new RMI reader object that provides remote access to the 
	 * given reader. The operations will synchronize on this RMI reader object 
	 * itself. 
	 *
	 * @param  theReader  Original reader.
	 *
	 * @exception  RemoteException
	 *     Thrown if a remote error occurred.
	 */
	public RmiReader
		(Reader theReader)
		throws RemoteException
		{
		super();
		myBackend = new BackendImpl (theReader);
		}

	/**
	 * Construct a new RMI reader object that provides remote access to the 
	 * given reader. The operations will synchronize on the given lock object. 
	 *
	 * @param  theReader  Original reader.
	 * @param  theLock    Lock object.
	 *
	 * @exception  RemoteException
	 *     Thrown if a remote error occurred.
	 */
	public RmiReader
		(Reader theReader,
		 Object theLock)
		throws RemoteException
		{
		super (theLock);
		myBackend = new BackendImpl (theReader);
		}

// Exported operations inherited and overridden from class Reader.

	/**
	 * Read a single character.  This method will block until a character is
	 * available, an I/O error occurs, or the end of the stream is reached.
	 *
	 * @return     The character read, as an integer in the range 0 to 16383
	 *             (<tt>0x00-0xffff</tt>), or -1 if 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 int read()
		throws IOException
		{
		synchronized (lock)
			{
			int result = read (oneCharBuffer, 0, 1);
			return result == -1 ? -1 : ((int) oneCharBuffer[0]) & 0xFFFF;
			}
		}

	/**
	 * Read characters into an array.  This method will block until some input
	 * is available, an I/O error occurs, or the end of the stream is reached.
	 * <P>
	 * <I>Note:</I> It is best to avoid reading a very large number of 
	 * characters in a single <CODE>read()</CODE> call (i.e., giving a very 
	 * large array for <CODE>cbuf</CODE>). This is because the backend object 
	 * must retain in its internal buffer all the characters read, in order to 
	 * guarantee the RmiReader's behavior after a RemoteException as described 
	 * above. Thus, calling <CODE>read(cbuf)</CODE> causes the backend object to 
	 * allocate an internal buffer of <CODE>cbuf.length</CODE> characters. 
	 *
	 * @param       cbuf  Destination buffer.
	 *
	 * @return      The number of bytes read, or -1 if 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 int read
		(char cbuf[])
		throws IOException
		{
		return read (cbuf, 0, cbuf.length);
		}

	/**
	 * Read characters into a portion of an array.  This method will block
	 * until some input is available, an I/O error occurs, or the end of the
	 * stream is reached.
	 * <P>
	 * <I>Note:</I> It is best to avoid reading a very large number of 
	 * characters 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 characters read, in order to 
	 * guarantee the RmiReader's behavior after a RemoteException as described 
	 * above. Thus, calling <CODE>read(cbuf,off,len)</CODE> causes the backend 
	 * object to allocate an internal buffer of <CODE>len</CODE> characters. 
	 *
	 * @param      cbuf  Destination buffer.
	 * @param      off   Offset at which to start storing characters.
	 * @param      len   Maximum number of characters to read.
	 *
	 * @return     The number of characters read, or -1 if 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
		(char[] cbuf,
		 int off,
		 int len)
		throws IOException
		{
		if (cbuf == null)
			{
			throw new NullPointerException();
			}
		if (off < 0 || len < 0 || off + len > cbuf.length)
			{
			throw new IndexOutOfBoundsException();
			}
		synchronized (lock)
			{
			if (myBackend == null)
				{
				throw new IOException
					("org.jpwg.jini.print.data.RmiReader.read(): stream closed");
				}
			return doRead (cbuf, off, len);
			}
		}

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

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

		// Determine actual number of new characters read.
		int newCount = newChars.length;

		// Update position.
		myPosition += newCount;

		// Return new characters.
		if (cbuf != null)
			{
			System.arraycopy (newChars, 0, cbuf, off, newCount);
			}
		return newCount;
		}

	/**
	 * Skip characters.  This method will block until some characters are
	 * available, an I/O error occurs, or the end of the stream is reached.
	 * <P>
	 * <I>Note:</I> It is best to avoid skipping a very large number of 
	 * characters in a single <CODE>skip(n)</CODE> call. This is because the 
	 * backend object must retain in its internal buffer all the characters 
	 * skipped over, in order to guarantee the RmiReader'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> characters. 
	 *
	 * @param  n  The number of characters to be skipped. If < 0, 0 is used.
	 *
	 * @return  The actual number of characters skipped.
	 *
	 * @exception  IOException
	 *     Thrown if an I/O error occurred. The IOException may be a 
	 *     RemoteException indicating a remote error occurred. 
	 */
	public long skip
		(long n)
		throws IOException
		{
		synchronized (lock)
			{
			// Verify stream is open.
			if (myBackend == null)
				{
				throw new IOException
					("org.jpwg.jini.print.data.RmiReader.skip(): stream closed");
				}
	
			// Skip at most Integer.MAX_VALUE characters.
			return doRead (null, 0, (int) Math.min (n, Integer.MAX_VALUE));
			}
		}

	/**
	 * Close the stream. Once a stream has been closed, further 
	 * <CODE>read()</CODE>, <CODE>skip()</CODE>, <CODE>ready()</CODE>, 
	 * <CODE>mark()</CODE>, or <CODE>reset()</CODE> invocations will throw an 
	 * IOException. Closing a previously-closed stream, however, has no effect. 
	 *
	 * @exception  IOException
	 *     Thrown if an I/O error occurred. The IOException may be a 
	 *     RemoteException indicating a remote error occurred. 
	 */
	public void close()
		throws IOException
		{
		synchronized (lock)
			{
			if (myBackend != null)
				{
				myBackend.close();
				myBackend = null;
				}
			}
		}

	}