/*
 *   
 *
 * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation. 
 * 
 * This program 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
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt). 
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA 
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions. 
 */

package com.sun.midp.io;

import java.io.*;

/**
 * This class adds read buffering to the
 * <code>ConnectionBaseAdapter</code>.
 * <p>
 * Implements {@link InputStream#available()}, however a subclass must
 * specify a buffer size greater than 0 and override
 * {@link #readBytesNonBlocking(byte[], int, int)} in order to for available
 * to work properly.
 */
public abstract class BufferedConnectionAdapter extends ConnectionBaseAdapter {

    /**
     * The end of file flag.
     */
    protected boolean eof;

    /**
     * The internal buffer array where the data is stored.
     * When necessary, it may be replaced by another array
     * of a different size.
     */
    protected byte buf[];

    /**
     * The index one greater than the index of the last valid
     * byte in the buffer.
     * This value is always in the range
     * <code>0</code> through <code>buf.length</code>;
     * elements <code>buf[0]</code> through <code>buf[count-1]
     * </code>contain buffered input data obtained
     * from the underlying input stream.
     */
    protected int count;

    /**
     * The current position in the buffer. This is the index
     * of the next character to be read from the
     * <code>buf</code> array.
     * <p>
     * This value is always in the range <code>0</code>
     * through <code>count</code>. If it is less
     * than <code>count</code>, then <code>buf[pos]</code>
     * is the next byte to be supplied as input;
     * if it is equal to <code>count</code>, then
     * the  next <code>read</code> or <code>skip</code>
     * operation will require more bytes to be
     * read from the contained input stream.
     */
    protected int pos;

    /**
     * Initializes the connection.
     *
     * @param sizeOfBuffer size of the internal buffer or 0 for no buffer
     */
    protected BufferedConnectionAdapter(int sizeOfBuffer) {
        if (sizeOfBuffer > 0) {
            buf = new byte[sizeOfBuffer];
        }
    }

    /**
     * Reads up to <code>len</code> bytes of data from the input stream into
     * an array of bytes, blocks until at least one byte is available.
     * <p>
     * Do not use this method if <code>openInputStream</code> has been called
     * since the input stream may be buffering data.
     *
     * @param      b     the buffer into which the data is read.
     * @param      off   the start offset in array <code>b</code>
     *                   at which the data is written.
     * @param      len   the maximum number of bytes to read.
     * @return     the total number of bytes read into the buffer, or
     *             <code>-1</code> if there is no more data because the end of
     *             the stream has been reached.
     * @exception  IOException  if an I/O error occurs.
     */
    public int readBytes(byte b[], int off, int len) throws IOException {
        int bytesRead;

        if (count == 0) {
            if (eof) {
                return -1;
            }

            if (buf == null || len >= buf.length) {
                return nonBufferedRead(b, off, len);
            } else {
                int res = nonBufferedRead(buf, 0, buf.length);

                pos = 0;

                if (res <= 0) {
                    return res;
                } else {
                    count = res;
                }
            }
        }

        if (len > count) {
            len = count;
        }

        System.arraycopy(buf, pos, b, off, len);
        count -= len;
        pos   += len;
        return len;
    };

    /**
     * Reads up to <code>len</code> bytes of data from the input stream into
     * an array of bytes, but does not block if no bytes available. A subclass
     * should implement this to so the available method on the InputStream
     * will be useful.
     * Sets the <code>eof</code> field of the connection when the native read
     * returns -1.
     * <p>
     * The <code>readBytesNonBlocking</code> method of
     * <code>ConnectionBaseAdapter</code> does nothing and returns 0.
     *
     * @param      b     the buffer into which the data is read.
     * @param      off   the start offset in array <code>b</code>
     *                   at which the data is written.
     * @param      len   the maximum number of bytes to read.
     * @return     the total number of bytes read into the buffer, or
     *             <code>-1</code> if there is no more data because the end of
     *             the stream has been reached.
     * @exception  IOException  if an I/O error occurs.
     */
    protected int readBytesNonBlocking(byte b[], int off, int len)
        throws IOException {
        return 0;
    }

    /**
     * Returns the number of bytes that can be read (or skipped over) from
     * this input stream without blocking by the next caller of a method for
     * this input stream.  The next caller might be the same thread or
     * another thread.
     *
     * <p>The <code>available</code> method always returns <code>0</code> if
     * {@link #readBytesNonBlocking(byte[], int, int)} is
     * not overridden by the subclass or there is not buffer.
     *
     * @return     the number of bytes that can be read from this input stream
     *             without blocking.
     * @exception  IOException  if an I/O error occurs.
     */
    public int available() throws IOException {
        int bytesRead;

        if (buf == null) {
            return 0;
        }

        if (count > 0) {
            return count;
        }

        bytesRead = readBytesNonBlocking(buf, 0, buf.length);

        if (bytesRead == -1) {
            return 0;
        }
	
	/*
	 * Reset the current buffer position and count of bytes
	 * available. These variables must be reset to match
	 * the processing in readBytes.
	 */
	pos = 0;
        count = bytesRead;

        return count;
    }

    /**
     * Reads up to <code>len</code> bytes of data from the input stream into
     * an array of bytes, blocks until at least one byte is available.
     * Sets the <code>eof</code> field of the connection when there is
     * no more data in the stream to read.
     *
     * @param      b     the buffer into which the data is read.
     * @param      off   the start offset in array <code>b</code>
     *                   at which the data is written.
     * @param      len   the maximum number of bytes to read.
     * @return     the total number of bytes read into the buffer, or
     *             <code>-1</code> if there is no more data because the end of
     *             the stream has been reached.
     * @exception  IOException  if an I/O error occurs.
     */
    protected abstract int nonBufferedRead(byte b[], int off, int len)
        throws IOException;
}
