/*
 *                    PDB web development code
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  If you do not have a copy,
 * see:
 *
 *      http://www.gnu.org/copyleft/lesser.html
 *
 * Copyright for this code is held jointly by the individual
 * authors.  These should be listed in @author doc comments.
 *
 *
 * Created on Mar 26, 2009
 * Created by ap3
 *
 */

package org.biojava.nbio.core.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.*;


/** A in memory cache using soft references. (can be garbage collected)
 * 
 * This code is based on: http://java-interview-faqs.blogspot.com/2008/09/building-faster-and-efficient-cache.html 
 * */


public class SoftHashMap<K, V> extends AbstractMap<K, V> {

	private final static Logger logger = LoggerFactory.getLogger(SoftHashMap.class);

   public static final int DEFAULT_LIMIT = 1;

   /** The internal HashMap that stores SoftReference to actual data. */

   private final Map<K, SoftReference<V>> map = new HashMap<K, SoftReference<V>>();

   /** Maximum Number of references you dont want GC to collect. */

   private final int max_limit;

   /** The FIFO list of hard references, order of last access. */

   private final LinkedList<V> hardCache = new LinkedList<V>();

   /** Reference queue for cleared SoftReference objects. */

   private final ReferenceQueue<V> queue = new ReferenceQueue<V>();

   public SoftHashMap() {

      this(1000);

   }

   public SoftHashMap(int hardSize) {

      max_limit = hardSize;

   }

   @Override
public V get(Object key) {

      V result = null;

      // We get the SoftReference represented by that key

      SoftReference<V> soft_ref = map.get(key);

      if (soft_ref != null) {

         try {

            // From the SoftReference we get the value, which can be

            // null if it was not in the map, or it was removed in

            // the clearGCCollected() method defined below

            result = soft_ref.get();

            if (result == null) {

               // If the value has been garbage collected, remove the

               // entry from the HashMap.

               map.remove(key);

            } else {

               // We now add this object to the beginning of the hard

               // reference queue. One reference can occur more than

               // once, because lookups of the FIFO queue are slow, so

               // we don't want to search through it each time to remove

               // duplicates.

               synchronized (hardCache){
                  hardCache.addFirst(result);

                  if (hardCache.size() > max_limit) {

                     // Remove the last entry if list greater than MAX_LIMIT

                     hardCache.removeLast();

                  }
               }

            }
         } catch (Exception e){
        	 logger.error("Exception: ", e);
         }

      }

      return result;

   }



   /**

    * We define our own subclass of SoftReference which contains not only the

    * value but also the key to make it easier to find the entry in the HashMap

    * after it's been garbage collected.

    */

   private static class SoftValue<K, V> extends SoftReference<V> {

      private final Object key; // always make data member final



      /**

       * Did you know that an outer class can access private data members and

       * methods of an inner class? I didn't know that! I thought it was only

       * the inner class who could access the outer class's private

       * information. An outer class can also access private members of an

       * inner class inside its inner class.

       */

      private SoftValue(V k, K key, ReferenceQueue<? super V> q) {

         super(k, q);

         this.key = key;

      }

   }



   /**

    * Here we go through the ReferenceQueue and remove garbage collected

    * SoftValue objects from the HashMap by looking them up using the

    * SoftValue.key data member.

    */

   @SuppressWarnings("unchecked") // every Reference in queue is stored as a SoftValue
   private void clearGCCollected() {

      SoftValue<K, V> sv;

      while ((sv = (SoftValue<K, V>) queue.poll()) != null) {

         map.remove(sv.key); // we can access private data!

      }

   }



   /**

    * Here we put the key, value pair into the HashMap using a SoftValue

    * object.

    */

   @Override
public synchronized V put(K key, V value) {

      clearGCCollected();

      logger.debug("Putting {} on cache. size: {}", key, size());
      
      map.put(key, new SoftValue<K, V>(value, key, queue));
      
      return value;

   }



	@Override
	public V remove(Object key) {
		clearGCCollected();
		logger.debug("Removing {} from cache. size: {}", key, size());
		return map.remove(key).get();
	}



   @Override
public void clear() {

      synchronized (hardCache){
         hardCache.clear();
      }

      clearGCCollected();
      logger.debug("clearing cache");
      map.clear();

   }



   @Override
public int size() {

      clearGCCollected();

      return map.size();

   }



   @Override
public Set<Map.Entry<K, V>> entrySet() {

      throw new UnsupportedOperationException();

   }

}