

package gov.va.med.cds.util;


import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;


/**
 * An LRU cache, based on <code>LinkedHashMap</code>.<br>
 * This cache has a fixed maximum number of elements (<code>cacheSize</code>). If the cache is full and another
 * entry is added, the LRU (least recently used) entry is dropped.
 * <p>
 * This class is thread-safe. All methods of this class are synchronized.<br>
 */
public class ConcurrentLRUCache<K, V>
{
    private static final float HASH_TABLE_LOAD_FACTOR = 0.75f;
    private LinkedHashMap<K, V> map;
    private int cacheSize;


    /**
     * Creates a new LRU cache.
     * 
     * @param aCacheSize the maximum number of entries that will be kept in this cache.
     */
    public ConcurrentLRUCache( int aCacheSize )
    {
        this.cacheSize = aCacheSize;
        int hashTableCapacity = ( int )Math.ceil( aCacheSize / HASH_TABLE_LOAD_FACTOR ) + 1;
        map = new LinkedHashMap<K, V>( hashTableCapacity, HASH_TABLE_LOAD_FACTOR, true )
        {
            // (an anonymous inner class)
            private static final long serialVersionUID = 1;


            @Override
            protected boolean removeEldestEntry( Map.Entry<K, V> eldestEntry )
            {
                return size() > ConcurrentLRUCache.this.cacheSize;
            }
        };
    }


    /**
     * This method is used to check if a key exists in the map
     * 
     * @param aKey - the key who's presence in the map is to be tested
     * @return true if this map contains a mapping for aKey
     */
    public synchronized boolean containsKey( K aKey )
    {
        return map.containsKey( aKey );
    }


    /**
     * Retrieves an entry from the cache.<br>
     * The retrieved entry becomes the MRU (most recently used) entry.
     * 
     * @param key the key whose associated value is to be returned.
     * @return the value associated to this key, or null if no value with this key exists in the cache.
     */
    public synchronized V get( K aKey )
    {
        return map.get( aKey );
    }


    /**
     * Adds an entry to this cache. If the cache is full, the LRU (least recently used) entry is dropped.
     * 
     * @param key the key with which the specified value is to be associated.
     * @param aValue a value to be associated with the specified key.
     */
    public synchronized void put( K aKey, V aValue )
    {
        map.put( aKey, aValue );
    }


    /**
     * Clears the cache.
     */
    public synchronized void clear( )
    {
        map.clear();
    }


    /**
     * Returns the number of used entries in the cache.
     * 
     * @return the number of entries currently in the cache.
     */
    public synchronized int usedEntries( )
    {
        return map.size();
    }


    /**
     * Returns a <code>Collection</code> that contains a copy of all cache entries.
     * 
     * @return a <code>Collection</code> with a copy of the cache content.
     */
    public synchronized Collection<Map.Entry<K, V>> getAll( )
    {
        return new ArrayList<Map.Entry<K, V>>( map.entrySet() );
    }


    public synchronized int getCacheSize( )
    {
        return cacheSize;
    }
}
