/*****************************************************************************/
/*                                                                           */
/*           Producer and Consumer: Monitor Test Program                     */
/*                                                                           */
/*                     January 1999, Toshimi Minoura                         */
/*                                                                           */
/*****************************************************************************/

// Producer Threads place Objects in a BoundedBuffer,
// and they are retrieved by Consumer Threads.

class BoundedBuffer {
  static final int BufferSize = 3;  // constant defining size of this buffer
  private Object data[];            // storage for data queued
  private int headIdx;              // index to head of queued data
  private int tailIdx;              // index to tail of queued data
  private int nElements;            // number of elements in this buffer

  public BoundedBuffer() {              // constructor
    data = new Object[BufferSize];      // create array for Objects
    for (int i = 0; i < BufferSize; i++) {
      data[i] = null;                   // initially, no element
    }
    headIdx = 0;                        // position of data[0]
    tailIdx = BufferSize - 1;           // position preceding data[0]
    nElements = 0;                      // initially, no elements
  }

  public synchronized void enqueue(String name, Object x) {
    while (nElements == BufferSize) {          // buffer is full
      System.out.println("Thread " + name + " will be blocked");
      try { wait();}                           // block calling Thread  
      catch (java.lang.InterruptedException e) { };
    }
    if (++tailIdx == BufferSize) tailIdx = 0;   // end of buffer, wrap around
    System.out.println("Object " + x + " enqueued in data[" + tailIdx + "]");
    data[tailIdx] = x;                          // store argument object
    nElements++;                                // one element added
    notify();                                   // activate blocked Consumer
  }

  public synchronized Object dequeue(String name) {
    while (nElements == 0) {                    // buffer is empty
      System.out.println("Thread " + name + " will be blocked");
      try { wait();}                            // block calling Thread
      catch (java.lang.InterruptedException e) { };
    }
    Object temp = data[headIdx];                // object to be returned
    data[headIdx] = null;                       // no object in this slot
    System.out.println("Object " + temp + " dequeued from data["
                        + headIdx + "]");
    if (++headIdx == BufferSize) headIdx = 0;   // wrap around
    nElements--;                                // one element removed
    notify();                                   // activate blocked Producer
    return temp;
  }

  public synchronized void dataPrint() {        // print stack content
     System.out.print("BoundedBuffer: headIdx = " + headIdx +
                                   ", tailIdx = " + tailIdx + ", data = ");
    for (int i = 0; i < BufferSize; i++) {
      System.out.print(data[i]);    // print every value in data[]
      if (i < BufferSize - 1) System.out.print(", ");
    }
    System.out.println("");
  }
}
 
class Producer extends Thread {
  static final int BJobs = 10;     // number of elements produced
  String name;                     // name of this Producer
  BoundedBuffer queue ;            // queue this Producer is connected to
  public Producer(String name, BoundedBuffer queue) {   // constructor
    this.name = name;              // copy argument
    this.queue = queue;            // copy argument
  }
  public void run() {          
    System.out.println("Producer " + name + " started");
    queue.dataPrint();                         // print queue content

    for (int i = 1; i <= BJobs; i++) { 
      int delayTime = (int) (Math.random() * 2000);  // betwen 0 and 2000
      System.out.println("Producer " + name + " will create a job in " +
                          delayTime + " msecs");
      try { sleep(delayTime);}                 // delay execution
      catch (java.lang.InterruptedException e) { };

      queue.enqueue(name, name + '.' + i);     // insert element into queue
      queue.dataPrint();                       // print queue content
    }
    System.out.println("Producer " + name + " completed");
  }  
}
 
class Consumer extends Thread {
  static final int BJobs = 10; // number of elements consumed
  String name;                     // name of this Consumer
  BoundedBuffer queue ;            // queue this Consumer is connected to
  public Consumer(String name, BoundedBuffer queue) {   // constructor
    this.name = name;              // copy argument
    this.queue = queue;            // copy argument
  }
  public void run() {          
    System.out.println("Consumer " + name + " started");
    for (int i = 1; i <= BJobs; i++) { 
      System.out.println("Item " + queue.dequeue(name) + " deleted");
      queue.dataPrint();                       // print queue content
      int delayTime = (int) (Math.random() * 5000); // between 0 and 5000
      System.out.println("Consumer " + name + " will consume the job in " +
                         delayTime + " msecs");
      try { sleep(delayTime);}                 // delay execution
      catch (java.lang.InterruptedException e) { };
    }
  System.out.println("Consumer " + name + " completed");
  }  
}
 
class ProdCons {
  public static void main(String argv[]) {          
    BoundedBuffer queue = new BoundedBuffer();
   
    System.out.println("main() started");
    new Producer("p1", queue).start();  // create a Producer and start it
    new Consumer("c1", queue).start();  // create a Consumer and start it
    System.out.println("main() completed");
  }  
}


