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