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