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 BoundedCache.POLICY.
019<p>
020ThreadSafety: This class <b>is</b> thread safe and can be used by multiple 
021threads concurrently.
022
023@author   hursh jain
024@version  1.0 7/16/2010
025**/
026public final class BoundedCache
027{
028/**
029least_used, fifo
030*/
031public enum Policy
032  { 
033  LEAST_USED,
034  FIFO;
035  }
036
037final   String    myName;
038      Map     cache;
039volatile  boolean   closed = false;
040final   Log     log;
041final   Policy    cachePolicy;
042      int     bound;
043
044/**
045Instantiates 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**/
052public 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/**
077Creates a memory cache with a system-assigned name, logger and the specified
078policy and bound.
079*/
080public 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/**
092Sets the upper bound of this cache.
093*/
094public void setBound(int items) {
095  this.bound = items;
096  }
097
098/**
099Gets the current bound of this cache.
100*/
101public long getBound() {
102  return bound;
103  }
104
105/**
106Removes the object specified by the key.
107*/
108public void expire(java.lang.Object key)
109  {
110  synchronized (cache) {
111    cache.remove(key);
112    }
113  }
114
115/**
116Returns the item for the specified key or <tt>null</tt> if the item does 
117not exist.
118*/
119public 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/**
134Returns the item for the specified key or <tt>null</tt> if the item does 
135not exist.
136*/
137public 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/**
151Returns the underlying storage read-only map for this cache.
152*/
153public Map getAll() {
154  return Collections.unmodifiableMap(cache);
155  }
156
157/**
158Puts the item for the specified key. Returns the previous item for this key 
159or <tt>null</tt> if no previous item existed.
160*/
161public 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/**
175Closes this cache, which makes all items in the cache unavailable. Any items
176needed 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,
179because it releases resources, including any internal threads spawned and used
180by this implementation.
181**/
182public void close()
183  {
184  this.closed = true;
185  cache.clear();
186  log.info("*** cache:[", myName, "] closed. ***"); 
187  }
188
189/**
190Returns true if this cache has been closed, false otherwise.
191**/
192public boolean isClosed()
193  {
194  return this.closed;
195  }
196
197public void clear() {
198  synchronized (cache) { 
199    cache.clear();
200    }
201  }
202
203public 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
216private class MyLinkedHashMap extends LinkedHashMap
217{
218MyLinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) 
219  {
220  super(initialCapacity, loadFactor, accessOrder);
221  }
222  
223protected boolean removeEldestEntry(Map.Entry eldest) {
224  return size() > bound;  
225  }
226}
227
228
229public static void main(String[] args)
230  {
231  new Test();
232  }
233
234static 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