Generics Java

/*
 * Circular Object Buffer
 * Copyright (C) 2002-2004 Stephen Ostermiller
 * http://ostermiller.org/contact.pl?regarding=Java+Utilities
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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 for more details.
 *
 * See COPYING.TXT for details.
 */
/**
 * Implements the Circular Buffer producer/consumer model for Objects.
 * More information about this class is available from  * "http://ostermiller.org/utils/CircularObjectBuffer.html">ostermiller.org.
 * 


 * This class is thread safe.
 *
 * @see CircularCharBuffer
 * @see CircularByteBuffer
 *
 * @author Stephen Ostermiller http://ostermiller.org/contact.pl?regarding=Java+Utilities
 * @param  Type of object allowed in this circular buffer
 * @since ostermillerutils 1.00.00
 */
public class CircularObjectBuffer  {
  /**
   * The default size for a circular object buffer.
   *
   * @since ostermillerutils 1.00.00
   */
  private final static int DEFAULT_SIZE = 1024;
  /**
   * A buffer that will grow as things are added.
   *
   * @since ostermillerutils 1.00.00
   */
  public final static int INFINITE_SIZE = -1;
  /**
   * 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.
   *
   * @since ostermillerutils 1.00.00
   */
  protected ElementType[] buffer;
  /**
   * Index of the first Object available to be read.
   *
   * @since ostermillerutils 1.00.00
   */
  protected volatile int readPosition = 0;
  /**
   * Index of the first Object available to be written.
   *
   * @since ostermillerutils 1.00.00
   */
  protected volatile int writePosition = 0;
  /**
   * If this buffer is infinite (should resize itself when full)
   *
   * @since ostermillerutils 1.00.00
   */
  protected volatile boolean infinite = 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
   *
   * @since ostermillerutils 1.00.00
   */
  protected boolean blockingWrite = 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.
   *
   * @since ostermillerutils 1.00.00
   */
  protected boolean inputDone = false;
  /**
   * 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.
   *
   * @since ostermillerutils 1.00.00
   */
  public void clear(){
    synchronized (this){
      readPosition = 0;
      writePosition = 0;
      inputDone = false;
    }
  }
  /**
   * Get number of Objects that are available to be read.
   * 


   * 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
   *
   * @since ostermillerutils 1.00.00
   */
  public int getAvailable(){
    synchronized (this){
      return available();
    }
  }
  /**
   * Get the number of Objects this buffer has free for
   * writing.
   * 


   * 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
   *
   * @since ostermillerutils 1.00.00
   */
  public int getSpaceLeft(){
    synchronized (this){
      return spaceLeft();
    }
  }
  /**
   * Get the capacity of this buffer.
   * 


   * 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
   *
   * @since ostermillerutils 1.00.00
   */
  public int getSize(){
    synchronized (this){
      return buffer.length;
    }
  }
  @SuppressWarnings("unchecked") private ElementType[] createArray(int size){
    return (ElementType[]) new Object[size];
  }
  /**
   * double the size of the buffer
   *
   * @since ostermillerutils 1.00.00
   */
  private void resize(){
    ElementType[] newBuffer = createArray(buffer.length * 2);
    int available = available();
    if (readPosition <= writePosition){
      // any space between the read and
      // the first write needs to be saved.
      // In this case it is all in one piece.
      int length = writePosition - readPosition;
      System.arraycopy(buffer, readPosition, newBuffer, 0, length);
    } else {
      int length1 = buffer.length - readPosition;
      System.arraycopy(buffer, readPosition, newBuffer, 0, length1);
      int length2 = writePosition;
      System.arraycopy(buffer, 0, newBuffer, length1, length2);
    }
    buffer = newBuffer;
    readPosition = 0;
    writePosition = available;
  }
  /**
   * Space available in the buffer which can be written.
   *
   * @since ostermillerutils 1.00.00
   */
  private int spaceLeft(){
    if (writePosition < readPosition){
      // any space between the first write and
      // the read except one Object is available.
      // In this case it is all in one piece.
      return (readPosition - writePosition - 1);
    }
    // space at the beginning and end.
    return ((buffer.length - 1) - (writePosition - readPosition));
  }
  /**
   * Objects available for reading.
   *
   * @since ostermillerutils 1.00.00
   */
  private int available(){
    if (readPosition <= writePosition){
      // any space between the first read and
      // the first write is available.  In this case i
      // is all in one piece.
      return (writePosition - readPosition);
    }
    // space at the beginning and end.
    return (buffer.length - (readPosition - writePosition));
  }
  /**
   * Create a new buffer with a default capacity.
   * Writing to a full buffer will block until space
   * is available rather than throw an exception.
   *
   * @since ostermillerutils 1.00.00
   */
  public CircularObjectBuffer(){
    this (DEFAULT_SIZE, true);
  }
  /**
   * Create a new buffer with given capacity.
   * Writing to a full buffer will block until space
   * is available rather than throw an exception.
   * 


   * 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 desired capacity of the buffer in Objects or CircularObjectBuffer.INFINITE_SIZE.
   *
   * @since ostermillerutils 1.00.00
   */
  public CircularObjectBuffer(int size){
    this (size, true);
  }
  /**
   * Create a new buffer with a default capacity and
   * given blocking behavior.
   *
   * @param blockingWrite true writing to a full buffer should block
   *        until space is available, false if an exception should
   *        be thrown instead.
   *
   * @since ostermillerutils 1.00.00
   */
  public CircularObjectBuffer(boolean blockingWrite){
    this (DEFAULT_SIZE, blockingWrite);
  }
  /**
   * 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 desired capacity of the buffer in Objects or CircularObjectBuffer.INFINITE_SIZE.
   * @param blockingWrite true writing to a full buffer should block
   *        until space is available, false if an exception should
   *        be thrown instead.
   *
   * @since ostermillerutils 1.00.00
   */
  public CircularObjectBuffer(int size, boolean blockingWrite){
    if (size == INFINITE_SIZE){
      buffer = createArray(DEFAULT_SIZE);
      infinite = true;
    } else {
      buffer = createArray(size);
      infinite = false;
    }
    this.blockingWrite = blockingWrite;
  }
  /**
   * 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.
   *
   * @since ostermillerutils 1.00.00
   */
  public ElementType read() throws InterruptedException {
    while (true){
      synchronized (this){
        int available = available();
        if (available > 0){
          ElementType result = buffer[readPosition];
          readPosition++;
          if (readPosition == buffer.length){
            readPosition = 0;
          }
          return result;
        } else if (inputDone){
          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 Destination buffer.
   * @return The number of Objects read, or -1 there will
   *     be no more objects available.
   * @throws InterruptedException if the thread is interrupted while waiting.
   *
   * @since ostermillerutils 1.00.00
   */
  public int read(ElementType[] buf) throws InterruptedException {
    return read(buf, 0, buf.length);
  }
  /**
   * Get Objects into a portion of an array from this buffer.  This
   * method should be called by the consumer.
   * This method will block until some input is available,
   * an I/O error occurs, or the end of the stream is reached.
   *
   * @param buf Destination buffer.
   * @param off Offset at which to start storing Objects.
   * @param len Maximum number of Objects to read.
   * @return The number of Objects read, or -1 there will
   *     be no more objects available.
   * @throws InterruptedException if the thread is interrupted while waiting.
   *
   * @since ostermillerutils 1.00.00
   */
  public int read(ElementType[] buf, int off, int len) throws InterruptedException {
    while (true){
      synchronized (this){
        int available = available();
        if (available > 0){
          int length = Math.min(len, available);
          int firstLen = Math.min(length, buffer.length - readPosition);
          int secondLen = length - firstLen;
          System.arraycopy(buffer, readPosition, buf, off, firstLen);
          if (secondLen > 0){
            System.arraycopy(buffer, 0, buf, off+firstLen,  secondLen);
            readPosition = secondLen;
          } else {
            readPosition += length;
          }
          if (readPosition == buffer.length) {
            readPosition = 0;
          }
          return length;
        } else if (inputDone){
          return -1;
        }
      }
      Thread.sleep(100);
    }
  }
  /**
   * 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 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 interrupted while waiting.
   *
   * @since ostermillerutils 1.00.00
   */
  public long skip(long n) throws InterruptedException, IllegalArgumentException {
    while (true){
      synchronized (this){
        int available = available();
        if (available > 0){
          int length = Math.min((int)n, available);
          int firstLen = Math.min(length, buffer.length - readPosition);
          int secondLen = length - firstLen;
          if (secondLen > 0){
            readPosition = secondLen;
          } else {
            readPosition += length;
          }
          if (readPosition == buffer.length) {
            readPosition = 0;
          }
          return length;
        } else if (inputDone){
          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.
   *
   * @since ostermillerutils 1.00.00
   */
  public void done(){
    synchronized (this){
      inputDone = 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 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.
   *
   * @since ostermillerutils 1.00.00
   */
  public void write(ElementType[] buf) throws Exception{
    write(buf, 0, buf.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 Array of Objects
   * @param off Offset from which to start writing Objects
   * @param len - 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.
   *
   * @since ostermillerutils 1.00.00
   */
  public void write(ElementType[] buf, int off, int len) throws Exception{
    while (len > 0){
      synchronized (CircularObjectBuffer.this){
        if (inputDone) throw new IllegalStateException("CircularObjectBuffer.done() has been called, CircularObjectBuffer.write() failed.");
        int spaceLeft = spaceLeft();
        while (infinite && spaceLeft < len){
          resize();
          spaceLeft = spaceLeft();
        }
        if (!blockingWrite && spaceLeft < len) throw new Exception("CircularObjectBuffer is full; cannot write " + len + " Objects");
        int realLen = Math.min(len, spaceLeft);
        int firstLen = Math.min(realLen, buffer.length - writePosition);
        int secondLen = Math.min(realLen - firstLen, buffer.length - readPosition - 1);
        int written = firstLen + secondLen;
        if (firstLen > 0){
          System.arraycopy(buf, off, buffer, writePosition, firstLen);
        }
        if (secondLen > 0){
          System.arraycopy(buf, off+firstLen, buffer, 0, secondLen);
          writePosition = secondLen;
        } else {
          writePosition += written;
        }
        if (writePosition == buffer.length) {
          writePosition = 0;
        }
        off += written;
        len -= written;
      }
      if (len > 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 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.
   *
   * @since ostermillerutils 1.00.00
   */
  public void write(ElementType o) throws Exception{
    boolean written = false;
    while (!written){
      synchronized (CircularObjectBuffer.this){
        if (inputDone) throw new IllegalStateException("CircularObjectBuffer.done() has been called, CircularObjectBuffer.write() failed.");
        int spaceLeft = spaceLeft();
        while (infinite && spaceLeft < 1){
          resize();
          spaceLeft = spaceLeft();
        }
        if (!blockingWrite && spaceLeft < 1) throw new Exception("CircularObjectBuffer is full; cannot write 1 Object");
        if (spaceLeft > 0){
          buffer[writePosition] = o;
          writePosition++;
          if (writePosition == buffer.length) {
            writePosition = 0;
          }
          written = true;
        }
      }
      if (!written){
        Thread.sleep(100);
      }
    }
  }
}