MemoryCache.java

biz/hammurapi/cache/MemoryCache.java

Violations

Inspector Message Severity Location
Java Inspector 048 Copyrights information should be present in each file. 1
Java Inspector 082 Parenthesis are redundant. 2 279:37
Java Inspector 089 Undocumented field 2 49:9
Java Inspector 089 Undocumented field 2 50:9
Java Inspector 089 Undocumented field 2 53:9
Java Inspector 089 Constructor is not properly documented 2 80:17
Java Inspector 089 Undocumented parameter key 2 80:17
Java Inspector 089 Undocumented parameter value 2 80:17
Java Inspector 089 Undocumented parameter time 2 80:17
Java Inspector 089 Parameter expires is not documented 2 80:17
Java Inspector 089 Javadoc contains tag for non-existent parameter referent 2 80:17
Java Inspector 089 Javadoc contains tag for non-existent parameter q 2 80:17
Java Inspector 089 Method is not properly documented 2 92:17
Java Inspector 089 Undocumented parameter name 2 92:17
Java Inspector 089 Undocumented parameter value 2 92:17
Java Inspector 089 Javadoc contains tag for non-existent parameter string 2 92:17
Java Inspector 089 Javadoc contains tag for non-existent parameter i 2 92:17
Java Inspector 089 Javadoc contains tag for non-existent parameter l 2 92:17
Java Inspector 089 Undocumented method 2 115:17
Java Inspector 089 Undocumented method 2 136:17
Java Inspector 089 Undocumented method 2 171:17
Java Inspector 089 Parameter producer documentation is too short. It is only 1 words. Should be at least 3 words. 2 208:9
Java Inspector 089 Parameter producer documentation is too short. It is only 1 words. Should be at least 3 words. 2 222:9
Java Inspector 089 Undocumented method 2 245:9
Java Inspector 089 Undocumented method 2 271:9
Java Inspector 089 Undocumented method 2 289:49
Java Inspector 089 Undocumented method 2 293:49
Java Inspector 089 Undocumented method 2 297:49
Java Inspector 089 Undocumented method 2 334:9
Java Inspector 089 Undocumented method 2 345:9
Java Inspector 089 Undocumented method 2 356:9
Java Inspector 089 Undocumented method 2 386:9
Java Inspector 089 Undocumented method 2 401:9
Java Inspector 089 Undocumented method 2 408:9
Java Inspector 089 Undocumented method 2 431:9
Java Inspector 089 Undocumented exception ConfigurationException 2 438:9
Java Inspector 089 Undocumented method 2 448:9
Java Inspector 026 Avoid hardwired string literals. Allowed literals: [] 3 132:33
Java Inspector 026 Avoid hardwired string literals. Allowed literals: [] 3 144:71
Java Inspector 026 Avoid hardwired string literals. Allowed literals: [] 3 186:48
Java Inspector 026 Avoid hardwired string literals. Allowed literals: [] 3 190:48
Java Inspector 026 Avoid hardwired string literals. Allowed literals: [] 3 250:40
Java Inspector 026 Avoid hardwired string literals. Allowed literals: [] 3 276:40
Java Inspector 026 Avoid hardwired string literals. Allowed literals: [] 3 318:48
Java Inspector 026 Avoid hardwired string literals. Allowed literals: [] 3 326:48
Java Inspector 026 Avoid hardwired string literals. Allowed literals: [] 3 352:32
Java Inspector 026 Avoid hardwired string literals. Allowed literals: [] 3 373:48
Java Inspector 026 Avoid hardwired string literals. Allowed literals: [] 3 382:57
Java Inspector 051 It is good practice to call in any case super() in a constructor. 3 108:17
Java Inspector 051 It is good practice to call in any case super() in a constructor. 3 128:17
Java Inspector 051 It is good practice to call in any case super() in a constructor. 3 165:17
Java Inspector 051 It is good practice to call in any case super() in a constructor. 3 208:9

Source code

1/*
2 * hgcommons 9
3 * Hammurapi Group Common Library
4 * Copyright (C) 2003 Hammurapi Group
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 *
20 * URL: http://www.hammurapi.biz/hammurapi-biz/ef/xmenu/hammurapi-group/products/products/hgcommons/index.html
21 * e-Mail: support@hammurapi.biz
22 */
23package biz.hammurapi.cache;
24
25import java.lang.ref.ReferenceQueue;
26import java.lang.ref.SoftReference;
27import java.util.HashMap;
28import java.util.HashSet;
29import java.util.Iterator;
30import java.util.LinkedList;
31import java.util.Map;
32import java.util.Set;
33import java.util.Timer;
34import java.util.TimerTask;
35import java.util.WeakHashMap;
36
37import biz.hammurapi.config.Component;
38import biz.hammurapi.config.ConfigurationException;
39import biz.hammurapi.metrics.MeasurementCategory;
40import biz.hammurapi.util.Acceptor;
41
42
43/**
44 * Memory sensitive cache. Uses soft references to cache objects.
45 * @author Pavel Vlasov
46 * @version $Revision: 1.7 $
47 */
48public class MemoryCache extends AbstractProducer implements Cache, Component {
49 protected Map cache=new HashMap();
50 protected Map reverseCache=new WeakHashMap();
51
52 private ReferenceQueue queue=new ReferenceQueue();
53 protected Producer producer;
54 private Cache fallBack;
55 private MeasurementCategory measurementCategory;
56
57 private void addMeasurement(String name, double value, long time) {
58 if (measurementCategory!=null) {
59 measurementCategory.addMeasurement(name, value, time);
60 }
61 }
62
63 private void addMeasurement(String name, double value) {
64 if (measurementCategory!=null) {
65 measurementCategory.addMeasurement(name, value, 0);
66 }
67 }
68
69 private class CacheReference extends SoftReference {
70
71 private Object key;
72 private long expires;
73 private long time;
74
75 /**
76 * @param referent
77 * @param q
78 * @param expires
79 */
80 public CacheReference(Object key, Object value, long time, long expires) {
81 super(value, queue);
82 this.key=key;
83 this.expires=expires;
84 this.time=time;
85 }
86
87 /**
88 * @param string
89 * @param i
90 * @param l
91 */
92 public void addMeasurement(String name, double value) {
93 MemoryCache.this.addMeasurement(name, value);
94 }
95 }
96
97 /**
98 * Used as entry if fallBack cache is used.
99 * @author Pavel Vlasov
100 * @version $Revision: 1.7 $
101 */
102 private class FallBackEntry {
103 Object value;
104 private long expires;
105 private long time;
106 private Object key;
107
108 FallBackEntry(Object key, Object value, long time, long expires) {
109 this.key=key;
110 this.value=value;
111 this.expires=expires;
112 this.time=time;
113 }
114
115 protected void finalize() throws Throwable {
116 if (fallBack.isActive() && (expires<=0 || System.currentTimeMillis()<expires)) {
117 fallBack.put(key, value, time, expires);
118 }
119
120 super.finalize();
121 }
122 }
123
124 private static class ReferenceThread extends Thread {
125 private ReferenceQueue queue;
126 private Map cache;
127
128 private ReferenceThread(ReferenceQueue queue, Map cache) {
129 this.queue=queue;
130 this.cache=cache;
131 setDaemon(true);
132 setName("Removes cleared entries");
133 start();
134 }
135
136 public void run() {
137 try {
138 while (true) {
139 CacheReference cacheReference = (CacheReference) queue.remove();
140 Object key=cacheReference.key;
141 synchronized (cache) {
142 cache.remove(key);
143 }
144 cacheReference.addMeasurement("garbage collected", 1);
145 }
146 } catch (InterruptedException e) {
147 return;
148 }
149 }
150 }
151
152 private Thread referenceThread=new ReferenceThread(queue, cache);
153
154 private static class JanitorTask extends TimerTask {
155 private Map cache;
156 private MeasurementCategory measurementCategory;
157 private Map reverseCache;
158
159 private void addMeasurement(String name, double value, long time) {
160 if (measurementCategory!=null) {
161 measurementCategory.addMeasurement(name, value, time);
162 }
163 }
164
165 private JanitorTask(Map cache, Map reverseCache, MeasurementCategory measurementCategory) {
166 this.cache=cache;
167 this.reverseCache=reverseCache;
168 this.measurementCategory=measurementCategory;
169 }
170
171 public void run() {
172 long now=System.currentTimeMillis();
173 int expired=0;
174 synchronized (cache) {
175 Iterator it=new LinkedList(cache.keySet()).iterator();
176 while (it.hasNext()) {
177 Object key=it.next();
178 CacheReference cacheReference = (CacheReference) cache.get(key);
179 long expires = (cacheReference).expires;
180 if (expires>0 && expires<now) {
181 cache.remove(key);
182 reverseCache.remove(cacheReference.get());
183 expired++;
184 }
185 }
186 addMeasurement("size", cache.size(), now);
187 }
188
189 if (expired>0) {
190 addMeasurement("expired", expired, now);
191 }
192 }
193 }
194
195 private TimerTask janitorTask;
196
197 /**
198 * Default cache cleanup interval.
199 */
200 public static final long CLEANUP_INTERVAL=60000;
201
202 /**
203 * Constructs cache with default cleanup interval (1 minute).
204 * @param producer Producer
205 * @param fallBack Fallback cache
206 * @param measurementCategory Measurement category to report cache statistics
207 */
208 public MemoryCache(Producer producer, Cache fallBack, MeasurementCategory measurementCategory) {
209 this(producer, fallBack, measurementCategory, null, CLEANUP_INTERVAL);
210 }
211
212 /**
213 * Constructs cache with default cleanup interval (1 minute).
214 * @param producer Producer
215 * @param fallBack Fallback cache
216 * @param measurementCategory Measurement category to report cache statistics
217 * @param cleanupInterval Interval between removals of expired entries.
218 * @param timer Timer which will invoke cleanup tasks, if it is null then a new timer is created internally.
219 * Use this parameter to share timers between multiple caches in order to reduce number of threads in the
220 * application.
221 */
222 public MemoryCache(Producer producer, Cache fallBack, MeasurementCategory measurementCategory, Timer timer, long cleanupInterval) {
223 super();
224
225 this.producer=producer;
226 if (producer!=null) {
227 producer.addCache(this);
228 }
229
230 this.fallBack=fallBack;
231 if (fallBack!=null) {
232 addCache(fallBack);
233 }
234
235 this.measurementCategory=measurementCategory;
236 this.timer=timer;
237
238 this.cleanupInterval=cleanupInterval;
239 }
240
241 private boolean isOwnTimer;
242 private Timer timer;
243 private long cleanupInterval;
244
245 public void put(Object key, Object value, long time, long expirationTime) {
246 checkShutdown();
247 synchronized (cache) {
248 cache.put(key, new CacheReference(key, toValue(key, value, time, expirationTime), time, expirationTime));
249 reverseCache.put(value, key);
250 addMeasurement("put", 1, time);
251 }
252 }
253
254 /**
255 * @param key
256 * @param value
257 * @param expirationTime
258 * @return
259 */
260 private Object toValue(Object key, Object value, long time, long expirationTime) {
261 return fallBack==null ? value : new FallBackEntry(key, value, time, expirationTime);
262 }
263
264 private Object fromValue(Object object) {
265 if (object instanceof FallBackEntry) {
266 return ((FallBackEntry) object).value;
267 }
268 return object;
269 }
270
271 public Entry get(Object key) {
272 checkShutdown();
273 synchronized (cache) {
274 CacheReference cr=(CacheReference) cache.get(key);
275 long now = System.currentTimeMillis();
276 addMeasurement("get", 1, now);
277 boolean doFallBack=true;
278 if (cr!=null) {
279 if ((cr.expires>0 && cr.expires<now) || cr.get()==null) {
280 cache.remove(key);
281 reverseCache.remove(fromValue(cr.get()));
282 doFallBack=false;
283 } else {
284 final Object o=fromValue(cr.get());
285 final long et=cr.expires;
286 final long time=cr.time;
287 return new Entry() {
288
289 public long getExpirationTime() {
290 return et;
291 }
292
293 public long getTime() {
294 return time;
295 }
296
297 public Object get() {
298 return o;
299 }
300
301 };
302 }
303 }
304
305 if (doFallBack && fallBack!=null) {
306 Entry entry = fallBack.get(key);
307 if (entry!=null) {
308 cache.put(key, new CacheReference(key, toValue(key, entry.get(), entry.getTime(), entry.getExpirationTime()), entry.getTime(), entry.getExpirationTime()));
309 reverseCache.put(entry.get(), key);
310 return entry;
311 }
312 }
313
314 if (key instanceof ProducingKey) {
315 Entry entry = ((ProducingKey) key).get();
316 cache.put(key, new CacheReference(key, toValue(key, entry.get(), entry.getTime(), entry.getExpirationTime()), entry.getTime(), entry.getExpirationTime()));
317 reverseCache.put(entry.get(),key);
318 addMeasurement("produce", 1, now);
319 return entry;
320 }
321
322 if (producer!=null) {
323 Entry entry = producer.get(key);
324 cache.put(key, new CacheReference(key, toValue(key, entry.get(), entry.getTime(), entry.getExpirationTime()), entry.getTime(), entry.getExpirationTime()));
325 reverseCache.put(entry.get(),key);
326 addMeasurement("produce", 1, now);
327 return entry;
328 }
329
330 return null;
331 }
332 }
333
334 public void clear() {
335 synchronized (cache) {
336 cache.clear();
337 reverseCache.clear();
338
339 if (fallBack!=null) {
340 fallBack.clear();
341 }
342 }
343 }
344
345 public void remove(Object key) {
346 synchronized (cache) {
347 CacheReference cr = (CacheReference) cache.remove(key);
348 if (cr!=null) {
349 reverseCache.remove(fromValue(cr.get()));
350 }
351 }
352 addMeasurement("remove", 1);
353 onRemove(key);
354 }
355
356 public void remove(Acceptor acceptor) {
357 synchronized (cache) {
358 Iterator it=cache.keySet().iterator();
359 int removed=0;
360 while (it.hasNext()) {
361 Object key=it.next();
362 if (acceptor.accept(key)) {
363 Entry entry=(Entry) cache.get(key);
364 if (entry!=null) {
365 reverseCache.remove(fromValue(entry.get()));
366 }
367 it.remove();
368 removed++;
369 onRemove(key);
370 }
371 }
372 if (removed>0) {
373 addMeasurement("remove", removed);
374 }
375 }
376 }
377
378 private boolean shutDown=false;
379
380 private void checkShutdown() {
381 if (shutDown) {
382 throw new IllegalStateException("Shut down");
383 }
384 }
385
386 public void stop() {
387 if (!shutDown) {
388 cache.clear();
389 reverseCache.clear();
390 referenceThread.interrupt();
391 if (janitorTask!=null) {
392 janitorTask.cancel();
393 }
394 if (isOwnTimer && timer!=null) {
395 timer.cancel();
396 }
397 shutDown=true;
398 }
399 }
400
401 protected void finalize() throws Throwable {
402 if (isActive()) {
403 stop();
404 }
405 super.finalize();
406 }
407
408 public Set keySet() {
409 HashSet ret = new HashSet();
410 synchronized (cache) {
411 ret.addAll(cache.keySet());
412 }
413
414 if (producer!=null) {
415 Set pkeys=producer.keySet();
416 if (pkeys!=null) {
417 ret.addAll(pkeys);
418 }
419 }
420
421 if (fallBack!=null) {
422 Set fkeys=fallBack.keySet();
423 if (fkeys!=null) {
424 ret.addAll(fkeys);
425 }
426 }
427
428 return ret;
429 }
430
431 public boolean isActive() {
432 return !shutDown;
433 }
434
435 /**
436 * Creates timer if neccessary, creates cleanup task and schedules it.
437 */
438 public void start() throws ConfigurationException {
439 if (timer==null) {
440 timer=new Timer(true);
441 isOwnTimer=true;
442 }
443
444 janitorTask=new JanitorTask(cache, reverseCache, measurementCategory);
445 timer.schedule(janitorTask, cleanupInterval, cleanupInterval);
446 }
447
448 public void setOwner(Object owner) {
449 // Nothing
450 }
451}
452
453
454