package util.misc;

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * This class implements a Cache using soft references.
 * As soon as the garbage collector tries to remove
 * an object, it gets removed from the cache.
 *
 * @since 2.7
 */
public class SoftReferenceCache<T,K> {
  /**
   * A map with references to the objects
   */
  private Map<T, SoftReference<K>> mCacheHashMap = Collections.synchronizedMap(new HashMap<T, SoftReference<K>>());
  /**
   * This map is used for the garbage collection of all items
   */
  private Map<SoftReference<K>, T> mRefHashMap = Collections.synchronizedMap(new HashMap<SoftReference<K>, T>());

  /**
   * This queue gets notified if an element gets removed by the garbage collector
   */
  private ReferenceQueue mRefQueue = new ReferenceQueue();

  /**
   * Get the value from the Cache
   * @param key get value for this key
   */
  public K get(T key) {
    cleanUp();
    SoftReference<K> ref = mCacheHashMap.get(key);

    K value = null;

    if (ref != null) {
      value = ref.get();
    }

    return value;
  }

  /**
   * Put a object into the cache
   *
   * @param key key for this object
   * @param object object to store in the cache
   */
  public void put(T key, K object) {
    cleanUp();
    SoftReference<K> ref = new SoftReference<K>(object, mRefQueue);
    mCacheHashMap.put(key, ref);
    mRefHashMap.put(ref, key);
  }

  /**
   * Remove item from cache
   * @param key remove item that references this key
   */
  public void remove(T key) {
    cleanUp();
    SoftReference<K> ref = mCacheHashMap.remove(key);
    if (ref != null) {
      mRefHashMap.remove(ref);
    }
  }

  /**
   * Clear the cache
   */
  public void clear() {
    mCacheHashMap.clear();
    mRefHashMap.clear();
  }

  /**
   * Cleanup the cache. Every garbage collected item must be removed
   */
  public void cleanUp(){
    Reference ref = mRefQueue.poll();
    while (ref != null) {
      ref = mRefQueue.poll();
      mRefHashMap.remove(ref);
      if (ref != null) {
        T key = mRefHashMap.get(ref);
        mCacheHashMap.remove(key);
      }
    }
  }

  /**
   * Checks if the cache contains a specific key
   *
   * @param key search for this key
   * @return <code>true</code> if this cache contains this key
   */
  public boolean containsKey(T key) {
    return mCacheHashMap.containsKey(key);
  }
}
