/*
 * HttpInputStream.java
 *
 * Brazil project web application toolkit,
 * export version: 2.3 
 * Copyright (c) 1999-2004 Sun Microsystems, Inc.
 *
 * Sun Public License Notice
 *
 * The contents of this file are subject to the Sun Public License Version 
 * 1.0 (the "License"). You may not use this file except in compliance with 
 * the License. A copy of the License is included as the file "license.terms",
 * and also available at http://www.sun.com/
 * 
 * The Original Code is from:
 *    Brazil project web application toolkit release 2.3.
 * The Initial Developer of the Original Code is: cstevens.
 * Portions created by cstevens are Copyright (C) Sun Microsystems, Inc.
 * All Rights Reserved.
 * 
 * Contributor(s): cstevens, suhler.
 *
 * Version:  2.2
 * Created by cstevens on 99/09/15
 * Last modified by suhler on 04/11/30 15:19:46
 *
 * Version Histories:
 *
 * 2.2 04/11/30-15:19:46 (suhler)
 *   fixed sccs version string
 *
 * 2.1 02/10/01-16:37:04 (suhler)
 *   version change
 *
 * 1.8 02/04/18-11:18:42 (suhler)
 *   changed the semantics of copyTo() to make guarantees about the number
 *   of bytes read from the stream.  This *shouldn't* break anything (I hope)
 *
 * 1.7 00/03/29-16:47:15 (cstevens)
 *   Streaming media didn't work because data from server wasn't flushed to client
 *   until the end of the request.
 *
 * 1.6 99/11/09-20:24:44 (cstevens)
 *   bugs revealed by writing tests.
 *
 * 1.5 99/10/14-13:01:39 (cstevens)
 *   Documentation.
 *
 * 1.4 99/10/08-16:56:06 (cstevens)
 *   allow passing the temporary buffer used for copying to the copyTo() method.
 *   Makes it easier to work with the Request.sendResponse(InputStream) method
 *   so that sendResponse can use the server-requested buffer size.
 *
 * 1.3 99/10/07-13:18:20 (cstevens)
 *   Documentation for HttpInputStream
 *
 * 1.2 99/09/15-14:52:08 (cstevens)
 *   import *;
 *
 * 1.2 99/09/15-14:39:36 (Codemgr)
 *   SunPro Code Manager data about conflicts, renames, etc...
 *   Name history : 1 0 util/http/HttpInputStream.java
 *
 * 1.1 99/09/15-14:39:35 (cstevens)
 *   date and time created 99/09/15 14:39:35 by cstevens
 *
 */

package sunlabs.brazil.util.http;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PushbackInputStream;

/**
 * This class is an input stream that provides added methods that are
 * of help when reading the result of an HTTP request.  By setting up
 * this input stream, the user can conveniently read lines of text and
 * copy the contents of an input stream to an output stream.
 * <p>
 * The underlying assumption of this class is that when reading the result
 * of an HTTP request, each byte in the input stream represents an 8-bit
 * ASCII character, and as such, it is perfectly valid to treat each byte
 * as a character.  Locale-based conversion is not appropriate in this
 * circumstance, so the <code>java.io.BufferedReader.readLine</code> method
 * should not be used.
 *
 * @author	Colin Stevens (colin.stevens@sun.com)
 * @version		2.2
 */
public class HttpInputStream
    extends FilterInputStream
{
    /**
     * The default size of the temporary buffer used when copying from an
     * input stream to an output stream.
     *
     * @see	#copyTo(OutputStream, int, byte[])
     */
    public static int defaultBufsize = 4096;

    /**
     * Creates a new HttpInputStream that reads its input from the
     * specified input stream.
     *
     * @param	in
     *		The underlying input stream.
     */
    public
    HttpInputStream(InputStream in)
    {
	super(in);
    }

    /**
     * Reads the next line of text from the input stream.
     * <p>
     * A line is terminated by "\r", "\n", "\r\n", or the end of the input
     * stream.  The line-terminating characters are discarded.
     *
     * @return	The next line from the input stream, or <code>null</code>
     *		if the end of the input stream is reached and no bytes
     *		were found.
     *
     * @throws	IOException if the underlying input stream throws an
     *		IOException while being read.
     */
    public String
    readLine()
	throws IOException
    {
	return readLine(Integer.MAX_VALUE);
    }

    /**
     * Reads the next line of text from the input stream, up to the
     * limit specified.
     * <p>
     * A line is terminated by "\r", "\n", "\r\n", the end of the input
     * stream, or when the specified number of characters have been read.
     * The line-terminating characters are discarded.  It is not possible
     * to distinguish, based on the result, between a line that was
     * exactly <code>limit</code> characters long and a line that was
     * terminated because <code>limit</code> characters were read.
     *
     * @return	The next line from the input stream, or <code>null</code>
     *		if the end of the input stream is reached and no bytes
     *		were found.
     *
     * @throws	IOException if the underlying input stream throws an
     *		IOException while being read.
     */
    public String
    readLine(int limit)
	throws IOException
    {
	StringBuffer sb = new StringBuffer();

	while (limit-- > 0) {
	    int ch = read();
	    if (ch == '\r') {
		ch = read();
		if (ch != '\n') {
		    if ((in instanceof PushbackInputStream) == false) {
			in = new PushbackInputStream(in);
		    }
		    ((PushbackInputStream) in).unread(ch);
		}
		break;
	    } else if (ch == '\n') {
		break;
	    } else if (ch < 0) {
		if (sb.length() == 0) {
		    return null;
		}
		break;
	    } else {
		sb.append((char) ch);
	    }
	}
	return sb.toString();
    }

    /**
     * Reads <code>buf.length</code> bytes from the input stream.  This
     * method reads repeatedly from the input stream until the specified
     * number of bytes have been read or the end of the input stream
     * is reached.
     * <p>
     * The standard <code>InputStream.read</code> method will generally
     * return less than the specified number of bytes if the underlying
     * input stream is "bursty", such as from a network source.  Sometimes
     * it is important to read the exact number of bytes desired.
     *
     * @param	buf
     *		Buffer in which the data is stored.  If buffer is of
     *		length 0, this method will return immediately.
     *
     * @return	The number of bytes read.  This will be less than
     *		<code>buf.length</code> if the end of the input stream was
     *		reached.
     *
     * @throws	IOException if the underlying input stream throws an
     *		IOException while being read.
     */
    public int
    readFully(byte[] buf)
	throws IOException
    {
	return readFully(buf, 0, buf.length);
    }

    /**
     * Reads the specified number of bytes from the input stream.  This
     * method reads repeatedly from the input stream until the specified
     * number of bytes have been read or the end of the input stream is
     * reached.
     * <p>
     * The standard <code>InputStream.read</code> method will generally
     * return less than the specified number of bytes if the underlying
     * input stream is "bursty", such as from a network source.  Sometimes
     * it is important to read the exact number of bytes desired.
     *
     * @param	buf
     *		Buffer in which the data is stored.
     *
     * @param	off
     *		The starting offset into the buffer.
     *
     * @param	len
     *		The number of bytes to read.
     *
     * @return	The number of bytes read.  This will be less than
     *		<code>len</code> if the end of the input stream was reached.
     *
     * @throws	IOException if the underlying input stream throws an
     *		IOException while being read.
     */
    public int
    readFully(byte[] buf, int off, int len)
	throws IOException
    {
	int total = 0;

	while (len > 0) {
	    int count = read(buf, off, len);
	    if (count < 0) {
		break;
	    }
	    total += count;
	    off += count;
	    len -= count;
	}

	return total;
    }

    /**
     * Copies bytes from this input stream to the specified output stream
     * until end of the input stream is reached.
     *
     * @param	out
     *		The output stream to copy the data to.
     *
     * @return	The number of bytes copied to the output stream.
     *
     * @throws	IOException if the underlying input stream throws an
     *		IOException while being read or if the output stream
     *		throws an IOException while being written.  It may not be
     *		possible to distinguish amongst the two conditions.
     */
    public int
    copyTo(OutputStream out)
	throws IOException
    {
	return copyTo(out, -1, null);
    }

    /**
     * Copies bytes from this input stream to the specified output stream
     * until the specified number of bytes are copied or the end of the
     * input stream is reached.
     *
     * @param	out
     *		The output stream to copy the data to.
     *
     * @param	len
     *		The number of bytes to copy, or < 0 to copy until the end
     *		of this stream.
     *
     * @return	The number of bytes copied to the output stream.
     *
     * @throws	IOException if the underlying input stream throws an
     *		IOException while being read or if the output stream
     *		throws an IOException while being written.  It may not be
     *		possible to distinguish amongst the two conditions.
     */
    public int
    copyTo(OutputStream out, int len)
	throws IOException
    {
	return copyTo(out, len, null);
    }

    /**
     * Copies bytes from this input stream to the specified output stream
     * until the specified number of bytes are copied or the end of the
     * input stream is reached.
     *
     * @param	out
     *		The output stream to copy the data to.
     *
     * @param	len
     *		The number of bytes to copy, or < 0 to copy until the end
     *		of this stream.
     *
     * @param	buf
     *		The buffer used to for holding the temporary results while
     *		copying data from this input stream to the output stream.
     *		May be <code>null</code> to allow this method copy in
     *		chunks of length <code>defaultBufsize</code>.
     *
     * @return	The number of bytes read from the input stream, and
     *		copied to the output stream.
     *
     * @throws	IOException if the underlying input stream throws an
     *		IOException while being read.
     */

    public int
    copyTo(OutputStream out, int len, byte[] buf)
	throws IOException
    {
	if (len < 0) {
	    len = Integer.MAX_VALUE;
	}
	if (buf == null) {
	    buf = new byte[Math.min(defaultBufsize, len)];
	}

	int total = 0;
	while (len > 0) {
	    int count = read(buf, 0, Math.min(buf.length, len));
	    if (count < 0) {
		break;
	    }
	    total += count;
	    len -= count;
	    try {
		out.write(buf, 0, count);
		out.flush();
	    } catch (IOException e) {
		break;
	    }
	}
	return total;
    }
}