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<Date, MyObject> dynamic = new ObjectCache<Date, MyObject>()
055 *
056 * // Create a LRU with specified size
057 * ObjectCache<Date, MyObject> lru = new ObjectCache<Date, MyObject>( 50000 );
058 *
059 * // Create a FIFO of specified size
060 * ObjectCache<Date, MyObject> fifo = new ObjectCache<Date, MyObject>( 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 }