001    package com.sptci.prevayler;
002    
003    import java.io.Serializable;
004    import java.util.Collection;
005    import java.util.LinkedHashMap;
006    import java.util.LinkedHashSet;
007    import java.util.Map;
008    
009    /**
010     * A class used as the storage mechanism for storing the indices for
011     * prevalent objects in the prevalent system.
012     *
013     * <p>&copy; Copyright 2008 <a href='http://sptci.com/' target='_top'>Sans Pareil Technologies, Inc.</a></p>
014     * @author Rakesh Vidyadharan 2008-05-22
015     * @version $Id: IndexStorage.java 18 2008-07-20 03:35:47Z sptrakesh $
016     */
017    public class IndexStorage implements Serializable
018    {
019      private static final long serialVersionUID = 2L;
020    
021      /**
022       * The constant used to index null keys.  This is useful to be able to
023       * find instances where an indexed field is null.
024       */
025      private static final String NULL_VALUE = "SPTODB_NULL_FIELD_INDEX_KEY";
026    
027      /**
028       * A map used to maintain indices for a prevalent object.  The <code>key
029       * </code> to the map is the name(s) of the field(s) that are indexed,
030       * and the values are {@link IndexStorage.FieldStorage} instances.
031       */
032      private final Map<String,FieldStorage> storage =
033          new LinkedHashMap<String,FieldStorage>();
034    
035      /**
036       * Add a new index for the specified field to the store.
037       *
038       * @see IndexStorage.FieldStorage#add
039       * @param field The name of the field that is indexed.
040       * @param index The index for the field specified.
041       * @param object The prevalent object to associate with the index.
042       */
043      public void add( final String field, final Object index,
044          final PrevalentObject object )
045      {
046        if ( ! storage.containsKey( field ) )
047        {
048          storage.put( field, new FieldStorage() );
049        }
050    
051        if ( index instanceof Collection )
052        {
053          //System.out.format( "Indexing field: %s value: %s object: %s%n", field, index, object );
054          final FieldStorage fieldStorage = storage.get( field );
055    
056          for ( Object key : (Collection) index )
057          {
058            fieldStorage.add( key, object );
059          }
060        }
061        else
062        {
063          storage.get( field ).add( index, object );
064        }
065      }
066    
067      /**
068       * Add a new index for the specified fields to the store.
069       *
070       * @see IndexStorage.FieldStorage#add
071       * @param fields The array of field names that are being indexed.
072       * @param index The index for the fields specified.
073       * @param object The prevalent object to associate with the index.
074       */
075      public void add( final String[] fields, final Collection index,
076          final PrevalentObject object )
077      {
078        final String name = getFieldName( fields );
079        FieldStorage fieldStorage = storage.get( name );
080    
081        if ( fieldStorage == null )
082        {
083          fieldStorage = new FieldStorage();
084          storage.put( getFieldName( fields ), fieldStorage );
085        }
086    
087        for ( Object key : index )
088        {
089          fieldStorage.add( key, object );
090        }
091      }
092    
093      /**
094       * Remove the indices for the specified prevalent object from the store.
095       *
096       * @see IndexStorage.FieldStorage#remove
097       * @param object The prevalent object to remove from the store.
098       */
099      public void remove( final PrevalentObject object )
100      {
101        if ( object == null ) return;
102    
103        for ( FieldStorage store : storage.values() )
104        {
105          store.remove( object );
106        }
107      }
108    
109      /**
110       * Remove the index entry for the specified reference object
111       * that is refernced by the specified parent prevalent object.
112       *
113       * @param field The name of the field that was indexed.
114       * @param key The referenced object that was indexed.
115       * @param value The parent prevalent object that holds a reference to
116       *   the indexed child prevalent object.
117       */
118      public void remove( final String field, final Object key,
119          final PrevalentObject value )
120      {
121        final FieldStorage store = storage.get( field );
122        if ( store == null ) return;
123    
124        store.remove( key, value );
125      }
126    
127      /**
128       * Return the collection of prevalent objects that match the specified
129       * <code>index</code> value.
130       *
131       * @param field The name of the field that was indexed in the prevalent
132       *   object.
133       * @param index The value of the indexed field to use to retrieve the
134       *   objects.
135       * @return The collection of matching indexed objects that represent the
136       *   prevalent objects.
137       */
138      public Collection<IndexedObject> get( final String field,
139          final Object index )
140      {
141        final Collection<IndexedObject> collection =
142            new LinkedHashSet<IndexedObject>();
143        final FieldStorage fs = storage.get( field );
144    
145        if ( fs != null )
146        {
147          final Collection<IndexedObject> coll = fs.get( index );
148          if ( coll != null ) collection.addAll( coll );
149        }
150    
151        return collection;
152      }
153    
154      /**
155       * Determines when the specified index exists in the store.
156       *
157       * @see IndexStorage.FieldStorage#isIndexed
158       * @param fields The array of field names that are indexed.
159       * @param values The collection of values for the fields that are to be
160       *   checked for existence in the store.
161       * @return Returns <code>true</code> if the values are indexed in the
162       *   store.
163       */
164      public boolean isIndexed( final String[] fields, final Object values )
165      {
166        final FieldStorage fieldStorage = storage.get( getFieldName( fields ) );
167        return ( fieldStorage != null ) && fieldStorage.isIndexed( values );
168      }
169    
170      /**
171       * Determines when the specified index exists in the store.
172       *
173       * @see IndexStorage.FieldStorage#isIndexed
174       * @param field The field name that is indexed.
175       * @param value The value that is to be checked for existence.
176       * @return Returns <code>true</code> if the value is indexed in the
177       *   store.
178       */
179      public boolean isIndexed( final String field, final Object value )
180      {
181        final FieldStorage fieldStorage = storage.get( field );
182        return ( fieldStorage != null ) && fieldStorage.isIndexed( value );
183      }
184    
185      /**
186       * Determine whether the specified field is indexed.  This may be used to
187       * quickly determine whether a specified field in an already processed
188       * object has been annotated as indexed or not.
189       *
190       * @param field The name of the field that is to be checked.
191       * @return Returns <code>true</code> if the field is indexed.
192       */
193      public boolean isFieldIndexed( final String field )
194      {
195        return storage.containsKey( field );
196      }
197    
198      /**
199       * Return the name used to represent the specified array of field names.
200       *
201       * @param fields The array of field names being indexed.
202       * @return The normalised name that represents the field names.
203       */
204      protected String getFieldName( final String[] fields )
205      {
206        final StringBuilder builder = new StringBuilder( 64 );
207        for ( String name : fields )
208        {
209          builder.append( name ).append( "#" );
210        }
211    
212        return builder.toString();
213      }
214    
215      /**
216       * The storage used to maintain the indices for a field or combination of
217       * fields.
218       */
219      private class FieldStorage implements Serializable
220      {
221        private static final long serialVersionUID = 1L;
222    
223        /**
224         * The map used to maintain the indexed values and the prevalent objects
225         * that match the indexed values.  The <code>key</code> for the map are
226         * the indexed field(s) value(s) and the <code>value</code> is a
227         * collection that contains the prevalent object instances that match
228         * the index.  If the index is unique the collection will have only
229         * one instance in it.
230         *
231         * <p><b>Note:</b> The indexed values are not stored directly since that
232         * will drastically increate memory requirements for the system.  The
233         * values stored are instances of {@link IndexedObject} from which the
234         * indexed object instance may be retrieved.</p>
235         */
236        private final Map<Object,Collection<IndexedObject>> fieldMap =
237          new LinkedHashMap<Object,Collection<IndexedObject>>();
238    
239        /**
240         * Add the specified index and corresponding prevalent object to the
241         * store.
242         *
243         * @param index The index that is being added.
244         * @param object The prevalent object associated with the index.
245         */
246        private void add( final Object index, final PrevalentObject object )
247        {
248          final Object key = ( index == null ) ? NULL_VALUE : index;
249    
250          if ( ! fieldMap.containsKey( key ) )
251          {
252            Collection<IndexedObject> collection =
253              new LinkedHashSet<IndexedObject>();
254            fieldMap.put( key, collection );
255          }
256    
257          fieldMap.get( key ).add(
258              new IndexedObject( object.getClass(), object.getObjectId() ) );
259        }
260    
261        private void remove( final Object key, final PrevalentObject object )
262        {
263          final Object index = ( key == null ) ? NULL_VALUE : key;
264          final Collection<IndexedObject> collection = fieldMap.get( index );
265    
266          if ( collection != null )
267          {
268            collection.remove(
269                new IndexedObject( object.getClass(), object.getObjectId() ) );
270    
271            if ( collection.isEmpty() ) { fieldMap.remove( index ); }
272          }
273        }
274    
275        /**
276         * Remove the specified prevalent object from the store.
277         *
278         * @param object The prevalent object to remove from the store.
279         */
280        private void remove( final PrevalentObject object )
281        {
282          final LinkedHashSet<Object> remove = new LinkedHashSet<Object>();
283    
284          for ( Map.Entry<Object,Collection<IndexedObject>> entry :
285              fieldMap.entrySet() )
286          {
287            final Collection<IndexedObject> collection = entry.getValue();
288            collection.remove(
289                new IndexedObject( object.getClass(), object.getObjectId() ) );
290    
291            if ( collection.isEmpty() )
292            {
293              remove.add( entry.getKey() );
294            }
295          }
296    
297          for ( Object key : remove )
298          {
299            fieldMap.remove( key );
300          }
301        }
302    
303        /**
304         * Return the objects stored in {@link #fieldMap} for the specified
305         * index <code>key</code>.
306         *
307         * @param index The index whose matching values are to be returned.
308         * @return The collection of indexed objects matching the specified
309         *   index.
310         */
311        private Collection<IndexedObject> get( final Object index )
312        {
313          final Object key = ( index == null ) ? NULL_VALUE : index;
314          return fieldMap.get( key );
315        }
316    
317        /**
318         * Determine whether the specified value is indexed in the store.
319         *
320         * @param values The object(s) that are indexed.
321         * @return boolean Returns <code>true</code> if indexed.
322         */
323        private boolean isIndexed( final Object values )
324        {
325          final Object key = ( values == null ) ? NULL_VALUE : values;
326          return fieldMap.containsKey( key );
327        }
328      }
329    }