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> */publicclass CachingHashtable<K, V> extends Hashtable<K,V> { /** * DOCUMENT ME! */private Cleaner cleaner; /** * DOCUMENT ME! */private Hashtable<K,CacheElement<V>> cache; /** * DOCUMENT ME! */privateboolean threaded = true; /** * DOCUMENT ME! */privatelong 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! */publicboolean isEmpty() { if(threaded) { return cache.isEmpty(); } else { cleaner.clean(); return cache.isEmpty(); } } /** * * @param ttl new Time to Live value for this table */publicvoid setTimeToLive(long ttl) { this.ttl = ttl; this.cleaner.ttl = ttl; } /** * * @return the Time to Live for elements in this table */publiclong 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; } returnnew 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 publicvoid 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! */publicboolean 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! */publicboolean containsKey(Object o) { if(!threaded) { cleaner.clean(); } return cache.containsKey(o); } /** * DOCUMENT ME! * * @param o DOCUMENT ME! * * @return DOCUMENT ME! */publicboolean containsValue(Object o) { return contains(o); } /** * DOCUMENT ME! * * @return DOCUMENT ME! */public Enumeration<V> elements() { returnnew 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! */publicboolean equals(Object o) { if(o instanceof CachingHashtable&&((CachingHashtable)o).cache.equals(this.cache)) { return true; } else { return false; } } /** * DOCUMENT ME! */publicvoid 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! */publicint 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! */publicvoid 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. */publicvoid shutdown() { this.threaded = false; cleaner.shutdown(); } /** * DOCUMENT ME! * * @return DOCUMENT ME! */publicint size() { if(threaded) { return cache.size(); } else { cleaner.clean(); return cache.size(); } } /** Starts the processing. */publicvoid 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! */privatevoid 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 $ */privatestaticclass CacheElement<V> { /** * DOCUMENT ME! */public V payload; /** * DOCUMENT ME! */publiclong 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 $ */privatestaticclass 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! */publicboolean hasMoreElements() { return enu.hasMoreElements(); } /** * DOCUMENT ME! * * @return DOCUMENT ME! */public V nextElement() { return enu.nextElement().payload; } } /** * DOCUMENT ME! * * @author $author$ * @version $Revision: 1.4 $ */privateclass Cleaner extends Thread { /** * DOCUMENT ME! */private Hashtable<K,? extends CacheElement> cache; /** * DOCUMENT ME! */privateboolean running = false; /** * DOCUMENT ME! */privatelong 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! */publicboolean isRunning() { return running; } /** * DOCUMENT ME! */publicvoid 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! */publicvoid run() { while(running) { clean(); try { Thread.sleep(ttl); } catch(InterruptedException e) { } } } /** * DOCUMENT ME! */publicvoid shutdown() { this.running = false; } /** * DOCUMENT ME! */publicvoid startup() { this.running = true; super.start(); } } /** * DOCUMENT ME! * * @author $author$ * @version $Revision: 1.4 $ */privatestaticclass 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; } } }
Related examples in the same category