1 package org.apache.jcs.utils.struct;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.Serializable;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.HashSet;
26 import java.util.Hashtable;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.NoSuchElementException;
31 import java.util.Set;
32
33 import org.apache.commons.logging.Log;
34 import org.apache.commons.logging.LogFactory;
35 import org.apache.jcs.engine.control.group.GroupAttrName;
36 import org.apache.jcs.engine.stats.StatElement;
37 import org.apache.jcs.engine.stats.Stats;
38 import org.apache.jcs.engine.stats.behavior.IStatElement;
39 import org.apache.jcs.engine.stats.behavior.IStats;
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 public class LRUMap<K, V>
56 implements Map<K, V>
57 {
58
59 private final static Log log = LogFactory.getLog( LRUMap.class );
60
61
62 private final DoubleLinkedList<LRUElementDescriptor<K, V>> list;
63
64
65 protected Map<K, LRUElementDescriptor<K, V>> map;
66
67
68 int hitCnt = 0;
69
70
71 int missCnt = 0;
72
73
74 int putCnt = 0;
75
76
77 int maxObjects = -1;
78
79
80 private int chunkSize = 1;
81
82
83
84
85
86 public LRUMap()
87 {
88 list = new DoubleLinkedList<LRUElementDescriptor<K, V>>();
89
90
91
92 map = new Hashtable<K, LRUElementDescriptor<K, V>>();
93
94 }
95
96
97
98
99
100
101 public LRUMap( int maxObjects )
102 {
103 this();
104 this.maxObjects = maxObjects;
105 }
106
107
108
109
110
111
112 public int size()
113 {
114 return map.size();
115 }
116
117
118
119
120
121
122 public void clear()
123 {
124 map.clear();
125 list.removeAll();
126 }
127
128
129
130
131
132
133 public boolean isEmpty()
134 {
135 return map.size() == 0;
136 }
137
138
139
140
141
142
143 public boolean containsKey( Object key )
144 {
145 return map.containsKey( key );
146 }
147
148
149
150
151
152
153 public boolean containsValue( Object value )
154 {
155 return map.containsValue( value );
156 }
157
158
159
160
161 public Collection<V> values()
162 {
163 List<V> valueList = new ArrayList<V>(map.size());
164
165 for (LRUElementDescriptor<K, V> value : map.values())
166 {
167 valueList.add(value.getPayload());
168 }
169
170 return valueList;
171 }
172
173
174
175
176 public void putAll( Map<? extends K, ? extends V> source )
177 {
178 if ( source != null )
179 {
180 for (Map.Entry<? extends K, ? extends V> entry : source.entrySet())
181 {
182 this.put( entry.getKey(), entry.getValue() );
183 }
184 }
185 }
186
187
188
189
190
191 public V get( Object key )
192 {
193 V retVal = null;
194
195 if ( log.isDebugEnabled() )
196 {
197 log.debug( "getting item for key " + key );
198 }
199
200 LRUElementDescriptor<K, V> me = map.get( key );
201
202 if ( me != null )
203 {
204 hitCnt++;
205 if ( log.isDebugEnabled() )
206 {
207 log.debug( "LRUMap hit for " + key );
208 }
209
210 retVal = me.getPayload();
211
212 list.makeFirst( me );
213 }
214 else
215 {
216 missCnt++;
217 log.debug( "LRUMap miss for " + key );
218 }
219
220
221 return retVal;
222 }
223
224
225
226
227
228
229
230
231
232 public V getQuiet( Object key )
233 {
234 V ce = null;
235
236 LRUElementDescriptor<K, V> me = map.get( key );
237 if ( me != null )
238 {
239 if ( log.isDebugEnabled() )
240 {
241 log.debug( "LRUMap quiet hit for " + key );
242 }
243
244 ce = me.getPayload();
245 }
246 else if ( log.isDebugEnabled() )
247 {
248 log.debug( "LRUMap quiet miss for " + key );
249 }
250
251 return ce;
252 }
253
254
255
256
257
258 public V remove( Object key )
259 {
260 if ( log.isDebugEnabled() )
261 {
262 log.debug( "removing item for key: " + key );
263 }
264
265
266 LRUElementDescriptor<K, V> me = map.remove( key );
267
268 if ( me != null )
269 {
270 list.remove( me );
271 return me.getPayload();
272 }
273
274 return null;
275 }
276
277
278
279
280
281
282 public V put(K key, V value)
283 {
284 putCnt++;
285
286 LRUElementDescriptor<K, V> old = null;
287 synchronized ( this )
288 {
289
290 addFirst( key, value );
291
292 old = map.put(list.getFirst().getKey(), list.getFirst());
293
294
295 if ( old != null && list.getFirst().getKey().equals(old.getKey()))
296 {
297 list.remove( old );
298 }
299 }
300
301 int size = map.size();
302
303
304 if ( this.maxObjects >= 0 && size > this.maxObjects )
305 {
306 if ( log.isDebugEnabled() )
307 {
308 log.debug( "In memory limit reached, removing least recently used." );
309 }
310
311
312 int chunkSizeCorrected = Math.min( size, getChunkSize() );
313
314 if ( log.isDebugEnabled() )
315 {
316 log.debug( "About to remove the least recently used. map size: " + size + ", max objects: "
317 + this.maxObjects + ", items to spool: " + chunkSizeCorrected );
318 }
319
320
321
322
323
324 for ( int i = 0; i < chunkSizeCorrected; i++ )
325 {
326 synchronized ( this )
327 {
328 if ( list.getLast() != null )
329 {
330 if (list.getLast() != null )
331 {
332 processRemovedLRU(list.getLast().getKey(), list.getLast().getPayload());
333 if ( !map.containsKey(list.getLast().getKey()))
334 {
335 log.error( "update: map does not contain key: "
336 + list.getLast().getKey() );
337 verifyCache();
338 }
339 if ( map.remove( list.getLast().getKey() ) == null )
340 {
341 log.warn( "update: remove failed for key: "
342 + list.getLast().getKey() );
343 verifyCache();
344 }
345 }
346 else
347 {
348 throw new Error( "update: last.ce is null!" );
349 }
350 list.removeLast();
351 }
352 else
353 {
354 verifyCache();
355 throw new Error( "update: last is null!" );
356 }
357 }
358 }
359
360 if ( log.isDebugEnabled() )
361 {
362 log.debug( "update: After spool map size: " + map.size() );
363 }
364 if ( map.size() != dumpCacheSize() )
365 {
366 log.error( "update: After spool, size mismatch: map.size() = " + map.size() + ", linked list size = "
367 + dumpCacheSize() );
368 }
369 }
370
371 if ( old != null )
372 {
373 return old.getPayload();
374 }
375 return null;
376 }
377
378
379
380
381
382
383
384 private synchronized void addFirst(K key, V val)
385 {
386 LRUElementDescriptor<K, V> me = new LRUElementDescriptor<K, V>(key, val);
387 list.addFirst( me );
388 }
389
390
391
392
393
394
395 private int dumpCacheSize()
396 {
397 return list.size();
398 }
399
400
401
402
403 @SuppressWarnings("unchecked")
404 public void dumpCacheEntries()
405 {
406 log.debug( "dumpingCacheEntries" );
407 for ( LRUElementDescriptor<K, V> me = list.getFirst(); me != null; me = (LRUElementDescriptor<K, V>) me.next )
408 {
409 if ( log.isDebugEnabled() )
410 {
411 log.debug( "dumpCacheEntries> key=" + me.getKey() + ", val=" + me.getPayload() );
412 }
413 }
414 }
415
416
417
418
419 public void dumpMap()
420 {
421 log.debug( "dumpingMap" );
422 for (Map.Entry<K, LRUElementDescriptor<K, V>> e : map.entrySet())
423 {
424 LRUElementDescriptor<K, V> me = e.getValue();
425 if ( log.isDebugEnabled() )
426 {
427 log.debug( "dumpMap> key=" + e.getKey() + ", val=" + me.getPayload() );
428 }
429 }
430 }
431
432
433
434
435
436 @SuppressWarnings("unchecked")
437 protected void verifyCache()
438 {
439 if ( !log.isDebugEnabled() )
440 {
441 return;
442 }
443
444 boolean found = false;
445 log.debug( "verifycache: mapContains " + map.size() + " elements, linked list contains " + dumpCacheSize()
446 + " elements" );
447 log.debug( "verifycache: checking linked list by key " );
448 for (LRUElementDescriptor<K, V> li = list.getFirst(); li != null; li = (LRUElementDescriptor<K, V>) li.next )
449 {
450 K key = li.getKey();
451 if ( !map.containsKey( key ) )
452 {
453 log.error( "verifycache: map does not contain key : " + li.getKey() );
454 log.error( "li.hashcode=" + li.getKey().hashCode() );
455 log.error( "key class=" + key.getClass() );
456 log.error( "key hashcode=" + key.hashCode() );
457 log.error( "key toString=" + key.toString() );
458 if ( key instanceof GroupAttrName )
459 {
460 GroupAttrName<?> name = (GroupAttrName<?>) key;
461 log.error( "GroupID hashcode=" + name.groupId.hashCode() );
462 log.error( "GroupID.class=" + name.groupId.getClass() );
463 log.error( "AttrName hashcode=" + name.attrName.hashCode() );
464 log.error( "AttrName.class=" + name.attrName.getClass() );
465 }
466 dumpMap();
467 }
468 else if ( map.get( li.getKey() ) == null )
469 {
470 log.error( "verifycache: linked list retrieval returned null for key: " + li.getKey() );
471 }
472 }
473
474 log.debug( "verifycache: checking linked list by value " );
475 for (LRUElementDescriptor<K, V> li3 = list.getFirst(); li3 != null; li3 = (LRUElementDescriptor<K, V>) li3.next )
476 {
477 if ( map.containsValue( li3 ) == false )
478 {
479 log.error( "verifycache: map does not contain value : " + li3 );
480 dumpMap();
481 }
482 }
483
484 log.debug( "verifycache: checking via keysets!" );
485 for (Iterator<K> itr2 = map.keySet().iterator(); itr2.hasNext(); )
486 {
487 found = false;
488 Serializable val = null;
489 try
490 {
491 val = (Serializable) itr2.next();
492 }
493 catch ( NoSuchElementException nse )
494 {
495 log.error( "verifycache: no such element exception" );
496 continue;
497 }
498
499 for (LRUElementDescriptor<K, V> li2 = list.getFirst(); li2 != null; li2 = (LRUElementDescriptor<K, V>) li2.next )
500 {
501 if ( val.equals( li2.getKey() ) )
502 {
503 found = true;
504 break;
505 }
506 }
507 if ( !found )
508 {
509 log.error( "verifycache: key not found in list : " + val );
510 dumpCacheEntries();
511 if ( map.containsKey( val ) )
512 {
513 log.error( "verifycache: map contains key" );
514 }
515 else
516 {
517 log.error( "verifycache: map does NOT contain key, what the HECK!" );
518 }
519 }
520 }
521 }
522
523
524
525
526
527
528 @SuppressWarnings("unchecked")
529 protected void verifyCache( Object key )
530 {
531 if ( !log.isDebugEnabled() )
532 {
533 return;
534 }
535
536 boolean found = false;
537
538
539 for (LRUElementDescriptor<K, V> li = list.getFirst(); li != null; li = (LRUElementDescriptor<K, V>) li.next )
540 {
541 if ( li.getKey() == key )
542 {
543 found = true;
544 log.debug( "verifycache(key) key match: " + key );
545 break;
546 }
547 }
548 if ( !found )
549 {
550 log.error( "verifycache(key), couldn't find key! : " + key );
551 }
552 }
553
554
555
556
557
558
559
560
561 protected void processRemovedLRU(K key, V value )
562 {
563 if ( log.isDebugEnabled() )
564 {
565 log.debug( "Removing key: [" + key + "] from LRUMap store, value = [" + value + "]" );
566 log.debug( "LRUMap store size: '" + this.size() + "'." );
567 }
568 }
569
570
571
572
573
574
575 public void setChunkSize( int chunkSize )
576 {
577 this.chunkSize = chunkSize;
578 }
579
580
581
582
583 public int getChunkSize()
584 {
585 return chunkSize;
586 }
587
588
589
590
591 public IStats getStatistics()
592 {
593 IStats stats = new Stats();
594 stats.setTypeName( "LRUMap" );
595
596 ArrayList<IStatElement> elems = new ArrayList<IStatElement>();
597
598 IStatElement se = null;
599
600 se = new StatElement();
601 se.setName( "List Size" );
602 se.setData( "" + list.size() );
603 elems.add( se );
604
605 se = new StatElement();
606 se.setName( "Map Size" );
607 se.setData( "" + map.size() );
608 elems.add( se );
609
610 se = new StatElement();
611 se.setName( "Put Count" );
612 se.setData( "" + putCnt );
613 elems.add( se );
614
615 se = new StatElement();
616 se.setName( "Hit Count" );
617 se.setData( "" + hitCnt );
618 elems.add( se );
619
620 se = new StatElement();
621 se.setName( "Miss Count" );
622 se.setData( "" + missCnt );
623 elems.add( se );
624
625
626 IStatElement[] ses = elems.toArray( new StatElement[0] );
627 stats.setStatElements( ses );
628
629 return stats;
630 }
631
632
633
634
635
636
637
638
639
640
641
642 public synchronized Set<Map.Entry<K, V>> entrySet()
643 {
644
645 Set<Map.Entry<K, LRUElementDescriptor<K, V>>> entries = map.entrySet();
646
647 Set<Map.Entry<K, V>> unWrapped = new HashSet<Map.Entry<K, V>>();
648
649 for (Map.Entry<K, LRUElementDescriptor<K, V>> pre : entries )
650 {
651 Map.Entry<K, V> post = new LRUMapEntry<K, V>(pre.getKey(), pre.getValue().getPayload());
652 unWrapped.add(post);
653 }
654
655 return unWrapped;
656 }
657
658
659
660
661 public Set<K> keySet()
662 {
663
664 return map.keySet();
665 }
666
667
668
669
670 public int getMaxObjects()
671 {
672 return maxObjects;
673 }
674 }