
package org.k2d2.framework.threadpackage.semaphore;

/**
 * Also called counting semaphores, Djikstra semaphores are used to control access to
 * a set of resources. A Djikstra semaphore has a count associated with it and each
 * acquire() call reduces the count. A thread that tries to acquire() a Djikstra
 * semaphore with a zero count blocks until someone else calls release() thus increasing
 * the count.
 * <b>When to use</b>
 * Recommended when applications require a counting semaphore. Implementing
 * a counting semaphore using wait()/notify() and counters within your application
 * code makes your code less readable and quickly increases the complexity
 * (especially when you have the need for multiple counting semaphores). Can also
 * be used to port code from POSIX environment.
 *
 * @author Karthik Rangaraju
 */
public class DjikstraSemaphore
{
    private int mCount;
    private int mMaxCount;
    private Object mStarvationLock = new Object();

    /**
     * Creates a Djikstra semaphore with the specified max count and initial count set
     * to the max count (all resources released)
     * @param pMaxCount is the max semaphores that can be acquired
     */
    public DjikstraSemaphore(int pMaxCount)
    {
        this(pMaxCount, pMaxCount);
    }

    /**
     * Creates a Djikstra semaphore with the specified max count and an initial count
     * of acquire() operations that are assumed to have already been performed.
     * @param pMaxCount is the max semaphores that can be acquired
     * @pInitialCount is the current count (setting it to zero means all semaphores
     * have already been acquired). 0 <= pInitialCount <= pMaxCount
     */
    public DjikstraSemaphore(int pMaxCount, int pInitialCount)
    {
        mCount = pInitialCount;
        mMaxCount = pMaxCount;
    }

    /**
     * If the count is non-zero, acquires a semaphore and decrements the count by 1,
     * otherwise blocks until a release() is executed by some other thread.
     * @throws InterruptedException is the thread is interrupted when blocked
     * @see #tryAcquire()
     * @see #acquireAll()
     */
    public synchronized void acquire() throws InterruptedException
    {
        // Using a spin lock to take care of rogue threads that can enter
        // before a thread that has exited the wait state acquires the monitor
        while (mCount == 0)
        {
            wait();
        }
        mCount--;
        synchronized (mStarvationLock)
        {
            if (mCount == 0)
            {
                mStarvationLock.notify();
            }
        }
    }

    /**
     * Non-blocking version of acquire().
     * @return true if semaphore was acquired (count is decremented by 1), false
     * otherwise
     */
    public synchronized boolean tryAcquire()
    {
        if (mCount != 0)
        {
            mCount--;
            synchronized (mStarvationLock)
            {
                if (mCount == 0)
                {
                    mStarvationLock.notify();
                }
            }
            return true;
        }
        else
        {
            return false;
        }
    }

    /**
     * Releases a previously acquires semaphore and increments the count by one. Does not
     * check if the thread releasing the semaphore was a thread that acquired the
     * semaphore previously. If more releases are performed than acquires, the count is
     * not increased beyond the max count specified during construction.
     * @see #release(int pCount)
     * @see #releaseAll()
     */
    public synchronized void release()
    {
        mCount++;
        if (mCount > mMaxCount)
        {
            mCount = mMaxCount;
        }
        notify();
    }

    /**
     * Same as release() except that the count is increased by pCount instead of 1. The
     * resulting count is capped at max count specified in the constructor
     * @param pCount is the amount by which the counter should be incremented
     * @see #release()
     */
    public synchronized void release(int pCount)
    {
        if (mCount + pCount > mMaxCount)
        {
            mCount = mMaxCount;
        }
        else
        {
            mCount += pCount;
        }
        notifyAll();
    }

    /**
     * Tries to acquire all the semaphores thus bringing the count to zero.
     * @throws InterruptedException if the thread is interrupted when blocked on this call
     * @see #acquire()
     * @see #releaseAll()
     */
    public synchronized void acquireAll() throws InterruptedException
    {
        for (int index = 0; index < mMaxCount; index++)
        {
            acquire();
        }
    }

    /**
     * Releases all semaphores setting the count to max count.
     * Warning: If this method is called by a thread that did not make a corresponding
     * acquireAll() call, then you better know what you are doing!
     * @see #acquireAll()
     */
    public synchronized void releaseAll()
    {
        release(mMaxCount);
        notifyAll();
    }

    /**
     * This method blocks the calling thread until the count drops to zero.
     * The method is not stateful and hence a drop to zero will not be recognized
     * if a release happens before this call. You can use this method to implement
     * threads that dynamically increase the resource pool or that log occurences
     * of resource starvation. Also called a reverse-sensing semaphore
     * @throws InterruptedException if the thread is interrupted while waiting
     */
    public void starvationCheck() throws InterruptedException
    {
        synchronized (mStarvationLock)
        {
            if (mCount != 0)
            {
                mStarvationLock.wait();
            }
        }
    }
}

