001 /* 002 @license.text@ 003 */ 004 package biz.hammurapi.cache; 005 006 import java.lang.ref.SoftReference; 007 import java.util.HashMap; 008 import java.util.HashSet; 009 import java.util.Iterator; 010 import java.util.Map; 011 import java.util.Set; 012 013 import biz.hammurapi.config.ConfigurationException; 014 import biz.hammurapi.util.Acceptor; 015 016 017 /** 018 * Memory sensitive cache. Uses soft references to cache objects. 019 * Doesn't create helper threads - housekeepeng happens on every 'cleanupInterval' get() invocation. 020 * @author Pavel Vlasov 021 * @version $Revision: 1.3 $ 022 */ 023 public class PassiveMemoryCache extends AbstractProducer implements Cache { 024 private Map cache=new HashMap(); 025 private Producer producer; 026 private Cache fallBack; 027 private int cleanupInterval; 028 private int cleanupCounter; 029 030 private class CacheReference extends SoftReference { 031 private long expires; 032 private long time; 033 034 /** 035 * @param referent 036 * @param q 037 * @param expires 038 */ 039 public CacheReference(Object value, long time, long expires) { 040 super(value); 041 this.expires=expires; 042 this.time=time; 043 } 044 } 045 046 /** 047 * Used as entry if fallBack cache is used. 048 * @author Pavel Vlasov 049 * @version $Revision: 1.3 $ 050 */ 051 private class FallBackEntry { 052 Object value; 053 private long expires; 054 private long time; 055 private Object key; 056 057 FallBackEntry(Object key, Object value, long time, long expires) { 058 this.key=key; 059 this.value=value; 060 this.expires=expires; 061 this.time=time; 062 } 063 064 protected void finalize() throws Throwable { 065 if (fallBack.isActive() && (expires<=0 || System.currentTimeMillis()<expires)) { 066 fallBack.put(key, value, time, expires); 067 } 068 069 super.finalize(); 070 } 071 } 072 073 private void cleanup() { 074 long now=System.currentTimeMillis(); 075 synchronized (cache) { 076 Iterator it=cache.entrySet().iterator(); 077 while (it.hasNext()) { 078 Map.Entry entry=(Map.Entry) it.next(); 079 CacheReference cacheReference = (CacheReference) entry.getValue(); 080 if (cacheReference.get()==null || (cacheReference.expires>0 && cacheReference.expires<now)) { 081 it.remove(); 082 } 083 } 084 } 085 cleanupCounter=0; 086 } 087 088 private void checkShutdown() { 089 if (shutDown) { 090 throw new IllegalStateException("Shut down"); 091 } 092 } 093 094 /** 095 * 096 */ 097 public PassiveMemoryCache(Producer producer, Cache fallBack, int cleanupInterval) { 098 super(); 099 100 this.producer=producer; 101 if (producer!=null) { 102 producer.addCache(this); 103 } 104 105 this.fallBack=fallBack; 106 if (fallBack!=null) { 107 addCache(fallBack); 108 } 109 110 this.cleanupInterval=cleanupInterval; 111 } 112 113 public void put(Object key, Object value, long time, long expirationTime) { 114 checkShutdown(); 115 synchronized (cache) { 116 cache.put(key, new CacheReference(toValue(key, value, time, expirationTime), time, expirationTime)); 117 } 118 } 119 120 /** 121 * @param key 122 * @param value 123 * @param expirationTime 124 * @return 125 */ 126 private Object toValue(Object key, Object value, long time, long expirationTime) { 127 return fallBack==null ? value : new FallBackEntry(key, value, time, expirationTime); 128 } 129 130 private Object fromValue(Object object) { 131 if (object instanceof FallBackEntry) { 132 return ((FallBackEntry) object).value; 133 } 134 return object; 135 } 136 137 138 139 public Entry get(Object key) { 140 checkShutdown(); 141 if (cleanupInterval>0 && cleanupCounter++ > cleanupInterval) { 142 cleanup(); 143 } 144 145 synchronized (cache) { 146 CacheReference cr=(CacheReference) cache.get(key); 147 long now = System.currentTimeMillis(); 148 boolean doFallBack=true; 149 if (cr!=null) { 150 if ((cr.expires>0 && cr.expires<now) || cr.get()==null) { 151 cache.remove(key); 152 doFallBack=false; 153 } else { 154 final Object o=fromValue(cr.get()); 155 final long et=cr.expires; 156 final long time=cr.time; 157 return new Entry() { 158 159 public long getExpirationTime() { 160 return et; 161 } 162 163 public long getTime() { 164 return time; 165 } 166 167 public Object get() { 168 return o; 169 } 170 171 }; 172 } 173 } 174 175 if (doFallBack && fallBack!=null) { 176 Entry entry = fallBack.get(key); 177 if (entry!=null) { 178 cache.put(key, new CacheReference(toValue(key, entry.get(), entry.getTime(), entry.getExpirationTime()), entry.getTime(), entry.getExpirationTime())); 179 return entry; 180 } 181 } 182 183 if (key instanceof ProducingKey) { 184 Entry entry = ((ProducingKey) key).get(); 185 cache.put(key, new CacheReference(toValue(key, entry.get(), entry.getTime(), entry.getExpirationTime()), entry.getTime(), entry.getExpirationTime())); 186 return entry; 187 } 188 189 if (producer!=null) { 190 Entry entry = producer.get(key); 191 cache.put(key, new CacheReference(toValue(key, entry.get(), entry.getTime(), entry.getExpirationTime()), entry.getTime(), entry.getExpirationTime())); 192 return entry; 193 } 194 195 return null; 196 } 197 } 198 199 public void clear() { 200 synchronized (cache) { 201 cache.clear(); 202 203 if (fallBack!=null) { 204 fallBack.clear(); 205 } 206 } 207 } 208 209 public void remove(Object key) { 210 synchronized (cache) { 211 cache.remove(key); 212 } 213 onRemove(key); 214 } 215 216 public void remove(Acceptor acceptor) { 217 synchronized (cache) { 218 Iterator it=cache.keySet().iterator(); 219 while (it.hasNext()) { 220 Object key=it.next(); 221 if (acceptor.accept(key)) { 222 it.remove(); 223 onRemove(key); 224 } 225 } 226 } 227 } 228 229 private boolean shutDown=false; 230 231 public void stop() { 232 if (!shutDown) { 233 cache.clear(); 234 shutDown=true; 235 } 236 } 237 238 protected void finalize() throws Throwable { 239 if (isActive()) { 240 stop(); 241 } 242 super.finalize(); 243 } 244 245 public Set keySet() { 246 HashSet ret = new HashSet(); 247 synchronized (cache) { 248 ret.addAll(cache.keySet()); 249 } 250 251 if (producer!=null) { 252 Set pkeys=producer.keySet(); 253 if (pkeys!=null) { 254 ret.addAll(pkeys); 255 } 256 } 257 258 if (fallBack!=null) { 259 Set fkeys=fallBack.keySet(); 260 if (fkeys!=null) { 261 ret.addAll(fkeys); 262 } 263 } 264 265 return ret; 266 } 267 268 /* (non-Javadoc) 269 * @see biz.hammurapi.cache.Cache#isActive() 270 */ 271 public boolean isActive() { 272 return !shutDown; 273 } 274 275 public void start() throws ConfigurationException { 276 // TODO Nothing 277 278 } 279 280 public void setOwner(Object owner) { 281 // TODO Nothing 282 283 } 284 }