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