Coverage Report - net.wotonomy.control.EOEditingContext
 
Classes in this File Line Coverage Branch Coverage Complexity
EOEditingContext
0% 
0% 
2.436
 
 1  
 /*
 2  
 Wotonomy: OpenStep design patterns for pure Java applications.
 3  
 Copyright (C) 2001 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.control;
 20  
 
 21  
 import java.lang.ref.WeakReference;
 22  
 import java.util.Collection;
 23  
 import java.util.Enumeration;
 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  
 import java.util.Set;
 30  
 
 31  
 import net.wotonomy.foundation.NSArray;
 32  
 import net.wotonomy.foundation.NSDictionary;
 33  
 import net.wotonomy.foundation.NSMutableArray;
 34  
 import net.wotonomy.foundation.NSMutableDictionary;
 35  
 import net.wotonomy.foundation.NSNotification;
 36  
 import net.wotonomy.foundation.NSNotificationCenter;
 37  
 import net.wotonomy.foundation.NSRunLoop;
 38  
 import net.wotonomy.foundation.NSSelector;
 39  
 import net.wotonomy.foundation.internal.WotonomyException;
 40  
 
 41  
 // swing dependency for undo manager
 42  
 //import javax.swing.undo.UndoManager;
 43  
 
 44  
 /**
 45  
 * EOEditingContext provides transactional support for 
 46  
 * fetching, editing, and committing changes made on a 
 47  
 * collection of objects to a parent object store. <br><br>
 48  
 *
 49  
 * EOEditingContext is itself a subclass of EOObjectStore,
 50  
 * and this means that EOEditingContexts can use other 
 51  
 * EOEditingContexts as their parent.  However, there
 52  
 * still must exist an EOObjectStore as the root of the 
 53  
 * editing hierarchy that can maintain persistent state.
 54  
 *
 55  
 * @author michael@mpowers.net
 56  
 * @author $Author: cgruber $
 57  
 * @version $Revision: 894 $
 58  
 */
 59  0
 public class EOEditingContext 
 60  
     extends EOObjectStore 
 61  
     implements EOObserving
 62  
 {
 63  
     /**
 64  
     * Key for the NSNotification posted after this editing context
 65  
     * saves changes.  Object of the notification will be this editing 
 66  
     * context, and user info will contain InsertedKey, UpdatedKey,
 67  
     * and DeletedKey (keys are defined in EOObjectStore).
 68  
     */
 69  
     public static final String 
 70  
         EditingContextDidSaveChangesNotification = 
 71  
         "EOEditingContextDidSaveChangesNotification";
 72  
 
 73  
     /**
 74  
     * Key for the NSNotification posted after this editing context
 75  
     * observes changes.  Object of the notification will be this editing 
 76  
     * context, and user info will contain InsertedKey, UpdatedKey, InvalidatedKey,
 77  
     * and DeletedKey (keys are defined in EOObjectStore), however
 78  
     * the objects in the corresponding Lists will be the actual
 79  
     * objects, not their ids.
 80  
     */
 81  
     public static final String 
 82  
         ObjectsChangedInEditingContextNotification = 
 83  
         "EOObjectsChangedInEditingContextNotification";
 84  
         
 85  
     /**
 86  
     * The default run loop ordering processes recent changes
 87  
     * before delayed observers are notified and before dispatching 
 88  
     * the AWT event queue.
 89  
     */
 90  
     public static int 
 91  0
         EditingContextFlushChangesRunLoopOrdering = 300000;
 92  
 
 93  0
     private static NSSelector runLaterSelector = 
 94  0
         new NSSelector( "flushRecentChanges",
 95  0
             new Class[] { Object.class } );
 96  
 
 97  0
     private static EOObjectStore defaultParentObjectStore = null;
 98  0
     private static double defaultFetchTimestampLag = 0;
 99  0
     private static boolean retainsRegisteredObjects = true;
 100  
         
 101  
     private EOObjectStore parentStore;        
 102  
     private WeakReference delegate;
 103  
     private WeakReference messageHandler;
 104  
     private List editorSet;
 105  
     private double fetchTimestamp;
 106  
     private boolean lockBeforeModify;
 107  
     private boolean propagateDeletesAfterEvent;
 108  
     private boolean stopValidationAfterError;
 109  
     private NSMutableArray insertedObjects;
 110  
     private NSMutableArray insertedObjectsBuffer;
 111  
     private NSArray insertedObjectsProxy;
 112  
     private NSMutableArray updatedObjects;
 113  
     private NSMutableArray updatedObjectsBuffer;
 114  
     private NSArray updatedObjectsProxy;
 115  
     private NSMutableArray deletedObjects;
 116  
     private NSMutableArray deletedObjectsBuffer;
 117  
     private NSArray deletedObjectsProxy;
 118  
     private NSMutableArray deletedIDsBuffer;
 119  
     private NSMutableArray invalidatedObjectsBuffer;
 120  
     private NSMutableArray invalidatedIDsBuffer;
 121  
     private Registrar registrar;
 122  
 //    private UndoManager undoManager;
 123  
 
 124  
     // so we don't have to trouble EOObserverCenter
 125  
     private boolean ignoreChanges;
 126  
     
 127  
     // for delayed handling of processRecentChanges
 128  
     private boolean willRunLater;
 129  
     
 130  
     // for handling of notifications posted 
 131  
     //   while we're in the saveChanges method
 132  
     private boolean isInvalidating;
 133  
 
 134  
     // for i18n or other customization
 135  0
     static protected String MessageChangeConflict =
 136  0
         "Another user changed an object you are editing: ";
 137  
  
 138  
     /**
 139  
     * Default constructor creates a new editing context
 140  
     * that uses the default object store.  If the default
 141  
     * object store has not been set, an exception is thrown.
 142  
     */ 
 143  
     public EOEditingContext()
 144  
     {
 145  0
         this( defaultParentObjectStore() );
 146  0
     }
 147  
 
 148  
     /**
 149  
     * Creates a new editing context that uses the specified
 150  
     * object store as its parent object store.
 151  
     */
 152  0
     public EOEditingContext( EOObjectStore anObjectStore )
 153  0
     {
 154  0
         if ( anObjectStore == null ) 
 155  
         {
 156  0
             throw new IllegalArgumentException(
 157  0
                 "A parent object store must be specified." );
 158  
         }
 159  
         
 160  0
         parentStore = anObjectStore;
 161  0
         delegate = null;
 162  0
         messageHandler = null;
 163  0
         editorSet = new LinkedList();
 164  0
         fetchTimestamp = 0;
 165  0
         lockBeforeModify = false;
 166  0
         propagateDeletesAfterEvent = true;
 167  0
         stopValidationAfterError = true;
 168  0
         insertedObjects = new NSMutableArray();
 169  0
         insertedObjectsBuffer = new NSMutableArray();
 170  0
         insertedObjectsProxy = NSArray.arrayBackedByList( insertedObjects );
 171  0
         updatedObjects = new NSMutableArray();
 172  0
         updatedObjectsBuffer = new NSMutableArray();
 173  0
         updatedObjectsProxy = NSArray.arrayBackedByList( updatedObjects );
 174  0
         deletedObjects = new NSMutableArray();
 175  0
         deletedObjectsBuffer = new NSMutableArray();
 176  0
         deletedObjectsProxy = NSArray.arrayBackedByList( deletedObjects );
 177  0
         deletedIDsBuffer = new NSMutableArray();
 178  0
         invalidatedObjectsBuffer = new NSMutableArray();
 179  0
         invalidatedIDsBuffer = new NSMutableArray();
 180  
         
 181  0
         if ( instancesRetainRegisteredObjects() )
 182  
         {
 183  0
             registrar = new Registrar( this );
 184  0
         }
 185  
         else
 186  
         {
 187  0
             registrar = new WeakRegistrar( this );
 188  
         }
 189  
         
 190  0
         ignoreChanges = false;
 191  0
         willRunLater = false;
 192  0
         isInvalidating = false;
 193  
 
 194  
         // create undo manager
 195  
         //TODO: this should be NSUndoManager
 196  
 //        undoManager = new UndoManager();
 197  
 
 198  
         // register for notifications
 199  0
         NSSelector handleNotification = 
 200  0
             new NSSelector( "handleNotification",
 201  0
                 new Class[] { NSNotification.class } );
 202  
         // any from parent store
 203  0
         NSNotificationCenter.defaultCenter().addObserver(
 204  0
             this, handleNotification, null, parentStore );
 205  
         // global id change from any
 206  0
         NSNotificationCenter.defaultCenter().addObserver(
 207  0
             this, handleNotification, EOGlobalID.GlobalIDChangedNotification, null );
 208  
 //new net.wotonomy.ui.swing.NotificationInspector( null, parentStore );                
 209  0
     }
 210  
 
 211  
     /**
 212  
     * Registers the specified object as an editor for this
 213  
     * context.  The object is expected to implement
 214  
     * EOEditingContext.Editor.
 215  
     */
 216  
     public void addEditor ( Object anEditor )
 217  
     {
 218  0
         if ( anEditor == null ) return;
 219  0
         editorSet.add( new WeakReference( anEditor ) );
 220  0
     }
 221  
 
 222  
     /**
 223  
     * Returns a read-only List of objects associated with the object 
 224  
     * with the specified id for the specified property 
 225  
     * relationship, or may return a placeholder array that
 226  
     * will defer the fetch until needed (aka an array fault).
 227  
     * All objects must be registered in the specified editing context.
 228  
     * This implementation calls to its parent object store's
 229  
     * implementation if the requested source object is not 
 230  
     * registered in this editing context.
 231  
     * The specified relationship key must produce a result of
 232  
     * type Collection for the source object or an exception is thrown.
 233  
     */
 234  
     public NSArray arrayFaultWithSourceGlobalID ( 
 235  
         EOGlobalID aGlobalID,
 236  
         String aRelationshipKey,
 237  
         EOEditingContext aContext )
 238  
     {
 239  0
         NSArray result = null;
 240  0
         Object source = registrar.objectForGlobalID( aGlobalID );
 241  
         
 242  
         // if not registered in our context
 243  0
         if ( source == null )
 244  
         {
 245  
             // get the object registered into our context
 246  0
             result = parentStore.arrayFaultWithSourceGlobalID( 
 247  0
                 aGlobalID, aRelationshipKey, this );
 248  0
         }
 249  
         else // source is registered in our context
 250  
         {
 251  
             // get existing value
 252  
             Object value;
 253  0
             if ( source instanceof EOKeyValueCoding )
 254  
             {
 255  0
                 value = ((EOKeyValueCoding)source).storedValueForKey( 
 256  0
                     aRelationshipKey );
 257  0
             }
 258  
             else // handle directly
 259  
             {
 260  0
                 value = EOKeyValueCodingSupport.storedValueForKey( 
 261  0
                     source, aRelationshipKey );
 262  
             }
 263  
             
 264  0
             if ( value == null )
 265  
             {
 266  
                 // do the same as if the source was null
 267  0
                 result = parentStore.arrayFaultWithSourceGlobalID( 
 268  0
                     aGlobalID, aRelationshipKey, this );
 269  0
             }
 270  
             else
 271  0
             if ( value instanceof NSArray )
 272  
             {
 273  0
                 result = (NSArray) value;
 274  0
             }
 275  
             else // not NSArray
 276  0
             if ( value instanceof Collection )
 277  
             {
 278  
                 // convert to NSArray
 279  0
                 result = new NSArray( (Collection) value );
 280  0
             }
 281  
             else
 282  
             {
 283  0
                 throw new WotonomyException( 
 284  0
                     "Relationship key did not return a collection: " 
 285  0
                     + aGlobalID + " : " + aRelationshipKey );   
 286  
             }
 287  
         }
 288  
         
 289  
         // if our context is not the specified context
 290  0
         if ( aContext != this ) 
 291  
         {
 292  0
             result = (NSArray) clone( this, result, aContext );
 293  
         }
 294  
         
 295  0
         return result;
 296  
     }
 297  
  
 298  
     /**
 299  
     * Returns a snapshot of the specified object as it
 300  
     * existed when it was last read or committed to the
 301  
     * parent object store.
 302  
     */
 303  
     public NSDictionary committedSnapshotForObject ( 
 304  
         Object anObject )
 305  
     {
 306  0
         byte[] snapshot = (byte[])
 307  0
             registrar.getCommitSnapshot( anObject );
 308  0
         if ( snapshot == null )
 309  
         {
 310  
             // this object not modified: take a current snapshot
 311  0
             snapshot = takeSnapshot( anObject );
 312  
         }                
 313  0
         return convertSnapshotToDictionary( snapshot );
 314  
     }
 315  
 
 316  
     /**
 317  
     * Returns a snapshot of the specified object as it
 318  
     * existed before the edits triggered by the current
 319  
     * event loop were processed.
 320  
     */
 321  
     public NSDictionary currentEventSnapshotForObject ( 
 322  
         Object anObject )
 323  
     {
 324  0
         byte[] result = (byte[])
 325  0
             registrar.getCurrentSnapshot( anObject );
 326  0
         if ( result == null )
 327  
         {
 328  0
             return committedSnapshotForObject( anObject );   
 329  
         }
 330  0
         return convertSnapshotToDictionary( result );
 331  
     }
 332  
     
 333  
     /**
 334  
     * Returns the delegate for this editing context,
 335  
     * or null if no delegate has been set.
 336  
     */
 337  
     public Object delegate ()
 338  
     {
 339  0
         if ( delegate == null ) return null;
 340  0
         return delegate.get();
 341  
     }
 342  
 
 343  
     /**
 344  
     * Deletes the specified object from this editing context.
 345  
     * The editing context marks the object as deleted and
 346  
     * will notify the parent store when changes are committed.
 347  
     */
 348  
     public void deleteObject ( 
 349  
         Object anObject )
 350  
     {
 351  0
         willChange();
 352  
 
 353  
                 int i;
 354  
                 // remove from added objects if necessary
 355  0
                 i = insertedObjects.indexOfIdenticalObject( anObject );
 356  0
                 if ( i != NSArray.NotFound )
 357  
                 {
 358  0
                         insertedObjects.removeObjectAtIndex( i );
 359  
                         
 360  
             // if in the inserted objects buffer
 361  0
             int index = insertedObjectsBuffer.indexOfIdenticalObject( anObject );
 362  0
             if ( index != NSArray.NotFound )
 363  
             {
 364  
                 // remove from inserted objects buffer
 365  0
                 insertedObjectsBuffer.removeObjectAtIndex( index );
 366  
             }
 367  
 
 368  
             // now forget the object ever existed.
 369  0
             forgetObject( anObject );
 370  
 
 371  
             // we're done
 372  0
             return;
 373  
                 }
 374  
                 else // otherwise add to deleted objects list
 375  
                 {
 376  0
                         deletedObjects.addObject( anObject );
 377  
                 }
 378  
                 
 379  
                 // remove from updated objects if necessary
 380  0
                 i = updatedObjects.indexOfIdenticalObject( anObject );
 381  0
                 if ( i != NSArray.NotFound )
 382  
         {
 383  0
             updatedObjects.removeObjectAtIndex( i );
 384  
         }
 385  
 
 386  
         // add to buffer
 387  0
         deletedObjectsBuffer.addObject( anObject );            
 388  0
         deletedIDsBuffer.addObject( globalIDForObject( anObject ) );            
 389  0
     }
 390  
 
 391  
     /**
 392  
     * Returns a read-only List of all objects marked as deleted
 393  
     * in this editing context.
 394  
     */
 395  
     public NSArray deletedObjects ()
 396  
     {
 397  0
         return deletedObjectsProxy;
 398  
     }
 399  
 
 400  
     /**
 401  
     * Called by child editing contexts when they no longer
 402  
     * need to track the specified id.
 403  
     * This implementation forwards the call to the parent store.
 404  
     */
 405  
     public void editingContextDidForgetObjectWithGlobalID ( 
 406  
         EOEditingContext aContext,
 407  
         EOGlobalID aGlobalID )
 408  
     {
 409  0
         parentStore.editingContextDidForgetObjectWithGlobalID(
 410  0
             aContext, aGlobalID );
 411  0
     }
 412  
 
 413  
     /**
 414  
     * Returns a read-only List of registered editors of this
 415  
     * editing context.
 416  
     */
 417  
     public NSArray editors ()
 418  
     {
 419  0
         NSMutableArray result = new NSMutableArray();
 420  
         Object o;
 421  0
         Iterator i = editorSet.iterator();
 422  0
         while ( i.hasNext() )
 423  
         {
 424  0
             o = ((WeakReference)i.next()).get();
 425  0
             if ( o != null )
 426  
             {
 427  0
                 result.addObject( o );
 428  0
             }
 429  
             else
 430  
             {
 431  0
                 i.remove();
 432  
             }
 433  0
         }
 434  0
         return result;
 435  
     }
 436  
     
 437  
 /*
 438  
     public static void encodeObjectWithCoder ( 
 439  
         Object anObject,
 440  
         NSCoder aCoder )
 441  
     {
 442  
         throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
 443  
     }
 444  
 */
 445  
 
 446  
     /**
 447  
     * Returns the object for the specified id.
 448  
     * If the object is registered in in this context
 449  
     * but not in the specified context, 
 450  
     * this implementation will create a copy of the object 
 451  
     * and register it in the specified context.
 452  
     * Otherwise it will forward the call to the parent
 453  
     * object store.
 454  
     */
 455  
     public /*EOEnterpriseObject*/ Object faultForGlobalID ( 
 456  
         EOGlobalID aGlobalID,
 457  
         EOEditingContext aContext )
 458  
     {
 459  0
         Object result = registrar.objectForGlobalID( aGlobalID );
 460  
         
 461  
         // if not registered in our context
 462  0
         if ( result == null )
 463  
         {
 464  
             // get the object registered into our context
 465  0
             result = parentStore.faultForGlobalID( aGlobalID, this );
 466  
         }
 467  
         
 468  
         // if our context is not the specified context
 469  0
         if ( aContext != this ) 
 470  
         {
 471  0
             result = registerClone( aGlobalID, this, result, aContext );
 472  
         }
 473  
         
 474  0
         return result;
 475  
     }
 476  
 
 477  
     /**
 478  
     * Returns a fault representing an object of 
 479  
     * the specified entity type with values from 
 480  
     * the specified dictionary.  
 481  
     * This implementation calls faultForRawRow 
 482  
     * on the parent store.
 483  
     */
 484  
     public Object faultForRawRow ( 
 485  
         Map aDictionary,
 486  
         String anEntityName )
 487  
     {
 488  0
         return parentStore.faultForRawRow(
 489  0
             aDictionary, anEntityName, this );
 490  
     }
 491  
 
 492  
     /**
 493  
     * Returns a fault representing an object of 
 494  
     * the specified entity type with values from 
 495  
     * the specified dictionary.  The fault should
 496  
     * belong to the specified editing context.
 497  
     * This implementation forwards the call to
 498  
     * the parent store.
 499  
     */
 500  
     public /*EOEnterpriseObject*/ Object faultForRawRow ( 
 501  
         Map aDictionary,
 502  
         String anEntityName,
 503  
         EOEditingContext aContext )
 504  
     {
 505  0
         return parentStore.faultForRawRow(
 506  0
             aDictionary, anEntityName, aContext );
 507  
     }
 508  
 
 509  
     /**
 510  
     * Returns the fetch timestamp for this editing context.
 511  
     */
 512  
     public double fetchTimestamp ()
 513  
     {
 514  0
         return fetchTimestamp;
 515  
     }
 516  
 
 517  
     /**
 518  
     * Unregisters the specified object from this editing context,
 519  
     * removing all references to it.  Use this method to remove
 520  
     * an object from the context without marking it for deletion.
 521  
     */ 
 522  
     public void forgetObject ( 
 523  
         Object anObject )
 524  
     {
 525  0
         EOGlobalID id = registrar.globalIDForObject( anObject );
 526  0
         if ( id == null ) 
 527  
         {
 528  0
             System.err.println( 
 529  0
                 "EOEditingContext.forgetObject: not registered: " + anObject );
 530  0
             return;
 531  
         }
 532  
         
 533  
         // unregister object
 534  0
         registrar.forgetObject( anObject );   
 535  
         
 536  
         // remove from all, inserted, updated, and deleted lists
 537  
         int index;
 538  0
         index = updatedObjects.indexOfIdenticalObject( anObject );
 539  0
         if ( index != NSArray.NotFound )
 540  
         {
 541  0
             updatedObjects.removeObjectAtIndex( index );   
 542  
         }
 543  0
         index = insertedObjects.indexOfIdenticalObject( anObject );
 544  0
         if ( index != NSArray.NotFound )
 545  
         {
 546  0
             insertedObjects.removeObjectAtIndex( index );   
 547  0
             return;
 548  
         }
 549  0
         index = deletedObjects.indexOfIdenticalObject( anObject );
 550  0
         if ( index != NSArray.NotFound )
 551  
         {
 552  0
             deletedObjects.removeObjectAtIndex( index );   
 553  0
             return;
 554  
         }
 555  
 
 556  
         // notify parent context
 557  0
         parentStore.editingContextDidForgetObjectWithGlobalID( this, id );
 558  0
     }
 559  
 
 560  
     /**
 561  
     * Returns the id for the specified object, or null
 562  
     * if the object is not registered in this context.
 563  
     */
 564  
     public EOGlobalID globalIDForObject ( 
 565  
         Object anObject )
 566  
     {
 567  0
         return registrar.globalIDForObject( anObject );
 568  
     }
 569  
     
 570  
     /**
 571  
     * Returns an array of ids for an array of objects.
 572  
     */
 573  
     private NSArray globalIDsForObjects( 
 574  
         List anObjectList )
 575  
     {
 576  0
         NSMutableArray result = new NSMutableArray();
 577  0
         Iterator it = anObjectList.iterator();
 578  0
         while ( it.hasNext() )
 579  
         {
 580  0
             result.add( globalIDForObject( it.next() ) );
 581  0
         }
 582  0
         return result;
 583  
     }
 584  
 
 585  
     /**
 586  
     * Returns whether this editing context has changes that
 587  
     * have not yet been committed to the parent object store.
 588  
     */
 589  
     public boolean hasChanges ()
 590  
     {
 591  0
         if ( updatedObjects.count() > 0 ) return true;
 592  0
         if ( insertedObjects.count() > 0 ) return true;
 593  0
         if ( deletedObjects.count() > 0 ) return true;
 594  0
         return false;
 595  
     }
 596  
 
 597  
     /**
 598  
     * Given a newly instantiated object, this method 
 599  
     * initializes its properties to values appropriate
 600  
     * for the specified id.  The object should already
 601  
     * belong to the specified editing context.  
 602  
     * This method is called to populate faults.
 603  
     * This implementation will try to apply the values
 604  
     * from an object with a matching id in this editing 
 605  
     * context if possible, calling to the parent object 
 606  
     * store only if such an object is not found.
 607  
     */
 608  
     public void initializeObject ( 
 609  
         /*EOEnterpriseObject*/ Object anObject,
 610  
         EOGlobalID aGlobalID,
 611  
         EOEditingContext aContext )
 612  
     {
 613  0
         Object existingObject = registrar.objectForGlobalID( aGlobalID );
 614  
         
 615  
         // if not registered in our context
 616  0
         if ( existingObject == null )
 617  
         {
 618  
             // get the object registered into our context
 619  0
             existingObject = parentStore.faultForGlobalID( aGlobalID, this );
 620  
         }
 621  
         
 622  0
         if ( aContext == this )
 623  
         {
 624  
             // initialize the object
 625  0
             parentStore.initializeObject(
 626  0