001    package org.rakeshv.utils;
002    
003    import java.util.ArrayList;
004    import java.util.Collection;
005    import java.util.Formatter;
006    import java.util.HashSet;
007    import java.util.Random;
008    import java.util.Set;
009    
010    
011    /**
012     * A simple decorator around a {@link LinkedHashMap} and {@link 
013     * DynamicCache} that can be used to cache <code>object</code> 
014     * instances.
015     *
016     * <p><b>Note 1:</b> Key's added to the internal {@link #cache} must 
017     * have a proper <code>hashCode()</code> implementation since they
018     * are stored in a <code>Map</code>.</p>
019     *
020     * <p><b>Note 2:</b> Instances of this class <i>will not</i> be
021     * garbage collected if your reference to it goes out of scope.  You
022     * <i>must</i> invoke the {@link #release()} method if you wish to
023     * consign instances of this class to the garbage collector.</p>
024     *
025     * <p><b>Note 3:</b> You must set the system property <code>
026     * org.rakeshv.utils.ObjectCache.totalItems</code> to the value you
027     * desire if you wish to restrict the number of items that are cached
028     * if you use the {@link #ObjectCache()} default constructor.
029     * The property <i>must</i> be specified prior to creating a new
030     * instance of this class.  See sample code provided for examples on 
031     * how to specify the property.</p>
032     *
033     * <p><b>Note 4:</b> This class uses a <code>LRU</code> model for
034     * managing the cache by default.  When new instances are requested 
035     * after the cache size has grown to the configured total size, new 
036     * instances are added to the cache, and the least accessed instances 
037     * are removed.</p>
038     *
039     * <p>The following code shows sample usage of the cache:</p>
040     * <pre>
041     *  import org.rakeshv.utils.DataStructure;
042     *  import org.rakeshv.utils.ObjectCache;
043     *
044     *    ...
045     *
046     *    // Create a dynamic cache that uses SoftReferences to dynamically
047     *    // control cache size.  This works only if the system property
048     *    // for default cache size is not set.
049     *    // Set the automatic garbage collected value mapping removal to
050     *    // run every 5 minutes rather than the default 10 minutes
051     *    // (specified in seconds)
052     *    System.getProperties().setProperty( 
053     *        "org.rakeshv.utils.DynamicCache.cleanInterval", "300" );
054     *    ObjectCache&lt;Date, MyObject&gt; dynamic = new ObjectCache&lt;Date, MyObject&gt;()
055     *
056     *    // Create a LRU with specified size
057     *    ObjectCache&lt;Date, MyObject&gt; lru = new ObjectCache&lt;Date, MyObject&gt;( 50000 );
058     *
059     *    // Create a FIFO of specified size
060     *    ObjectCache&lt;Date, MyObject&gt; fifo = new ObjectCache&lt;Date, MyObject&gt;( 50000, DataStructure.FIFO );
061     *
062     *    // Get the statistics for cache usage
063     *    System.out.format( "lru statistics: %s%n", lru.getStatistics() );
064     *    System.out.format( "Total fifo requests: %d%n", fifo.getStatistics().getTotalRequests() );
065     *    System.out.format( "Total successful fifo requests: %d%n", fifo.getStatistics().getSuccessfulRequests() );
066     *    System.out.format( "Total entries removed from fifo: %d%n", fifo.getStatistics().getTotalRemoved() );
067     * </pre>
068     *
069     * <p>Copyright 2004-2006 Rakesh Vidyadharan</p>
070     * @author Rakesh Vidyadharan 2004 September 1
071     * @version $Id: ObjectCache.java,v 1.17 2006/03/24 18:24:11 rakesh Exp $
072     */
073    public class ObjectCache<K,V> extends Object
074    {
075      /**
076       * The <code>System property</code> name used to fetch user defined
077       * maximum number of items to cache.
078       */
079      public static final String TOTAL_ITEMS_PROPERTY = 
080        "org.rakeshv.utils.ObjectCache.totalItems";
081    
082      /**
083       * The <code>Map</code> that is used to store the object 
084       * instances that are to be shared.
085       */
086      private Map<K,V> cache;
087    
088      /**
089       * A random number generator used to ensure that {@link #key} values
090       * generated are unique.
091       */
092      private Random random;
093    
094      /**
095       * The <code>key</code> used to store instances of this class.
096       */
097      private String key;
098    
099      /**
100       * The maximum number of items to hold in the {@link #cache}.
101       * This value will default to <code>Integer.MAX_VALUE</code> if the
102       * system property {@link #TOTAL_ITEMS_PROPERTY} is not specified.
103       */
104      private int totalItems;
105    
106      /**
107       * Default constructor.  Initialises the instance variables.
108       *
109       * @see SharedObject#fetchTotalItemsProperty
110       * @see #init
111       */
112      public ObjectCache()
113      {
114        super();
115        totalItems = SharedObject.fetchTotalItemsProperty( 
116            ObjectCache.TOTAL_ITEMS_PROPERTY );
117        init();
118    
119        if ( totalItems == Integer.MAX_VALUE )
120        {
121          initCache( DataStructure.DYNAMIC );
122        }
123        else
124        {
125          initCache( DataStructure.LRU );
126        }
127      }
128    
129      /**
130       * Create a new instance with the specified size for the {@link
131       * #cache}.
132       *
133       * @see #init()
134       * @see #initCache( int )
135       * @param size The maximum number of objects to store in the cache.
136       */
137      public ObjectCache( int size )
138      {
139        super();
140        totalItems = size;
141        init();
142        initCache( DataStructure.LRU );
143      }
144    
145      /**
146       * Create a new instance with the specified size for the {@link
147       * #cache} and the datastructure to use.
148       *
149       * <p>The valid value for <code>cacheType</code> are:</p>
150       * <ol>
151       *   <li>{@link DataStructure#FIFO}
152       *   <li>{@link DataStructure#LRU}
153       * </ol>
154       *
155       * @see #init()
156       * @see #initCache( int )
157       * @param size The maximum number of shared objects to store in the
158       *   cache.
159       */
160      public ObjectCache( int size, int cacheType )
161      {
162        super();
163        totalItems = size;
164        init();
165        initCache( cacheType );
166      }
167    
168      /**
169       * Initialise the {@link #cache}.  The cache is initialised
170       * with the appropriate cacheType.
171       *
172       * <p>The valid value for <code>cacheType</code> are:</p>
173       * <ol>
174       *   <li>{@link DataStructure#FIFO}
175       *   <li>{@link DataStructure#LRU}
176       *   <li>{@link DataStructure#DYNAMIC}
177       * </ol>
178       *
179       * <p>If an invalid value is specified for <code>cacheType</code>
180       * then a {@link LRU} datastructure is used.</p>
181       *
182       * @param cacheType The type of datastructure to use to manage the
183       *   cache.
184       */
185      private void initCache( int cacheType )
186      {
187        switch ( cacheType )
188        {
189          case DataStructure.FIFO:
190            if ( totalItems == Integer.MAX_VALUE )
191            {
192              cache = new FIFO<K,V>();
193            }
194            else
195            {
196              cache = new FIFO<K,V>( totalItems );
197            }
198            break;
199          case DataStructure.LRU:
200            if ( totalItems == Integer.MAX_VALUE )
201            {
202              cache = new LRU<K,V>();
203            }
204            else
205            {
206              cache = new LRU<K,V>( totalItems );
207            }
208            break;
209          case DataStructure.DYNAMIC:
210          default:
211            cache = new DynamicCache<K,V>();
212            break;
213        }
214      }
215    
216      /**
217       * Initialise the class instance
218       */
219      private void init()
220      {
221        random = new Random();
222        key = this.getClass().getName() + "#" + random.nextLong();
223        System.getProperties().put( key, this );
224      }
225    
226      /**
227       * Make this instance of the class eligible for garbage collection.
228       * The instance will be eligible for garbage collection after this
229       * method has been invoked, and any references client code has to
230       * this object go out of scope.
231       */
232      public void release()
233      {
234        if ( key != null )
235        {
236          System.getProperties().remove( key );
237        }
238        cache = null;
239      }
240    
241      /**
242       * Returns the hash code value for {@link #cache}. The hash code of a 
243       * map is defined to be the sum of the hashCodes of each entry in the 
244       * map's entrySet view. This ensures that <code>t1.equals(t2)</code>
245       * implies that <code>t1.hashCode()==t2.hashCode()</code> for any two 
246       * maps <code>t1</code> and <code>t2</code>, as required by the 
247       * general contract of <code>Object.hashCode</code>.
248       */
249      public int hashCode()
250      {
251        return cache.hashCode();
252      }
253    
254      /**
255       * Compares the specified object with this instance for equality. 
256       * Returns <code>true</code> if the given object is also a 
257       * ObjectCache and the two {@link #cache} objects represent the same 
258       * mappings. More formally, two maps 
259       * <code>t1</code> and <code>t2</code> represent the same mappings if 
260       * <code>t1.entrySet().equals(t2.entrySet())</code>. This ensures 
261       * that the equals method works properly across different 
262       * implementations of the Map interface.
263       */
264      public boolean equals( Object object )
265      {
266        boolean result = false;
267    
268        if ( object.getClass().equals( this.getClass() ) )
269        {
270          ObjectCache obj = (ObjectCache) object;
271          result = ( cache.equals( obj.cache ) );
272        }
273    
274        return result;
275      }
276    
277      /**
278       * Returns a string representation of this object.  Returns some
279       * useful information about this class and the data it holds.
280       *
281       * @return String The string representation.
282       */
283      @Override
284      public String toString()
285      {
286        StringBuilder builder = new StringBuilder( 128 );
287        Formatter formatter = new Formatter( builder );
288        formatter.format( "%s size: %d maximum: %d%n",
289            getClass().getName(), cache.size(), totalItems );
290    
291        return builder.toString();
292      }
293    
294      /**
295       * Add the specified key-value pair to {@link #cache}.  If a 
296       * restriction was specified for {@link #totalItems}, then the 
297       * earliest key-value pair is removed and the new pair added if
298       * the size of the cache exceeds totalItems.
299       *
300       * @param key Key with which the specified value is to be associated.
301       * @param value Value to be associated with the specified key.
302       * @throws IllegalArgumentException If a <code>null</code> value was
303       *   specified for the key.
304       */
305      public synchronized void put( K key, V value )
306        throws IllegalArgumentException
307      {
308        if ( key == null )
309        {
310          throw new IllegalArgumentException( 
311              "Null keys are not allowed in the datastructure." );
312        }
313    
314        cache.put( key, value );
315      }
316    
317      /**
318       * Returns a set view of the keys contained in {@link #cache}. To 
319       * preserve encapsulation of the cache, the set is disconnected from 
320       * the {@link #cache}.
321       *
322       * @return A set of the keys contained in the cache.
323       */
324      public Set<K> keySet()
325      {
326        return new HashSet<K>( cache.keySet() );
327      }
328    
329      /**
330       * Returns a collection view of the values contained in {@link #cache}.
331       * To preserve encapsulation of the cache, the collection is u
332       * disconnected from the {@link #cache}.
333       *
334       * @return A collection of the keys contained in the cache.
335       */
336      public Collection<V> values()
337      {
338        return new ArrayList<V>( cache.values() );
339      }
340    
341      /**
342       * Returns <code>true</code> if {@link #cache} contains no key-value 
343       * mappings.
344       *
345       * @return <code>true</code> if the cache contains no key-value 
346       *   mappings.
347       */
348      public boolean isEmpty()
349      {
350        return cache.isEmpty();
351      }
352    
353      /**
354       * Returns <code>true</code> if the {@link #cache} contains a mapping 
355       * for the specified key. More formally, returns <code>true</code> if 
356       * and only if the cache contains at a mapping for a key <code>k</code>
357       * such that <code>(key==null ? k==null : key.equals(k))</code>. 
358       * (There can be at most one such mapping.)
359       *
360       * @param key Key whose presence in the cache is to be tested.
361       * @return <code>true</code> if the cache contains a mapping for the 
362       *   specified key.
363       */
364      public boolean containsKey( Object key )
365      {
366        return cache.containsKey( key );
367      }
368    
369      /**
370       * Returns <code>true</code> if the {@link #cache} maps one or more 
371       * keys to the specified value. More formally, returns 
372       * <code>true</code> if and only if the cache contains at least one 
373       * mapping to a value <code>v</code> such that 
374       * <code>(value==null ? v==null : value.equals(v))</code>. This 
375       * operation will probably require time linear in the map size for 
376       * most implementations of the Map interface.
377       *
378       * @param value Value whose presence in the cache is to be tested.
379       * @return <code>true</code> if the cache maps one or more keys to 
380       *   the specified value.
381       */
382      public boolean containsValue( Object value )
383      {
384        return cache.containsValue( value );
385      }
386    
387      /**
388       * Returns the value to which the {@link #cache} maps the specified 
389       * key.  Returns <code>null</code> if the map contains no mapping for 
390       * this key. A return value of <code>null</code> does not necessarily 
391       * indicate that the map contains no mapping for the key; it is also 
392       * possible that the map explicitly maps the key to <code>null</code>.
393       * The {@link #containsKey( Object )} operation may be used to 
394       * distinguish these two cases.  
395       *
396       * <p>More formally, if the cache contains a mapping from a key 
397       * <code>k</code> to a value <code>v</code> such that 
398       * <code>(key==null ? k==null : key.equals(k))</code>, then this 
399       * method returns <code>v</code>; otherwise it returns 
400       * <code>null</code>. (There can be at most one such mapping.)</p>
401       *
402       * @param key Key whose associated value is to be returned.
403       * @return <code>value</code> to which the cache maps the specified 
404       *   key, or <code>null</code> if the map contains no mapping for 
405       *   this key.
406       */
407      public Object get( Object key )
408      {
409        return cache.get( key );
410      }
411    
412      /**
413       * Return the number of objects stored in {@link #cache}.
414       *
415       * @return int The number of cached objects.
416       */
417      public int size()
418      {
419        return cache.size();
420      }
421    
422      /**
423       * Remove all the entries from {@link #cache}.  This operation
424       * will reset all {@link LinkedHashMap.Statistics} associated with
425       * the cache.
426       */
427      public synchronized void clear()
428      {
429        cache.clear();
430      }
431    
432      /**
433       * Removes the mapping for this key from the {@link #cache} if it is 
434       * present.  More formally, if the cache contains a mapping from key 
435       * <code>k</code> to value <code>v</code> such that 
436       * <code>(key==null ? k==null : key.equals(k))</code>, that mapping 
437       * is removed. (The map can contain at most one such mapping.) 
438       *
439       * <p>Returns the value to which the map previously associated the 
440       * key, or <code>null</code> if the map contained no mapping for this 
441       * key. (A null return can also indicate that the map previously 
442       * associated <code>null</code> with the specified key.) The map 
443       * will not contain a mapping for the specified key once the call 
444       * returns.</p>
445       *
446       * @param key Key whose mapping is to be removed from the map.
447       * @return Previous value associated with specified key, or 
448       *   <code>null</code> if there was no mapping for key.
449       */
450      public synchronized Object remove( Object key )
451      {
452        return cache.remove( key );
453      }
454      
455      /**
456       * Returns {@link #totalItems}.
457       *
458       * @return int The value/reference of/to totalItems.
459       */
460      public final int getTotalItems()
461      {
462        return totalItems;
463      }
464      
465      /**
466       * Set/change {@link #totalItems}.  This can be used to modify the
467       * cache size dynamically.
468       *
469       * @param totalItems The value to set.
470       */
471      public final void setTotalItems( int totalItems )
472      {
473        this.totalItems = totalItems;
474        cache.setMaxEntries( totalItems );
475      }
476      
477      /**
478       * Returns a {@link LinkedHashMap.Statistics} instance that contains
479       * information about cache usage.
480       *
481       * @return Statistics The value/reference of/to statistics.
482       */
483      public final Map.Statistics getStatistics()
484      {
485        return cache.getStatistics();
486      }
487    }