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

package org.jpwg.jini.print.data;

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

import java.rmi.server.UnicastRemoteObject;

import java.util.Iterator;

/**
 * Class IteratorMultiDoc is an implementation of interface {@link 
 * net.jini.print.data.MultiDoc MultiDoc} which is layered on top of an {@link 
 * java.util.Iterator Iterator} which is traversing a collection of docs. An 
 * IteratorMultiDoc contains a hidden remote backend object that provides access 
 * to the underlying iterator in an idempotent fashion. If an IteratorMultiDoc 
 * object is serialized and sent to a remote Java Virtual Machine (JVM), the 
 * actual iterator stays in its original JVM; only the IteratorMultiDoc object 
 * (with a remote reference to the hidden backend object) travels to the remote 
 * JVM. 
 * <P>
 * This implementation assumes that once an IteratorMultiDoc object is 
 * constructed on top of a given iterator, the iterator will not be accessed 
 * directly. If the underlying iterator is accessed directly, the 
 * IteratorMultiDoc object's behavior is undefined. 
 * <P>
 * <B>DRAFT STANDARD VERSION 1.0 (23-MAY-2000)</B>
 *
 * @author  Alan Kaminsky
 * @version 22-May-2000
 */
public class IteratorMultiDoc
	implements net.jini.print.data.MultiDoc
	{

// Hidden helper interface.

	/**
	 * Interface IteratorMultiDoc.Backend defines the interface for the hidden 
	 * backend object that provides access to the underlying Iterator object. 
	 */
	private static interface Backend
		extends Remote
		{

	// Exported operations.

		/**
		 * Obtain the doc at the given sequence number from the underlying 
		 * iterator. Successive calls must specify nondecreasing sequence 
		 * numbers, otherwise an IllegalStateException is thrown. 
		 *
		 * @param  seq  Sequence number of the doc to return. Docs are numbered
		 *              starting at 0.
		 *
		 * @return  Doc at the given sequence number, or null if there are no
		 *          more docs.
		 *
		 * @exception  IllegalStateException
		 *     (Unchecked exception) Thrown if <CODE>seq</CODE>'s value is less 
		 *     than the value it had on the previous call to 
		 *     <CODE>getDoc()</CODE>.
		 * @exception  ClassCastException
		 *     (Unchecked exception) Thrown if the underlying iterator contains 
		 *     an object that does not implement interface {@link 
		 *     net.jini.print.data.Doc}.
		 * @exception  RemoteException
		 *     Thrown if a remote error occurred.
		 */
		public net.jini.print.data.Doc getDoc
			(int seq)
			throws RemoteException;
 
		}

// Hidden helper class.

	/**
	 * Class IteratorMultiDoc.BackendImpl provides the remote implementation for 
	 * the hidden backend object that provides access to the underlying Iterator 
	 * object. 
	 */
	private static class BackendImpl
		extends UnicastRemoteObject
		implements Backend
		{

	// Hidden data members.

		/**
		 * Underlying iterator of Docs.
		 */
		private transient Iterator myIterator;
		/**
		 * Most recent Doc obtained from <CODE>myIterator</CODE>, or null if 
		 * there are no more Docs. 
		 */
		private transient net.jini.print.data.Doc myDoc = null;
		/**
		 * Sequence number of <CODE>myDoc</CODE>.
		 */
		private transient int mySeq = -1;

	// Exported constructors.

		/**
		 * Construct a new IteratorMultiDoc backend object that will access the 
		 * given iterator. It is assumed that all objects returned by the given 
		 * iterator's {@link java.util.Iterator#next() <CODE>next()</CODE>} 
		 * method are objects that implement interface {@link 
		 * net.jini.print.data.Doc net.jini.print.data.Doc}. 
		 *
		 * @param  theIterator  Iterator of Doc objects.
		 *
		 * @exception  NullPointerException
		 *     (unchecked exception) Thrown if <CODE>theIterator</CODE> is null.
		 * @exception  RemoteException
		 *     Thrown if a remote error occurred.
		 */
		public BackendImpl
			(Iterator theIterator)
			throws RemoteException
			{
			super();
			if (theIterator == null)
				{
				throw new NullPointerException
					("org.jpwg.jini.print.data.IteratorMultiDoc.BackendImpl.BackendImpl(): theIterator is null");
				}
			myIterator = theIterator;
			}

	// Exported operations.

		/**
		 * Obtain the doc at the given sequence number from the underlying 
		 * iterator. Successive calls must specify nondecreasing sequence 
		 * numbers, otherwise an IllegalStateException is thrown. 
		 *
		 * @param  seq  Sequence number of the doc to return. Docs are numbered
		 *              starting at 0.
		 *
		 * @return  Doc at the given sequence number, or null if there are no
		 *          more docs.
		 *
		 * @exception  IllegalStateException
		 *     (Unchecked exception) Thrown if <CODE>seq</CODE>'s value is less 
		 *     than the value it had on the previous call to 
		 *     <CODE>getDoc()</CODE>.
		 * @exception  ClassCastException
		 *     (Unchecked exception) Thrown if the underlying iterator contains 
		 *     an object that does not implement interface {@link 
		 *     net.jini.print.data.Doc net.jini.print.data.Doc}.
		 * @exception  RemoteException
		 *     Thrown if a remote error occurred.
		 */
		public net.jini.print.data.Doc getDoc
			(int seq)
			throws RemoteException
			{
			// Verify precondition on seq.
			if (seq < 0 || seq < mySeq)
				{
				throw new IllegalStateException();
				}

			// Advance through the underlying iterator until we hit the Doc with 
			// the given sequence number or we hit the end of the iteration. 
			if (mySeq < seq)
				{
				do
					{
					myDoc =
						myIterator.hasNext() ?
							(net.jini.print.data.Doc) myIterator.next() :
							null;
					++ mySeq;
					}
				while (myDoc != null && mySeq < seq);
				}

			// Return result.
			return myDoc;
			}
 
		}

// Hidden data members.

	/**
	 * Remote backend object.
	 * @serial
	 */
	private Backend myBackend;
	/**
	 * Current Doc object.
	 * @serial
	 */
	private net.jini.print.data.Doc myDoc;
	/**
	 * Sequence number of <CODE>myDoc</CODE>.
	 * @serial
	 */
	private int mySeq;
	/**
	 * Next MultiDoc object, or null if we haven't gone on to the next MultiDoc 
	 * object yet. 
	 * @serial
	 */
	private IteratorMultiDoc myNextMultiDoc;

// Exported constructors.

	/**
	 * Construct a new iterator multidoc object that provides access to the doc 
	 * objects from the given iterator. It is assumed that all objects returned 
	 * by the given iterator's {@link java.util.Iterator#next() 
	 * <CODE>next()</CODE>} method are objects that implement interface {@link 
	 * net.jini.print.data.Doc net.jini.print.data.Doc}. 
	 *
	 * @param  theIterator  Iterator of one or more Doc objects.
	 *
	 * @exception  NullPointerException
	 *     (unchecked exception) Thrown if <CODE>theIterator</CODE> is null.
	 * @exception  IllegalArgumentException
	 *     (unchecked exception) Thrown if <CODE>theIterator</CODE> is empty.
	 * @exception  ClassCastException
	 *     (Unchecked exception) Thrown if the first object in the given 
	 *     iterator does not implement interface {@link 
	 *     net.jini.print.data.Doc net.jini.print.data.Doc}. 
	 * @exception  RemoteException
	 *     Thrown if a remote error occurred while creating the hidden remote 
	 *     backend object. 
	 */
	public IteratorMultiDoc
		(Iterator theIterator)
		throws RemoteException
		{
		this (new BackendImpl (theIterator), 0);
		if (myDoc == null)
			{
			throw new IllegalArgumentException
				("org.jpwg.jini.print.data.IteratorMultiDoc.IteratorMultiDoc(): theIterator is empty");
			}
		}

// Hidden constructors.

	/**
	 * Construct a new iterator multidoc object with the given backend object 
	 * and doc sequence number. The Doc object with that sequence number is 
	 * filled in from the backend object.
	 *
	 * @param  theBackend  Backend object.
	 * @param  seq         Doc sequence number. 
	 *
	 * @exception  ClassCastException
	 *     (Unchecked exception) Thrown if the object at the given sequence 
	 *     number in the given iterator does not implement interface {@link 
	 *     net.jini.print.data.Doc net.jini.print.data.Doc}. 
	 * @exception  RemoteException
	 *     Thrown if a remote error occurred.
	 */
	private IteratorMultiDoc
		(Backend theBackend,
		 int seq)
		throws RemoteException
		{
		myBackend = theBackend;
		mySeq = seq;
		myDoc = myBackend.getDoc (mySeq);
		myNextMultiDoc = null;
		}

// Exported operations inherited and overridden from interface MultiDoc.

	/**
	 * Obtain the current doc object. Although the return type is interface 
	 * {@link javax.print.data.Doc javax.print.data.Doc} as inherited from the 
	 * superinterface method, the returned doc object is actually an instance of 
	 * the subinterface {@link Doc net.jini.print.data.Doc}. 
	 *
	 * @return  Current doc object.
	 */
	public javax.print.Doc getDoc()
		{
		return myDoc;
		}

	/**
	 * Go to the multidoc object that contains the next doc object in the 
	 * sequence of doc objects. Although the return type is interface {@link 
	 * javax.print.data.MultiDoc javax.print.data.MultiDoc} as inherited from 
	 * the superinterface method, the returned multidoc object is actually an 
	 * instance of the subinterface net.jini.print.data.MultiDoc. 
	 *
	 * @return  Multidoc object containing the next doc object, or null if there
	 *          are no further doc objects.
	 *
	 * @exception  ClassCastException
	 *     (Unchecked exception) Thrown if the next object from the underlying 
	 *     iterator does not implement interface {@link 
	 *     net.jini.print.data.Doc net.jini.print.data.Doc}. 
	 * @exception  RemoteException
	 *     Thrown if a remote error occurred.
	 */
	public javax.print.MultiDoc next()
		throws RemoteException
		{
		if (myNextMultiDoc == null)
			{
			myNextMultiDoc = new IteratorMultiDoc (myBackend, mySeq+1);
			}
		return myNextMultiDoc.myDoc == null ? null : myNextMultiDoc;
		}

	}