import java.util.ArrayList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
   A shared buffer that can store a limited number of data items.
   The buffer behaves as a queue and is implemented using an ArrayList.
*/
public class Buffer
{
   /**
      Constructs an empty buffer
      @param ms the maximum size of the buffer
   */
   public Buffer(int ms)
   {
      b = new ArrayList<String>();
      maxSize = ms;
      l = new ReentrantLock();
      bufferNotFullCondition = l.newCondition();
      bufferNotEmptyCondition = l.newCondition();
   }

   /**
      Adds a data item to the buffer.
      @param s the data item to add
   */
   public void add(String s)
         throws InterruptedException
   {
      l.lock();
      try
      {
         while (b.size() == maxSize)
         {  System.out.println("Buffer full, waiting to add " + s);
            bufferNotFullCondition.await();
         }
         b.add(s);
         System.out.println("Adding " + s);
         bufferNotEmptyCondition.signalAll();
      }
      finally
      {
         l.unlock();
      }
   }
   
   /**
      Removes a data item from the buffer.
   */
   public String remove()
         throws InterruptedException
   {
      String s;
      l.lock();
      try
      {
         while (b.size() == 0)
         {  System.out.println("Buffer empty, waiting to remove");
            bufferNotEmptyCondition.await();
         }
         s = b.remove(0);
         System.out.println("Removing " + s);
         bufferNotFullCondition.signalAll();
      }
      finally
      {
         l.unlock();
      }
      return s;
   }
   
   private ArrayList<String> b;
   private final int maxSize;
   private Lock l;
   private Condition bufferNotFullCondition;
   private Condition bufferNotEmptyCondition;
}
