/*
 * Copyright (C) 2004 TiongHiang Lee
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not,  write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Email: thlee@onemindsoft.org
 */

package org.onemind.commons.java.lang.ref;

import java.util.*;
import java.lang.ref.*;
/**
 * @author TiongHiang Lee (thlee@onemindsoft.org)
 * @version $Id: SoftHashMap.java,v 1.1 2004/10/23 15:24:35 thlee Exp $ $Name:  $
 * Credits: Article by Heinz Kabutz at http://archive.devx.com/java/free/articles/Kabutz01/Kabutz01-1.asp
 */
public class SoftHashMap extends AbstractMap
{

    /** 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 extends SoftReference
    {
        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(Object k, Object key, ReferenceQueue q)
        {
            super(k, q);
            this.key = key;
        }
    }

    /** The internal HashMap that will hold the SoftReference. */
    private final Map hash = new HashMap();

    /** The number of "hard" references to hold internally. */
    private final int HARD_REF_SIZE;

    /** The FIFO list of hard references, order of last access. */
    private final LinkedList hardRefCache;

    /** Reference queue for cleared SoftReference objects. */
    private final ReferenceQueue queue = new ReferenceQueue();

    /**
     * Constructor     
     */
    public SoftHashMap()
    {
        this(0);
    }

    /**
     * Constructor
     * @param hardSize the hard reference size to maintain
     */
    public SoftHashMap(int hardSize)
    {
        HARD_REF_SIZE = hardSize;
        if (HARD_REF_SIZE>0)
        {
            hardRefCache = new LinkedList();
        } else 
        {
            hardRefCache = null;
        }
    }

    public Object get(Object key)
    {
        Object result = null;
        // get the SoftReference represented by that key
        SoftReference soft_ref = (SoftReference) hash.get(key);
        if (soft_ref != null)
        {
            // From the SoftReference we get the value, which can be
            // null if it was not in the map, or it was removed in
            // the processQueue() method defined below
            result = soft_ref.get();
            if (result == null)
            {
                // If the value has been garbage collected, remove the
                // entry from the HashMap.
                hash.remove(key);
            } else
            {
                if (HARD_REF_SIZE>0)
                {
                    // 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.
	                hardRefCache.addFirst(result);
	                if (hardRefCache.size() > HARD_REF_SIZE)
	                {
	                    // Remove the last entry if list longer than HARD_SIZE
	                    hardRefCache.removeLast();
	                }
                }
            }
        }
        return result;
    }

    /** 
     * Go through the ReferenceQueue and remove garbage
     * collected SoftValue objects from the HashMap
     */
    private void _cleanCollectedValues()
    {
        SoftValue sv;
        while ((sv = (SoftValue) queue.poll()) != null)
        {
            hash.remove(sv.key); // we can access private data!
        }
    }

    /**
     *  Here we put the key, value pair into the HashMap using
     *  a SoftValue object. 
     */
    public Object put(Object key, Object value)
    {
        _cleanCollectedValues(); // throw out garbage collected values first
        return hash.put(key, new SoftValue(value, key, queue));
    }

    /**
     * {@inheritDoc}
     */
    public Object remove(Object key)
    {
        _cleanCollectedValues(); // throw out garbage collected values first
        return hash.remove(key);
    }

    /** 
     * {@inheritDoc}
     */
    public void clear()
    {
        if (HARD_REF_SIZE>0)
        {
            hardRefCache.clear();
        }
        _cleanCollectedValues(); // throw out garbage collected values
        hash.clear();
    }

    /** 
     * {@inheritDoc}
     */
    public int size()
    {
        _cleanCollectedValues(); // throw out garbage collected values first
        return hash.size();
    }

    /** 
     * {@inheritDoc}
     */
    public Set entrySet()
    {
        // no, no, you may NOT do that!!! GRRR
        throw new UnsupportedOperationException();
    }
}