import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;

public class ReadersWriters {
    public static void main(String[] args) throws Exception {
        if (args.length > 0) FAIR = true;
        System.err.format("*** WRITERS=%d, READERS=%d, FAIR=%b%n%n", WRITERS, READERS, FAIR);
        
        RWDictionary rwd = new RWDictionary(FAIR);
        
        ArrayList<Thread> threads = new ArrayList<Thread>();
        for (int i=1; i<=WRITERS; i++) threads.add(new Writer(rwd, i));
        for (int i=1; i<=READERS; i++) threads.add(new Reader(rwd, i));
        
        for (Thread t : threads) t.start();
        /* *** */
        for (Thread t : threads) t.join();
        
        System.err.println("\r\n<enter> to exit");
        System.in.read();
    }
    
    static int WRITERS = 2;
    static int READERS = 4;
    static boolean FAIR = false;
    static int WRITER_SLEEP = 500;
    static int READER_SLEEP = 100;
    static int PUT_SLEEP = 100;
    static int GET_SLEEP = 100;

    static String[] digits = new String[] {
            "zero", "one", "two", "three", "four",
            "five", "six", "seven", "eight", "nine"    };

    static class Writer extends Thread {
        private RWDictionary rwd;
        private int id;
            
        public Writer(RWDictionary rwd, int id) {
            this.rwd = rwd;
            this.id = id;
            this.setName("W"+id);
        }

        public void run() {
            for (int key = id % 2; key < 10; key+=2) {
                String value = digits[key].toUpperCase();
                String oldvalue = rwd.put(key, value);
                try { Thread.sleep((int)(Math.random() * WRITER_SLEEP)); } catch (InterruptedException ex) {}
            }
        }
    }

    static class Reader extends Thread {
        private RWDictionary rwd;
        private int id;
            
        public Reader(RWDictionary rwd, int id) {
            this.rwd = rwd;
            this.id = id;
            this.setName("R"+id);
        }

        public void run() {      
            for (int key = 0; key < 10; key+=1) {
                String value = rwd.get(key);
                try { Thread.sleep((int)(Math.random() * READER_SLEEP)); } catch (InterruptedException ex) {}
            }
        }
    }
    
    static class RWDictionary {
        public RWDictionary(boolean FAIR) {
            for (int i=0; i<10; i++) m.put(i, digits[i]);
            this.FAIR = FAIR;
        }
        
        private boolean FAIR = false;
        private final Map<Integer, String> m = new TreeMap<Integer, String>();
        private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(FAIR);
        private final Lock r = rwl.readLock();
        private final Lock w = rwl.writeLock();

        public String get(Integer key) {
            r.lock();
            
            try { 
                String id = Thread.currentThread().getName();
                System.err.format("\t\t%s get %d=...?%n", id, key);

                String value = m.get(key); 

                try { Thread.sleep((int)(Math.random() * GET_SLEEP)); } catch (InterruptedException ex) {}
                System.err.format("\t\t%s get %d=%s!%n", id, key, value);

                return value;

            } finally { r.unlock(); }
        }
        
        public String put(Integer key, String value) {
            w.lock();

            try { 
                String id = Thread.currentThread().getName();
                System.err.format("%s put %d=...->%s?%n", id, key, value);
                
                String oldvalue = m.put(key, value); 
                
                try { Thread.sleep((int)(Math.random() * PUT_SLEEP)); } catch (InterruptedException ex) {}
                System.err.format("%s put %d=%s->%s!%n", id, key, oldvalue, value);

                return oldvalue;

            } finally { w.unlock(); }
        }
    }
}



