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