/*-----------------------------------------------------------------------------
 *
 * Enhydra JdbcS
 * The Initial Developer of the Original Code is Lutris Technologies Inc.
 * Portions created by Lutris are Copyright 1997-2000 Lutris Technologies Inc.
 * All Rights Reserved.
 *
 * The contents of this file are subject to the Enhydra 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
 * www.enhydra.org/license/epl.html
 *
 * Software distributed under the License is distributed on an "ASIS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 * the specific language governing rights and limitations under the License.
 *
 * -----------------------------------------------------------------------------
 * $Id$
 * -----------------------------------------------------------------------------
 */


package org.enhydra.jdbc.util;

import java.util.*;

/**
 * Simple implementation of a cache, using Least Recently Used algorithm
 * for discarding members when the cache fills up
 */
public class LRUCache {
    /** The cache */
    private Hashtable cache = new Hashtable();
    /** The linked list to keep track of least/most recently used */
    private LinkedList lru = new LinkedList();
    /** The maximum size of the cache */
    private int maxSize;

    /** 
     * Constructor
     */
    public LRUCache(int maxSize) {
	this.maxSize = maxSize;
    }

    /**
     * Puts a new object in the cache.  If the cache is full, it removes
     * the least recently used object.  The new object becomes the most
     * recently used object.
     */
    public void put(Object key, Object value) {
	List removed = new ArrayList();
	synchronized (this) {
	    // make room if needed
	    while (cache.size() + 1 > maxSize) {
		removed.add(removeLRU());
	    }
	    // remove the key from the list if it's in the cache already
	    if (cache.containsKey(key)) {
		lru.remove(key);
	    }
	    // the last item in the list is the most recently used
	    lru.addLast(key);
	    // put it in the actual cache
	    cache.put(key, value);
	}
	cleanupAll(removed);
	
    }

    /**
     * Gets an object from the cache.  This object is set to be the
     * most recenty used
     */
    public synchronized Object get(Object key) {
	// check for existence in cache
	if (!cache.containsKey(key)) {
	    return null;
	}
	// set to most recently used
	lru.remove(key);
	lru.addLast(key);
	// return the object
	return cache.get(key);
    }

    /**
     * Removes the object from the cache and the lru list
     */
    public synchronized Object remove(Object key) {
	// check for existence in cache
	if (!cache.containsKey(key)) {
	    return null;
	}
	// remove from lru list
	lru.remove(key);
	// remove from cache
	Object obj = cache.remove(key);
	return obj;
    }

    private synchronized Object removeLRU() {
	Object obj = cache.remove(lru.getFirst());
	lru.removeFirst();
	return obj;
    }

    /**
     * Resize the cache
     */
    public void resize(int newSize) {
	if (newSize <= 0) {
	    return;
	}
	List removed = new ArrayList();
	synchronized (this) {
	    maxSize = newSize;
	    while (cache.size() > maxSize) {
		removed.add(removeLRU());
	    }
	}
	cleanupAll(removed);
    }

    private void cleanupAll(List removed) {
	Iterator it = removed.iterator();
	while (it.hasNext()) {
	    cleanupObject(it.next());
	}
    }

    /**
     * Override this method to do special cleanup on an object,
     * such as closing a statement or a connection
     */
    protected void cleanupObject(Object o) {}
}


