Coverage Report - net.wotonomy.datastore.FileSoup
 
Classes in this File Line Coverage Branch Coverage Complexity
FileSoup
0% 
0% 
2.077
 
 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  0
     public FileSoup( String aPath )
 44  0
     {
 45  0
             homeDirectory = new File( aPath );
 46  
             
 47  
         // if specified directory does not exist
 48  0
         if ( ! homeDirectory.exists() )
 49  
         {
 50  0
             homeDirectory.mkdirs();
 51  
         }
 52  
 
 53  
         // if existing path is a file, exit with error
 54  0
         if ( homeDirectory.isDirectory() )
 55  
         {
 56  0
             new RuntimeException( "DataStore: Specified directory is a file." );
 57  
         }
 58  
 
 59  
         // read indices
 60  
         DataIndex index;
 61  0
         indices = new HashMap();
 62  0
             String[] files = getHomeDirectory().list();
 63  0
         for ( int i = 0; i < files.length; i++ )
 64  
         {
 65  0
             if ( files[i].endsWith( INDEX_SUFFIX ) )
 66  
             {
 67  0
                 index = (DataIndex) readFile( files[i] );
 68  0
                 if ( index != null )
 69  
                 {        
 70  0
                     indices.put( index.getName(), index );
 71  
                 }
 72  
             }
 73  
         }
 74  
 
 75  
         // read unique identifier
 76  0
         nextUniqueIdentifier = (DataKey) readFile( ID_ID );
 77  0
         if ( nextUniqueIdentifier == null )
 78  0
             nextUniqueIdentifier = new DataKey( "1" );
 79  
 
 80  0
     }
 81  
     
 82  
     public File getHomeDirectory()
 83  
     {
 84  0
             return homeDirectory;
 85  
     }
 86  
     
 87  
     // index management
 88  
     
 89  
     public void addIndex( String aName, String aProperty ) 
 90  
     {
 91  0
             DataIndex index = new DefaultDataIndex( aName, aProperty );
 92  0
             indices.put( index.getName(), index );
 93  0
                 buildIndex( index );
 94  0
                 writeIndices();
 95  0
     }
 96  
     
 97  
     public void removeIndex( String aName ) 
 98  
     {
 99  0
             indices.remove( aName );
 100  0
                 writeIndices();
 101  0
     }
 102  
 
 103  
     public Collection getAllIndices() 
 104  
     {
 105  0
             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  0
 long millis = System.currentTimeMillis();
 120  
 
 121  0
         anIndex.clear();
 122  
 
 123  0
         int count = 0;
 124  
                 DataKey key;
 125  
         Object o;
 126  
         Object value;
 127  0
                 String property = anIndex.getProperty();
 128  
                 
 129  0
         String[] files = getHomeDirectory().list();
 130  0
         for ( int i = 0; i < files.length; i++ )
 131  
         {
 132  0
             if ( ( ! files[i].equals( ID_ID.toString() ) 
 133  0
                         && ( ! files[i].endsWith( INDEX_SUFFIX ) ) ) )
 134  
             {
 135  0
                             key = new DataKey( files[i] );
 136  0
                 o = getObjectByKey( key );
 137  0
                 value = getValueFromObject( o, property );
 138  0
                 anIndex.addObject( key, value );
 139  0
                 count++;
 140  
             }
 141  
         }
 142  
         
 143  
 //System.out.print( count + " objects: " );
 144  
 //System.out.println( System.currentTimeMillis() - millis + " milliseconds" );
 145  0
     }    
 146  
     protected void writeIndices()
 147  
     {
 148  
             DataIndex index;
 149  0
             Iterator it = getAllIndices().iterator();
 150  0
         while ( it.hasNext() )
 151  
         {
 152  0
             index = (DataIndex) it.next();
 153  0
             writeFile( index.getName() + INDEX_SUFFIX, index );
 154  0
         }
 155  0
     }
 156  
 
 157  
         // object management
 158  
 
 159  
         // this implementation currently uses up a valid key increment
 160  
         public DataKey registerTemporaryObject( Object anObject )
 161  
         {
 162  0
                 DataKey id = getNextKey();
 163  
                 
 164  0
                 if ( anObject instanceof UniquelyIdentifiable )
 165  
                 {
 166  0
             ((UniquelyIdentifiable)anObject).setUniqueIdentifier( id );
 167  
                 }
 168  
                 
 169  0
                 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  0
             DataKey id = getNextKey();
 180  
 
 181  0
         if ( anObject instanceof UniquelyIdentifiable )
 182  
         { // set id if necessary
 183  0
             ((UniquelyIdentifiable)anObject).setUniqueIdentifier( id );
 184  
         }
 185  
 
 186  0
         writeFile( id.toString(), anObject );
 187  
 
 188  
                 // update indices        
 189  
                 DataIndex index;
 190  0
                 Iterator it = indices.values().iterator();
 191  0
                 while ( it.hasNext() )
 192  
                 {
 193  0
                         index = (DataIndex)it.next();
 194  0
                         index.addObject( id, 
 195  0
                                 getValueFromObject( anObject, index.getProperty() ) ); 
 196  0
                 }
 197  
         
 198  0
                 writeIndices();
 199  
 
 200  0
                 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  0
         Object existing = getObjectByKey( aKey );
 211  0
         if ( existing != null )
 212  
         {        
 213  0
             if ( ! deleteFile( aKey.toString() ) )
 214  
             {
 215  0
                 existing = null; // return error
 216  0
             }
 217  
                         else
 218  
                         {
 219  
                 // update indices        
 220  
                 DataIndex index;
 221  0
                 Iterator it = indices.values().iterator();
 222  0
                 while ( it.hasNext() )
 223  
                 {
 224  0
                     index = (DataIndex)it.next();
 225  0
                     index.removeObject( aKey,
 226  0
                         getValueFromObject( existing, index.getProperty() ) );
 227  0
                 }
 228  
 
 229  0
                 writeIndices();
 230  
                         }
 231  
         }
 232  
         
 233  0
         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  0
         Object existing = getObjectByKey( aKey );
 243  0
         if ( existing == null )
 244  
         {
 245  0
             System.err.println( "FileSoup.updateObject: " +
 246  0
                 "existing object could not be found with id: " + aKey );
 247  0
                     return null;
 248  
         }
 249  
         
 250  0
             Object result = null;
 251  0
         if ( updatedObject instanceof UniquelyIdentifiable )
 252  
         {
 253  
                         // update key if changed
 254  0
                         ((UniquelyIdentifiable)updatedObject).setUniqueIdentifier( aKey );
 255  
         }
 256  
 
 257  0
         if ( writeFile( aKey.toString(), updatedObject ) )
 258  
         {
 259  0
             result = updatedObject;
 260  
 
 261  
             // update indices        
 262  
             DataIndex index;
 263  0
             Iterator it = indices.values().iterator();
 264  0
             while ( it.hasNext() )
 265  
             {
 266  0
                 index = (DataIndex)it.next();
 267  0
                 index.updateObject( aKey,
 268  0
                     getValueFromObject( existing, index.getProperty() ),
 269  0
                     getValueFromObject( updatedObject, index.getProperty() ) );
 270  0
             }
 271  
 
 272  0
             writeIndices();
 273  
         }
 274  
         
 275  
 //System.out.println( "FileSoup.updateObject: " + updatedObject + " -> " + result );
 276  0
             return getObjectByKey( aKey );        
 277  
     }
 278  
         
 279  
     protected DataKey getNextKey()
 280  
     {
 281  0
                 DataKey id = (DataKey) nextUniqueIdentifier.clone();
 282  
         // while ( id isn't yet in use by the soup ) increment();
 283  0
                 nextUniqueIdentifier.increment();
 284  0
         writeFile( ID_ID.toString(), nextUniqueIdentifier );
 285  0
             return id;
 286  
     }
 287  
     
 288  
     protected DataKey getNextTempKey()
 289  
     {
 290  0
             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  0
         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  0
                 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  0
             DataIndex index = (DataIndex) indices.get( anIndexName );
 322  
 
 323  0
         if ( index == null )
 324  
         {
 325  0
             return queryByProperty( anIndexName, beginKey, endKey );
 326  
         }
 327  
 
 328  0
         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  0
             if ( aPropertyName == null ) aPropertyName = IDENTITY_PROPERTY;
 339  0
             DataIndex index = new DefaultDataIndex( "temporary", aPropertyName );
 340  0
         buildIndex( index );
 341  0
         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  0
             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  0
         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  0
             DataIndex index = (DataIndex) indices.get( anIndexName );
 375  
 
 376  0
         if ( index == null )
 377  
         {
 378  0
             return reverseQueryByProperty( anIndexName, beginKey, endKey );
 379  
         }
 380  
         
 381  0
                 List items = index.query( endKey, beginKey );
 382  0
                 Collections.reverse( items );
 383  0
         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  0
             if ( aPropertyName == null ) aPropertyName = IDENTITY_PROPERTY;
 401  0
             DataIndex index = new DefaultDataIndex( "temporary", aPropertyName );
 402  0
         buildIndex( index );
 403  0
                 List items = index.query( endKey, beginKey );
 404  0
                 Collections.reverse( items );
 405  0
         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  0
             return queryByProperty( IDENTITY_PROPERTY, beginKey, endKey );
 420  
     }
 421  
 
 422  
     public Object getValueFromObject( Object anObject, String aProperty )
 423  
     {
 424  0
             if ( IDENTITY_PROPERTY.equals( aProperty ) ) return anObject;
 425  0
                 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