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 public void dumpCacheEntries()
404 {
405 log.debug( "dumpingCacheEntries" );
406 for ( LRUElementDescriptor<K, V> me = list.getFirst(); me != null; me = (LRUElementDescriptor<K, V>) me.next )
407 {
408 if ( log.isDebugEnabled() )
409 {
410 log.debug( "dumpCacheEntries> key=" + me.getKey() + ", val=" + me.getPayload() );
411 }
412 }
413 }
414
415
416
417
418 public void dumpMap()
419 {
420 log.debug( "dumpingMap" );
421 for (Map.Entry<K, LRUElementDescriptor<K, V>> e : map.entrySet())
422 {
423 LRUElementDescriptor<K, V> me = e.getValue();
424 if ( log.isDebugEnabled() )
425 {
426 log.debug( "dumpMap> key=" + e.getKey() + ", val=" + me.getPayload() );
427 }
428 }
429 }
430
431
432
433
434
435 protected void verifyCache()
436 {
437 if ( !log.isDebugEnabled() )
438 {
439 return;
440 }
441
442 boolean found = false;
443 log.debug( "verifycache: mapContains " + map.size() + " elements, linked list contains " + dumpCacheSize()
444 + " elements" );
445 log.debug( "verifycache: checking linked list by key " );
446 for (LRUElementDescriptor<K, V> li = list.getFirst(); li != null; li = (LRUElementDescriptor<K, V>) li.next )
447 {
448 K key = li.getKey();
449 if ( !map.containsKey( key ) )
450 {
451 log.error( "verifycache: map does not contain key : " + li.getKey() );
452 log.error( "li.hashcode=" + li.getKey().hashCode() );
453 log.error( "key class=" + key.getClass() );
454 log.error( "key hashcode=" + key.hashCode() );
455 log.error( "key toString=" + key.toString() );
456 if ( key instanceof GroupAttrName )
457 {
458 GroupAttrName name = (GroupAttrName) key;
459 log.error( "GroupID hashcode=" + name.groupId.hashCode() );
460 log.error( "GroupID.class=" + name.groupId.getClass() );
461 log.error( "AttrName hashcode=" + name.attrName.hashCode() );
462 log.error( "AttrName.class=" + name.attrName.getClass() );
463 }
464 dumpMap();
465 }
466 else if ( map.get( li.getKey() ) == null )
467 {
468 log.error( "verifycache: linked list retrieval returned null for key: " + li.getKey() );
469 }
470 }
471
472 log.debug( "verifycache: checking linked list by value " );
473 for (LRUElementDescriptor<K, V> li3 = list.getFirst(); li3 != null; li3 = (LRUElementDescriptor<K, V>) li3.next )
474 {
475 if ( map.containsValue( li3 ) == false )
476 {
477 log.error( "verifycache: map does not contain value : " + li3 );
478 dumpMap();
479 }
480 }
481
482 log.debug( "verifycache: checking via keysets!" );
483 for (Iterator<K> itr2 = map.keySet().iterator(); itr2.hasNext(); )
484 {
485 found = false;
486 Serializable val = null;
487 try
488 {
489 val = (Serializable) itr2.next();
490 }
491 catch ( NoSuchElementException nse )
492 {
493 log.error( "verifycache: no such element exception" );
494 continue;
495 }
496
497 for (LRUElementDescriptor<K, V> li2 = list.getFirst(); li2 != null; li2 = (LRUElementDescriptor<K, V>) li2.next )
498 {
499 if ( val.equals( li2.getKey() ) )
500 {
501 found = true;
502 break;
503 }
504 }
505 if ( !found )
506 {
507 log.error( "verifycache: key not found in list : " + val );
508 dumpCacheEntries();
509 if ( map.containsKey( val ) )
510 {
511 log.error( "verifycache: map contains key" );
512 }
513 else
514 {
515 log.error( "verifycache: map does NOT contain key, what the HECK!" );
516 }
517 }
518 }
519 }
520
521
522
523
524
525
526 protected void verifyCache( Object key )
527 {
528 if ( !log.isDebugEnabled() )
529 {
530 return;
531 }
532
533 boolean found = false;
534
535
536 for (LRUElementDescriptor<K, V> li = list.getFirst(); li != null; li = (LRUElementDescriptor<K, V>) li.next )
537 {
538 if ( li.getKey() == key )
539 {
540 found = true;
541 log.debug( "verifycache(key) key match: " + key );
542 break;
543 }
544 }
545 if ( !found )
546 {
547 log.error( "verifycache(key), couldn't find key! : " + key );
548 }
549 }
550
551
552
553
554
555
556
557
558 protected void processRemovedLRU(K key, V value )
559 {
560 if ( log.isDebugEnabled() )
561 {
562 log.debug( "Removing key: [" + key + "] from LRUMap store, value = [" + value + "]" );
563 log.debug( "LRUMap store size: '" + this.size() + "'." );
564 }
565 }
566
567
568
569
570
571
572 public void setChunkSize( int chunkSize )
573 {
574 this.chunkSize = chunkSize;
575 }
576
577
578
579
580 public int getChunkSize()
581 {
582 return chunkSize;
583 }
584
585
586
587
588 public IStats getStatistics()
589 {
590 IStats stats = new Stats();
591 stats.setTypeName( "LRUMap" );
592
593 ArrayList<IStatElement> elems = new ArrayList<IStatElement>();
594
595 IStatElement se = null;
596
597 se = new StatElement();
598 se.setName( "List Size" );
599 se.setData( "" + list.size() );
600 elems.add( se );
601
602 se = new StatElement();
603 se.setName( "Map Size" );
604 se.setData( "" + map.size() );
605 elems.add( se );
606
607 se = new StatElement();
608 se.setName( "Put Count" );
609 se.setData( "" + putCnt );
610 elems.add( se );
611
612 se = new StatElement();
613 se.setName( "Hit Count" );
614 se.setData( "" + hitCnt );
615 elems.add( se );
616
617 se = new StatElement();
618 se.setName( "Miss Count" );
619 se.setData( "" + missCnt );
620 elems.add( se );
621
622
623 IStatElement[] ses = elems.toArray( new StatElement[0] );
624 stats.setStatElements( ses );
625
626 return stats;
627 }
628
629
630
631
632
633
634
635
636
637
638
639 public synchronized Set<Map.Entry<K, V>> entrySet()
640 {
641
642 Set<Map.Entry<K, LRUElementDescriptor<K, V>>> entries = map.entrySet();
643
644 Set<Map.Entry<K, V>> unWrapped = new HashSet<Map.Entry<K, V>>();
645
646 for (Map.Entry<K, LRUElementDescriptor<K, V>> pre : entries )
647 {
648 Map.Entry<K, V> post = new LRUMapEntry<K, V>(pre.getKey(), pre.getValue().getPayload());
649 unWrapped.add(post);
650 }
651
652 return unWrapped;
653 }
654
655
656
657
658 public Set<K> keySet()
659 {
660
661 return map.keySet();
662 }
663
664
665
666
667 public int getMaxObjects()
668 {
669 return maxObjects;
670 }
671 }