Mega Code Archive

 
Categories / Java / Development Class
 

Implementation of a Least Recently Used cache policy

/*  * JBoss, Home of Professional Open Source  * Copyright 2005, JBoss Inc., and individual contributors as indicated  * by the @authors tag. See the copyright.txt in the distribution for a  * full listing of individual contributors.  *  * This 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 software 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 software; if not, write to the Free  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.  */ /*  * JBoss, Home of Professional Open Source  * Copyright 2005, JBoss Inc., and individual contributors as indicated  * by the @authors tag. See the copyright.txt in the distribution for a  * full listing of individual contributors.  *  * This 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 software 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 software; if not, write to the Free  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.  */ import java.util.HashMap; import java.util.Map; /**  * Implementation of a Least Recently Used cache policy.  *   * @author <a href="mailto:simone.bordet@compaq.com">Simone Bordet</a>  * @version $Revision: 2883 $  */ @SuppressWarnings("unchecked") public class LRUCachePolicy implements CachePolicy {   // Constants -----------------------------------------------------   // Attributes ----------------------------------------------------   /**    * The map holding the cached objects    */   protected Map 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 #create    */   public LRUCachePolicy() {   }   /**    * Creates a LRU cache policy object with the specified minimum and maximum    * capacity.    *     * @param min    * @param max    *     * @see #create    */   public LRUCachePolicy(int min, int max) {     if (min < 2 || min > max) {       throw new IllegalArgumentException("Illegal cache capacities");     }     m_minCapacity = min;     m_maxCapacity = max;   }   /**    * Create map holding entries.    *     * @return the map    */   protected Map createMap() {     return new HashMap();   }   // Public --------------------------------------------------------   // Service implementation ----------------------------------------------   /**    * Initializes the cache, creating all required objects and initializing their    * values.    *     * @see #start    * @see #destroy    */   public void create() {     m_map = createMap();     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 #create    * @see #stop    */   public void start() {   }   /**    * 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() {     if (m_list != null) {       flush();     }   }   /**    * Destroys the cache that is now unusable. <br>    * To have it working again it must be re-{@link #create}ed and re-{@link #start}ed.    *     * @see #create    */   public void destroy() {     if (m_map != null)       m_map.clear();     if (m_list != null)       m_list.clear();   }   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");     }     if (m_map.containsKey(key)) {       throw new IllegalStateException("Attempt to put in the cache an object that is already there");     }     m_list.demote();     LRUCacheEntry entry = createCacheEntry(key, o);     m_map.put(key, entry);     m_list.promote(entry);   }   public void remove(Object key) {     if (key == null) {       throw new IllegalArgumentException("Removing an object using a null key");     }     Object value = m_map.remove(key);     if (value != null) {       m_list.remove((LRUCacheEntry) value);     }     // else Do nothing, the object isn't in the cache list   }   public void flush() {     LRUCacheEntry entry = null;     while ((entry = m_list.m_tail) != null) {       ageOut(entry);     }   }   public int size() {     return m_list.m_count;   }   // Y overrides ---------------------------------------------------   // Package protected ---------------------------------------------   // Protected -----------------------------------------------------   /**    * Factory method for the linked list used by this cache implementation.    *     * @return the lru list    */   protected LRUList createList() {     return new LRUList();   }   /**    * Callback method called when the cache algorithm ages out of the cache the    * given entry. <br>    * The implementation here is removing the given entry from the cache.    *     * @param entry    */   protected void ageOut(LRUCacheEntry entry) {     remove(entry.m_key);   }   /**    * Callback method called when a cache miss happens.    */   protected void cacheMiss() {   }   /**    * Factory method for cache entries    *     * @param key    * @param value    * @return the entry    */   protected LRUCacheEntry createCacheEntry(Object key, Object value) {     return new LRUCacheEntry(key, value);   }   // Private -------------------------------------------------------   // Inner classes -------------------------------------------------   /**    * Double queued list used to store cache entries.    */   public class LRUList {     /** The maximum capacity of the cache list */     @SuppressWarnings("hiding")     public int m_maxCapacity;     /** The minimum capacity of the cache list */     @SuppressWarnings("hiding")     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 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");       }       entryPromotion(entry);       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;             entryAdded(entry);           } 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;             entryAdded(entry);           } else if (m_count < m_maxCapacity) {             entry.m_prev = null;             entry.m_next = m_head;             m_head.m_prev = entry;             m_head = entry;             ++m_count;             int oldCapacity = m_capacity;             ++m_capacity;             entryAdded(entry);             capacityChanged(oldCapacity);           } 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 void demote() {       if (m_capacity < 1) {         throw new IllegalStateException("Can't work with capacity < 1");       }       if (m_count > m_maxCapacity) {         throw new IllegalStateException("Cache list entries number (" + m_count             + ") > than the maximum allowed (" + m_maxCapacity + ")");       }       if (m_count == m_maxCapacity) {         LRUCacheEntry entry = m_tail;         // the entry will be removed by ageOut         ageOut(entry);       } else {       } // cache is not full, do nothing     }     /**      * Removes from the cache list the specified entry.      *       * @param entry      */     protected 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;       entryRemoved(entry);     }     /**      * Callback that signals that the given entry is just about to be added.      *       * @param entry      */     protected void entryPromotion(LRUCacheEntry entry) {     }     /**      * Callback that signals that the given entry has been added to the cache.      *       * @param entry      */     protected void entryAdded(LRUCacheEntry entry) {     }     /**      * Callback that signals that the given entry has been removed from the      * cache.      *       * @param entry      */     protected void entryRemoved(LRUCacheEntry entry) {     }     /**      * Callback that signals that the capacity of the cache is changed.      *       * @param oldCapacity      *          the capacity before the change happened      */     protected void capacityChanged(int oldCapacity) {     }     protected void clear() {       LRUCacheEntry entry = m_head;       m_head = null;       m_tail = null;       m_count = 0;       for (; entry != null; entry = entry.m_next)         entryRemoved(entry);     }     public String toString() {       String s = Integer.toHexString(super.hashCode());       s += " size: " + m_count;       for (LRUCacheEntry entry = m_head; entry != null; entry = entry.m_next) {         s += "\n" + entry;       }       return s;     }   }   /**    * Double linked cell used as entry in the cache list.    */   public class LRUCacheEntry {     /** Reference to the next cell in the list */     public LRUCacheEntry m_next;     /** Reference to the previous cell in the list */     public 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.      *       * @param key      * @param object      */     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.     }     public String toString() {       return "key: " + m_key + ", object: "           + (m_object == null ? "null" : Integer.toHexString(m_object.hashCode())) + ", entry: "           + Integer.toHexString(super.hashCode());     }   } } /**  * Interface that specifies a policy for caches.  * <p>  * Implementation classes can implement a LRU policy, a random one, a MRU one,  * or any other suitable policy.  *   * @author <a href="mailto:simone.bordet@compaq.com">Simone Bordet</a>  * @version $Revision: 2787 $  */ interface CachePolicy {   /**    * Returns the object paired with the specified key if it's present in the    * cache, otherwise must return null. <br>    * Implementations of this method must have complexity of order O(1).    * Differently from {@link #peek} this method not only return whether the    * object is present in the cache or not, but also applies the implemented    * policy that will "refresh" the cached object in the cache, because this    * cached object was really requested.    *     * @param key    *          the key paired with the object    * @return the object    * @see #peek    */   Object get(Object key);   /**    * Returns the object paired with the specified key if it's present in the    * cache, otherwise must return null. <br>    * Implementations of this method must have complexity of order O(1). This    * method should not apply the implemented caching policy to the object paired    * with the given key, so that a client can query if an object is cached    * without "refresh" its cache status. Real requests for the object must be    * done using {@link #get}.    *     * @param key    *          the key paired with the object    * @see #get    * @return the object    */   Object peek(Object key);   /**    * Inserts the specified object into the cache following the implemented    * policy. <br>    * Implementations of this method must have complexity of order O(1).    *     * @param key    *          the key paired with the object    * @param object    *          the object to cache    * @see #remove    */   void insert(Object key, Object object);   /**    * Remove the cached object paired with the specified key. <br>    * Implementations of this method must have complexity of order O(1).    *     * @param key    *          the key paired with the object    * @see #insert    */   void remove(Object key);   /**    * Flushes the cached objects from the cache.    */   void flush();   /**    * @return the size of the cache    */   int size();   void create() throws Exception;   void start() throws Exception;   void stop();   void destroy(); }