/*
 * 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.datastructure;

import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
 * A lookup cache implements simple lookup caching algorithm for looking up things. The derived class simply implement the
 * produce(Object key) method which is assumed an expensive operation and the results will be cached by the lookup cache
 * implementation. There's no public method on lookup cache, the developer should provide application specific lookup interface.
 * @author TiongHiang Lee (thlee@onemindsoft.org)
 * @version $Id: LookupCache.java,v 1.4 2004/09/30 13:26:26 thlee Exp $ $Name:  $
 */
public abstract class LookupCache
{

    /** the logger * */
    private static final Logger _logger = Logger.getLogger(LookupCache.class.getName());

    /** the hit cache * */
    private Map _cache = new HashMap();

    /** the negative cache * */
    private Set _negCache;

    /** indicate whether to do negative caching * */
    private boolean _doNegCache = true;

    /**
     * {@inheritDoc}
     */
    public LookupCache()
    {
        this(true);
    }

    /**
     * {@inheritDoc}
     * @param doNegCache whether to do negative caching
     */
    public LookupCache(boolean doNegCache)
    {
        setDoNegativeCache(doNegCache);
    }

    /**
     * The main lookup method. The developer should provide another application specific method that call this method to return what
     * the application wants
     * @param key the key
     * @return the object or null
     * @todo add synchronization when lookup the same key to avoid double loading
     */
    protected final Object lookup(Object key)
    {
        if (_doNegCache)
        {
            if (_negCache.contains(key))
            {
                if (_logger.isLoggable(Level.FINEST))
                {
                    _logger.finest("Returning negative cache hit");
                }
                return null;
            }
        }
        Object o = _cache.get(key);
        if (o == null)
        {
            //TODO: synchronization for looking up same key
            o = produce(key);
            if (o != null)
            {
                if (_logger.isLoggable(Level.FINEST))
                {
                    _logger.finest("Put hit of " + key + " to cache");
                }
                _cache.put(key, o);
            } else
            {
                if (_doNegCache)
                {
                    _logger.finest("Put negative hit of " + key + " to cache");
                    _negCache.add(key);
                }
            }
        } else
        {
            if (_logger.isLoggable(Level.FINEST))
            {
                _logger.finest("Returning positive cache hit of " + key);
            }
        }
        return o;
    }

    /**
     * Produce the object given the key. This is assumed to be an expensive operation and it will be called by the lookup method.
     * The result will be cached by the lookup method and negative result also will be cached to if the doNegCache is turned on.
     * @param key the key
     * @return the result or null if no result
     */
    protected abstract Object produce(Object key);

    /**
     * Turn on/off the negative cache
     * @param b true to turn on the neg cache
     */
    protected void setDoNegativeCache(boolean b)
    {
        _doNegCache = b;
        if (b && (_negCache == null))
        {
            _negCache = new HashSet();
        }
    }

    /**
     * Get whether the object is in negative cache
     * @param o the object
     * @return true if is in negative cache
     */
    protected boolean isInNegCache(Object o)
    {
        return _negCache.contains(o);
    }

    /**
     * Test if the key is in cache
     * @param o the object
     * @return true if is in cache
     */
    protected boolean isInCache(Object o)
    {
        return _cache.containsKey(o);
    }

    /**
     * Clear all the negative cache
     */
    protected void clearNegCache()
    {
        if (_negCache != null)
        {
            _negCache.clear();
        }
    }
}