/*
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * (the "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at <http://www.mozilla.org/MPL/>.
 * 
 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
 * language governing rights and limitations under the License.
 * 
 * The Original Code is the Venice Web Communities System.
 * 
 * The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
 * for Silverwrist Design Studios.  Portions created by Eric J. Bowersox are
 * Copyright (C) 2002-03 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.util;

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

/**
 * A class implementing a memory-sensitive cache.  The cache is an LRU cache which uses two size limits,
 * the <EM>soft limit</EM> and the <EM>hard limit</EM>.  The soft limit dictates the maximum size of the
 * cache; all elements in the cache have <CODE>SoftReference</CODE>s to them, which allows the JVM to expel
 * them from memory if necessary.  However, the most recent elements in the cache, up to the hard limit,
 * have ordinary references on them, which prevents them from being expelled.  Therefore, under low memory
 * conditions, all elements in the cache may be discarded except for the most recent ones.
 *
 * @author Eric J. Bowersox &lt;erbo@silcom.com&gt;
 * @version X
 */
public class HardSoftCache
{
  /*--------------------------------------------------------------------------------
   * Internal class to hold the hard hash entries
   *--------------------------------------------------------------------------------
   */

  private static class HardHashMap extends LinkedHashMap
  {
    /*====================================================================
     * Attributes
     *====================================================================
     */

    private int m_maxsize;   // maximum size of the cache

    /*====================================================================
     * Constructor
     *====================================================================
     */

    private HardHashMap(int capacity)
    {
      super(capacity+2,0.95F,true);
      m_maxsize = capacity;

    } // end constructor

    /*====================================================================
     * Overrides from class LinkedHashMap
     *====================================================================
     */

    protected boolean removeEldestEntry(Map.Entry eldest)
    {
      return (this.size()>m_maxsize);

    } // end removeEldestEntry

  } // end class HardHashMap

  /*--------------------------------------------------------------------------------
   * Internal class to hold the soft hash entries
   *--------------------------------------------------------------------------------
   */

  private static class SoftHashMap extends LinkedHashMap
  {
    /*====================================================================
     * Attributes
     *====================================================================
     */

    private int m_maxsize;   // maximum size of the cache

    /*====================================================================
     * Constructor
     *====================================================================
     */

    private SoftHashMap(int capacity)
    {
      super(capacity+2,0.95F,true);
      m_maxsize = capacity;

    } // end constructor

    /*====================================================================
     * Overrides from class HashMap
     *====================================================================
     */

    public Object put(Object key, Object value)
    {
      SoftReference newref = new SoftReference(value);
      Reference r = (Reference)(super.put(key,newref));
      Object rc = null;
      if (r!=null)
      { // get the old object
	rc = r.get();
	r.clear();

      } // end if

      return rc;

    } // end put

    public void putAll(Map t)
    {
      Iterator it = t.entrySet().iterator();
      while (it.hasNext())
      { // run each item through our modified put()
	Map.Entry ntry = (Map.Entry)(it.next());
	this.put(ntry.getKey(),ntry.getValue());

      } // end while

    } // end putAll

    public Object remove(Object key)
    {
      Reference r = (Reference)(super.remove(key));
      Object rc = null;
      if (r!=null)
      { // get the old object
	rc = r.get();
	r.clear();

      } // end if

      return rc;

    } // end remove

    /*====================================================================
     * Overrides from class LinkedHashMap
     *====================================================================
     */

    public void clear()
    {
      Iterator it = this.values().iterator();
      while (it.hasNext())
      { // dump all the references we contain
	Reference r = (Reference)(it.next());
	r.clear();

      } // end while

      super.clear();

    } // end clear

    public Object get(Object key)
    {
      Reference r = (Reference)(super.get(key));
      if (r==null)
	return null;
      Object rc = r.get();
      if (rc==null)
      { // reference has been dumped - remove it from the map
	super.remove(key);
	r.clear();

      } // end if

      return rc;

    } // end get

    protected boolean removeEldestEntry(Map.Entry eldest)
    {
      if (this.size()<=m_maxsize)
	return false;
      Reference r = (Reference)(eldest.getValue());
      r.clear();
      return true;

    } // end removeEldestEntry

  } // end class SoftHashMap

  /*--------------------------------------------------------------------------------
   * Attributes
   *--------------------------------------------------------------------------------
   */

  private HardHashMap m_hard_map;   // the "hard map"
  private SoftHashMap m_soft_map;   // the "soft map"

  /*--------------------------------------------------------------------------------
   * Constructor
   *--------------------------------------------------------------------------------
   */

  /**
   * Constructs a new cache object.
   *
   * @param hard_limit The "hard limit" for this cache.  Has a minimum value of 1.
   * @param soft_limit The "soft limit" for this cache.  Has a minimum value of 2 times the value of the
   *                   "hard limit."
   */
  public HardSoftCache(int hard_limit, int soft_limit)
  {
    // Pre-condition the input parameters.
    if (hard_limit<1)
      hard_limit = 1;
    if (soft_limit<(hard_limit * 2))
      soft_limit = hard_limit * 2;
    m_hard_map = new HardHashMap(hard_limit);
    m_soft_map = new SoftHashMap(soft_limit);

  } // end constructor

  /*--------------------------------------------------------------------------------
   * External operations
   *--------------------------------------------------------------------------------
   */

  /**
   * Returns the value to which this cache maps the specified key. Returns <CODE>null</CODE> if the cache
   * contains no mapping for this key.
   *
   * @param key The key whose associated value is to be returned.
   * @return The value to which this cache maps the specified key.
   */
  public synchronized Object get(Object key)
  {
    if (key==null)
      throw new NullPointerException("HardSoftCache.get");
    Object hard_rc = m_hard_map.get(key);
    Object soft_rc = m_soft_map.get(key);
    return ((hard_rc!=null) ? hard_rc : soft_rc);

  } // end get

  /**
   * Associates the specified value with the specified key in this cache. If the cache previously contained a
   * mapping for this key, the old value is replaced.
   *
   * @param key The key with which the specified value is to be associated.
   * @param value The value to be associated with the specified key.
   */
  public synchronized void put(Object key, Object value)
  {
    if (key==null)
      throw new NullPointerException("HardSoftCache.put(key)");
    if (value==null)
      throw new NullPointerException("HardSoftCache.put(value)");
    m_hard_map.put(key,value);
    m_soft_map.put(key,value);

  } // end put

  /**
   * Removes the mapping for this key from this cache if present.
   *
   * @param key The key whose mapping is to be removed from the map.
   */
  public synchronized void remove(Object key)
  {
    if (key==null)
      throw new NullPointerException("HardSoftCache.remove");
    m_hard_map.remove(key);
    m_soft_map.remove(key);

  } // end remove

  /**
   * Returns the number of key-value mappings in this cache.
   *
   * @return The number of key-value mappings in this cache.
   */
  public synchronized int size()
  {
    return m_soft_map.size();

  } // end size

  /**
   * Returns <CODE>true</CODE> if this cache contains no key-value mappings.
   *
   * @return <CODE>true</CODE> if this cache contains no key-value mappings.
   */
  public synchronized boolean isEmpty()
  {
    return m_hard_map.isEmpty();

  } // end isEmpty

  /**
   * Removes all mappings from this cache.
   */
  public synchronized void clear()
  {
    m_hard_map.clear();
    m_soft_map.clear();

  } // end clear

  /**
   * Returns <CODE>true</CODE> if this cache contains a mapping for the specified key.
   *
   * @param key The key whose presence in this cache is to be tested.
   * @return <CODE>true</CODE> if this cache contains a mapping for the specified key.
   */
  public synchronized boolean containsKey(Object key)
  {
    if (key==null)
      throw new NullPointerException("HardSoftCache.containsKey");
    return (m_soft_map.get(key)!=null);

  } // end containsKey

} // end class HardSoftCache
