001 package org.rakeshv.utils;
002
003 import java.lang.ref.ReferenceQueue;
004 import java.lang.ref.SoftReference;
005
006 import java.util.ArrayList;
007 import java.util.Collection;
008 import java.util.Date;
009 import java.util.HashSet;
010 import java.util.Iterator;
011 import java.util.Set;
012 import java.util.Timer;
013 import java.util.TimerTask;
014
015 import java.util.concurrent.ConcurrentHashMap;
016 import java.util.logging.Level;
017 import java.util.logging.Logger;
018
019 /**
020 * A <code>Map</code> used to implement a dynamic cache that grows
021 * only up to the heap space available to the process. The map
022 * <code>values</code> are stored as <code>SoftReferences</code> thus
023 * ensuring that the <code>least recently used values</code> will be
024 * garbage collected when heap space runs short.
025 *
026 * <p>This class uses a <code>ConcurrentHashMap</code> internally to
027 * manage the cache.</p>
028 *
029 * <p>Mappings for garbage collected values from the <code>Map</code>
030 * will be removed when invoking the following methods:</p>
031 * <ol>
032 * <li>{@link #put}
033 * <li>{@link #remove}
034 * <li>{@link #clear}
035 * </ol>
036 *
037 * <p>In addition to the above, the {@link DynamicCache.Cleaner} task
038 * will run at the interval specified for the
039 * <code>org.rakeshv.utils.DynamicCache.cleanInterval</code> system
040 * property (in seconds), or at <code>600 second</code> intervals if
041 * no property is specified. This will ensure that memory is not
042 * wasted on holding mappings for garbage collected values, or for
043 * maintaing the values in the <code>ReferenceQueue</code>.</p>
044 *
045 * <p>Copyright 2004-2006 Rakesh Vidyadharan</p>
046 * @author Rakesh Vidyadharan 2004 October 1
047 * @version $Id: DynamicCache.java,v 1.9 2006/03/14 20:08:21 rakesh Exp $
048 */
049 class DynamicCache<K,V> implements Map<K,V>
050 {
051 /**
052 * Default concurrency level for the map.
053 */
054 static final int CONCURRENCY = 16;
055
056 /**
057 * The name of the system property that defines the interval at which
058 * the {@link DynamicCache.Cleaner} will run.
059 */
060 static final String CLEAN_INTERVAL_PROPERTY =
061 "org.rakeshv.utils.DynamicCache.cleanInterval";
062
063 /**
064 * The default value for the {@link #CLEAN_INTERVAL_PROPERTY} in
065 * seconds.
066 *
067 * {@value}
068 */
069 private static final String CLEAN_INTERVAL_VALUE = "600";
070
071 /**
072 * The logger used to write information about cache activities.
073 */
074 private static final Logger logger =
075 Logger.getLogger( "org.rakeshv.utils.DynamicCache" );
076
077 /**
078 * The {@link Cleaner} that is used to remove garbage collected
079 * {@link Value} instances from each instance of the cache.
080 */
081 private static final Cleaner cleaner;
082
083 /**
084 * Initialise the {@link #cleaner}. Uses the <code>
085 * org.rakeshv.utils.DynamicCache.cleanInterval</code> system
086 * property to determine the interval at which the {@link #cleaner}
087 * will be run. If no value is specified (in minutes) for the
088 * property, the caches will be cleaned at 10 minute intervals
089 * (600 seconds).
090 */
091 static
092 {
093 cleaner = new Cleaner();
094 int interval = Integer.parseInt( System.getProperties().getProperty(
095 CLEAN_INTERVAL_PROPERTY, CLEAN_INTERVAL_VALUE ) );
096 logger.info( "Scheduling cleaning of garbage collected values " +
097 "at intervals of " + interval + " seconds." );
098 ( new Timer( true ) ).scheduleAtFixedRate(
099 cleaner, new Date(), interval * 1000 );
100 }
101
102 /**
103 * The instance of <code>ConcurrentHashMap</code> that is used to
104 * hold the <code>key-value</code> entries.
105 */
106 private final ConcurrentHashMap<K, Value<V>> map;
107
108 /**
109 * The reference que for the garbage collected values.
110 */
111 private final ReferenceQueue<V> queue = new ReferenceQueue<V>();
112
113 /**
114 * An instance of {@link LinkedHashMap.Statistics} that is used to
115 * track usage of the cache.
116 */
117 private LinkedHashMap.Statistics statistics =
118 new LinkedHashMap.Statistics();
119
120 /**
121 * Default constructor. Creates a new Map with using the super
122 * class constructor.
123 */
124 DynamicCache()
125 {
126 super();
127 map = new ConcurrentHashMap<K, Value<V>>();
128 cleaner.list.add( this );
129 }
130
131 /**
132 * Constructs a new instance with the specified initial capacity.
133 *
134 * @param capacity The maximum size for this map.
135 */
136 DynamicCache( int capacity )
137 {
138 super();
139 map = new ConcurrentHashMap<K, Value<V>>(
140 capacity );
141 cleaner.list.add( this );
142 }
143
144 /**
145 * Constructs a new instance with the specified initial capacity and
146 * load factor.
147 *
148 * @param capacity The maximum size for this map.
149 * @param loadFactor The load-factor to use.
150 */
151 DynamicCache( int capacity, float loadFactor )
152 {
153 super();
154 map = new ConcurrentHashMap<K, Value<V>>(
155 capacity, loadFactor, CONCURRENCY );
156 cleaner.list.add( this );
157 }
158
159 /**
160 * Constructs an empty DynamicCache instance with the specified
161 * initial capacity, load factor and ordering mode.
162 *
163 * @param capacity The maximum size for this map.
164 * @param loadFactor The load-factor to use.
165 * @param concurrency The estimated number of concurrently updating
166 * threads. The implementation performs internal sizing to try to
167 * accommodate this many threads.
168 */
169 DynamicCache( int capacity, float loadFactor, int concurrency )
170 {
171 super();
172 map = new ConcurrentHashMap<K, Value<V>>(
173 capacity, loadFactor, concurrency );
174 cleaner.list.add( this );
175 }
176
177 /**
178 * Constructs an ordered DynamicCache instance with the same
179 * mappings as the specified map. The maximum size of this map
180 * is set to the size of the specified map.
181 *
182 * @see #putAll
183 * @param map The map to use to create the new instance.
184 * @throws NullPointerException If the specified map is null.
185 */
186 DynamicCache( java.util.Map<K,V> map )
187 {
188 this();
189 putAll( map );
190 cleaner.list.add( this );
191 }
192
193 /**
194 * Returns the total number of entries in the cache. Since this
195 * cache is not size limited, it does not keep track of any user
196 * specified maximum value.
197 *
198 * @return int The total capacity of the cache.
199 */
200 public final int getMaxEntries()
201 {
202 return size();
203 }
204
205 /**
206 * Set the maximum number of items that can be stored in the map.
207 * This method does nothing since a dynamic cache does not support
208 * setting hard size limits.
209 *
210 * @param maxEntries The value to set.
211 */
212 public final void setMaxEntries( int maxEntries ) {}
213
214 /**
215 * Returns {@link #statistics}. This may be used by client
216 * applications to monitor cache usage.
217 *
218 * @return Statistics The value/reference of/to statistics.
219 */
220 public final Map.Statistics getStatistics()
221 {
222 return statistics;
223 }
224
225 /**
226 * Returns the number of key-value mappings in this map. If the
227 * map contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
228 * <tt>Integer.MAX_VALUE</tt>.
229 *
230 * @return the number of key-value mappings in this map.
231 */
232 public final int size()
233 {
234 return map.size();
235 }
236
237 /**
238 * Returns <tt>true</tt> if this map contains no key-value mappings.
239 *
240 * @return <tt>true</tt> if this map contains no key-value mappings.
241 */
242 public final boolean isEmpty()
243 {
244 return map.isEmpty();
245 }
246
247 /**
248 * Returns <tt>true</tt> if this map contains a mapping for the
249 * specified key. More formally, returns <tt>true</tt> if and only if
250 * this map contains a mapping for a key <tt>k</tt> such that
251 * <tt>(key==null ? k==null : key.equals(k))</tt>. (There can be
252 * at most one such mapping.)
253 *
254 * @param key Key whose presence in this map is to be tested.
255 * @return <tt>true</tt> If this map contains a mapping for the
256 * specified key.
257 * @throws ClassCastException If the key is of an inappropriate type
258 * for this map (optional).
259 * @throws NullPointerException If the key is <tt>null</tt> and this
260 * map does not permit <tt>null</tt> keys (optional).
261 */
262 public final boolean containsKey( Object key )
263 {
264 return map.containsKey( key );
265 }
266
267 /**
268 * Returns <tt>true</tt> if this map maps one or more keys to the
269 * specified value. More formally, returns <tt>true</tt> if and only
270 * if this map contains at least one mapping to a value <tt>v</tt>
271 * such that <tt>(value==null ? v==null : value.equals(v))</tt>.
272 * This operation will probably require time linear in the map size
273 * for most implementations of the <tt>Map</tt> interface.
274 *
275 * @param value Value whose presence in this map is to be tested.
276 * @return <tt>true</tt> If this map maps one or more keys to the
277 * specified value.
278 * @throws ClassCastException If the value is of an inappropriate
279 * type for this map (optional).
280 * @throws NullPointerException If the value is <tt>null</tt> and
281 * this map does not permit <tt>null</tt> values (optional).
282 * @throws UnsupportedOperationException This operation is not
283 * supported by this map implementation.
284 */
285 public final boolean containsValue( Object value )
286 {
287 throw new UnsupportedOperationException(
288 "Fetching by value not supported by this map implementation" );
289 }
290
291 /**
292 * Returns the value to which this map maps the specified key.
293 * Returns <tt>null</tt> if the map contains no mapping for this key.
294 * A return value of <tt>null</tt> does not <i>necessarily</i>
295 * indicate that the map contains no mapping for the key; it's also
296 * possible that the map explicitly maps the key to <tt>null</tt>.
297 * The <tt>containsKey</tt> operation may be used to distinguish
298 * these two cases.
299 *
300 * <p>More formally, if this map contains a mapping from a key
301 * <tt>k</tt> to a value <tt>v</tt> such that
302 * <tt>(key==null ? k==null : key.equals(k))</tt>,
303 * then this method returns <tt>v</tt>; otherwise
304 * it returns <tt>null</tt>. (There can be at most one such mapping.)
305 *
306 * @param key Key whose associated value is to be returned.
307 * @return The value to which this map maps the specified key, or
308 * <tt>null</tt> if the map contains no mapping for this key.
309 * @throws ClassCastException If the key is of an inappropriate type
310 * for this map (optional).
311 * @throws NullPointerException If the key is <tt>null</tt> and this
312 * map does not permit <tt>null</tt> keys (optional).
313 *
314 * @see #containsKey( Object )
315 */
316 public final V get( Object key )
317 {
318 V result = null;
319 statistics.updateTotalRequests();
320
321 Value<V> value = map.get( key );
322 if ( value != null )
323 {
324 result = value.get();
325 statistics.updateSuccessfulRequests();
326 }
327
328 return result;
329 }
330
331 /**
332 * Associates the specified value with the specified key in this map.
333 * If the map previously contained a mapping for
334 * this key, the old value is replaced by the specified value. (A
335 * map <tt>m</tt> is said to contain a mapping for a key <tt>k</tt>
336 * if and only if {@link #containsKey(Object) m.containsKey(k)} would
337 * return <tt>true</tt>.))
338 *
339 * @see #clean
340 * @param key Key with which the specified value is to be associated.
341 * @param value Value to be associated with the specified key.
342 * @return Previous value associated with specified key, or
343 * <tt>null</tt> if there was no mapping for key. A <tt>null</tt>
344 * return can also indicate that the map previously associated
345 * <tt>null</tt> with the specified key, if the implementation
346 * supports <tt>null</tt> values.
347 * @throws UnsupportedOperationException If the <tt>put</tt>
348 * operation is not supported by this map.
349 * @throws ClassCastException If the class of the specified key or
350 * value prevents it from being stored in this map.
351 * @throws IllegalArgumentException If some aspect of this key or
352 * value prevents it from being stored in this map.
353 * @throws NullPointerException If this map does not permit
354 * <tt>null</tt> keys or values, and the specified key or value is
355 * <tt>null</tt>.
356 */
357 public final V put( K key, V value )
358 {
359 V v = null;
360 Value<V> ref = map.get( key );
361 if ( ref != null )
362 {
363 v = ref.get();
364 }
365
366 int count = clean();
367 logger.finer( "Removed " + count +
368 " garbage collected mappings for map with key type " +
369 key.getClass().getName() + " and value type " +
370 value.getClass().getName() + "." );
371 map.put( key, new Value< V>( key, value, queue ) );
372
373 return v;
374 }
375
376 /**
377 * Removes the mapping for this key from this map if it is present.
378 * More formally, if this map contains a mapping
379 * from key <tt>k</tt> to value <tt>v</tt> such that
380 * <code>(key==null ? k==null : key.equals(k))</code>, that mapping
381 * is removed. (The map can contain at most one such mapping.)
382 *
383 * <p>Returns the value to which the map previously associated the
384 * key, or <tt>null</tt> if the map contained no mapping for this
385 * key. (A <tt>null</tt> return can also indicate that the map
386 * previously associated <tt>null</tt> with the specified key if the
387 * implementation supports <tt>null</tt> values.) The map will not
388 * contain a mapping for the specified key once the call returns.
389 *
390 * @see #clean
391 * @param key Key whose mapping is to be removed from the map.
392 * @return Previous value associated with specified key, or
393 * <tt>null</tt> if there was no mapping for key.
394 * @throws ClassCastException If the key is of an inappropriate type
395 * for this map (optional).
396 * @throws NullPointerException If the key is <tt>null</tt> and this
397 * map does not permit <tt>null</tt> keys (optional).
398 * @throws UnsupportedOperationException if the <tt>remove</tt>
399 * method is not supported by this map.
400 */
401 public final V remove( Object key )
402 {
403 clean();
404 V v = null;
405 SoftReference<V> ref = map.remove( key );
406 if ( ref != null )
407 {
408 v = ref.get();
409 }
410
411 return v;
412 }
413
414 /**
415 * Copies all of the mappings from the specified map to this map
416 * The effect of this call is equivalent to that
417 * of calling {@link #put(Object,Object) put(k, v)} on this map once
418 * for each mapping from key <tt>k</tt> to value <tt>v</tt> in the
419 * specified map. The behavior of this operation is unspecified if
420 * the specified map is modified while the operation is in progress.
421 *
422 * @see #put
423 * @param t Mappings to be stored in this map.
424 * @throws UnsupportedOperationException If the <tt>putAll</tt>
425 * method is not supported by this map.
426 * @throws ClassCastException If the class of a key or value in the
427 * specified map prevents it from being stored in this map.
428 * @throws IllegalArgumentException Some aspect of a key or value in
429 * the specified map prevents it from being stored in this map.
430 * @throws NullPointerException If the specified map is <tt>null</tt>,
431 * or if this map does not permit <tt>null</tt> keys or values, and
432 * the specified map contains <tt>null</tt> keys or values.
433 */
434 public void putAll( java.util.Map<? extends K, ? extends V> t )
435 {
436 for ( Map.Entry<? extends K, ? extends V> entry :
437 t.entrySet() )
438 {
439 put( entry.getKey(), entry.getValue() );
440 }
441 }
442
443 /**
444 * Removes all mappings from this map (optional operation).
445 *
446 * @throws UnsupportedOperationException Clear is not supported by
447 * this map.
448 */
449 public void clear()
450 {
451 clean();
452 map.clear();
453 }
454
455 /**
456 * Returns a set view of the keys contained in this map. The set is
457 * backed by the map, so changes to the map are reflected in the set,
458 * and vice-versa. If the map is modified while an iteration over
459 * the set is in progress (except through the iterator's own
460 * <tt>remove</tt> operation), the results of the iteration are
461 * undefined. The set supports element removal, which removes the
462 * corresponding mapping from the map, via the
463 * <tt>Iterator.remove</tt>, <tt>Set.remove</tt>, <tt>removeAll</tt>
464 * <tt>retainAll</tt>, and <tt>clear</tt> operations.
465 * It does not support the add or <tt>addAll</tt> operations.
466 *
467 * @return A set view of the keys contained in this map.
468 */
469 public Set<K> keySet()
470 {
471 return map.keySet();
472 }
473
474 /**
475 * Returns a collection view of the values contained in this map. The
476 * collection is backed by the map, so changes to the map are
477 * reflected in the collection, and vice-versa. If the map is
478 * modified while an iteration over the collection is in progress
479 * (except through the iterator's own <tt>remove</tt> operation), the
480 * results of the iteration are undefined. The collection supports
481 * element removal, which removes the corresponding mapping from the
482 * map, via the <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
483 * <tt>removeAll</tt>, <tt>retainAll</tt> and <tt>clear</tt>
484 * operations. It does not support the add or <tt>addAll</tt>
485 * operations.
486 *
487 * @return A collection view of the values contained in this map.
488 */
489 public Collection<V> values()
490 {
491 ArrayList<V> values = new ArrayList<V>( map.size() );
492 for ( SoftReference<V> v : map.values() )
493 {
494 values.add( ( v == null ) ? null : v.get() );
495 }
496
497 return values;
498 }
499
500 /**
501 * Returns a set view of the mappings contained in this map. Each
502 * element in the returned set is a <code>Map.Entry</code>. The set
503 * is backed by the map, so changes to the map are reflected in the
504 * set, and vice-versa. If the map is modified while an iteration
505 * over the set is in progress (except through the iterator's own
506 * <tt>remove</tt> operation, or through
507 * the <tt>setValue</tt> operation on a map entry returned by the
508 * iterator) the results of the iteration are undefined. The set
509 * supports element removal, which removes the corresponding mapping
510 * from the map, via the
511 * <tt>Iterator.remove</tt>, <tt>Set.remove</tt>, <tt>removeAll</tt>,
512 * <tt>retainAll</tt> and <tt>clear</tt> operations. It does not
513 * support the <tt>add</tt> or <tt>addAll</tt> operations.
514 *
515 * @return A set view of the mappings contained in this map.
516 */
517 public Set<Map.Entry<K, V>> entrySet()
518 {
519 Set<Map.Entry<K,V>> set = new HashSet<Map.Entry<K,V>>();
520 for ( Map.Entry<K, Value<V>> entry : map.entrySet() )
521 {
522 V v = null;
523 Value<V> value = entry.getValue();
524
525 if ( value != null )
526 {
527 v = value.get();
528 }
529
530 set.add( new Entry<K,V>( entry.getKey(), v ) );
531 }
532
533 return set;
534 }
535
536 /**
537 * Compares the specified object with this map for equality. Returns
538 * <tt>true</tt> if the given object is also a map and the two Maps
539 * represent the same mappings. More formally, two maps <tt>t1</tt>
540 * and <tt>t2</tt> represent the same mappings if
541 * <tt>t1.entrySet().equals(t2.entrySet())</tt>. This ensures that
542 * the <tt>equals</tt> method works properly across different
543 * implementations of the <tt>Map</tt> interface.
544 *
545 * @param o Object to be compared for equality with this map.
546 * @return <tt>true</tt> If the specified object is equal to this map.
547 */
548 public boolean equals( Object o )
549 {
550 return map.equals( o );
551 }
552
553 /**
554 * Returns the hash code value for this map. The hash code of a map
555 * is defined to be the sum of the hashCodes of each entry in the
556 * map's entrySet view. This ensures that <tt>t1.equals(t2)</tt>
557 * implies that <tt>t1.hashCode()==t2.hashCode()</tt> for any two maps
558 * <tt>t1</tt> and <tt>t2</tt>, as required by the general
559 * contract of Object.hashCode.
560 *
561 * @return the hash code value for this map.
562 * @see #equals
563 */
564 public int hashCode()
565 {
566 return map.hashCode();
567 }
568
569 /**
570 * Clean out entries from the map whose <code>value</code> has been
571 * garbage collected. The garbage collected values are available
572 * in the {@link #queue} reference queue.
573 *
574 * @return int The number of entries that were removed from the
575 * {@link #map}.
576 */
577 private int clean()
578 {
579 int count = 0;
580 Value<? extends V> value = null;
581 while ( ( value = (Value<? extends V>) queue.poll() ) != null )
582 {
583 logger.finest( "Removing garbage collected value from " +
584 "cache with key type " + value.key.getClass().getName() +
585 " and key value " + value.key );
586 map.remove( value.key );
587 statistics.updateTotalRemoved();
588 ++count;
589 }
590
591 return count;
592 }
593
594 /**
595 * A sub-class of <code>SoftReference</code> used to represent the
596 * <code>value</code> stored in the {@link #map}.
597 */
598 private class Value<V> extends SoftReference<V>
599 {
600 /**
601 * The key associated with the value.
602 */
603 private final K key;
604
605 /**
606 * Create a new instance of the value object.
607 */
608 public Value( K key, V value, ReferenceQueue<V> queue )
609 {
610 super( value, queue );
611 this.key = key;
612 }
613 }
614
615 /**
616 * An implementation of the <code>Map.Entry</code> interface.
617 * This is used to return the results for the {@link #entrySet}
618 * method.
619 */
620 public class Entry<K,V> implements Map.Entry<K,V>
621 {
622 /**
623 * The key value for the map entry.
624 */
625 private final K key;
626
627 /**
628 * The value for the map entry.
629 */
630 private final V value;
631
632 /**
633 * Initialise a new instance with the specified parameters.
634 *
635 * @param key The key to use for the entry.
636 * @param value The value to use for the entry.
637 */
638 public Entry( K key, V value )
639 {
640 this.key = key;
641 this.value = value;
642 }
643
644 /**
645 * Returns the key corresponding to this entry.
646 *
647 * @return K The key corresponding to this entry.
648 * @throws IllegalStateException Implementations may, but are not
649 * required to, throw this exception if the entry has been
650 * removed from the backing map
651 */
652 public final K getKey() throws IllegalStateException
653 {
654 return key;
655 }
656
657 /**
658 * Returns the value corresponding to this entry. If the mapping
659 * has been removed from the backing map (by the iterator's remove
660 * operation), the results of this call are undefined.
661 *
662 * @return V The value corresponding to this entry.
663 * @throws IllegalStateException Implementations may, but are not
664 * required to, throw this exception if the entry has been
665 * removed from the backing map
666 */
667 public final V getValue() throws IllegalStateException
668 {
669 return value;
670 }
671
672 /**
673 * Replaces the value corresponding to this entry with the
674 * specified value (optional operation). (Writes through to the
675 * map.) The behavior of this call is undefined if the mapping has
676 * already been removed from the map (by the iterator's remove
677 * operation).
678 *
679 * @param value New value to be stored in this entry.
680 * @return V Old value corresponding to the entry.
681 * @throws UnsupportedOperationException The put operation is not
682 * supported by this operation.
683 */
684 public final V setValue( V value ) throws UnsupportedOperationException
685 {
686 throw new UnsupportedOperationException(
687 "setValue not supported by Map.Entry implementation." );
688 }
689
690 /**
691 * Compares the specified object with this entry for equality.
692 * Returns <code>true</code> if the given object is also a map
693 * entry and the two entries represent the same mapping. More
694 * formally, two entries <code>e1</code> and <code>e2</code>
695 * represent the same mapping if:
696 *
697 * <pre>
698 * (e1.getKey()==null ? e2.getKey()==null : e1.getKey().equals(e2.getKey())) &&
699 * (e1.getValue()==null ? e2.getValue()==null : e1.getValue().equals(e2.getValue()))
700 * </pre>
701 *
702 * <p>This ensures that the equals method works properly across
703 * different implementations of the <code>Map.Entry</code>
704 * interface.</p>
705 *
706 * @param object Object to be compared for equality with this map
707 * entry.
708 * @return boolean <code>true</code> if the specified object is
709 * equal to this map entry.
710 */
711 @Override
712 public final boolean equals( Object object )
713 {
714 if ( this == object ) return true;
715 boolean result = false;
716
717 if ( this.getClass() == object.getClass() )
718 {
719 Map.Entry entry = (Map.Entry) object;
720 result = ( ( key == entry.getKey() ) ||
721 key.equals( entry.getKey() ) );
722 result = result && ( ( value == entry.getValue() ) ||
723 ( value != null && value.equals( entry.getValue() ) ) );
724 }
725
726 return result;
727 }
728
729 /**
730 * Returns the hash code value for this map entry. The hash code of a
731 * map entry e is defined to be:
732 *
733 * <pre>
734 * (e.getKey()==null ? 0 : e.getKey().hashCode()) ^
735 * (e.getValue()==null ? 0 : e.getValue().hashCode())
736 * </pre>
737 *
738 * <p>This ensures that <code>e1.equals(e2)</code> implies that
739 * <code>e1.hashCode()==e2.hashCode()</code> for any two Entries
740 * <code>e1</code> and <code>e2</code>, as required by the general
741 * contract of <code>Object.hashCode</code>.</p>
742 *
743 * @return int The hash code value for this map entry.
744 */
745 @Override
746 public final int hashCode()
747 {
748 int result = ( ( key == null ) ? 0 : key.hashCode() );
749 result ^= ( ( value == null ) ? 0 : value.hashCode() );
750 return result;
751 }
752 }
753
754 /**
755 * A <code>TimerTask</code> that is used to schedule clearing of
756 * mappings for garbage collected {@link DynamicCache.Value} objects.
757 */
758 private static class Cleaner extends TimerTask
759 {
760 /**
761 * A collection of instances of {@link DynamicCache} from which
762 * the garbage collected values are to be removed.
763 */
764 private final ArrayList<DynamicCache> list =
765 new ArrayList<DynamicCache>();
766
767 /**
768 * Default constructor. No special actions required.
769 */
770 Cleaner()
771 {
772 super();
773 }
774
775 /**
776 * The action to be performed by this task when run by a <code>
777 * Timer</code>. Iterates through {@link #list} and invokes
778 * {@link DynamicCache#clean()} on each instance.
779 */
780 @Override
781 public void run()
782 {
783 Object key = null;
784 Object value = null;
785 for ( DynamicCache cache : list )
786 {
787 try
788 {
789 Iterator iterator = cache.entrySet().iterator();
790 while ( ( key == null || value == null ) && iterator.hasNext() )
791 {
792 Map.Entry entry = (Map.Entry) iterator.next();
793 if ( entry != null )
794 {
795 key = entry.getKey();
796 value = entry.getValue();
797 }
798 }
799 }
800 catch ( java.util.ConcurrentModificationException cmex ) {}
801
802 int count = cache.clean();
803
804 if ( value != null )
805 {
806 logger.fine( "Cleaned garbage collected values from cache " +
807 "with key type " + key.getClass().getName() +
808 " and value type " + value.getClass().getName() +
809 ". Removed " + count + " entries." );
810 }
811 else
812 {
813 logger.fine( "Cleaned garbage collected values from cache." +
814 " Removed " + count + " entries." );
815 }
816 }
817 }
818 }
819 }