/* 
 * E-XML Library:  For XML, XML-RPC, HTTP, and related.
 * Copyright (C) 2002-2008  Elias Ross
 * 
 * genman@noderunner.net
 * http://noderunner.net/~genman
 * 
 * 1025 NE 73RD ST
 * SEATTLE WA 98115
 * USA
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * $Id$
 */

package net.noderunner.http;

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

/**
 * A utility class for sending binary data to an HTTP server or client.
 * Example usage:
 * <pre>
 * HttpClient client = new RetryHttpClient("http://example.net");
 * InputStream fileIS = new FileInputStream("somefile.jpeg");
 * DataPoster dataPoster = new GeneralDataPoster(fileIS, -1);
 * RequestLine rl = new RequestLineImpl( ... );
 * ClientRequest request = new ClientRequestImpl(rl, dataPoster);
 *
 * client.writeRequest(request);
 * ClientResponse response = client.readResponse();
 * </pre>
 */
public class GeneralDataPoster implements DataPoster {

	/**
	 * Default copy buffer size to create upon
	 * initialization.  Default size is 1024 bytes.
	 */
	protected int DEFAULT_BUFFER_SIZE = 1024;

	private InputStream is;
	private int len;
	private byte buf[];
	private boolean sentOnce;

	/**
	 * Constructs a new, uninitialized <code>GeneralDataPoster</code>.
	 * The {@link #init init} method must be called before
	 * this object can be used.
	 */
	public GeneralDataPoster() {
	}

	/**
	 * Constructs a new <code>GeneralDataPoster</code> that outputs data
	 * from the specified stream.  Calls {@link #init init}.
	 */
	public GeneralDataPoster(InputStream is, int len) {
		init(is, len);
	}

	/**
	 * Sets the input stream to use and the number of bytes to send.
	 *
	 * @param is input stream to read for output
	 * @param len if zero, we do nothing, if &lt; 0, we send
	 * chunked data, if &gt; 0 we sent only <code>len</code> number of
	 * bytes
	 */
	public void init(InputStream is, int len) {
		if (is == null && len != 0)
			throw new IllegalArgumentException("Null InputStream with non-zero length");
		this.is = is;
		this.len = len;
		if (buf == null)
			buf = new byte[DEFAULT_BUFFER_SIZE];
	}

	/**
	 * Copies our input stream data to the supplied output stream.
	 * Does nothing if the number of bytes to send is zero.
	 *
	 * @throws IllegalHttpStateException if the input stream was never
	 * set
	 * @throws HttpException if the data was already read from our
	 * stream, and the input stream cannot be <code>reset</code>
	 */
	public void sendData(OutputStream os)
		throws IOException
	{
		if (os == null)
			throw new IllegalArgumentException("OutputStream is null");
		if (len == 0)
			return;
		if (sentOnce) {
			if (!is.markSupported())
				throw new HttpException("Cannot re-post data");
			is.reset();
		}
		sentOnce = true;
		if (is.markSupported()) {
			is.mark(len > 0 ? len : Integer.MAX_VALUE);
		}
		InputStream is2 = is;
		if (len < 0)
			os = new ChunkedOutputStream(os);
		else
			// we don't have to count anything
			is2 = new LimitedInputStream(is, len);

		int sent = 0;
		while (true) {
			int got = is2.read(buf);
			if (got == -1)
				break;
			os.write(buf, 0, got);
			sent += got;
		}
		os.flush();
		if (os instanceof ChunkedOutputStream)
			((ChunkedOutputStream)os).doneOutput();
		if (len > 0 && sent != len)
			throw new HttpException("Expected " + len + 
					" bytes, could send only " + sent);
	}

	/**
	 * Returns a debug string.
	 */
	public String toString() {
		return "GeneralDataPoster is=" + is + " len=" + len;
	}
}
