/**
 *******************************************************************************
 * Copyright (C) 2001-2006, International Business Machines Corporation and    *
 * others. All Rights Reserved.                                                *
 *******************************************************************************
 */
package com.ibm.icu.impl;

// See Allan Holub's 1999 column in JavaWorld, and Doug Lea's code for RWLocks with writer preference.


/**
 * <p>A simple Reader/Writer lock.  This assumes that there will
 * be little writing contention.  It also doesn't allow 
 * active readers to acquire and release a write lock, or
 * deal with priority inversion issues.</p>
 *
 * <p>Access to the lock should be enclosed in a try/finally block
 * in order to ensure that the lock is always released in case of
 * exceptions:<br><pre>
 * try {
 *     lock.acquireRead();
 *     // use service protected by the lock
 * }
 * finally {
 *     lock.releaseRead();
 * }
 * </pre></p>
 *
 * <p>The lock provides utility methods getStats and clearStats
 * to return statistics on the use of the lock.</p>
 */
public class ICURWLock {
    private Object writeLock = new Object();
    private Object readLock = new Object();
    private int wwc; // waiting writers
    private int rc; // active readers, -1 if there's an active writer
    private int wrc; // waiting readers

    private Stats stats = new Stats(); // maybe don't init to start...

    /**
     * Internal class used to gather statistics on the RWLock.
     */
    public final static class Stats {
        /**
         * Number of times read access granted (read count).
         */
        public int _rc;

        /**
         * Number of times concurrent read access granted (multiple read count).
         */
        public int _mrc;

        /**
         * Number of times blocked for read (waiting reader count).
         */
        public int _wrc; // wait for read

        /**
         * Number of times write access granted (writer count).
         */
        public int _wc;

        /**
         * Number of times blocked for write (waiting writer count).
         */
        public int _wwc;

        private Stats() {
        }

        private Stats(int rc, int mrc, int wrc, int wc, int wwc) {
            this._rc = rc;
            this._mrc = mrc;
            this._wrc = wrc;
            this._wc = wc;
            this._wwc = wwc;
        }

        private Stats(Stats rhs) {
            this(rhs._rc, rhs._mrc, rhs._wrc, rhs._wc, rhs._wwc);
        }

        /**
         * Return a string listing all the stats.
         */
        public String toString() {
            return " rc: " + _rc +
                " mrc: " + _mrc + 
                " wrc: " + _wrc +
                " wc: " + _wc +
                " wwc: " + _wwc;
        }
    }

    /**
     * Reset the stats.  Returns existing stats, if any.
     */
    public synchronized Stats resetStats() {
        Stats result = stats;
        stats = new Stats();
        return result;
    }

    /**
     * Clear the stats (stop collecting stats).  Returns existing stats, if any.
     */
    public synchronized Stats clearStats() {
        Stats result = stats;
        stats = null;
        return result;
    }
    
    /**
     * Return a snapshot of the current stats.  This does not reset the stats.
     */
    public synchronized Stats getStats() {
        return stats == null ? null : new Stats(stats);
    }

    // utilities

    private synchronized boolean gotRead() {
        ++rc;
        if (stats != null) {
            ++stats._rc;
            if (rc > 1) ++stats._mrc;
        }
        return true;
    }

    private synchronized boolean getRead() {
        if (rc >= 0 && wwc == 0) {
            return gotRead();
        }
        ++wrc;
        return false;
    }

    private synchronized boolean retryRead() {
        if (stats != null) ++stats._wrc;
        if (rc >= 0 && wwc == 0) {
            --wrc;
            return gotRead();
        }
        return false;
    }

    private synchronized boolean finishRead() {
        if (rc > 0) {
            return (0 == --rc && wwc > 0);
        }
        throw new IllegalStateException("no current reader to release");
    }
    
    private synchronized boolean gotWrite() {
        rc = -1;
        if (stats != null) {
            ++stats._wc;
        }
        return true;
    }

    private synchronized boolean getWrite() {
        if (rc == 0) {
            return gotWrite();
        }
        ++wwc;
        return false;
    }

    private synchronized boolean retryWrite() {
        if (stats != null) ++stats._wwc;
        if (rc == 0) {
            --wwc;
            return gotWrite();
        }
        return false;
    }

    private static final int NOTIFY_NONE = 0;
    private static final int NOTIFY_WRITERS = 1;
    private static final int NOTIFY_READERS = 2;

    private synchronized int finishWrite() {
        if (rc < 0) {
            rc = 0;
            if (wwc > 0) {
                return NOTIFY_WRITERS;
            } else if (wrc > 0) {
                return NOTIFY_READERS;
            } else {
                return NOTIFY_NONE;
            }
        }
        throw new IllegalStateException("no current writer to release");
    }
    
    /**
     * <p>Acquire a read lock, blocking until a read lock is
     * available.  Multiple readers can concurrently hold the read
     * lock.</p>
     *
     * <p>If there's a writer, or a waiting writer, increment the
     * waiting reader count and block on this.  Otherwise
     * increment the active reader count and return.  Caller must call
     * releaseRead when done (for example, in a finally block).</p> 
     */
    public void acquireRead() {
        if (!getRead()) {
            for (;;) {
                try {
                    synchronized (readLock) {
                        readLock.wait();
                    }
                    if (retryRead()) {
                        return;
                    }
                }
                catch (InterruptedException e) {
                }
            }
        }
    }

    /**
     * <p>Release a read lock and return.  An error will be thrown
     * if a read lock is not currently held.</p>
     *
     * <p>If this is the last active reader, notify the oldest
     * waiting writer.  Call when finished with work
     * controlled by acquireRead.</p>
     */
    public void releaseRead() {
        if (finishRead()) {
            synchronized (writeLock) {
                writeLock.notify();
            }
        }
    }

    /**
     * <p>Acquire the write lock, blocking until the write lock is
     * available.  Only one writer can acquire the write lock, and
     * when held, no readers can acquire the read lock.</p>
     *
     * <p>If there are no readers and no waiting writers, mark as
     * having an active writer and return.  Otherwise, add a lock to the
     * end of the waiting writer list, and block on it.  Caller
     * must call releaseWrite when done (for example, in a finally
     * block).<p> 
     */
    public void acquireWrite() {
        if (!getWrite()) {
            for (;;) {
                try {
                    synchronized (writeLock) {
                        writeLock.wait();
                    }
                    if (retryWrite()) {
                        return;
                    }
                }
                catch (InterruptedException e) {
                }
            }
        }
    }

    /**
     * <p>Release the write lock and return.  An error will be thrown
     * if the write lock is not currently held.</p>
     *
     * <p>If there are waiting readers, make them all active and
     * notify all of them.  Otherwise, notify the oldest waiting
     * writer, if any.  Call when finished with work controlled by
     * acquireWrite.</p> 
     */
    public void releaseWrite() {
        switch (finishWrite()) {
        case NOTIFY_WRITERS:
            synchronized (writeLock) {
                writeLock.notify();
            }
            break;
        case NOTIFY_READERS:
            synchronized (readLock) {
                readLock.notifyAll();
            }
            break;
        case NOTIFY_NONE:
            break;
        }
    }
}
