001    // Copyright (c) 2001 Hursh Jain (http://www.mollypages.org) 
002    // The Molly framework is freely distributable under the terms of an
003    // MIT-style license. For details, see the molly pages web site at:
004    // http://www.mollypages.org/. Use, modify, have fun !
005    
006    package fc.util.cache;  
007    
008    import java.text.*;
009    import java.util.*;
010    import fc.io.*;
011    import fc.util.*;
012    
013    /**
014    In memory cache with the ability to specify an item bound/cache-limit 
015    functionality. By default,the upper bound is Integer.MAX_VALUE.
016    <p>
017    Existing items are not automatically expired. They are only removed when a new
018    item is added and the bound size is crossed. Which item is removed is according to BoundedCache.POLICY.
019    <p>
020    ThreadSafety: This class <b>is</b> thread safe and can be used by multiple 
021    threads concurrently.
022    
023    @author   hursh jain
024    @version  1.0 7/16/2010
025    **/
026    public final class BoundedCache
027    {
028    /**
029    least_used, fifo
030    */
031    public enum Policy
032      { 
033      LEAST_USED,
034      FIFO;
035      }
036    
037    final   String    myName;
038          Map     cache;
039    volatile  boolean   closed = false;
040    final   Log     log;
041    final   Policy    cachePolicy;
042          int     bound;
043    
044    /**
045    Instantiates this class with the specified name, logger, policy and cache size.
046    
047    @param  name    String denoting the name for this object
048    @param  logger    a reference to a {@link fc.io.Log}
049    @param  cachePolicy item removal policy when cache is full
050    @param  bound   the upper bound of this cache.
051    **/
052    public BoundedCache(Log log, String name, Policy cachePolicy, int bound) 
053      {
054      if (log == null) {
055        log = Log.getDefault();
056        }
057        
058      this.log = log;
059      this.myName = name;
060      this.cachePolicy = cachePolicy;
061      this.bound = bound;
062      
063      if (cachePolicy == Policy.LEAST_USED) {
064        this.cache = Collections.synchronizedMap(new MyLinkedHashMap(
065                                bound, 0.75f, true));
066        }
067      else if (cachePolicy == Policy.FIFO) {
068        this.cache = Collections.synchronizedMap(new MyLinkedHashMap(
069                                bound, 0.75f, false));
070        }
071      else {
072        throw new IllegalArgumentException("Do not understand this cache policy: " + cachePolicy);
073        }
074      }
075    
076    /**
077    Creates a memory cache with a system-assigned name, logger and the specified
078    policy and bound.
079    */
080    public BoundedCache(Policy cachePolicy, int bound) 
081      {
082      this(null, 
083        "BoundedCache/created@" 
084        + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT)
085          .format(new Date()), 
086        cachePolicy, 
087        bound
088        );
089      }
090    
091    /**
092    Sets the upper bound of this cache.
093    */
094    public void setBound(int items) {
095      this.bound = items;
096      }
097    
098    /**
099    Gets the current bound of this cache.
100    */
101    public long getBound() {
102      return bound;
103      }
104    
105    /**
106    Removes the object specified by the key.
107    */
108    public void expire(java.lang.Object key)
109      {
110      synchronized (cache) {
111        cache.remove(key);
112        }
113      }
114    
115    /**
116    Returns the item for the specified key or <tt>null</tt> if the item does 
117    not exist.
118    */
119    public Object containsKey(Object key) 
120      {
121      Argcheck.isfalse(isClosed(), "Cache has been closed");
122    
123      Object item = null;
124      
125      synchronized (cache) {
126        item = cache.get(key);  
127        }
128        
129      return item != null;
130      }
131      
132      
133    /**
134    Returns the item for the specified key or <tt>null</tt> if the item does 
135    not exist.
136    */
137    public Object get(Object key) 
138      {
139      Argcheck.isfalse(isClosed(), "Cache has been closed");
140    
141      Object item = null;
142      
143      synchronized (cache) {
144        item = cache.get(key);  
145        }
146        
147      return item;
148      }
149    
150    /**
151    Returns the underlying storage read-only map for this cache.
152    */
153    public Map getAll() {
154      return Collections.unmodifiableMap(cache);
155      }
156    
157    /**
158    Puts the item for the specified key. Returns the previous item for this key 
159    or <tt>null</tt> if no previous item existed.
160    */
161    public Object put(Object key, Object val)
162      {
163      Argcheck.isfalse(isClosed(), "Memory cache has been closed");
164        
165      Object item = null;
166      
167      synchronized (cache) {
168        item = cache.put(key, val);     
169        }
170        
171      return item;
172      }
173    
174    /**
175    Closes this cache, which makes all items in the cache unavailable. Any items
176    needed for later should be taken out of the cache before closing it.
177    <p>
178    <b>Note:</b>, it is a good idea to close the cache once it's not needed,
179    because it releases resources, including any internal threads spawned and used
180    by this implementation.
181    **/
182    public void close()
183      {
184      this.closed = true;
185      cache.clear();
186      log.info("*** cache:[", myName, "] closed. ***"); 
187      }
188    
189    /**
190    Returns true if this cache has been closed, false otherwise.
191    **/
192    public boolean isClosed()
193      {
194      return this.closed;
195      }
196    
197    public void clear() {
198      synchronized (cache) { 
199        cache.clear();
200        }
201      }
202    
203    public String toString()
204      {
205      final int size = cache.size();
206      String temp = this.myName + " [bound=" + bound + ", used=";
207      temp += size;
208      temp += "] items;";
209      temp += " [Policy = " + cachePolicy + "] ";
210      temp += isClosed() ? "cache is closed. " : "cache is open.";
211      return temp;
212      }
213    
214    
215    
216    private class MyLinkedHashMap extends LinkedHashMap
217    {
218    MyLinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) 
219      {
220      super(initialCapacity, loadFactor, accessOrder);
221      }
222      
223    protected boolean removeEldestEntry(Map.Entry eldest) {
224      return size() > bound; 
225      }
226    }
227    
228    
229    public static void main(String[] args)
230      {
231      new Test();
232      }
233    
234    static private class Test
235      {
236      Test() 
237        {
238        try {
239          System.out.println("Testing FIFO...");
240          BoundedCache mycache = new BoundedCache(Policy.FIFO, 3);
241          
242          System.out.println("putting key1, " + mycache.put("key1", "val1"));
243          System.out.println("putting key2, " + mycache.put("key2", "key2"));
244          System.out.println("putting key3, " + mycache.put("key3", "key3"));
245          System.out.println("putting key4, " + mycache.put("key4", "key4"));
246    
247          System.out.println("get key1 = " + mycache.get("key1"));
248          System.out.println("get key2 = " + mycache.get("key2"));
249          System.out.println("getkey3 = " + mycache.get("key3"));
250          System.out.println("get key4 = " + mycache.get("key4"));
251          System.out.println("mycache.toString() = " + mycache);
252          
253          System.out.println("=========================");
254          System.out.println("Testing LRU...");
255        
256          mycache = new BoundedCache(Policy.LEAST_USED, 3);
257          System.out.println("putting key1, " + mycache.put("key1", "val1"));
258          System.out.println("putting key2, " + mycache.put("key2", "key2"));
259          System.out.println("putting key3, " + mycache.put("key3", "key3"));
260          System.out.println("putting key4, " + mycache.put("key4", "key4"));
261          
262          System.out.println("getting key2 [4times] and key3[1 time]");
263          System.out.println(mycache.get("key2"));
264          System.out.println(mycache.get("key2"));
265          System.out.println(mycache.get("key2"));
266          System.out.println(mycache.get("key2"));
267          System.out.println(mycache.get("key3"));
268          
269          System.out.println("putting key5, " + mycache.put("key5", "key5"));
270    
271          System.out.println("get key1=" + mycache.get("key1"));
272          System.out.println("get key2=" + mycache.get("key2"));
273          System.out.println("get key3=" + mycache.get("key3"));
274          System.out.println("get key4=" + mycache.get("key4"));
275          System.out.println("get key5=" + mycache.get("key5"));
276    
277          System.out.println("mycache.toString() = " + mycache);
278          }
279        catch (Exception e) {
280          e.printStackTrace();
281          }
282        } //~init
283      } //~class test
284    
285    }     //~BoundedCache