User: simone
Date: 00/10/16 16:20:51
Added: src/main/org/jboss/util LRUCachePolicy.java
Log:
Implementation of a LRU cache algorithm
Revision Changes Path
1.1 jboss/src/main/org/jboss/util/LRUCachePolicy.java
Index: LRUCachePolicy.java
===================================================================
/*
* jBoss, the OpenSource EJB server
*
* Distributable under GPL license.
* See terms of license at gnu.org.
*/
package org.jboss.util;
import java.util.HashMap;
/**
* Implementation of a Least Recently Used cache policy.
*
* @author Simone Bordet ([EMAIL PROTECTED])
* @version $Revision: 1.1 $
*/
public class LRUCachePolicy
implements CachePolicy
{
// Constants -----------------------------------------------------
// Attributes ----------------------------------------------------
/**
* The map holding the cached objects
*/
protected HashMap m_map;
/**
* The linked list used to implement the LRU algorithm
*/
protected LRUList m_list;
/**
* The maximum capacity of this cache
*/
protected int m_maxCapacity;
/**
* The minimum capacity of this cache
*/
protected int m_minCapacity;
// Static --------------------------------------------------------
// Constructors --------------------------------------------------
/**
* Creates a LRU cache policy object with zero cache capacity.
* @see #init
*/
public LRUCachePolicy()
{
}
/**
* Creates a LRU cache policy object with the specified minimum
* and maximum capacity.
* @see #init
*/
public LRUCachePolicy(int min, int max)
{
if (min < 2 || min > max) {throw new IllegalArgumentException("Illegal
cache capacities");}
m_minCapacity = min;
m_maxCapacity = max;
}
// Public --------------------------------------------------------
// Service implementation ----------------------------------------------
/**
* Initializes the cache, creating all required objects and initializing their
* values.
* @see #start
* @see #destroy
*/
public void init() throws Exception
{
m_map = new HashMap();
m_list = createList();
m_list.m_maxCapacity = m_maxCapacity;
m_list.m_minCapacity = m_minCapacity;
m_list.m_capacity = m_maxCapacity;
}
/**
* Starts this cache that is now ready to be used.
* @see #init
* @see #stop
*/
public void start() throws Exception
{
}
/**
* Stops this cache thus {@link #flush}ing all cached objects. <br>
* After this method is called, a call to {@link #start} will restart the
cache.
* @see #start
* @see #destroy
*/
public void stop()
{
flush();
}
/**
* Destroys the cache that is now unusable. <br>
* To have it working again it must be re-{@link #init}ialized and
* re-{@link #start}ed.
* @see #init
*/
public void destroy()
{
m_map = null;
m_list = null;
m_minCapacity = 0;
m_maxCapacity = 0;
}
public Object get(Object key)
{
if (key == null)
{
throw new IllegalArgumentException("Requesting an object using
a null key");
}
LRUCacheEntry value = (LRUCacheEntry)m_map.get(key);
if (value != null)
{
m_list.promote(value);
return value.m_object;
}
else
{
cacheMiss();
return null;
}
}
public Object peek(Object key)
{
if (key == null)
{
throw new IllegalArgumentException("Requesting an object using
a null key");
}
LRUCacheEntry value = (LRUCacheEntry)m_map.get(key);
if (value == null)
{
return null;
}
else
{
return value.m_object;
}
}
public void insert(Object key, Object o)
{
if (o == null) {throw new IllegalArgumentException("Cannot insert a
null object in the cache");}
if (key == null) {throw new IllegalArgumentException("Cannot insert an
object in the cache with null key");}
Object obj = m_map.get(key);
if (obj == null)
{
synchronized (m_list)
{
m_list.demote();
LRUCacheEntry entry = createCacheEntry(key, o);
m_list.promote(entry);
m_map.put(key, entry);
}
}
else {throw new IllegalStateException("Attempt to put in the cache an
object that is already there");}
}
public void remove(Object key)
{
if (key == null) {throw new IllegalArgumentException("Removing an
object using a null key");}
Object value = m_map.get(key);
if (value != null)
{
m_list.remove((LRUCacheEntry)value);
}
else {} // Do nothing, the object isn't in the cache list
m_map.remove(key);
}
public void flush()
{
LRUCacheEntry entry = null;
while ((entry = m_list.m_tail) != null)
{
ageOut(entry);
}
}
// Y overrides ---------------------------------------------------
// Package protected ---------------------------------------------
// Protected -----------------------------------------------------
/**
* Factory method for the linked list used by this cache implementation.
*/
protected LRUList createList() {return new LRUList();}
/**
* Callback method called when the cache algorithm ages out of the cache
* the given entry. <br>
*/
protected void ageOut(LRUCacheEntry entry)
{
}
/**
* Callback method called when a cache miss happens.
*/
protected void cacheMiss()
{
}
/**
* Factory method for cache entries
*/
protected LRUCacheEntry createCacheEntry(Object key, Object value)
{
return new LRUCacheEntry(key, value);
}
// Private -------------------------------------------------------
// Inner classes -------------------------------------------------
/**
* Double queued list used to store cache entries.
*/
protected class LRUList
{
/** The maximum capacity of the cache list */
public int m_maxCapacity;
/** The minimum capacity of the cache list */
public int m_minCapacity;
/** The current capacity of the cache list */
public int m_capacity;
/** The number of cached objects */
public int m_count;
/** The head of the double linked list */
public LRUCacheEntry m_head;
/** The tail of the double linked list */
public LRUCacheEntry m_tail;
/** The cache misses happened */
public int m_cacheMiss;
/**
* Creates a new double queued list.
*/
protected LRUList()
{
m_head = null;
m_tail = null;
m_count = 0;
}
/**
* Promotes the cache entry <code>entry</code> to the last used
position
* of the list. <br>
* If the object is already there, does nothing.
* @param entry the object to be promoted, cannot be null
* @see #demote
* @throws IllegalStateException if this method is called with a full
cache
*/
protected synchronized void promote(LRUCacheEntry entry)
{
if (entry == null) {throw new IllegalArgumentException("Trying
to promote a null object");}
if (m_capacity < 1) {throw new IllegalStateException("Can't
work with capacity < 1");}
entry.m_time = System.currentTimeMillis();
if (entry.m_prev == null)
{
if (entry.m_next == null)
{
// entry is new or there is only the head
if (m_count == 0) // cache is empty
{
m_head = entry;
m_tail = entry;
++m_count;
}
else if (m_count == 1 && m_head == entry) {}
// there is only the head and I want to promote it, do nothing
else if (m_count < m_capacity)
{
entry.m_prev = null;
entry.m_next = m_head;
m_head.m_prev = entry;
m_head = entry;
++m_count;
}
else {throw new IllegalStateException("Attempt
to put a new cache entry on a full cache");}
}
else {} // entry is the head, do nothing
}
else
{
if (entry.m_next == null) // entry is the tail
{
LRUCacheEntry beforeLast = entry.m_prev;
beforeLast.m_next = null;
entry.m_prev = null;
entry.m_next = m_head;
m_head.m_prev = entry;
m_head = entry;
m_tail = beforeLast;
}
else // entry is in the middle of the list
{
LRUCacheEntry previous = entry.m_prev;
previous.m_next = entry.m_next;
entry.m_next.m_prev = previous;
entry.m_prev = null;
entry.m_next = m_head;
m_head.m_prev = entry;
m_head = entry;
}
}
}
/**
* Demotes from the cache the least used entry. <br>
* If the cache is not full, does nothing.
* @see #promote
*/
protected synchronized void demote()
{
if (m_capacity < 1) {throw new IllegalStateException("Can't
work with capacity < 1");}
if (m_count > m_capacity) {throw new
IllegalStateException("Cache list entries number (" + m_count + ") > than the maximum
allowed (" + m_capacity + ")");}
if (m_count == m_capacity)
{
LRUCacheEntry entry = m_tail;
remove(entry);
ageOut(entry);
}
else {} // cache is not full, do nothing
}
/**
* Removes from the cache list the specified entry.
*/
protected synchronized void remove(LRUCacheEntry entry)
{
if (entry == null) {throw new IllegalArgumentException("Cannot
remove a null entry from the cache");}
if (m_count < 1) {throw new IllegalStateException("Trying to
remove an entry from an empty cache");}
// entry.m_key = entry.m_object = null;
if (m_count == 1)
{
m_head = m_tail = null;
}
else
{
if (entry.m_prev == null) // the head
{
m_head = entry.m_next;
m_head.m_prev = null;
entry.m_next = null;
}
else if (entry.m_next == null) // the tail
{
m_tail = entry.m_prev;
m_tail.m_next = null;
entry.m_prev = null;
}
else // in the middle
{
entry.m_next.m_prev = entry.m_prev;
entry.m_prev.m_next = entry.m_next;
entry.m_prev = null;
entry.m_next = null;
}
}
--m_count;
}
}
/**
* Double linked cell used as entry in the cache list.
*/
protected class LRUCacheEntry
{
/** Reference to the next cell in the list */
protected LRUCacheEntry m_next;
/** Reference to the previous cell in the list */
protected LRUCacheEntry m_prev;
/** The key used to retrieve the cached object */
public Object m_key;
/** The cached object */
public Object m_object;
/** The timestamp of the creation */
public long m_time;
/**
* Creates a new double linked cell, storing the object we
* want to cache and the key that is used to retrieve it.
*/
protected LRUCacheEntry(Object key, Object object)
{
m_key = key;
m_object = object;
m_next = null;
m_prev = null;
m_time = 0; // Set when inserted in the list.
}
}
}