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

package org.jpwg.jini.print.data;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.Reader;
import java.io.UnsupportedEncodingException;

import java.rmi.RemoteException;

import javax.print.attribute.DocAttributeSet;

import javax.print.DocFlavor;

/**
 * Class InputStreamDoc is an implementation of interface {@link 
 * net.jini.print.data.Doc Doc} that provides the bytes read from an input 
 * stream as a piece of print data in a doc flavor with MIME type given by the 
 * client and representation class name = <CODE>"java.io.InputStream"</CODE>. 
 * The {@link #getPrintData() <CODE>getPrintData()</CODE>} method returns an 
 * InputStream, and the return value can be cast from class Object to class 
 * InputStream. 
 * <P>
 * To make the input stream available to a potentially remote Jini Print Service 
 * instance, class InputStreamDoc makes use of a remote input stream (class 
 * {@link RmiInputStream RmiInputStream}) under the hood. 
 * <P>
 * To provide the contents of a local file with a given name as print data for a 
 * Print Job, use an InputStreamDoc constructed as follows: 
 * <PRE>
 *     String theFileName = ...;
 *     FileInputStream theFileInputStream =
 *         new FileInputStream (theFileName);
 *     String theMimeType = ...;
 *     DocAttributeSet theAttributes = ...;
 *     InputStreamDoc theDoc =
 *         new InputStreamDoc
 *             (new BufferedInputStream (theFileInputStream),
 *              theMimeType,
 *              theAttributes);
 * </PRE>
 * The BufferedInputStream is added on top of the FileInputStream to read the 
 * file in chunks, thus improving efficiency. 
 * <P>
 * Once the file is open, there is the possibility that the Print Job that is 
 * reading the file will not close the file due to a distributed failure. Thus, 
 * consider whether you need to provide a lease along with the doc (see 
 * interface {@link net.jini.print.data.LeasedDoc LeasedDoc} and class {@link 
 * LeasedInputStreamDoc LeasedInputStreamDoc}). For example, you could close the 
 * file when the lease expires or is canceled. 
 * <P>
 * To provide the contents of a local file denoted by a {@link java.io.File 
 * File} object as print data for a Print Job, use an InputStreamDoc constructed 
 * as follows: 
 * <PRE>
 *     File theFile = ...;
 *     FileInputStream theFileInputStream =
 *         new FileInputStream (theFile);
 *     String theMimeType = ...;
 *     DocAttributeSet theAttributes = ...;
 *     InputStreamDoc theDoc =
 *         new InputStreamDoc
 *             (new BufferedInputStream (theFileInputStream),
 *              theMimeType,
 *              theAttributes);
 * </PRE>
 * To provide the contents of a file at a URL address (denoted by a {@link 
 * java.net.URL URL} object) as print data for a Print Job, use an 
 * InputStreamDoc constructed as follows: 
 * <PRE>
 *     URL theURL = ...;
 *     String theMimeType = ...;
 *     DocAttributeSet theAttributes = ...;
 *     InputStreamDoc theDoc =
 *         new InputStreamDoc
 *             (new BufferedInputStream (theURL.openStream()),
 *              theMimeType,
 *              theAttributes);
 * </PRE>
 * Whenever you use an InputStreamDoc object, you open an input stream to read 
 * the file or other print data source locally on the client. The InputStreamDoc 
 * object then passes that input stream to the Print Job as the print data 
 * representation object. Thus, in the case of a URL, the printer will go 
 * through the client to get the print data; the printer will not access the URL 
 * directly. (For a {@link net.jini.print.data.Doc Doc} object where the printer 
 * accesses a URL directly, see class {@link URLDoc URLDoc}.) 
 * <P>
 * <B>DRAFT STANDARD VERSION 1.0 (23-MAY-2000)</B>
 *
 * @author  Alan Kaminsky
 * @version 18-May-2000
 */
public class InputStreamDoc
	extends AbstractDoc
	{

// Hidden data members.

	/**
	 * Original input stream. This field is <I>not</I> serialized. In a remote 
	 * JVM, this field is null. A remote input stream wrapping the original 
	 * input stream is stored in the superclass as the print data representation 
	 * object. 
	 */
	private transient InputStream myOriginalInputStream;

// Exported constructors.

	/**
	 * Construct an input stream doc object. 
	 *
	 * @param  theData        Byte stream to be printed.
	 * @param  theMimeType    MIME media type string giving the format of
	 *                        <CODE>theData</CODE>.
	 * @param  theAttributes  Unmodifiable set of printing attributes, or null
	 *                        to request the Print Job's default values. 
	 *
	 * @exception  NullPointerException
	 *     (unchecked exception) Thrown if <CODE>theData</CODE> is null or
	 *     <CODE>theMimeType</CODE> is null.
	 * @exception  IllegalArgumentException
	 *     (unchecked exception) Thrown if <CODE>theMimeType</CODE> does not 
	 *     obey the syntax for a MIME media type string.
	 * @exception  RemoteException
	 *     Thrown if a remote error occurred while constructing the hidden 
	 *     remote input stream. 
	 */
	public InputStreamDoc
		(InputStream theData,
		 String theMimeType,
		 DocAttributeSet theAttributes)
		throws RemoteException
		{
		super
			(new DocFlavor.INPUT_STREAM (theMimeType),
			 new RmiInputStream (verify (theData)),
			 theAttributes);
		myOriginalInputStream = theData;
		}

	private static InputStream verify
		(InputStream theData)
		{
		if (theData == null)
			{
			throw new NullPointerException
				("org.jpwg.jini.print.data.InputStreamDoc.InputStreamDoc(): theData is null");
			}
		return theData;
		}

// Exported operations inherited and overridden from class AbstractDoc.

	/**
	 * Obtain the print data representation object that contains this doc 
	 * object's piece of print data in the format corresponding to the supported 
	 * doc flavor. The <CODE>getPrintData()</CODE> method returns a 
	 * java.io.InputStream, and the return value can be cast from class Object 
	 * to class java.io.InputStream. 
	 * <P>
	 * If this doc object is sent to a remote location, 
	 * <CODE>getPrintData()</CODE> will return a remote input stream that allows 
	 * the remote Print Job to read the original input stream over RMI. On the 
	 * local JVM that created this doc object, <CODE>getPrintData()</CODE> will 
	 * return the original input stream, allowing the Print Job to read the 
	 * original input stream directly. 
	 *
	 * @return  Print data representation object. 
	 *
	 * @exception  IOException
	 *     Thrown if the representation class is a stream and there was an I/O 
	 *     error while constructing the stream. 
	 */
	public Object getPrintData()
		throws IOException
		{
		return
			myOriginalInputStream == null ?
				super.getPrintData() :
				myOriginalInputStream;
		}

	/**
	 * Obtain a reader for extracting character print data from this doc. For 
	 * this method to work, this doc's flavor must have a MIME 
	 * media type of <CODE>"text"</CODE> with an optional <CODE>"charset"</CODE> 
	 * parameter; the default character set is UTF-8. This doc's print data 
	 * representation object is obtained, then a reader for reading the print 
	 * data from the print data representation object as a stream of characters 
	 * is created and returned.
	 * <P>
	 * Note that the reader layered on top of this doc's print data 
	 * representation object (input stream) will be created and will execute in 
	 * the caller's Java Virtual Machine (JVM). If this doc is being used in a 
	 * remote JVM from where it was created, be aware that this method will 
	 * return a local reader layered on top of the remote input stream. 
	 *
	 * @return  Reader for reading the print data characters from this doc. If a
	 *          reader cannot be created because this doc does not meet the 
	 *          criteria stated above, null is returned. 
	 *
	 * @exception  UnsupportedEncodingException
	 *     Thrown if a plain text doc's character set is not supported.
	 * @exception  IOException
	 *     Thrown if there was an I/O error while creating the reader.
	 */
	public Reader getReaderForText()
		throws UnsupportedEncodingException, IOException
		{
		DocFlavor flavor = getDocFlavor();
		if (flavor.getMediaType().equals ("text"))
			{
			String encoding =
				(String) (flavor.getParameter ("charset"));
			return
				new InputStreamReader
					((InputStream) getPrintData(),
					 encoding == null ? "utf-8" : encoding);
			}
		else
			{
			return null;
			}
		}

	/**
	 * Obtain an input stream for extracting byte print data from this doc. This 
	 * method merely returns the same print data representation object (input 
	 * stream) that {@link #getPrintData() <CODE>getPrintData()</CODE>} returns.
	 *
	 * @return  Input stream for reading the print data bytes from this doc.
	 *
	 * @exception  IOException
	 *     Thrown if there was an I/O error while creating the input stream.
	 */
	public InputStream getStreamForBytes()
		throws IOException
		{
		return (InputStream) getPrintData();
		}

	}