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    }