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