/*
** Module   : ConcurrentElements.java
** Abstract : represents multiple elements which must execute simultaneously
**
** Copyright (c) 2009-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- -----------------------Description------------------------
** 001 GES 20090318 Represents multiple elements which must execute
**                  simultaneously.
** 002 TJD 20230119 Allow restarting of Driver threads
*/

/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License
** along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package com.goldencode.harness;

import java.util.concurrent.*;

/**
 * Represents multiple elements which must execute simultaneously.
 * 
 * @param   <E>
 *          Type of element.
 */
public class ConcurrentElements<E>
implements SubQueue<E>
{
   /** The contained elements. */
   private LinkedBlockingQueue<E> queue = null;
   
   /** Blocking primitive to force threads to wait. */
   private CyclicBarrier barrier = null;
   
   /** Synchronization primitive (more granular than "this"). */
   private Object lock = new Object();
   
   /** Number of reserved. */ 
   private int reserved = 0;
   
   /**
    * Construct an instance representing multiple elements which must execute
    * simultaneously.
    */
   public ConcurrentElements()
   {
      queue = new LinkedBlockingQueue<E>();
   }
   
   /**
    * Add an element to the end of the queue. This can only be done until the
    * first time that {@link #dequeue} is called. After that the size of the
    * queue is fixed.
    *
    * @param    element
    *           The element to add.
    */
   public void add(E element)
   {
      synchronized (lock)
      {
         if (barrier == null)
         {
            try
            {
               queue.put(element);
            }
            
            catch (InterruptedException ie)
            {
               throw new RuntimeException("Cannot put element in queue.");
            }
         }
         else
         {
            throw new IllegalStateException("Too late to add elements!");
         }
      }
   }
   
   /**
    * Reports if there are any elements remaining in the queue. This can answer
    * the question of whether there will ever be elements available to
    * {@link #dequeue}. Note that due to dependencies between elements, it is
    * possible for a queue to be non-empty but to not yet be ready to accept
    * a <code>dequeue</code> request.
    *
    * @return   <code>true</code> if all queue elements have been dequeued.
    */
   public boolean isEmpty()
   {
      boolean empty = false;
      
      synchronized (lock)
      {
         empty = ((queue.size() - reserved) == 0);
      }
      
      return empty;
   }
   
   /**
    * Reports if the queue has elements to {@link #dequeue}. This will be
    * <code>false</code> if there are no elements remaining OR if the elements
    * that are contained have dependencies upon other elements such that no
    * dequeuing is possible until the dependencies are satisfied. In such a
    * case the queue is considered not ready.
    *
    * @return   <code>true</code> if the queue contains at least one element
    *           which is ready to be dequeued.
    */
   public boolean isReady()
   {
      return !isEmpty();
   }
   
   /**
    * Reports if the queue acts as a barrier. A barrier is a sub-queue that
    * ends the search for elements until such time as the barrier sub-queue is
    * the first sub-queue. This is a mechanism to force all prior sub-queues to
    * be fully processed before any following sub-queues can be processed.
    * <p>
    * Barriers DO NOT have any elements of their own to return. Rather, they
    * just are a marker of known "sections" of the containing queue.
    *
    * @return   Always <code>false</code>.
    */
   public boolean isBarrier()
   {
      return false;
   }
   
   /**
    * Associates the next element in the queue with the caller, without
    * actually calling {@link #dequeue} which can block. This allows the
    * calling code to release any resources before the subsequent potentially
    * blocking operation. Any subsequent call to {@link #isReady} MUST take
    * this reservation into account. This means that all calls to this method
    * must be matched with a subsequent call to <code>dequeue</code>.
    *
    * @return   <code>true</code> if the reservation was successful. Otherwise
    *           the queue is not ready and caller MUST NOT call
    *           <code>dequeue</code>. 
    */
   public boolean reserve()
   {
      synchronized (lock)
      {
         if (reserved < queue.size())
         {
            reserved++;
            return true;
         }
      }
      
      return false;
   }
   
   /**
    * Obtain the next element from the queue.
    *
    * @return   The element or <code>null</code> if the element was previously
    *           retrieved.
    */
   public E dequeue()
   {
      E element = null;
      
      synchronized (lock)
      {
         // first time in, we must initialize the barrier to the size of
         // the queue
         if (barrier == null)
            barrier = new CyclicBarrier(queue.size());
         
         // remove the object we are going to return later
         element = queue.poll();
         
         if (reserved > 0)
            reserved--;
      }
      
      // safety code
      if (element != null)
      {
         try
         {
            // block until all the other threads have requested the element
            barrier.await();
         }
         
         catch (InterruptedException ie)
         {
            // fail
            element = null;
         }
         
         catch (BrokenBarrierException bbe)
         {
            // fail
            element = null;
         }
      }
      
      return element;
   }

   /**
    * Reports if the subqueue can be ignored when checking if queue is empty
    *
    * @return   <code>true</code> if the queue can be ignored.
    */
   @Override
   public boolean isIgnored()
   {
      return false;
   }

}
