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>© 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 }