/* 
 * 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:[email protected]">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; 
        } 
    } 
} 
 
    
     
     
     
   |     
 
 |