1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 package org.melati.poem.util;
47
48 import java.lang.ref.ReferenceQueue;
49 import java.lang.ref.SoftReference;
50 import java.util.Enumeration;
51 import java.util.Hashtable;
52 import java.util.Vector;
53
54 import org.melati.poem.PoemException;
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84 public final class Cache {
85
86
87 private interface Node {
88
89 Object key();
90
91 Object value();
92 }
93
94
95 private static class HeldNode implements Node {
96 Object key;
97 Object value;
98 HeldNode nextMRU = null;
99 HeldNode prevMRU = null;
100
101 HeldNode(Object key, Object value) {
102 this.key = key;
103 this.value = value;
104 }
105
106 synchronized void putBefore(HeldNode nextMRU_P) {
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129 if (this.nextMRU != null)
130 this.nextMRU.prevMRU = prevMRU;
131
132 if (prevMRU != null)
133 prevMRU.nextMRU = this.nextMRU;
134
135 if (nextMRU_P != null) {
136 if (nextMRU_P.prevMRU != null)
137
138
139 nextMRU_P.prevMRU.nextMRU = this;
140 prevMRU = nextMRU_P.prevMRU;
141 nextMRU_P.prevMRU = this;
142 }
143 else
144 prevMRU = null;
145 this.nextMRU = nextMRU_P;
146 }
147
148
149
150
151
152 public Object key() {
153 return key;
154 }
155
156
157
158
159
160 public Object value() {
161 return value;
162 }
163
164
165
166
167
168 public String toString() {
169 StringBuffer ret = new StringBuffer();
170 if(prevMRU == null)
171 ret.append("null");
172 else
173 ret.append(prevMRU.key());
174 ret.append(">>"+ key + "=" + value + ">>");
175 if(nextMRU == null)
176 ret.append("null");
177 else
178 ret.append(nextMRU.key());
179 return ret.toString();
180 }
181
182
183 }
184
185
186
187
188 private static class DroppedNode extends SoftReference<Object> implements Node {
189
190 Object key;
191
192
193
194
195
196
197
198 DroppedNode(Object key, Object value, ReferenceQueue<Object> queue) {
199 super(value, queue);
200 this.key = key;
201 }
202
203
204
205
206
207 public Object key() {
208 return key;
209 }
210
211
212
213
214
215 public Object value() {
216 return this.get();
217 }
218 }
219
220 private Hashtable<Object, Object> table = new Hashtable<Object, Object>();
221 private HeldNode theMRU = null, theLRU = null;
222 private int heldNodes = 0;
223 private int maxSize;
224 private int collectedEver = 0;
225
226 private ReferenceQueue<Object> collectedValuesQueue = new ReferenceQueue<Object>();
227
228
229
230
231
232
233
234
235
236
237
238
239 public class InconsistencyException extends PoemException {
240
241
242 private static final long serialVersionUID = 1832694552964508864L;
243
244 public Vector<Object> problems;
245
246
247 public InconsistencyException(Vector<Object> probs) {
248 this.problems = probs;
249 }
250
251
252
253
254 public String getMessage() {
255 return EnumUtils.concatenated("\n", problems.elements());
256 }
257 }
258
259
260
261
262 private Vector<Object> invariantBreaches() {
263 Vector<Object> probs = new Vector<Object>();
264
265 if (theMRU != null && theMRU.prevMRU != null)
266 probs.addElement("theMRU.prevMRU == " + theMRU.prevMRU);
267 if (theLRU != null && theLRU.nextMRU != null)
268 probs.addElement("theLRU.nextMRU == " + theLRU.nextMRU);
269
270 Object[] held = new Object[heldNodes];
271 Hashtable<HeldNode, Boolean> heldHash = new Hashtable<HeldNode, Boolean>();
272
273 int countML = 0;
274 for (HeldNode n = theMRU; n != null; n = n.nextMRU, ++countML) {
275 if (table.get(n.key()) != n)
276 probs.addElement("MRU list check: table.get(" + n + ".key()) == " + table.get(n.key()));
277 if (countML < heldNodes)
278 held[countML] = n;
279 heldHash.put(n, Boolean.TRUE);
280 }
281
282 if (countML != heldNodes)
283 probs.addElement(countML + " nodes in MRU->LRU not " + heldNodes);
284
285 Hashtable<Object,HeldNode> keys = new Hashtable<Object,HeldNode>();
286 int countLM = 0;
287 for (HeldNode n = theLRU; n != null; n = n.prevMRU, ++countLM) {
288 HeldNode oldn = (HeldNode)keys.get(n.key());
289 if (oldn != null)
290 probs.addElement("key " + n.key() + " duplicated in " + n + " and " +
291 oldn);
292 keys.put(n.key(), n);
293
294 if (table.get(n.key()) != n)
295 probs.addElement("LRU list check: table.get(" + n + ".key()) == " + table.get(n.key()));
296 if (countLM < heldNodes) {
297 int o = heldNodes - (1 + countLM);
298 if (n != held[o])
299 probs.addElement("lm[" + countLM + "] == " + n + " != ml[" +
300 o + "] == " + held[o]);
301 }
302 }
303
304 for (Enumeration<Object> nodes = table.elements(); nodes.hasMoreElements();) {
305 Node n = (Node)nodes.nextElement();
306 if (n instanceof HeldNode && !heldHash.containsKey(n))
307 probs.addElement(n + " in table but not MRU->LRU");
308 }
309
310 if (countLM != heldNodes)
311 probs.addElement(countLM + " nodes in LRU->MRU not " + heldNodes);
312
313 return probs;
314 }
315
316
317
318
319 private void assertInvariant() {
320 boolean debug = false;
321 if (debug) {
322 Vector<Object> probs = invariantBreaches();
323 if (probs.size() != 0) {
324 throw new InconsistencyException(probs);
325 }
326 }
327 }
328
329
330
331
332
333 public Cache(int maxSize) {
334 setSize(maxSize);
335 }
336
337
338
339
340
341 public void setSize(int maxSize) {
342 if (maxSize < 0)
343 throw new IllegalArgumentException();
344 this.maxSize = maxSize;
345 }
346
347
348
349
350 public int getSize() {
351 return maxSize;
352 }
353
354
355
356
357
358
359
360 private synchronized void checkForGarbageCollection() {
361 DroppedNode collected;
362 while ((collected = (DroppedNode)collectedValuesQueue.poll()) != null) {
363 table.remove(collected.key());
364 ++collectedEver;
365 }
366 }
367
368
369
370
371
372
373
374
375
376
377
378 public synchronized void trim(int maxSize_P) {
379 checkForGarbageCollection();
380
381 HeldNode n = theLRU;
382 while (n != null && heldNodes > maxSize_P) {
383 HeldNode nn = n.prevMRU;
384 n.putBefore(null);
385 table.put(n.key, new DroppedNode(n.key, n.value, collectedValuesQueue));
386 --heldNodes;
387 n = nn;
388 }
389
390 if (n == null) {
391 theLRU = null;
392 theMRU = null;
393 } else
394 theLRU = n;
395
396 assertInvariant();
397 }
398
399
400
401
402
403
404
405
406 public synchronized void delete(Object key) {
407 Node n = (Node)table.get(key);
408 if (n == null)
409 return;
410
411 if (n instanceof HeldNode) {
412 HeldNode h = (HeldNode)n;
413
414 if (theLRU == h)
415 theLRU = h.prevMRU;
416 if (theMRU == h)
417 theMRU = h.nextMRU;
418
419 h.putBefore(null);
420
421 --heldNodes;
422 }
423
424 table.remove(key);
425
426 assertInvariant();
427 }
428
429
430
431
432
433
434
435 public synchronized void put(Object key, Object value) {
436 if (key == null || value == null)
437 throw new NullPointerException();
438
439 trim(maxSize);
440
441 if (maxSize == 0) {
442 table.put(key, new DroppedNode(key, value, collectedValuesQueue));
443 } else {
444 HeldNode node = new HeldNode(key, value);
445
446 Object previous = table.put(key, node);
447 if (previous != null) {
448
449 table.put(key, previous);
450 throw new CacheDuplicationException("Key already in cache for key=" + key + ", value="+value);
451 }
452
453 node.putBefore(theMRU);
454 theMRU = node;
455 if (theLRU == null) theLRU = node;
456
457 ++heldNodes;
458
459 assertInvariant();
460 }
461 }
462
463
464
465
466
467
468 public synchronized Object get(Object key) {
469
470 checkForGarbageCollection();
471
472 Node node = (Node)table.get(key);
473 if (node == null)
474 return null;
475 else {
476 HeldNode held;
477 if (node instanceof HeldNode) {
478 held = (HeldNode)node;
479 if (held != theMRU) {
480 if (held == theLRU)
481 theLRU = held.prevMRU;
482 held.putBefore(theMRU);
483 theMRU = held;
484 }
485 } else {
486 if (node.value() == null)
487
488
489
490 return null;
491 held = new HeldNode(key, node.value());
492 table.put(key, held);
493 ++heldNodes;
494 held.putBefore(theMRU);
495 theMRU = held;
496 if (theLRU == null)
497 theLRU = held;
498 trim(maxSize);
499 }
500
501 assertInvariant();
502 return held.value;
503 }
504 }
505
506
507
508
509
510
511
512 public synchronized void iterate(Procedure f) {
513 checkForGarbageCollection();
514 for (Enumeration<Object> n = table.elements(); n.hasMoreElements();) {
515 Object value = ((Node)n.nextElement()).value();
516 if (value != null)
517
518 f.apply(value);
519 }
520 }
521
522
523
524
525
526 public Enumeration<Object> getReport() {
527 return new ConsEnumeration<Object>("" +
528 maxSize + " maxSize, " +
529 theMRU + " theMRU, " +
530 theLRU + " theLRU, " +
531 collectedEver + " collectedEver",
532 new ConsEnumeration<Object>(
533 heldNodes + " held, " + table.size() + " total ",
534 invariantBreaches().elements()));
535 }
536
537
538
539
540 public final class Info {
541
542 private Info() {}
543
544
545
546
547 public Enumeration<Object> getHeldElements() {
548 checkForGarbageCollection();
549 return new MappedEnumeration<Object, Object>(
550 new FilteredEnumeration<Object>(table.elements()) {
551 public boolean isIncluded(Object o) {
552 return o instanceof HeldNode;
553 }
554 }) {
555 public Object mapped(Object o) {
556 return ((Node)o).value();
557 }
558 };
559 }
560
561
562
563
564 public Enumeration<Object> getDroppedElements() {
565 checkForGarbageCollection();
566 return new MappedEnumeration<Object, Object>(
567 new FilteredEnumeration<Object>(table.elements()) {
568 public boolean isIncluded(Object o) {
569 return o instanceof DroppedNode;
570 }
571 }) {
572 public Object mapped(Object o) {
573 return ((Node)o).value();
574 }
575 };
576 }
577
578
579
580
581 public Enumeration<Object> getReport() {
582 return Cache.this.getReport();
583 }
584
585
586 }
587
588
589
590
591 public Info getInfo() {
592 return new Info();
593 }
594
595
596
597
598 public void dumpAnalysis() {
599 for (Enumeration<Object> l = getReport(); l.hasMoreElements();)
600 System.err.println(l.nextElement());
601 }
602
603
604
605
606 public void dump() {
607 System.err.println("Keys: " + table.size());
608 System.err.println("maxSize: " + maxSize);
609 System.err.println("theMRU: " + theMRU);
610 System.err.println("theLRU: " + theLRU);
611 System.err.println("heldNodes: " + heldNodes);
612 System.err.println("collectedEver: " + collectedEver);
613 Enumeration<Object> e = table.keys();
614 while(e.hasMoreElements()) {
615 Object k = e.nextElement();
616 System.err.print(k );
617 System.err.print(" : ");
618 System.err.println(table.get(k) );
619 }
620 }
621 }
622
623
624
625