/* ============================================================================
 *                   The Apache Software License, Version 1.1
 * ============================================================================
 * 
 * Copyright (C) 1997-2001 The Apache Software Foundation. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modifica-
 * tion, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of  source code must  retain the above copyright  notice,
 *    this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 
 * 3. The end-user documentation included with the redistribution, if any, must
 *    include  the following  acknowledgment:  "This product includes  software
 *    developed  by the  Apache Software Foundation  (http://www.apache.org/)."
 *    Alternately, this  acknowledgment may  appear in the software itself,  if
 *    and wherever such third-party acknowledgments normally appear.
 * 
 * 4. The names "Jakarta", "Avalon" and  "Apache Software Foundation" must not 
 *    be used to endorse  or promote products derived  from this  software without  
 *    prior written permission. For written permission, please contact 
 *    apache@apache.org.
 * 
 * 5. Products  derived from this software may not  be called "Apache", nor may
 *    "Apache" appear  in their name,  without prior written permission  of the
 *    Apache Software Foundation.
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
 * APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
 * DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
 * ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
 * (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * This software  consists of voluntary contributions made  by many individuals
 * on  behalf of the Apache Software  Foundation. For more  information on the 
 * Apache Software Foundation, please see <http://www.apache.org/>.
 */
package org.apache.avalon.excalibur.concurrent.test;

import org.apache.testlet.AbstractTestlet;
import org.apache.testlet.TestFailedException;

import org.apache.avalon.excalibur.concurrent.ReadWriteLock;

/**
 * Test harness for the ReadWriteLock.
 *
 * @author <a href="mailto:leo.sutic@inspireinfrastructure.com">Leo Sutic</a>
 */
public class ReadWriteLockTestlet extends AbstractTestlet
{
    /**
	 * Worker thread that attempts to 
	 * aquire a write lock. Start it, wait a little while
	 * and call the hasSuccess() method.
	 */
	class TriesWriteLock extends Thread {
		
		protected ReadWriteLock m_lock;
		protected boolean m_success = false;
		
		public TriesWriteLock(ReadWriteLock lock) 
		{
			m_lock = lock;
		}
		
		public boolean hasSuccess() 
		{
			return m_success;
		}
		
		public void run () 
		{
			try 
			{
				m_lock.aquireWrite();
				m_success = true;
			}
			catch (Exception e) 
			{
				// Ignore. Errors are handled by the testlet 
				// monitoring hasSuccess().
			}
		}
	}
	
    /**
	 * Worker thread that attempts to 
	 * aquire a read lock. Start it, wait a little while
	 * and call the hasSuccess() method.
	 */
	class TriesReadLock extends Thread {
		
		protected ReadWriteLock m_lock;
		protected boolean m_success = false;
		
		public TriesReadLock(ReadWriteLock lock) 
		{
			m_lock = lock;
		}
		
		public boolean hasSuccess() 
		{
			return m_success;
		}
		
		public void run () 
		{
			try 
			{
				m_lock.aquireRead();
				m_success = true;
			} 
			catch (Exception e) 
			{
				// Ignore. Errors are handled by the testlet 
				// monitoring hasSuccess().
			}
		}
	}
	
	/**
	 * Does nothing.
	 */
    public void initialize()
    {
    }

    /**
     * Attempt to aquire and release read and write locks from 
	 * different threads.
     */
    public void testRWLock() throws Exception
    {
		ReadWriteLock lock = new ReadWriteLock();
        TriesReadLock rl = new TriesReadLock(lock);
		TriesWriteLock wl = new TriesWriteLock(lock);
		
		rl.start ();
		Thread.currentThread().sleep(100);
		assert("Attempted to aquire read lock.", rl.hasSuccess ());
		
		wl.start ();
		Thread.currentThread().sleep(100);
		assert("Attempted to aquire write lock.", !wl.hasSuccess ());
		
		lock.release();
		
		Thread.currentThread().sleep(100);
		assert("Attempted to aquire write lock after releasing read lock.", wl.hasSuccess ());
		
		lock.release();
		
		//
		// And see that the write lock is released properly.
		//
		rl = new TriesReadLock(lock);
		rl.start ();
		Thread.currentThread().sleep(100);
		assert("Attempted to aquire read lock.", rl.hasSuccess ());
		
		lock.release();
    }
	
	/**
	 * Test that the lock throws an IllegalStateException when
	 * one attempts to release an already released lock.
	 */
	public void testIllegalState () throws Exception
	{
		ReadWriteLock lock = new ReadWriteLock();
		try 
		{
			lock.release();
			throw new TestFailedException("ReadWriteLock did *not* signal illegal state when an attempt was made to release an unlocked lock.");
		} 
		catch (IllegalStateException ise)
		{
			// OK, we should receive this one.
		}
	}
	
	/**
	 * Tests that attempts to aquire a write lock
	 * are given higher priority than attempts
	 * at aquiring a read lock.
	 */
	public void testMultipleWriters () throws Exception
	{
		ReadWriteLock lock = new ReadWriteLock();
        TriesReadLock rla = new TriesReadLock(lock);
		TriesReadLock rlb = new TriesReadLock(lock);
		TriesWriteLock wla = new TriesWriteLock(lock);
        TriesWriteLock wlb = new TriesWriteLock(lock);
				
		rla.start ();
		Thread.currentThread().sleep(100);
		assert("Attempted to aquire read lock.", rla.hasSuccess ());
		
		wla.start ();
		wlb.start ();
		
		//
		// Give the write lock threads some time to attempt
		// to aquire a lock.
		//
		Thread.currentThread().sleep(100);
				
		rlb.start ();
		Thread.currentThread().sleep(100);
		
		//
		// Two write locks queued up, one read lock queued up.
		//
		//      rla holds read lock
		// wlb, wla -> (read lock is held) 
		//      rlb -> (has waiting write locks)
		//
		assert("Attempted to aquire write lock, and succeeded even though it shouldn't be possible (rla has the lock).", !wla.hasSuccess() && !wlb.hasSuccess() && !rlb.hasSuccess());

		//
		// Upon releasing the lock, the write lock attempt should succeed, while the read 
		// lock should still be waiting.
		//
		lock.release();
		Thread.currentThread().sleep(100);
		
		//
		// One write lock queued up, one read lock queued up.
		//
		//      wla or wlb holds write lock
		// wlb || wla -> (write lock is held)
		//        rlb -> (has waiting write lock)
		//
		assert("Attempted to aquire write lock after releasing read lock.", (wla.hasSuccess () || wlb.hasSuccess ()) && !rlb.hasSuccess ()
			                                                                && !(wla.hasSuccess () && wlb.hasSuccess ()));
		
		//
		// Release write lock again. The other one of wla and wlb should grab the lock.
		//
		lock.release();
		Thread.currentThread().sleep(100);
		
		//
		// Two write locks queued up, one read lock queued up.
		//
		//      wla or wlb holds write lock
		//        rlb -> (write lock is held)
		//
		assert("Attempted to aquire write lock after releasing read lock.", wla.hasSuccess () && wlb.hasSuccess () && !rlb.hasSuccess ());
		
		//
		// Release the lock - the waiting read lock should grab it.
		//
		lock.release();
 		Thread.currentThread().sleep(100);
		assert("Attempted to aquire write lock after releasing read lock.", wla.hasSuccess () && wlb.hasSuccess () && rlb.hasSuccess ());
	}
	
	/**
	 * Tests that the lock behaves correctly when
	 * multiple read locks are obtained.
	 */
	public void testMultipleReaders () throws Exception
	{
		ReadWriteLock lock = new ReadWriteLock();
        TriesReadLock rla = new TriesReadLock(lock);
		TriesReadLock rlb = new TriesReadLock(lock);
		TriesWriteLock wla = new TriesWriteLock(lock);
        
		rla.start ();
		rlb.start ();
		Thread.currentThread().sleep(100);
		assert("Attempted to aquire read multiple read locks.", rla.hasSuccess () && rlb.hasSuccess ());
		
		wla.start ();
		Thread.currentThread().sleep(100);
		assert("Write lock aquired even though read locks are held.", !wla.hasSuccess ());
		
		lock.release ();
		Thread.currentThread().sleep(100);
		assert("Write lock aquired even though read locks are held. (There should be one read lock left)", !wla.hasSuccess ());
		
		lock.release ();
		Thread.currentThread().sleep(100);
		assert("Write lock not aquired even though lock should be released.", wla.hasSuccess ());	
		
	}
	
	/**
	 * Tests the tryAquireXXX methods.
	 */
	public void testTrying () throws Exception
	{
		ReadWriteLock lock = new ReadWriteLock();
        TriesReadLock rla = new TriesReadLock(lock);
		TriesReadLock rlb = new TriesReadLock(lock);
		TriesWriteLock wla = new TriesWriteLock(lock);
        TriesWriteLock wlb = new TriesWriteLock(lock);
		
		//
		// Grab a read lock, try to aquire one more (should work), and try aquiring a write lock (should not work).
		//
		rla.start ();
		Thread.currentThread().sleep(100);
		assert ("Could not aquire a read lock.", rla.hasSuccess());
		
		assert ("Could not aquire a read lock, even though only a read lock is held.", lock.tryAquireRead());
		
		assert ("Could aquire a write lock.", !lock.tryAquireWrite());
		
		//
		// Release both locks.
		//
		lock.release();
		lock.release();
		
		//
		// Try aquiring a write lock (should work), a read lock (should fail) and another write lock (should fail).
		//
		assert ("Could not aquire a write lock.", lock.tryAquireWrite());
		assert ("Could aquire a read lock.", !lock.tryAquireRead());
		assert ("Could aquire a write lock.", !lock.tryAquireWrite());
		
		//
		// Release the write lock.
		//
		lock.release();
		
		assert ("Could not aquire a write lock after releasing the lock.", lock.tryAquireWrite());
	}
}

