//
// $Source: /home/cur/djb1/java/threads/RCS/Channel.java,v $
//
// $Id: Channel.java,v 1.1 1996/10/02 15:19:50 djb1 Exp $
//
// Channel class - an occam CHAN for multiple readers and writers.
//
// (C) Copyright 1996 Peter Welch <P.H.Welch@ukc.ac.uk> and
//                    Dave Beckett <D.J.Beckett@ukc.ac.uk>
// University of Kent at Canterbury
//

import java.lang.InterruptedException;

//{{{  description (javadoc format)
/**
 * Channel extends an occam CHAN for multiple readers and writers.
 *
 * There is full synchronisation between a reading and writing thread.  Any
 * thread may read or write on this channel.  Readers and writers are queued
 * separately.  A reader only completes when it gets to the front of its queue
 * and finds a writer.  A writer only completes when it gets to the front of
 * its queue and finds a reader.
 *<p>
 * There is no logical buffering of data in the channel.  The object passed
 * into the channel is not cloned, but rather a reference is passed to the
 * reader. Therefore, care must be taken that the object supplied by a writer
 * does not then have its content modified asynchronously by the writer. This
 * can be done by setting the reference to null, e.g. in a writer,<pre>
 *
 *   String s = "Hello World";    // create a new string
 *   chan.write(s);               // send the string
 *   s = null;                    // delete writer's reference to string
 *
 * </pre>
 * or alternatively, a clone can be supplied, e.g. <pre>
 *
 *   StringBuffer s = "Hello World";    // create a new string
 *   chan.write(s.clone());             // send the string
 *   s.reverse();                       // does not affect the reader's copy
 * </pre>
 *
 *@version $Revision: 1.1 $
 *@author  Peter Welch, P.H.Welch@ukc.ac.uk
 *@author  Dave Beckett, D.J.Beckett@ukc.ac.uk
 *@author  with annotations by Richard Beton
 */
//}}}

class Channel
{
  //{{{  private member data
  
  private Object channel_hold;          // buffer (not detectable to users)
  
  private boolean channel_empty = true; // synchronisation flag
  
  private Object read_monitor =         // all readers multiplex through this
    new Object ();
  
  private Object write_monitor =        // all writers multiplex through this
    new Object ();
  
  //}}}
  //{{{  read method
  /**
   *  <tt>read</tt> method: read a message from the channel. This method
   *  blocks until a corresponding thread calls the write method,
   *  at which point a rendezvous is formed and the data is passed.
   *
   *@return  the object sent from the writing thread.
   *
   *@exception InterruptedException
   *           Occurs if the wait methods are interrupted.
   */
  
  public Object read () throws InterruptedException
  {
    synchronized (read_monitor)
    {
      synchronized (this)
      {
        if (channel_empty)
        {
          channel_empty = false;           // first to the rendezvous
          wait ();                         // wait for the writer thread
          notify ();                       // schedule the writer to finish
        }
        else
        {
          channel_empty = true;            // second to the rendezvous
          notify ();                       // schedule the waiting writer thread
        }
        return channel_hold;
      }
    }
  }
  
  //}}}
  //{{{  write method
  /**
   *  <tt>write</tt> method: write a message to the channel. This method
   *  blocks until a corresponding thread calls the read method,
   *  at which point a rendezvous is formed and the data is passed.
   *
   *@param  o  the object to be sent to the reading thread.
   *
   *@exception InterruptedException
   *           Occurs if the wait methods are interrupted.
   */
  
  public void write (Object o) throws InterruptedException
  {
   synchronized (write_monitor)
   {
     synchronized (this)
     {
        channel_hold = o;
        if (channel_empty)
        {
          channel_empty = false;           // first to the rendezvous
          wait ();                         // wait for the reader thread
        }
        else
        {
          channel_empty = true;            // second to the rendezvous
          notify ();                       // schedule the waiting reader thread
          wait ();                         // let the reader regain this monitor
        }
      }
    }
  }
  
  //}}}
  //{{{  is_empty method
  /**
   *  <tt>is_empty</tt> method: determine whether a thread is waiting at the
   *  other end of the channel. This can be used either at the writing or the
   *  reading end of the channel.
   *
   *@return  true iff no thread is waiting at the other end.
   */
  
  public boolean is_empty ()
  {
    return channel_empty;
  }
  
  //}}}
}
