/**
 * 
 */
package sherpa.client;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Reusable single-slot queue with the ability of a producer to interrupt 
 * a waiting consumer.
 * 
 * @param <T> The type of item in the slot
 */
class SignalSlot <T> {
  private final Lock dataLock = new ReentrantLock();
  private final Condition availableCondition = dataLock.newCondition();
  
  // dataLock protects read/write of data and error
  private T data;
  private Throwable error;
  
  /** 
   * Write data into the slot.  There is an assumption that 
   * the old data has already been retrieved as it will be 
   * clobbered.
   *  
   * @param data Put the data into the slot
   */ 
  public void add(T data) {
    writeSlot(data, null);
  }

  /** 
   * Interrupt a waiting reader and put the slot into an error state. 
   */
  public void addError(Throwable t) {
    writeSlot(null, t);
  }
  
  // either data or error should be non-null
  private void writeSlot(T data, Throwable error) {
    dataLock.lock();
    try {
      this.error = error;
      this.data = data;
      availableCondition.signalAll();
    } finally {
      dataLock.unlock();
    }
  }

  /**
   * Get and clear the slot - MUST be called while holding the lock!! 
   * @return The data
   * @throws Throwable If producer encountered an error
   */
  private T getAndClearUnderLock() throws Throwable {
    if(error != null) {
      throw error;
    } else {    
      // Return and clear current
      T retValue = data;
      data = null;
      return retValue;
    }
  }
  
  /** 
   * Non-blocking read and remove.
   * @return The data or null if none exists
   * @throws Throwable If producer encountered an error
   */
  public T poll() throws Throwable {
    dataLock.lock();    
    try {
      return getAndClearUnderLock();
    } finally {
      dataLock.unlock();
    }
  }
  
  /** 
   * Blocking read and remove. If return is null, the thread
   * was interrupted by the producer.
   * @return The data or null if interrupted 
   * @throws Throwable If producer encountered an error
   */
  public T take() throws Throwable {
    dataLock.lock();      
    try {
      while(data == null && error == null) {   // loop in case of spurious wake-ups
        try {
          availableCondition.await();
        } catch(InterruptedException e) {
          // ignore and re-loop
        }
      }
      return getAndClearUnderLock();
    } finally {
      dataLock.unlock();
    }
  }
}
@@ -762,16 +762,44 @@
 the slot
+. data should never be null.
 %0A   */ %0A
@@ -822,24 +822,49 @@
 d(T data) %7B%0A
+    assert data != null;%0A
     writeSlo
@@ -961,16 +961,62 @@
 state. %0A
+   * @param t The error, should never be null%0A
    */%0A  
@@ -1047,24 +1047,46 @@
 owable t) %7B%0A
+    assert t != null;%0A
     writeSlo
@@ -1470,16 +1470,51 @@
 The data
+ or null if no data or error exists
 %0A   * @t
@@ -2152,24 +2152,8 @@
 . If
- return is null,
  the
@@ -2159,21 +2159,16 @@
 e thread
-%0A   *
  was int
@@ -2179,16 +2179,22 @@
 pted by 
+%0A   * 
 the prod
@@ -2201,55 +2201,106 @@
 ucer
-.%0A   * @return The data or null if interrupted 
+ due to an error, the producer's error will be thrown.%0A   * @return The data, should never be null
 %0A   
@@ -2480,47 +2480,8 @@
 l) %7B
-   // loop in case of spurious wake-ups
 %0A   
@@ -2606,26 +2606,124 @@
 loop
-%0A        %7D%0A      %7D
+ in case of spurious wake-ups%0A        %7D%0A      %7D%0A      %0A      // should only get to here if data or error is non-null
 %0A   

Clean up javadoc and make assertions clearer in SignalSlot