/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.jena.rdf.model.test ;
import junit.framework.*;
import org.apache.jena.rdf.model.* ;
import org.apache.jena.shared.Lock ;
public class TestConcurrency  extends TestSuite
{

    /** Creates new RDQLTestSuite */
        static public TestSuite suite() {
            return new TestConcurrency();
        }

	// Test suite to exercise the locking
	static long SLEEP = 100 ;
	static int threadCount = 0;

    // Note : reuse the model across tests.
    final static Model model1 = ModelFactory.createDefaultModel() ;
    final static Model model2 = ModelFactory.createDefaultModel() ;
        
    public TestConcurrency()
    {
        super("Model concurrency control") ;

        if ( true )
        {
            // Same model: inner and outer
            addTest(new Nesting("Lock nesting 1 - same model", 
                    model1, Lock.READ, Lock.READ, false)) ;
            addTest(new Nesting("Lock nesting 2 - same model",
                    model1, Lock.WRITE, Lock.WRITE, false)) ;
            addTest(new Nesting("Lock nesting 3 - same model",
                    model1, Lock.READ, Lock.WRITE, true)) ;
            addTest(new Nesting("Lock nesting 4 - same model",
                    model1, Lock.WRITE, Lock.READ, false)) ;
    
            // Different  model: inner and outer
            addTest(new Nesting("Lock nesting 1 - different models", 
                    model1, Lock.READ, model2, Lock.READ, false)) ;
            addTest(new Nesting("Lock nesting 2 - different models",
                    model1, Lock.WRITE, model2, Lock.WRITE, false)) ;
            addTest(new Nesting("Lock nesting 3 - different models",
                    model1, Lock.READ, model2, Lock.WRITE, false)) ;
            addTest(new Nesting("Lock nesting 4 - different models",
                    model1, Lock.WRITE, model2, Lock.READ, false)) ;
        }
        if ( true )
        {
            // Crude test                
            addTest(new Parallel("Parallel concurrency test")) ;
        }

    }

    static class Nesting extends TestCase
    {
        Model outerModel ;
        Model innerModel ;
        boolean outerLock ;
        boolean innerLock ;
        boolean exceptionExpected ;

        // Same model
        Nesting(String testName, Model model,
                boolean lock1, boolean lock2, boolean exExpected)
        {
            this(testName, model, lock1, model, lock2, exExpected) ;
        }

        // Potetnially different models
        Nesting(String testName,
                Model model1, boolean lock1,
                Model model2, boolean lock2, boolean exExpected)
        {
            super(testName);
            outerModel = model1 ; 
            outerLock = lock1 ;
            innerModel = model2 ;
            innerLock = lock2 ;
            exceptionExpected = exExpected ;
        }

        @Override
        protected void runTest()
        {
            boolean gotException = false ;
            try {
                outerModel.enterCriticalSection(outerLock) ;
                
                try {
                    try {
                        // Should fail if outerLock is READ and innerLock is WRITE
                        // and its on the same model, inner and outer.
                        innerModel.enterCriticalSection(innerLock) ;
                        
                    } finally { innerModel.leaveCriticalSection() ; }
                } catch (Exception ex)
                {
                    gotException = true ;
                }
                
            } finally { 
                outerModel.leaveCriticalSection() ;
            }
            
            if ( exceptionExpected )
                assertTrue("Failed to get expected lock promotion error", gotException) ;
            else
                assertTrue("Got unexpected lock promotion error", !gotException) ;
        }
    }
    
    
    static class Parallel extends TestCase
    {
        int threadTotal = 10 ; 
        
        
        Parallel(String testName)
        {
            super(testName) ;
        }
        
        @Override
        protected void runTest()
        {
            Model model = ModelFactory.createDefaultModel() ;
            Thread threads[] = new Thread[threadTotal] ;

            boolean getReadLock = Lock.READ ;
            for (int i = 0; i < threadTotal; i++)
            {
                String nextId = "T"+Integer.toString(++threadCount);
                threads[i] = new Operation(model, getReadLock) ;
                threads[i].setName(nextId) ;
                threads[i].start() ;
                
                getReadLock = ! getReadLock ;
            }
        
            boolean problems = false ;
            for ( int i = 0; i < threadTotal; i++)
            {
                try { threads[i].join(200*SLEEP) ; } catch (InterruptedException intEx) {}
            }

            // Try again for any we missed.
            for ( int i = 0; i < threadTotal; i++)
            {
                if ( threads[i].isAlive() )
                    try { threads[i].join(200*SLEEP) ; } catch (InterruptedException intEx) {}
                if ( threads[i].isAlive())
                {
                    System.out.println("Thread "+threads[i].getName()+" failed to finish") ;
                    problems = true ;
                }
            }
            
            
            
            assertTrue("Some thread failed to finish", !problems) ;
        }

        class Operation extends Thread
        {
             Model model ;
             boolean readLock ;
            
             Operation(Model m, boolean withReadLock)
             {
                 model = m ; readLock = withReadLock ;
             }
            
             @Override
            public void run()
             {
                 for ( int i = 0 ; i < 2 ; i++ )
                 {
                     try {
                         model.enterCriticalSection(readLock) ;
                         if ( readLock )
                             readOperation(false) ;
                         else
                             writeOperation(false) ;
                     } finally { model.leaveCriticalSection() ; }
                 }            
             }
        }
        // Operations ----------------------------------------------
    
        volatile int writers = 0 ; 
    
        // The example model operations
        void doStuff(String label, boolean doThrow)
        {
            String id = Thread.currentThread().getName() ;
            // Puase a while to cause other threads to (try to) enter the region.
            try { Thread.sleep(SLEEP) ; } catch (InterruptedException intEx){}
            if ( doThrow )
                throw new RuntimeException(label) ;
        }
    
        // Example operations
    
        public void readOperation(boolean doThrow)
        {
            if ( writers > 0 )
                System.err.println("Concurrency error: writers around!") ;
            doStuff("read operation", false) ;
            if ( writers > 0 )
                System.err.println("Concurrency error: writers around!") ;
        }

        public void writeOperation(boolean doThrow)
        {
            writers++ ;
            doStuff("write operation", false) ;
            writers-- ;

        }
    }
}
