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    }