
// ConcurrentFridge.java
// Michele Loreti and the jSpace Developers, 2017
// Modified by Andrew Davison, ad@coe.psu.ac.th, May 2023

/* A concurrent version of Fridge.java, where the Alice
   thread adds things to the fridge while threads Bob and
   Charlie wait for her 'available' message in order to
   start consuming those items.

   Explained at:
      https://github.com/pSpaces/Programming-with-Spaces/blob/master/tutorial-concurrent-programming.md

  Usage:
    > compile.bat ConcurrentFridge.java
    > run.bat ConcurrentFridge

  Other version: 
    * Fridge.java
*/


import java.util.*;

import org.jspace.*;


public class ConcurrentFridge
{

  public static void main(String[] args)
                   //  throws InterruptedException
  {
    // Space fridge = new SequentialSpace();
    Space fridge = new VisSpace();

    Thread t1 = new Thread(new Alice(fridge), "Alice");
    Thread t2 = new Thread(new Bob(fridge), "Bob");
    Thread t3 = new Thread(new Charlie(fridge), "Charlie");
                                 // Threads should be named
    t1.start();
    t2.start();
    t3.start();

    SpaceUtils.waitTerminated(t1, t2, t3);
    /* this point in the code is never reached since 
       Charlie, and maybe Bob, never terminate
    */
  } /* main */

} // end of ConcurrentFridge class


// ---------------------------------------------

class Alice implements Runnable
{
  private Space fridge;

  public Alice(Space fridge)
  {   this.fridge = fridge;   }


  public void run()
  // add items to the fridge, and leave an 'available' message
  {
    try {
      fridge.put("milk", 2);
      fridge.put("butter", 1);
      // SpaceUtils.pause(2000);  // optional delay
      fridge.put("available");
      System.out.println("Alice: added milk:2, butter:1");
    }
    catch (InterruptedException e) 
    {  System.out.println("Alice: " + e);  }
  } /* run */


}  // end of Alice class


// ---------------------------------------------

class Bob implements Runnable
{
  private Space fridge;

  public Bob(Space fridge) 
  {  this.fridge = fridge;   }


  public void run()
  /* when an 'available' message is seen, start removing
     everything from the fridge, one item at a time
  */
  {
    try {
      fridge.query(new ActualField("available"));  // waits for message
      while (true) {
        Object[] item = fridge.get( new FormalField(String.class),
                                    new FormalField(Integer.class));
                 // randomly get a 2-tuple from the space
        System.out.println("Bob: used " + Arrays.toString(item));
      }
    }
    catch (InterruptedException e) 
    {  System.out.println("Bob: " + e);  }

  } /* run */


}  // end of Bob class


// ---------------------------------------------

class Charlie implements Runnable
{
  private Space fridge;

  public Charlie(Space fridge)
  {  this.fridge = fridge;   }


  public void run()
  /* Same behaviour as Bob, but using queryp() means that 
     Charlie will not wait for an 'available' message, and
     may terminate without getting anything from the fridge
  */
  {
    try {
      // choose one of these queries:
      Object[] t = fridge.queryp(new ActualField("available"));
                            // no waiting for the message
      // Object[] t = fridge.query(new ActualField("available"));
      if (t != null)
        while (true) {
          Object[] item = fridge.get( new FormalField(String.class),
                                      new FormalField(Integer.class));
                 // randomly get a 2-tuple from the space
          System.out.println( "Charlie: used " + Arrays.toString(item));
        }
      else
        System.out.println("Charlie: gave up");
    }
    catch (InterruptedException e) 
    {  System.out.println("Charlie: " + e);  }
  } /* run */


}  // end of Charlie class
