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