View Javadoc

1   /*
2   Wotonomy: OpenStep design patterns for pure Java applications.
3   Copyright (C) 2000 Michael Powers
4   
5   This library is free software; you can redistribute it and/or
6   modify it under the terms of the GNU Lesser General Public
7   License as published by the Free Software Foundation; either
8   version 2.1 of the License, or (at your option) any later version.
9   
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  Lesser General Public License for more details.
14  
15  You should have received a copy of the GNU Lesser General Public
16  License along with this library; if not, see http://www.gnu.org
17  */
18  
19  package net.wotonomy.datastore;
20  
21  import java.io.File;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.LinkedList;
27  import java.util.List;
28  import java.util.Map;
29  
30  import net.wotonomy.foundation.internal.Introspector;
31  
32  abstract public class FileSoup implements DataSoup
33  {
34      public static final String INDEX_SUFFIX = ".idx";
35      public static final String MAP_SUFFIX = ".map";
36      private static final String ID_ID = "id";
37      private static final String IDENTITY_PROPERTY = "__SELF__";
38      
39      protected DataKey nextUniqueIdentifier;
40      protected File homeDirectory;
41      protected Map indices;
42  
43      public FileSoup( String aPath )
44      {
45      	homeDirectory = new File( aPath );
46      	
47          // if specified directory does not exist
48          if ( ! homeDirectory.exists() )
49          {
50              homeDirectory.mkdirs();
51          }
52  
53          // if existing path is a file, exit with error
54          if ( homeDirectory.isDirectory() )
55          {
56              new RuntimeException( "DataStore: Specified directory is a file." );
57          }
58  
59          // read indices
60          DataIndex index;
61          indices = new HashMap();
62              String[] files = getHomeDirectory().list();
63          for ( int i = 0; i < files.length; i++ )
64          {
65              if ( files[i].endsWith( INDEX_SUFFIX ) )
66              {
67                  index = (DataIndex) readFile( files[i] );
68                  if ( index != null )
69                  {	
70                      indices.put( index.getName(), index );
71                  }
72              }
73          }
74  
75          // read unique identifier
76          nextUniqueIdentifier = (DataKey) readFile( ID_ID );
77          if ( nextUniqueIdentifier == null )
78              nextUniqueIdentifier = new DataKey( "1" );
79  
80      }
81      
82      public File getHomeDirectory()
83      {
84      	return homeDirectory;
85      }
86      
87      // index management
88      
89      public void addIndex( String aName, String aProperty ) 
90      {
91      	DataIndex index = new DefaultDataIndex( aName, aProperty );
92      	indices.put( index.getName(), index );
93  		buildIndex( index );
94  		writeIndices();
95      }
96      
97      public void removeIndex( String aName ) 
98      {
99      	indices.remove( aName );
100 		writeIndices();
101     }
102 
103     public Collection getAllIndices() 
104     {
105     	return indices.values();
106     }
107     
108 //    public void addIndex( String aName, Index anIndex ) {}
109 //    public void removeIndex( String aName ) {}
110 //    public void addTaggedObject( String aKey, Serializable anObject )
111 //    public Object getTaggedObject( String aKey );
112 //    public void removeTaggedObject( String aKey );
113 //    public void setMetaData( 
114 //	String aMetaProperty, Serializable aValue, Serializable anObject );
115     
116     protected void buildIndex( DataIndex anIndex )
117     {
118 //System.out.print( "FileSoup.buildIndex: " + anIndex.getName() + " : " );
119 long millis = System.currentTimeMillis();
120 
121         anIndex.clear();
122 
123         int count = 0;
124 		DataKey key;
125         Object o;
126         Object value;
127 		String property = anIndex.getProperty();
128 		
129         String[] files = getHomeDirectory().list();
130         for ( int i = 0; i < files.length; i++ )
131         {
132             if ( ( ! files[i].equals( ID_ID.toString() ) 
133 			&& ( ! files[i].endsWith( INDEX_SUFFIX ) ) ) )
134             {
135 	    		key = new DataKey( files[i] );
136                 o = getObjectByKey( key );
137                 value = getValueFromObject( o, property );
138                 anIndex.addObject( key, value );
139                 count++;
140             }
141         }
142 	
143 //System.out.print( count + " objects: " );
144 //System.out.println( System.currentTimeMillis() - millis + " milliseconds" );
145     }    
146     protected void writeIndices()
147     {
148     	DataIndex index;
149     	Iterator it = getAllIndices().iterator();
150         while ( it.hasNext() )
151         {
152             index = (DataIndex) it.next();
153             writeFile( index.getName() + INDEX_SUFFIX, index );
154         }
155     }
156 
157 	// object management
158 
159 	// this implementation currently uses up a valid key increment
160 	public DataKey registerTemporaryObject( Object anObject )
161 	{
162 		DataKey id = getNextKey();
163 		
164 		if ( anObject instanceof UniquelyIdentifiable )
165 		{
166             ((UniquelyIdentifiable)anObject).setUniqueIdentifier( id );
167 		}
168 		
169 		return id;
170 	}
171 	
172     /***
173     * Adds the specified object to the soup and returns the key
174     * for the new object by which it may be subsequently retrieved.
175     * Null indicates an error, probably due to serialization.
176     */
177     public DataKey addObject( Object anObject ) 
178     {
179     	DataKey id = getNextKey();
180 
181         if ( anObject instanceof UniquelyIdentifiable )
182         { // set id if necessary
183             ((UniquelyIdentifiable)anObject).setUniqueIdentifier( id );
184         }
185 
186         writeFile( id.toString(), anObject );
187 
188 		// update indices	
189 		DataIndex index;
190 		Iterator it = indices.values().iterator();
191 		while ( it.hasNext() )
192 		{
193 			index = (DataIndex)it.next();
194 			index.addObject( id, 
195 				getValueFromObject( anObject, index.getProperty() ) ); 
196 		}
197 	
198 		writeIndices();
199 
200 		return id;
201     }
202         
203     /***
204     * Removes the specified object from the soup and returns
205     * the removed object as read from the soup (which is the
206     * original copy of the object).  Null indicates object not found.
207     */
208     public Object removeObject( DataKey aKey )
209     {
210         Object existing = getObjectByKey( aKey );
211         if ( existing != null )
212         {	
213             if ( ! deleteFile( aKey.toString() ) )
214             {
215                 existing = null; // return error
216             }
217 			else
218 			{
219                 // update indices	
220                 DataIndex index;
221                 Iterator it = indices.values().iterator();
222                 while ( it.hasNext() )
223                 {
224                     index = (DataIndex)it.next();
225                     index.removeObject( aKey,
226                         getValueFromObject( existing, index.getProperty() ) );
227                 }
228 
229                 writeIndices();
230 			}
231         }
232 	
233         return existing;
234     }
235     
236     /***
237     * Updates the specified object and returns the object
238     * as updated.  Null indicates an error writing the object.
239     */
240     public Object updateObject( DataKey aKey, Object updatedObject ) 
241     {
242         Object existing = getObjectByKey( aKey );
243         if ( existing == null )
244         {
245             System.err.println( "FileSoup.updateObject: " +
246                 "existing object could not be found with id: " + aKey );
247 	    	return null;
248         }
249 	
250     	Object result = null;
251         if ( updatedObject instanceof UniquelyIdentifiable )
252         {
253 			// update key if changed
254 			((UniquelyIdentifiable)updatedObject).setUniqueIdentifier( aKey );
255         }
256 
257         if ( writeFile( aKey.toString(), updatedObject ) )
258         {
259             result = updatedObject;
260 
261             // update indices	
262             DataIndex index;
263             Iterator it = indices.values().iterator();
264             while ( it.hasNext() )
265             {
266                 index = (DataIndex)it.next();
267                 index.updateObject( aKey,
268                     getValueFromObject( existing, index.getProperty() ),
269                     getValueFromObject( updatedObject, index.getProperty() ) );
270             }
271 
272             writeIndices();
273         }
274         
275 //System.out.println( "FileSoup.updateObject: " + updatedObject + " -> " + result );
276     	return getObjectByKey( aKey );	
277     }
278         
279     protected DataKey getNextKey()
280     {
281 		DataKey id = (DataKey) nextUniqueIdentifier.clone();
282         // while ( id isn't yet in use by the soup ) increment();
283 		nextUniqueIdentifier.increment();
284         writeFile( ID_ID.toString(), nextUniqueIdentifier );
285     	return id;
286     }
287     
288     protected DataKey getNextTempKey()
289     {
290     	return getNextKey();
291     }
292     
293     /***
294     * Gets object from data store whose identifier is equal
295     * to the specified object.
296     */
297     public Object getObjectByKey( DataKey aKey )
298     {
299         return readFile( aKey.toString() );
300     }
301     
302     // queries
303     
304 	/***
305 	* Returns an empty data view, suitable for creating
306 	* new entries in the soup.
307 	* @return A DataView containing no entries.
308 	*/
309 	public DataView createView()
310 	{
311 		return new DefaultDataView( this, new LinkedList() );
312 	}
313     
314     /***
315     * Queries by the specified pre-generated index, if it exists.
316     * Otherwise, falls through to queryByProperty.
317     */
318     public DataView queryByIndex( 
319     	String anIndexName, Object beginKey, Object endKey ) 
320     {
321     	DataIndex index = (DataIndex) indices.get( anIndexName );
322 
323         if ( index == null )
324         {
325             return queryByProperty( anIndexName, beginKey, endKey );
326         }
327 
328         return queryByKeys( index.query( beginKey, endKey ) );
329     }
330     
331     /***
332     * Generates an index based on the specified property
333     * and then executes the query.
334     */
335     public DataView queryByProperty( 
336     	String aPropertyName, Object beginKey, Object endKey ) 
337     {
338     	if ( aPropertyName == null ) aPropertyName = IDENTITY_PROPERTY;
339     	DataIndex index = new DefaultDataIndex( "temporary", aPropertyName );
340         buildIndex( index );
341         return queryByKeys( index.query( beginKey, endKey ) );
342     }
343 
344     /***
345     * Generates an index based on the specified property
346     * and then executes the query.
347     */
348     public DataView queryObjects( Object beginKey, Object endKey ) 
349     {
350     	return queryByProperty( IDENTITY_PROPERTY, beginKey, endKey );
351     }
352 
353     /***
354     * Returns a view containing the objects for the specified keys.
355     */
356     public DataView queryByKeys( Collection aKeyList ) 
357     {
358         return new DefaultDataView( this, aKeyList );
359     }
360     
361     /***
362     * As queryByIndex, but with objects returned in reverse order.
363     * @param anIndexName The index to be queried.
364     * @param beginValue The beginning value, or null for all values
365     * up to an including the end key.
366     * @param endValue The ending value, or null for all values
367     * since and including the begin key.
368     * @return A DataView containing the query results, or null 
369     * for invalid query parameters.
370     */
371     public DataView reverseQueryByIndex( 
372     	String anIndexName, Object beginKey, Object endKey )
373 	{
374     	DataIndex index = (DataIndex) indices.get( anIndexName );
375 
376         if ( index == null )
377         {
378             return reverseQueryByProperty( anIndexName, beginKey, endKey );
379         }
380 	
381 		List items = index.query( endKey, beginKey );
382 		Collections.reverse( items );
383         return queryByKeys( items );
384 	}
385     
386     /***
387     * As queryByProperty, but with objects returned in reverse order.
388     * @param aPropertyName The property to be queried.  If null,
389     * will query the objects directly with queryObjects().
390     * @param beginValue The beginning value, or null for all values
391     * up to an including the end key.
392     * @param endValue The ending value, or null for all values
393     * since and including the begin key.
394     * @return A DataView containing the query results, or null 
395     * for invalid query parameters.
396     */
397     public DataView reverseQueryByProperty( 
398     	String aPropertyName, Object beginKey, Object endKey )
399 	{
400     	if ( aPropertyName == null ) aPropertyName = IDENTITY_PROPERTY;
401     	DataIndex index = new DefaultDataIndex( "temporary", aPropertyName );
402         buildIndex( index );
403 		List items = index.query( endKey, beginKey );
404 		Collections.reverse( items );
405         return queryByKeys( items );
406 	}
407 
408     /***
409     * As queryObjects, but with objects returned in reverse order.
410     * @param beginValue The beginning value, or null for all values
411     * up to an including the end key.
412     * @param endValue The ending value, or null for all values
413     * since and including the begin key.
414     * @return A DataView containing the query results, or null 
415     * for invalid query parameters.
416     */
417     public DataView reverseQueryObjects( Object beginKey, Object endKey )
418     {
419     	return queryByProperty( IDENTITY_PROPERTY, beginKey, endKey );
420     }
421 
422     public Object getValueFromObject( Object anObject, String aProperty )
423     {
424 	    if ( IDENTITY_PROPERTY.equals( aProperty ) ) return anObject;
425 		return Introspector.getValueForObject( anObject, aProperty );
426 	}
427 	
428     // file access methods    
429     
430     abstract protected boolean writeFile( String name, Object anObject );
431     abstract protected Object readFile( String name );
432     abstract protected boolean deleteFile( String name );
433 
434 }
435 
436 /*
437  * $Log$
438  * Revision 1.2  2006/02/19 16:26:19  cgruber
439  * Move non-unit-test code to tests project
440  * Fix up code to work with proper imports
441  * Fix maven dependencies.
442  *
443  * Revision 1.1  2006/02/16 13:18:56  cgruber
444  * Check in all sources in eclipse-friendly maven-enabled packages.
445  *
446  * Revision 1.4  2003/08/14 19:29:38  chochos
447  * minor cleanup (imports, static method calls, etc)
448  *
449  * Revision 1.3  2001/03/05 22:12:11  mpowers
450  * Created the control package for a datastore-specific implementation
451  * of EOObjectStore.
452  *
453  * Revision 1.2  2001/02/15 21:12:41  mpowers
454  * Added accessors for key throughout the api.  This breaks compatibility.
455  * insertObject now returns the permanent key for the newly created object.
456  * The old way returned a copy of the object which was an additional read
457  * that was often ignored.  Now you can read it only if you need it.
458  * Furthermore, there was not other way of getting the permanent key.
459  *
460  * Revision 1.1.1.1  2000/12/21 15:47:20  mpowers
461  * Contributing wotonomy.
462  *
463  * Revision 1.3  2000/12/20 16:25:36  michael
464  * Added log to all files.
465  *
466  *
467  */
468