/*
 * MemoryStore.java
 *
 * Created on January 15, 2002, 10:26 AM
 * Last modified  on  $Date$ by $Author$
 */
package org.apache.commons.simplestore;

import java.util.Enumeration;
import java.io.IOException;
/**
 *
 * @author  Juozas Baliuka
 * @version $Revision$
 */

public  class MemoryStore implements Store //, java.util.Map
  {
    
    private static boolean DEBUG = false;
    
    
    //simple test
    
    public static void main(String args[])throws Exception{
        
        DEBUG = true;
        final int OBJECT_SIZE = 0xFFFF;
        final int MAX_STRONG_REF = 20;
         final int ITERATIONS  = MAX_STRONG_REF*2;
        
        MemoryStore mStore = new MemoryStore(
        
        new Store(){
            
            public void free(){}
            
            public Enumeration keys(){ return null; }
            
            public boolean containsKey(Object key){ return false;}
            
            public void remove(Object obj){}
            
            public void store(Object key,Object object){}
            
            public void hold(Object key,Object object){}
            
            public Object get(Object key){
                
                return  new int[OBJECT_SIZE];
            }
        }
        
        , MAX_STRONG_REF );
        
        
        System.out.println("operations ... ");
        
        Object obj =  mStore.get(new Integer(-1));//strong ref
       
        for(int i = 0; i < ITERATIONS ; i++ ){
            Object key = new Integer(i);
            Object o = mStore.get(key);// soft ref after iteration;
            mStore.get(new Integer(0));//MFU and MRU
        }
        
        System.gc();
        mStore.get(new Integer(0));//remove unused objects, private iterator can't do it.
        
        
        System.out.println("iterating cache ");
        System.out.println("must contain key -1");
        System.out.println("must contain 0 if MAX_STRONG_REF > 0 ... ");
        if( !  mStore.map.containsKey(new Integer(-1)) ||
            !( mStore.map.containsKey(new Integer(0)) && MAX_STRONG_REF > 0 )
          )
            System.out.println("failed");
        System.out.println("cache size is " + mStore.map.size() + " it depends on GC possible MAX_STRONG_REF < SIZE" );
        
        java.util.Iterator i = mStore.map.keySet().iterator();            
        while(i.hasNext())
            System.out.println(i.next());
        
    }
    
    
    static class SoftRef extends java.lang.ref.SoftReference{
        Object key;
        private  SoftRef(Object key,Object object,java.lang.ref.ReferenceQueue queue){
            super(object,queue);
            this.key = key;
            
        }
        
    }
    
    
    private Store store;
    private int maxStrongRefCount;
    private Object [] strongRefs;
    private int current = 0;
    private java.util.Map map = new java.util.HashMap();
    private java.lang.ref.ReferenceQueue queue = new java.lang.ref.ReferenceQueue();
    
    
    public static Store getInstance(Store store , int maxStrongRef){
        return new SynchronizedStore( new  MemoryStore(store, maxStrongRef) ) ;
    }
    
    
    /** Creates new MemoryStore */
    protected MemoryStore(Store store, int maxStrongRefCount ) {
        if(store == null)
            throw new NullPointerException();
        if(maxStrongRefCount < 0)
            throw new java.lang.IllegalArgumentException();
        this.store = store;
        this.maxStrongRefCount = maxStrongRefCount;
        if(maxStrongRefCount > 0)
            strongRefs = new Object[ maxStrongRefCount ];
        
    }
    // remove keys
    private void removeSoftRef(){
        
        SoftRef ref = (SoftRef)queue.poll();
        
        while( ref != null ){
            map.remove(ref.key);
            if(DEBUG)
                System.out.println( "Key " + ref.key + " removed from queue, map size is " + map.size()  );
            ref = (SoftRef)queue.poll();
        }
        
        
    }
    
    private void addStrongRef(Object object){
        
        if( strongRefs != null )
            strongRefs[ ( current++ ) % maxStrongRefCount ] = object;
        
    }
    private void internalStoreObject(Object key, Object object){
        
        addStrongRef(object);
        map.put(key,new SoftRef(key,object,queue));
        
    }
    
    
    /**
     * Returns the list of used keys as an Enumeration of Objects.
     */
    public Enumeration keys() {
        return store.keys();
    }
    
    /**
     * Remove the object associated to the given key.
     *
     * @param key the Key Object
     */
    public void remove(Object key) {
        removeSoftRef();
        map.remove(key);
    }
    
    /**
     * Indicates if the given key is associated to a contained object.
     *
     * @param key the Key Object
     */
    public boolean containsKey(Object key) {
        removeSoftRef();
        if(map.containsKey(key))return true;
        return store.containsKey(key);
    }
    
    /**
     * Frees some object out of the Store.
     */
    public void free() {
        removeSoftRef();
    }
    
    /**
     * Store the given object in a persistent state. It is up to the
     * caller to ensure that the key has a persistent state across
     * different JVM executions.
     *
     * @param key the Key Object
     * @param value the Value Object
     */
    public void store(Object key, Object object) throws IOException {
        removeSoftRef();
        store.store(key,object);
        internalStoreObject(key,object);
        
    }
    
    /**
     * Holds the given object in a volatile state. This means
     * the object store will discard held objects if the
     * virtual machine is restarted or some error happens.
     *
     * @param key the Key Object
     * @param value the Value Object
     */
    public void hold(Object key, Object value) throws IOException {
        store(key,value);
    }
    
    /**
     * Get the object associated to the given unique key.
     *
     * @param key the Key Object
     */
    public Object get(Object key) {
        removeSoftRef();
        
        Object object = null;
        
        java.lang.ref.Reference ref = (java.lang.ref.Reference)map.get(key);
        
        if(ref != null)
            object = ref.get();
        
        if(object == null)
            object = store.get(key);
        
        
        internalStoreObject(key,object);
        
        return object;
        
    }
    
}
