content: /*
   * Circular Object Buffer
   * Copyright (C) 2002-2010 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 LICENSE.txt for details.
   */
  package org.flashtool.util;
  
  import lombok.extern.slf4j.Slf4j; 
  /**
   * Implements the Circular Buffer producer/consumer model for Objects.
   * More information about this class is available from <a target="_top" href=
   * "http://ostermiller.org/utils/CircularObjectBuffer.html">ostermiller.org</a>.
   * <p>
   * This class is thread safe.
   *
   * @see CircularCharBuffer
   * @see CircularByteBuffer
   *
   * @author Stephen Ostermiller http://ostermiller.org/contact.pl?regarding=Java+Utilities
   * @param <ElementType> Type of object allowed in this circular buffer
   * @since ostermillerutils 1.00.00
   */
  
  
  @Slf4j
  public class CircularObjectBuffer <ElementType> {
   
      /**
       * 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.
       * <p>
       * 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.
       * <p>
       * 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.
       * <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
       *
       * @since ostermillerutils 1.00.00
       */
      public int getAvailable(){
          synchronized (this){
              return available();
          }
      }
   
      /**
       * 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
       *
       * @since ostermillerutils 1.00.00
       */
      public int getSpaceLeft(){
          synchronized (this){
              return spaceLeft();
          }
      }
   
      /**
       * 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
       *
       * @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.
       * <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 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.
       * <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 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.
       * <p>
       * 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 BufferOverflowException, IllegalStateException, InterruptedException {
          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 BufferOverflowException, IllegalStateException, InterruptedException {
          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 BufferOverflowException("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 BufferOverflowException, IllegalStateException, InterruptedException {
          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 BufferOverflowException("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);
              }
          }
      }
  }