// file: $isip/class/java/AudioCircularBuffer/AudioCircularBuffer.java
// version: $Id: AudioCircularBuffer.java,v 1.6 2004/07/21 16:15:35
// stanley Exp $
//

// import java's libraries.
//
import java.lang.*;
import java.nio.BufferOverflowException;

/**
 * Implements the circular buffer producer/consumer model for any java
 * object
 */
public class AudioCircularBuffer {
    
    //------------------------------------------------------------------------
    //
    // public constants
    //
    //------------------------------------------------------------------------
   
    /**
     * The default size for a circular object buffer.
     */
    public final static int DEFAULT_SIZE = 1024;
    
    /**
     * A buffer that will grow as things are added.
     */
    public final static int INFINITE_SIZE = -1;
    
    //-----------------------------------------------------------------------
    //
    // protected data
    //
    //-----------------------------------------------------------------------
    
    /**
     * The circular buffer. The actual capacity of the buffer is one
     * less than the actual length of the buffer so that an empty and
     * a full buffer can be distinguished.  an empty buffer will have
     * the readpostion and the writeposition equal to each other.  a
     * full buffer will have the writeposition one less than the
     * readpostion. There are two important indexes into the buffer:
     * the readposition, and the writeposition. the objects available
     * to be read go from the readposition to the writeposition,
     * wrapping around the end of the buffer.  the space available for
     * writing goes from the write position to one less than the
     * readposition, wrapping around the end of the buffer.
     */
    protected Object[] buffer_d;
    
    /**
     * Index of the first object available to be read.
     */
    protected volatile int read_position_d = 0;
    
    /**
     * Index of the first object available to be written.
     */
    protected volatile int write_position_d = 0;
    
    /**
     * This gives the current position of the pointer. Initially it is
     * set to be same as that of the read pointer.
     */
    protected volatile int curr_position_d = 0;
    
    /**
     * If this buffer is infinite (should resize itself when full)
     */
    protected volatile boolean infinite_d = false;
    
    /**
     * True if a write to a full buffer should block until the buffer
     * has room, false if the write method should throw an ioexception
     */
    protected boolean blocking_write_d = true;
        
    /**
     * True when no more input is coming into this buffer.  At that
     * point reading from the buffer may return  null if the buffer
     * is empty, otherwise a read will block until an Object is available.
     */
    protected boolean input_done_d = false;
    
    //-------------------------------------------------------------------------
    //
    // public methods
    //
    //-------------------------------------------------------------------------
    
    /**
     * Constructor for the class.  Create a new buffer with a default
     * capacity.  Writing to a full buffer will block until space is
     * available rather than throw an exception.
     */
    public AudioCircularBuffer() {
	this (DEFAULT_SIZE, true);
    }
    
    /**
     * Constructor for the class.  Create a new buffer with given
     * capacity.  Writing to a full buffer will block until space is
     * available rather than throw an exception.  <p> Note that the
     * buffer may reserve some Objects for special purposes and
     * capacity number of Objects may not be able to be written to the
     * buffer.  <p> Note that if the buffer is of INFINITE_SIZE it
     * will neither block or throw exceptions, but rather grow without
     * bound.
     *
     * @param size_a desired capacity of the buffer in Objects or
     * AudioCircularBuffer.INFINITE_SIZE.
     */
    public AudioCircularBuffer(int size_a) {
	this (size_a, true);
    }
    
    /**
     * Constructor for the class.  Create a new buffer with a default
     * capacity and given blocking behavior.
     *
     * @param blocking_write_a true writing to a full buffer should
     * block until space is available, false if an exception should be
     * thrown instead.
     */
    public AudioCircularBuffer(boolean blocking_write_a) {
	this (DEFAULT_SIZE, blocking_write_a);
    }
    
    /**
     * Constructor for the class.  Create a new buffer with the given
     * capacity and blocking behavior.  Note that the buffer may
     * reserve some Objects for special purposes and capacity number
     * of Objects may not be able to be written to the buffer. Note
     * that if the buffer is of INFINITE_SIZE it will neither block or
     * throw exceptions, but rather grow without bound.
     *
     * @param size_a desired capacity of the buffer in Objects or
     * AudioCircularBuffer.INFINITE_SIZE.
     * @param blocking_write_a true writing to a full buffer should
     * block until space is available, false if an exception should be
     * thrown instead.
     */
    public AudioCircularBuffer(int size_a, boolean blocking_write_a) {
	if (size_a == INFINITE_SIZE) {
	    
	    // if the size_a is infinite size set the circular buffer
	    // length to default size
	    // 
	    buffer_d = new Object[DEFAULT_SIZE];
	    infinite_d = true;
	} 
	else {
	    
	    // else set the size to the user defined size
	    //
	    buffer_d = new Object[size_a];
	    infinite_d = false;
	}

	this.blocking_write_d = blocking_write_a;
    }
    
    //-------------------------------------------------------------------------
    //
    // class-specific public methods:
    //  get methods
    //
    //-------------------------------------------------------------------------
    
    /**
     * Get an Object from the circular buffer. The offset gives the
     * distance of the index from the current pointer.
     *
     * @param o_a an object 
     * @param offset_a the index number from the current pointer.
     *
     * @return integer value containing the available objects to read
     * in the circular buffer.
     */
    public boolean getElement(Object o_a, int offset_a) {
	
	if ((offset_a > 0 && offset_a > getNumForward()) || 
	    (offset_a < 0 && offset_a < (-1 * getNumBackward()))) {
	    System.out.println("Exception in getElement() method");
	    return false;
	}
	
	// get the index whose contents should be read
	//
	int index = curr_position_d + offset_a;
	
	// get the capacity of the circular buffer
	//
	int capacity = getCapacity();
	while(index > (capacity - 1)) {
	    index = index - capacity;
	}
	
	// assign the value at that index to the Object
	//
	o_a = buffer_d[index]; 
	return true;
    }
    
    /**
     * Get an array of Objects from the circular buffer. The offset
     * gives the distance of the index from the current pointer.
     *
     * @param buff_a an array of objects
     * @param num_elem_a the number of elements that should be got
     * starting from the offset_a
     * @param offset_a the index number from the current pointer.
     *
     * @return integer value containing the available objects to read
     * in the circular buffer.
     */
    public boolean getElement(Object[] buff_a, int num_elem_a, 
			      int offset_a) {
	
	buff_a = new Object[num_elem_a];
	
	// loop over until you get the number of elements requested by
	// the user starting from the offset_a
	//
	for (int i = 0; i < num_elem_a; i++) {
	    getElement(buff_a[i], offset_a);
	    offset_a++;
	}
	return true;
    }
	
    /**
     * Get number of Objects that are available to be read.  <p>
     * Note that the number of Objects available plus the number
     * of Objects free may not add up to the capacity of this
     * buffer, as the buffer may reserve some space for other
     * purposes.
     *
     * @return integer value containing the available objects to read
     * in the circular buffer.
     */
    public int getNumElements(){
	synchronized (this){
	    return available();
	}
    }
    
    /**
     * Get number of valid Objects in front of the current pointer
     * (This doesnot include the current pointer).
     *
     * @return integer value indicating the number of Objects that
     * are before the current pointer.
     */
    public int getNumForward() {
	
	int num = 0;
	
	// if the circular buffer is empty return zero
	//
	if (isEmpty()) {
	    num = 0;
	}
	
	// if the current pointer and the read pointer are at the
	// same position
	//
	else if (curr_position_d == read_position_d) {
	    num = getNumElements() - 1;
	}
	else {
	    int capacity = getCapacity();
	    num = (write_position_d - curr_position_d + capacity) % capacity;
	}
	return num;
    }
    
    /**
     * Get number of valid Objects behind (lag of) the current pointer
     * (This doesnot include the current pointer).
     *
     * @return integer value indicating the number of Objects that
     * are behind the current pointer.
     */
    public int getNumBackward() {
	
	int num = 0;
	
	// if the circular buffer is empty return zero
	//
	if (isEmpty()) {
	    num = 0;
	}
	
	// if the current pointer and the write pointer are at the
	// same position
	//
	else if (curr_position_d == write_position_d) {
	    num = getNumElements() -1;
	}
	else {
	    int capacity = getCapacity();
	    num = (curr_position_d - read_position_d + capacity) % capacity;
	}
	return num;
    }
    
    /**
     * Get the capacity of this buffer.  <p> Note that the number
     * of Objects available plus the number of Objects free may
     * not add up to the capacity of this buffer, as the buffer
     * may reserve some space for other purposes.
     *
     * @return the size in Objects of this buffer
     */
    public int getCapacity() {
	synchronized (this) {
	    return buffer_d.length;
	}
    }
    
    //-----------------------------------------------------------------------
    //
    // class-specific other public methods:
    //  
    //
    //------------------------------------------------------------------------
	
    /**
     * Make this buffer ready for reuse.  The contents of the
     * buffer will be cleared and the streams associated with this
     * buffer will be reopened if they had been closed.
     *
     * @return void
     */
    public boolean clear() {
	synchronized (this) {
	    read_position_d = 0;
	    write_position_d = 0;
	    curr_position_d = 0;
	    input_done_d = false;
	}
	return true;
    }
    
    
    /**
     * Get the number of Objects this buffer has free for writing.
     * <p> Note that the number of Objects available plus the number
     * of Objects free may not add up to the capacity of this buffer,
     * as the buffer may reserve some space for other purposes.
     *
     * @return the available space in Objects of this buffer
     */
    public int getSpaceLeft() {
	synchronized (this) {
	    return spaceLeft();
	}
    }    
    
    /**
     * Check whether the circular buffer is full or not.  This
     * function internally calls the spaceleft() method and if the
     * spaceleft is zero then returns true.
     *
     * @return a boolean value indicating whether the circular buffer
     * is empty or not.
     */
    public boolean isFull() {
	
	// if the circular buffer is empty return false
	//
	if (isEmpty()) {
	    return false;
	}
	
	// if the space left in the circular buffer is zero then
	// return true
	//
	else if (spaceLeft() == 0) {
	    return true;
	}
	return false;
    }
    
    /**
     * Check whether the buffer is empty.  If the read and the write
     * pointer are both zero then the circular buffer is empty.
     *
     * @return a boolean value indicating whether the circular buffer
     * is empty or not.
     */	 
    public boolean isEmpty() {
	
	// if the read and the write pointer are the same then there
	// is no data to be read inbetween them. So the circular
	// buffer is empty.
	//
	if (read_position_d == write_position_d) {
	    return true;
	}
	else {
	    return false;
	}
    }
   
    
    /**
     * This function is used to indicate the status of the circular
     * buffer. It indicates whether the producer is still writing to
     * the buffer.
     *
     * @return returns a boolean value indicating whether the producer
     * is still writing to the buffer or not.
     */
    public boolean isDone() {

	return input_done_d;
    }


    /**
     * Get a single Object from this buffer.  This method should be
     * called by the consumer.  This method will block until a Object
     * is available or no more objects are available.
     *
     * @return The Object read, or null if there are no more objects
     *
     * @throws InterruptedException if the thread is interrupted while
     * waiting.
     */
    public Object read() throws InterruptedException {
	while (true) {
	    synchronized (this) {
		int available = available();
		if (available > 0) {
		    Object result = buffer_d[read_position_d];
		    read_position_d++;
		    if (read_position_d == buffer_d.length) {
			read_position_d = 0;
		    }
		    read_position_d = curr_position_d;
		    return result;
		} else if (input_done_d) {
		    return null;
		}
	    }
	    Thread.sleep(100);
	}
    }
    
    /**
     * Get Objects into an array from this buffer.  This method should
     * be called by the consumer.  This method will block until some
     * input is available, or there is no more input.
     *
     * @param buf_a destination buffer.
     *
     * @return the number of Objects read, or -1 there will be no more
     * objects available.
     */
    public int read(Object[] buf_a) {
	return read(buf_a, 0, buf_a.length);
    }
    
    /**
     * Get Objects into a portion of an array from this buffer.  This
     * method should be called by the consumer.  This method will
     * return zero if there is nothing available to read.
     *
     * @param buf_a destination buffer.
     * @param off_a offset at which to start storing Objects.
     * @param len_a maximum number of Objects to read.
     *
     * @return the number of Objects read, or -1 there will be no more
     * objects available.
     */
    public int read(Object[] buf_a, int off_a, int len_a) {
	synchronized (this) {
	    
	    // get the available number of data to be read
	    //
	    int available = available();
	    
	    if (available > 0) {
		
		// get the minimum of the two arguments
		//
		int length = Math.min(len_a, available);
		int firstLen = Math.min(length, buffer_d.length - read_position_d);
		int secondLen = length - firstLen;
		System.arraycopy(buffer_d, read_position_d, buf_a, off_a, firstLen);
		if (secondLen > 0) { 
		    System.arraycopy(buffer_d, 0, buf_a, off_a+firstLen,  secondLen);
		    read_position_d = secondLen;
		} 
		else {
		    read_position_d += length;
		}
		
		// if the read pointer is equal to the buffer length,
		// it means the read pointer has reached the end of
		// the circular buffer and so set the read pointer to
		// the beginning of the buffer.
		//
		if (read_position_d == buffer_d.length) {
		    read_position_d = 0;
		}
		
		// set the read pointer to current pointer
		//
		curr_position_d = read_position_d;
		return length;
	    }
	    else if (available == 0) {
		return 0;
	    }
	    
	}
	return -1;
    }
    
    
    /**
     * Skip Objects.  This method should be used by the consumer when
     * it does not care to examine some number of Objects.  This
     * method will block until some Objects are available, or there
     * will be no more Objects available.
     *
     * @param n_a the number of Objects to skip
     *
     * @return the number of Objects actually skipped
     *
     * @throws IllegalArgumentException if n is negative.
     * @throws InterruptedException if the thread is inturrupted while
     * waiting.
     */
    public long skip(long n_a) throws InterruptedException, 
				      IllegalArgumentException {
	while (true) {
	    synchronized (this) {
		
		// get the available number of data to be read
		//
		int available = available();
		if (available > 0) {
		    
		    // get the minimum of the two arguments
		    //
		    int length = Math.min((int)n_a, available);
		    int firstLen = Math.min(length, buffer_d.length - read_position_d);
		    int secondLen = length - firstLen;
		    if (secondLen > 0) {
			read_position_d = secondLen;
		    } 
		    else {
			read_position_d += length;
		    }
		    
		    // if the read pointer is equal to the buffer
		    // length, it means the read pointer has reached
		    // the end of the circular buffer and so set the
		    // read pointer to the beginning of the buffer.
		    //
		    if (read_position_d == buffer_d.length) {
			read_position_d = 0;
		    }
		    return length;
		
		    // if the recording is zero and there are no
		    // available data then return zero
		    //
		} 
		else if (input_done_d) {
		    return 0;
		}
	    }
	    Thread.sleep(100);
	}
    }
    
    /**
     * This method should be used by the producer to signal to the
     * consumer that the producer is done producing objects and that
     * the consumer should stop asking for objects once it has used up
     * buffered objects. Once the producer has signaled that it is
     * done, further write() invocations will cause an
     * IllegalStateException to be thrown. Calling done() multiple
     * times, however, has no effect.
     */
    public boolean done() { 
	synchronized (this) {
	    
	    // set the input_done_d to true indicating the end of
	    // recording
	    //
	    input_done_d = true;
	}
	return true;
    }
    
    /**
     * Fill this buffer with array of Objects.  This method should be
     * called by the producer.  If the buffer allows blocking writes,
     * this method will block until all the data has been written
     * rather than throw a BufferOverflowException.
     *
     * @param buf_a Array of Objects to be written
     *
     * @throws BufferOverflowException if buffer does not allow
     * blocking writes and the buffer is full.  If the exception is
     * thrown, no data will have been written since the buffer was set
     * to be non-blocking.
     * @throws IllegalStateException if done() has been called.
     * @throws InterruptedException if the write is interrupted.
     */
    public void write(Object[] buf_a) throws BufferOverflowException, 
					     IllegalStateException, 
					     InterruptedException {
	write(buf_a, 0, buf_a.length);
    }
    
    /**
     * Fill this buffer with a portion of an array of Objects.  This
     * method should be called by the producer.  If the buffer allows
     * blocking writes, this method will block until all the data has
     * been written rather than throw an IOException.
     *
     * @param buf_a array of Objects
     * @param off_a offset from which to start writing Objects
     * @param len_a number of Objects to write
     *
     * @throws BufferOverflowException if buffer does not allow
     * blocking writes and the buffer is full.  If the exception is
     * thrown, no data will have been written since the buffer was set
     * to be non-blocking.
     * @throws IllegalStateException if done() has been called.
     * @throws InterruptedException if the write is interrupted.
     */
    public void write(Object[] buf_a, int off_a, int len_a) throws 
	BufferOverflowException, IllegalStateException,	InterruptedException {
	while (len_a > 0) {
	    synchronized (AudioCircularBuffer.this) {
		
		int spaceLeft = spaceLeft();
		
		// if the length of the circular buffer is infinite
		// and the spaceleft is less than what needs to be
		// written then resize the buffer
		//
		while (infinite_d && spaceLeft < len_a) {
		    resize();
		    spaceLeft = spaceLeft();
		}
		
		// if not blocking write and the spaceleft is less
		// than the length of the buffer to be written 
		//
		if (!blocking_write_d && spaceLeft < len_a) throw new BufferOverflowException();
		int realLen = Math.min(len_a, spaceLeft);
		int firstLen = Math.min(realLen, buffer_d.length - 
					write_position_d);
		int secondLen = Math.min(realLen - firstLen, 
					 buffer_d.length - read_position_d - 1);
		int written = firstLen + secondLen;
		
		// array copy the buffer into the circular buffer 
		// 
		if (firstLen > 0) {
		    System.arraycopy(buf_a, 
				     off_a, 
				     buffer_d, 
				     write_position_d, 
				     firstLen);
		}
		if (secondLen > 0) {
		    System.arraycopy(buf_a, 
				     off_a + firstLen, 
				     buffer_d, 
				     0, 
				     secondLen);
		    write_position_d = secondLen;
		} 
		else {
		    write_position_d += written;
		}
		if (write_position_d == buffer_d.length) {
		    write_position_d = 0;
		}
		off_a += written;
		len_a -= written;
	    }
	    
	    // if more data needs to be written and there is no space
	    // left in the circular buffer then sleep for sometime
	    // hoping that the data from the circular buffer will be
	    // written and there will be space to write the data to
	    // the circular buffer
	    // 
	    if (len_a > 0) {
		Thread.sleep(100);
	    }
	}
    }
		 
    /**
     * Add a single Object to this buffer.  This method should be
     * called by the producer.  If the buffer allows blocking writes,
     * this method will block until all the data has been written
     * rather than throw an IOException.
     *
     * @param o_a object to be written.
     *
     * @throws BufferOverflowException if buffer does not allow
     * blocking writes and the buffer is full.  If the exception is
     * thrown, no data will have been written since the buffer was set
     * to be non-blocking.
     * @throws IllegalStateException if done() has been called.
     * @throws InterruptedException if the write is interrupted.
     */
    public void write(Object o_a) throws BufferOverflowException, 
					 IllegalStateException, 
					 InterruptedException {
	boolean written = false;
	while (!written) {
	    synchronized (AudioCircularBuffer.this) {
		if (input_done_d) throw new IllegalStateException("AudioCircularBuffer.done() has been called," 
								  + "AudioCircularBuffer.write() failed.");
		int spaceLeft = spaceLeft();
		
		// if the length of the circular buffer is infinite
		// and the spaceleft is less than one then resize the
		// circular buffer
		//
		while (infinite_d && spaceLeft < 1) {
		    resize();
		    spaceLeft = spaceLeft();
		}
		
		// if the circular buffer is full then throw an buffer
		// overflow exception
		//
		if (!blocking_write_d && spaceLeft < 1) throw new BufferOverflowException();
		
                // if space is left then write to the circular buffer
		//
		if (spaceLeft > 0) {
		    buffer_d[write_position_d] = o_a;
		    write_position_d++;
		    if (write_position_d == buffer_d.length) {
			write_position_d = 0;
		    }
		    written = true;
		}
	    }
	    
	    // if data couldnot be written sleep for some time and try
	    // again
	    //
	    if (!written) {
		Thread.sleep(100);
	    }
	}
    }
   
    //-------------------------------------------------------------------------
    //
    // class-specific public methods:
    // position methods
    //
    //-------------------------------------------------------------------------
 
    /**
     * Check whether the buffer is empty.  If the read and the write
     * pointer are both zero then the circular buffer is empty.
     *
     * @return a boolean value indicating whether the circular buffer
     * is empty or not.
     */	 
    public boolean resetCurr() {

	// reset the current pointer position to that of the read
	// pointer's position
	//
	curr_position_d = read_position_d;
	return true;
    }
    
    
    /**
     * move the current pointer to the desired index.
     *
     * @param offset_a the offset which gives the number of indeces
     * the current pointer should be moved.
     *
     * @return a boolean value indicating whether the circular buffer
     * is empty or not.
     */	 
    public  boolean seekCurr(int offset_a) {
	
	long capacity = getCapacity();
	
	// current pointer should reside between read pointer and
	// write pointer
	//
	if ((offset_a > 0 && offset_a > getNumForward()) ||
	    (offset_a < 0 && offset_a < (-1 * getNumBackward()))) {
	    System.out.println("Exception in seekCurr method");
	    return false;
	}
	
	// compute
	//
	curr_position_d += offset_a;
	
	if (curr_position_d > capacity - 1) {
	    curr_position_d -= capacity;
	}
	else if ((long)curr_position_d < 0) {
	    curr_position_d += capacity;
	}
	return true;
    }
    
    /**
     * Move the read read pointer forward. The offset is given by the
     * num_read_a
     *
     * @param num_read_a the offset that gives the number of indeces
     * the read pointer should be moved.
     *
     * @return a boolean value indicating the status.
     */	 
    public boolean setRead(int num_read_a) {
	
	// read pointer can't move backward
	//
	if (num_read_a < 0) {
	    System.out.println("The read pointer cannot be moved backwards.");
	    return false;
	}
	
	if (getNumElements() == num_read_a) {
	    
	    // reset the circular buffer to be empty
	    //
	    clear();
	}
	else {
	    
	    // get capacity
	    //
	    int capacity = getCapacity();
	    
	    // compute
	    //
	    int tmp_read_idx = read_position_d + num_read_a;
	    
	    if (tmp_read_idx > capacity - 1) {
		tmp_read_idx -= capacity;
	    }
	    
	    // move current pointer if necessary to make sure it is
	    // still in the proper range
	    //
	    if (getNumBackward() < num_read_a) {
		curr_position_d = tmp_read_idx;
	    }
	    
	    // move the read pointer
	    //
	    read_position_d = tmp_read_idx;
	}
	
	return true;
    }
    
    //------------------------------------------------------------------------
    //
    // class-specific public methods:
    //  error handler code
    //
    //------------------------------------------------------------------------
    
    /**
     * Does the error handling whenever an exception gets thrown. It
     * prints the exception and also prints the trace of methods from
     * where the error originated.
     *
     * @param e an object of exception class
     *
     * @return a boolean value indicating status
     */    
    public boolean errorHandler(Exception e) {
	
	// print the Exception that occurred
	//
	System.out.println("Exception in ISIPAudioInterface: " + e);
	
	// print the the trace of methods from where the error
	// originated
	//
	e.printStackTrace();

	// exit gracefully
	//
	return true;
    }

    //-----------------------------------------------------------------------
    //
    // debug method for this class
    //
    //-----------------------------------------------------------------------
    
    /**
     * A method used to print out debug information for this class
     *
     * @return string to represent this object
     */
    
    public String toString() {
	
	// define a string 
	//
	String result = "The capacity of the circular buffer " + getCapacity() +"\n";
	result += "The read pointer position " + read_position_d + "\n";
	result += "The write pointer position " + write_position_d + "\n";
	result += "The current pointer position " + curr_position_d + "\n";
	result += "The bytes available to be read " + getNumElements() + "\n";
	result += "The spaceleft on the circular buffer " + getSpaceLeft() + "\n";
	return result;	
    }
    
    //-------------------------------------------------------------------------
    //
    // class-specific other private methods:
    //  
    //
    //-------------------------------------------------------------------------
    
    /**
     * double the size of the buffer.
     *
     * @return void
     */
    private void resize() {

	// create new buffer
	//
	Object[] newBuffer = new Object[buffer_d.length * 2];

	int available = available();

	if (read_position_d <= write_position_d) {
	    
	    // any space between the read and the first write needs to
	    // be saved.  In this case it is all in one piece.
	    //
	    int length = write_position_d - read_position_d;
	    System.arraycopy(buffer_d, read_position_d, newBuffer, 0, length);
	} 
	else {
	    int length1 = buffer_d.length - read_position_d;
	    System.arraycopy(buffer_d, read_position_d, newBuffer, 0, length1);
	    int length2 = write_position_d;
	    System.arraycopy(buffer_d, 0, newBuffer, length1, length2);
	}

	// point the buffer to new buffer
	//
	buffer_d = newBuffer;
	read_position_d = 0;
	write_position_d = available;
    }
    
    /**
     * Space available in the buffer which can be written. It can be
     * noted that the space of the write pointer is not taken into
     * account.
     *
     * @return integer value that returns the spaceleft in the
     * circular buffer.
     */
    private int spaceLeft() {
	if (write_position_d < read_position_d) {
	    
	    // any space between the first write and the read except
	    // one Object is available.  In this case it is all in one
	    // piece.
	    //
	    return (read_position_d - write_position_d - 1);
	} 
	else {
	    
	    // space at the beginning and end.
	    //
	    return ((buffer_d.length - 1) - 
		    (write_position_d - read_position_d));
	}
    }
    
    /**
     * Objects available for reading.
     *
     * @return integer value of the number of elements that need to be
     * read from the circular buffer.
     */
    private int available() {
	if (read_position_d <= write_position_d) {
	    
	    // any space between the first read and the first write is
	    // available.  In this case i is all in one piece.
	    //
	    return (write_position_d - read_position_d);
	} 
	else {
	    // space at the beginning and end.
	    //
	    return (buffer_d.length - (read_position_d - write_position_d));
	    }
    }

    //-------------------------------------------------------------------------
    //
    // main diagnostic method for this class
    //
    //-------------------------------------------------------------------------
    
    /**
     * Diagnostic method for this class
     *
     * @param args arguments from the command line
     */
    public static void main (String[] args) {
	
	// declare a circular buffer
	//
	AudioCircularBuffer cb_d = new AudioCircularBuffer(100);
	
	// create a Object buffer
	//
	Object [] temp_1 = new Object[99];
	
	// assign values to the elements in the buffer
	//
	for (int i = 0; i < 99; i++) {
	    
	    Integer temp = new Integer(i); 
	    temp_1[i] = (Object) temp;
	}
	
	try {
	    // write the buffer to the circular buffer
	    //
	    cb_d.write(temp_1);
	    
	}
	catch (Exception e) { 
	    
	    System.out.println("EXCEPTION DURING WRITE.");
	    
	}
	
	// print the debug information
	//
	System.out.println(cb_d);
	
	// check whether the circular buffer is full
	//
	if(cb_d.isFull()) {
	    System.out.println("The circular buffer is full.");
	}
	
	Object [] temp_2 = new Object[99];
	
	// read from the circular buffer
	//
	cb_d.read(temp_2);
	
	// print the debug information
	//
	System.out.println(cb_d);
	
	// check if the circular buffer is empty
	//
	if(cb_d.isEmpty()) {
	    System.out.println("The circular buffer is empty.");
	}
    }
}
