Mega Code Archive

 
Categories / Java / Collections Data Structure
 

Caching Hashtable

/*  * CachingHashtable.java  *  * Created on January 13, 2005, 1:49 PM  *  * Copyright (C) 2005  Robert Cooper, Temple of the Screaming Penguin  *  * 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  */ import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.Map; import java.util.Set; /**  * This class provides a Hashtable that has a time-based limit on how long something  * remains in the table.  *  * <p>There are two modes that this class can operate in: threaded and unthreaded. When  * operating in threaded mode, it will spawn a separate thread process to clean elements  * out of the table as they expire. When in unthreaded mode, it will check expiration  * state as requests to the object are made.</p>  *  * <p>Each of these has advantages and disadvantages. As a rule, if you expect the table  * to grow large and be around for a while, best to use the threaded mode as it will  * help keep the static memory state lower and performance of table-wide access calls like  * .keys() will be better. If you expect to have a small, or short lived table, unthreaded  * will eliminate the overhead of the cleaner thread. Another consideration follows.</p>  *  * <p>The Time to Live value operates slightly differently between these two modes.  * In threaded mode, TTL is both the checking bound on an item AND the sleep timer  * between cleans of the cache. It is, therefore, possible to have a cache element  * returned with 2 * TTL - 1 millisecond since incept. When in unthreaded mode,  * objects are guaranteed not to have a lifespan exceeding the TTL.</p>  *  * <p>When no value is specified, threaded is true and TTL is 1 minute.</p>  * @version $Rev: 86 $  * @author <a href="mailto:cooper@screaming-penguin.com">Robert Cooper</a>  */ public class CachingHashtable<K, V> extends Hashtable<K,V> {     /**      * DOCUMENT ME!      */     private Cleaner cleaner;     /**      * DOCUMENT ME!      */     private Hashtable<K,CacheElement<V>> cache;     /**      * DOCUMENT ME!      */     private boolean threaded = true;     /**      * DOCUMENT ME!      */     private long ttl = 60000;     /**      * Creates a new CachingHashtable object.      */     public CachingHashtable() {         init(threaded,ttl,0,null);     }     /**      *      */     public CachingHashtable(boolean threaded) {         init(threaded,ttl,0,null);     }     /**      *      */     public CachingHashtable(long ttl) {         init(threaded,ttl,0,null);     }     /**      *      */     public CachingHashtable(boolean threaded,long ttl) {         init(threaded,ttl,0,null);     }     /**      *      */     public CachingHashtable(boolean threaded,long ttl,int initialCapacity) {         init(threaded,ttl,initialCapacity,null);     }     /**      * Creates a new CachingHashtable object.      *      * @param initialCapacity DOCUMENT ME!      */     public CachingHashtable(int initialCapacity) {         init(threaded,ttl,initialCapacity,null);     }     /**      *      */     public CachingHashtable(boolean threaded,int initialCapacity) {         init(threaded,ttl,initialCapacity,null);     }     /**      *      */     public CachingHashtable(long ttl,int initialCapacity) {         init(threaded,ttl,initialCapacity,null);     }     /**      * Creates a new CachingHashtable object.      *      * @param map DOCUMENT ME!      */     public CachingHashtable(Map<? extends K,? extends V> map) {         init(threaded,ttl,0,map);     }     /**      *      */     public CachingHashtable(long ttl,Map<? extends K,? extends V> map) {         init(threaded,ttl,0,map);     }     /**      *      */     public CachingHashtable(boolean threaded,long ttl,Map<? extends K,? extends V> map) {         init(threaded,ttl,0,map);     }     /**      * DOCUMENT ME!      *      * @return DOCUMENT ME!      */     public boolean isEmpty() {         if(threaded) {             return cache.isEmpty();         } else {             cleaner.clean();             return cache.isEmpty();         }     }     /**      *      * @param ttl new Time to Live value for this table      */     public void setTimeToLive(long ttl) {         this.ttl = ttl;         this.cleaner.ttl = ttl;     }     /**      *      * @return the Time to Live for elements in this table      */     public long getTimeToLive() {         return this.ttl;     }     /**      * DOCUMENT ME!      *      * @param key DOCUMENT ME!      *      * @return DOCUMENT ME!      */     public Long cacheTime(K key) {         CacheElement ce = cache.get(key);         if(ce == null) {             return null;         }         return new Long(ce.incept);     }     /**      * DOCUMENT ME!      *      * @return DOCUMENT ME!      */     public Map<K,Long> cacheTimes() {         HashMap<K,Long> set = new HashMap<K,Long>();         if(!threaded) {             cleaner.clean();         }         for(K key : cache.keySet()) {             set.put(key,new Long(cache.get(key).incept));         }         return set;     }     /**      * DOCUMENT ME!      */     //begin the long march of Hashtable overrides     public void clear() {         cache.clear();     }     /**      * DOCUMENT ME!      *      * @return DOCUMENT ME!      */     public Object clone() {         CachingHashtable<K,V> o = new CachingHashtable<K,V>(threaded,ttl);         o.cache = (Hashtable<K,CacheElement<V>>)this.cache.clone();         return o;     }     /**      * DOCUMENT ME!      *      * @param o DOCUMENT ME!      *      * @return DOCUMENT ME!      */     public boolean contains(Object o) {         if(!threaded) {             cleaner.clean();         }         for(CacheElement<V> element : cache.values()) {             if((element.payload == o)||o.equals(element.payload)) {                 return true;             }         }         return false;     }     /**      * DOCUMENT ME!      *      * @param o DOCUMENT ME!      *      * @return DOCUMENT ME!      */     public boolean containsKey(Object o) {         if(!threaded) {             cleaner.clean();         }         return cache.containsKey(o);     }     /**      * DOCUMENT ME!      *      * @param o DOCUMENT ME!      *      * @return DOCUMENT ME!      */     public boolean containsValue(Object o) {         return contains(o);     }     /**      * DOCUMENT ME!      *      * @return DOCUMENT ME!      */     public Enumeration<V> elements() {         return new CacheEnumeration(super.elements());     }     /**      * DOCUMENT ME!      *      * @return DOCUMENT ME!      */     public Set<Map.Entry<K,V>> entrySet() {         HashSet set = new HashSet();         if(!threaded) {             cleaner.clean();         }         for(K key : cache.keySet()) {             set.add(new MapEntry(key,cache.get(key)));         }         return set;     }     /**      * DOCUMENT ME!      *      * @param o DOCUMENT ME!      *      * @return DOCUMENT ME!      */     public boolean equals(Object o) {         if(o instanceof CachingHashtable&&((CachingHashtable)o).cache.equals(this.cache)) {             return true;         } else {             return false;         }     }     /**      * DOCUMENT ME!      */     public void finalize() {         cleaner.shutdown();     }     /**      * DOCUMENT ME!      *      * @param o DOCUMENT ME!      *      * @return DOCUMENT ME!      */     public V get(Object o) {         K key = (K)o;         if(threaded) {             if(cache.get(key) != null) {                 return cache.get(key).payload;             } else {                 return null;             }         } else {             CacheElement<V> ce = cache.get(key);             if((ce == null)||((System.currentTimeMillis() - ce.incept) >= ttl)) {                 cache.remove(key);                 return null;             } else {                 return ce.payload;             }         }     }     /**      * DOCUMENT ME!      *      * @return DOCUMENT ME!      */     public int hashCode() {         return cache.hashCode();     }     /**      * DOCUMENT ME!      *      * @return DOCUMENT ME!      */     public Set<K> keySet() {         if(threaded) {             return cache.keySet();         } else {             cleaner.clean();             return cache.keySet();         }     }     /**      * DOCUMENT ME!      *      * @return DOCUMENT ME!      */     public Enumeration<K> keys() {         if(threaded) {             return cache.keys();         } else {             cleaner.clean();             return cache.keys();         }     }     /**      * DOCUMENT ME!      *      * @param key DOCUMENT ME!      * @param value DOCUMENT ME!      *      * @return DOCUMENT ME!      */     public V put(K key,V value) {         CacheElement<V> element = new CacheElement<V>(value);         CacheElement<V> old = cache.put(key,element);         if(old != null) {             return old.payload;         } else {             return null;         }     }     /**      * DOCUMENT ME!      *      * @param map DOCUMENT ME!      */     public void putAll(Map<? extends K,? extends V> map) {         for(K key : map.keySet()) {             cache.put(key,new CacheElement<V>(map.get(key)));         }     }     /**      * DOCUMENT ME!      *      * @param o DOCUMENT ME!      *      * @return DOCUMENT ME!      */     public V remove(Object o) {         K key = (K)o;         if(threaded) {             return cache.remove(key).payload;         } else {             V value = this.get(key);             cache.remove(key);             return value;         }     }     /** Stops processing.      */     public void shutdown() {         this.threaded = false;         cleaner.shutdown();     }     /**      * DOCUMENT ME!      *      * @return DOCUMENT ME!      */     public int size() {         if(threaded) {             return cache.size();         } else {             cleaner.clean();             return cache.size();         }     }     /** Starts the processing.      */     public void startup() {         this.threaded = true;         cleaner.startup();     }     /**      * DOCUMENT ME!      *      * @return DOCUMENT ME!      */     public Collection<V> values() {         if(!threaded) {             cleaner.clean();         }         ArrayList<V> values = new ArrayList<V>(cache.size());         for(CacheElement<V> element : cache.values())             values.add(element.payload);         return values;     }     /**      * DOCUMENT ME!      *      * @param threaded DOCUMENT ME!      * @param ttl DOCUMENT ME!      * @param initialCapacity DOCUMENT ME!      * @param map DOCUMENT ME!      */     private void init(boolean threaded,long ttl,int initialCapacity,Map<? extends K,? extends V> map) {         if(map != null) {             initialCapacity = map.size();         }         cache = new Hashtable<K,CacheElement<V>>(initialCapacity);         this.ttl = ttl;         this.threaded = threaded;         if(map != null) {             putAll(map);         }         this.cleaner = new Cleaner(ttl,cache);         if(threaded) {             cleaner.startup();         }     }     /**      * DOCUMENT ME!      *      * @author $author$      * @version $Revision: 1.4 $      */     private static class CacheElement<V> {         /**          * DOCUMENT ME!          */         public V payload;         /**          * DOCUMENT ME!          */         public long incept = System.currentTimeMillis();         /**          * Creates a new CacheElement object.          *          * @param payload DOCUMENT ME!          */         public CacheElement(V payload) {             this.payload = payload;         }     }     /**      * DOCUMENT ME!      *      * @author $author$      * @version $Revision: 1.4 $      */     private static class CacheEnumeration<V> implements Enumeration<V> {         /**          * DOCUMENT ME!          */         Enumeration<CacheElement<V>> enu;         /**          * Creates a new CacheEnumeration object.          *          * @param enu DOCUMENT ME!          */         CacheEnumeration(Enumeration<CacheElement<V>> enu) {             this.enu = enu;         }         /**          * DOCUMENT ME!          *          * @return DOCUMENT ME!          */         public boolean hasMoreElements() {             return enu.hasMoreElements();         }         /**          * DOCUMENT ME!          *          * @return DOCUMENT ME!          */         public V nextElement() {             return enu.nextElement().payload;         }     }     /**      * DOCUMENT ME!      *      * @author $author$      * @version $Revision: 1.4 $      */     private class Cleaner extends Thread {         /**          * DOCUMENT ME!          */         private Hashtable<K,? extends CacheElement> cache;         /**          * DOCUMENT ME!          */         private boolean running = false;         /**          * DOCUMENT ME!          */         private long ttl;         /**          * Creates a new Cleaner object.          *          * @param ttl DOCUMENT ME!          * @param cache DOCUMENT ME!          */         Cleaner(long ttl,Hashtable<K,? extends CacheElement> cache) {             this.ttl = ttl;             this.cache = cache;             this.setDaemon(true);         }         /**          * DOCUMENT ME!          *          * @return DOCUMENT ME!          */         public boolean isRunning() {             return running;         }         /**          * DOCUMENT ME!          */         public void clean() {             ArrayList<K> toRemove = new ArrayList<K>();             for(K key : cache.keySet()) {                 CachingHashtable.CacheElement element = cache.get(key);                 if((System.currentTimeMillis() - element.incept) >= ttl) {                     toRemove.add(key);                 }             }             for(K key : toRemove) {                 cache.remove(key);             }         }         /**          * DOCUMENT ME!          */         public void run() {             while(running) {                 clean();                 try {                     Thread.sleep(ttl);                 } catch(InterruptedException e) {                 }             }         }         /**          * DOCUMENT ME!          */         public void shutdown() {             this.running = false;         }         /**          * DOCUMENT ME!          */         public void startup() {             this.running = true;             super.start();         }     }     /**      * DOCUMENT ME!      *      * @author $author$      * @version $Revision: 1.4 $      */     private static class MapEntry<K,V> implements Map.Entry {         /**          * DOCUMENT ME!          */         CacheElement<V> element;         /**          * DOCUMENT ME!          */         K key;         /**          * Creates a new MapEntry object.          *          * @param key DOCUMENT ME!          * @param element DOCUMENT ME!          */         MapEntry(K key,CacheElement<V> element) {             this.key = key;             this.element = element;         }         /**          * DOCUMENT ME!          *          * @return DOCUMENT ME!          */         public Object getKey() {             return key;         }         /**          * DOCUMENT ME!          *          * @param obj DOCUMENT ME!          *          * @return DOCUMENT ME!          */         public Object setValue(Object obj) {             return element.payload = (V)obj;         }         /**          * DOCUMENT ME!          *          * @return DOCUMENT ME!          */         public Object getValue() {             return element.payload;         }     } }