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
                 /*(EOEnterpriseObject)*/existingObject, aGlobalID, this );
 627  0
         }
 628  
         else // ( aContext != this )
 629  
         {
 630  
             // translates child relationships
 631  0
             copy( this, existingObject, aContext, anObject );
 632  
         }
 633  
 
 634  0
         aContext.registrar.setCommitSnapshot( anObject, null );
 635  0
         aContext.registrar.setCurrentSnapshot( anObject, null );
 636  0
     }
 637  
 
 638  
     /**
 639  
     * Inserts the specified object into this editing context.
 640  
     * This implementation calls insertObjectWithGlobalID
 641  
     * with an EOTemporaryGlobalID.
 642  
     */
 643  
     public void insertObject ( Object anObject )
 644  
     {
 645  0
         insertObjectWithGlobalID( 
 646  0
             anObject, new EOTemporaryGlobalID() );
 647  0
     }
 648  
 
 649  
     /**
 650  
     * Inserts the specified object into this editing context
 651  
     * with the specified id, which is expected to be a
 652  
     * temporary id.
 653  
     */
 654  
     public void insertObjectWithGlobalID ( 
 655  
         Object anObject,
 656  
         EOGlobalID aGlobalID )
 657  
     {
 658  0
         willChange();
 659  
         
 660  
         // if this object was marked for deletion
 661  0
         int index = deletedObjects.indexOfIdenticalObject( anObject );
 662  0
         if ( index != NSArray.NotFound )
 663  
         {
 664  
              // don't need to re-register: just update the lists
 665  
             
 666  
              // remove from deleted list
 667  0
              deletedObjects.removeObjectAtIndex( index );
 668  
     
 669  
              // if in the deleted ids buffer         
 670  0
              index = deletedIDsBuffer.indexOfIdenticalObject( anObject );
 671  0
              if ( index != NSArray.NotFound )
 672  
              {
 673  
                  // remove from deleted ids buffer
 674  0
                  deletedIDsBuffer.removeObjectAtIndex( index );
 675  
              }
 676  
     
 677  
              // if in the deleted objects buffer         
 678  0
              index = deletedObjectsBuffer.indexOfIdenticalObject( anObject );
 679  0
              if ( index != NSArray.NotFound )
 680  
              {
 681  
                  // remove from deleted objects buffer
 682  0
                  deletedObjectsBuffer.removeObjectAtIndex( index );
 683  0
              }
 684  
              else // not in the deleted objects buffer
 685  
              {
 686  
                  // add to the inserted objects buffer
 687  0
                  insertedObjectsBuffer.addObject( anObject );
 688  
              }
 689  
              
 690  
              // we're done
 691  0
              return;
 692  
         }
 693  
 
 694  
         // make sure object is not already in editing context
 695  0
         if ( objectForGlobalID( aGlobalID ) != null )
 696  
         {
 697  0
             throw new WotonomyException( 
 698  0
                 "Tried to insert but object was already registered:" 
 699  0
                 + aGlobalID );
 700  
         }
 701  
         
 702  
         // register object
 703  0
         recordObject( anObject, aGlobalID );
 704  
         
 705  
         // add to inserted list
 706  0
         insertedObjects.addObject( anObject );
 707  
         // add to buffer
 708  0
         insertedObjectsBuffer.addObject( anObject );
 709  0
     }
 710  
 
 711  
     /**
 712  
     * Returns a read-only List of the objects that have been 
 713  
     * inserted into this editing context.
 714  
     */
 715  
     public NSArray insertedObjects ()
 716  
     {
 717  0
         return insertedObjectsProxy;
 718  
     }
 719  
 
 720  
     /**
 721  
     * Turn all objects in this editing context into faults,
 722  
     * so that they will be fetched the next time they are 
 723  
     * accessed, and calls invalidateObjectsWithGlobalIDs
 724  
     * on the parent object store.
 725  
     */
 726  
     public void invalidateAllObjects ()
 727  
     {
 728  
         // register change so processRecentChanges is called
 729  0
         willChange();
 730  
         
 731  0
         invalidateAllObjectsQuietly();
 732  
         
 733  
         // post notification
 734  0
         NSNotificationCenter.defaultCenter().postNotification(
 735  0
             new NSNotification(
 736  0
                 InvalidatedAllObjectsInStoreNotification, this ) );
 737  0
     }
 738  
     
 739  
     /**
 740  
     * Only refaults all objects, does not notify will change
 741  
     * nor post notification, but does call parent store.
 742  
     * Called by invalidateAllObjects() and handleNotification().
 743  
     */ 
 744  
     private void invalidateAllObjectsQuietly()
 745  
     {
 746  
         // remember the ids
 747  0
         NSMutableArray ids = new NSMutableArray( registrar.registeredGlobalIDs() );
 748  
 
 749  
         // track of discarded IDs (from inserted objects)
 750  0
         NSMutableArray discardedIDs = new NSMutableArray();
 751  
 
 752  
         // refault all objects
 753  
         EOGlobalID id;
 754  
         Object o;
 755  0
         Enumeration e = ids.objectEnumerator();
 756  0
         while ( e.hasMoreElements() )
 757  
         {
 758  0
             id = (EOGlobalID) e.nextElement();
 759  0
             o = objectForGlobalID( id );
 760  
 
 761  
             // some objects may have been manually discarded
 762  0
             if ( o != null )
 763  
             {
 764  
                 // don't refault newly inserted objects
 765  0
                 if ( insertedObjects.indexOfIdenticalObject( o ) == NSArray.NotFound )
 766  
                 {
 767  0
                     refaultObject( o, id, this );
 768  0
                 }
 769  
                 else
 770  
                 {
 771  
                     // discard inserted objects
 772  0
                     forgetObject( o );
 773  0
                     discardedIDs.add( id );
 774  
                 }
 775  0
                 invalidatedObjectsBuffer.add( o );
 776  
             }
 777  0
             invalidatedIDsBuffer.add( id );
 778  0
         }
 779  0
         ids.removeAll( discardedIDs );
 780  
         
 781  
         // call to parent store (should call this after posting instead?)
 782  0
         isInvalidating = true;        
 783  0
         parentStore.invalidateObjectsWithGlobalIDs( ids );
 784  0
         isInvalidating = false;        
 785  0
     }
 786  
 
 787  
     /**
 788  
     * Turns the objects with the specified ids into faults,
 789  
     * so that they will be fetched the next time they are
 790  
     * accessed, and forwards the call to the parent object store.
 791  
     */
 792  
     public void invalidateObjectsWithGlobalIDs ( 
 793  
         List anArray )
 794  
     {
 795  
         // register change so processRecentChanges is called
 796  0
         willChange();
 797  
 
 798  
         // call to parent to invalidate objects
 799  0
         parentStore.invalidateObjectsWithGlobalIDs( anArray );
 800  
 
 801  
         Object o;
 802  
         EOGlobalID id;
 803  0
         Iterator it = anArray.iterator();
 804  0
         while ( it.hasNext() )
 805  
         {
 806  0
             id = (EOGlobalID) it.next();
 807  0
             if ( id != null )
 808  
             {
 809  0
                 o = objectForGlobalID( id );
 810  0
                 if ( o != null )
 811  
                 {
 812  0
                     Object result = notifyDelegate(
 813  0
                         "editingContextShouldInvalidateObject",
 814  0
                         new Class[] { EOEditingContext.class, Object.class, EOGlobalID.class },
 815  0
                         new Object[] { this, o, id } );
 816  0
                     if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) )
 817  
                     {
 818  
                         // refault the object
 819  0
                         refaultObject( o, id, this );
 820  0
                         invalidatedObjectsBuffer.add( o );
 821  0
                         invalidatedIDsBuffer.add( id );
 822  
                     }
 823  0
                 }
 824  
             }
 825  
             else
 826  
             {
 827  0
                 throw new WotonomyException(
 828  0
                     "Attempted to invalidate a null global id: " + anArray );
 829  
             }
 830  
         }
 831  
         
 832  0
     }
 833  
 /*
 834  
     public boolean invalidatesObjectsWhenFinalized (  )
 835  
     {
 836  
         throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
 837  
     }
 838  
 
 839  
     public boolean invalidatesObjectsWhenFreed (  )
 840  
     {
 841  
         throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
 842  
     }
 843  
 */
 844  
     /**
 845  
     * Returns whether the object referenced by the 
 846  
     * specified id is locked.  
 847  
     * This implementation simply forwards the call to
 848  
     * the parent object store.
 849  
     */
 850  
     public boolean isObjectLockedWithGlobalID ( 
 851  
         EOGlobalID aGlobalID,
 852  
         EOEditingContext aContext)
 853  
     {
 854  0
         return parentStore.isObjectLockedWithGlobalID(
 855  0
             aGlobalID, aContext );
 856  
     }
 857  
 
 858  
 /*    
 859  
     public void lock ()
 860  
     {
 861  
         throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
 862  
     }
 863  
 */
 864  
     /**
 865  
     * Locks the specified object in this editing context
 866  
     * by calling lockObjectWithGlobalID on the parent store.
 867  
     */
 868  
     public void lockObject ( 
 869  
         Object anObject )
 870  
     {
 871  0
         parentStore.lockObjectWithGlobalID( 
 872  0
             globalIDForObject( anObject ), this );
 873  0
     }
 874  
 
 875  
     /**
 876  
     * Locks the object referenced by the specified id
 877  
     * in the specified editing context.
 878  
     * This implementation simply forwards the call to
 879  
     * the parent object store.
 880  
     */
 881  
     public void lockObjectWithGlobalID ( 
 882  
         EOGlobalID aGlobalID,
 883  
         EOEditingContext aContext)
 884  
     {
 885  0
         parentStore.lockObjectWithGlobalID(
 886  0
             aGlobalID, aContext );
 887  0
     }
 888  
 
 889  
     /**
 890  
     * Returns whether this editing context attempts to
 891  
     * lock objects when they are first modified.
 892  
     */
 893  
     public boolean locksObjectsBeforeFirstModification ()
 894  
     {
 895  0
         return lockBeforeModify;
 896  
     }
 897  
 
 898  
     /**
 899  
     * Returns the message handler for this editing context,
 900  
     * or null if no message handler has been set.
 901  
     */
 902  
     public Object messageHandler ()
 903  
     {
 904  0
         if ( messageHandler == null ) return null;
 905  0
         return messageHandler.get();
 906  
     }
 907  
 
 908  
     /**
 909  
     * Returns the object registered in this editing context
 910  
     * for the specified id, or null if that id is not 
 911  
     * registered.
 912  
     */
 913  
     public Object objectForGlobalID ( 
 914  
         EOGlobalID aGlobalID )
 915  
     {
 916  0
         return registrar.objectForGlobalID( aGlobalID );
 917  
     }
 918  
 
 919  
     /**
 920  
     * Returns a read-only List of objects associated with the object 
 921  
     * with the specified id for the specified property 
 922  
     * relationship.  This method may not return an array fault
 923  
     * because array faults call this method to fetch on demand.
 924  
     * All objects must be registered the specified editing context.
 925  
     * The specified relationship key must produce a result of
 926  
     * type Collection for the source object or an exception is thrown.
 927  
     */
 928  
     public NSArray objectsForSourceGlobalID ( 
 929  
         EOGlobalID aGlobalID,
 930  
         String aRelationshipKey,
 931  
         EOEditingContext aContext )
 932  
     {
 933  
 //System.out.println( "EOEditingContext.objectsForSourceGlobalID: " 
 934  
 //+ aGlobalID + " : " + aRelationshipKey );        
 935  
     
 936  0
         NSArray result = null;
 937  
         
 938  0
 if ( aContext == this )
 939  
 {
 940  0
     throw new WotonomyException( "Assert failed: calling objectsForSourceGlobalID on ourself." );   
 941  
 }
 942  0
         Object source = registrar.objectForGlobalID( aGlobalID );
 943  
         
 944  
         // if not registered in our context
 945  0
         if ( source == null )
 946  
         {
 947  
             // get the object registered into our context
 948  0
             result = parentStore.objectsForSourceGlobalID( 
 949  0
                 aGlobalID, aRelationshipKey, this );
 950  0
         }
 951  
         else // source is registered in our context
 952  
         {
 953  
             // get existing value
 954  
             Object value;
 955  0
             if ( source instanceof EOKeyValueCoding )
 956  
             {
 957  0
                 value = ((EOKeyValueCoding)source).storedValueForKey( 
 958  0
                     aRelationshipKey );
 959  0
             }
 960  
             else // handle directly
 961  
             {
 962  0
                 value = EOKeyValueCodingSupport.storedValueForKey( 
 963  0
                     source, aRelationshipKey );
 964  
             }
 965  
             
 966  
             // if we don't have a valid value on our object
 967  0
             if ( ( value == null ) 
 968  0
             || ( ( value instanceof ArrayFault ) 
 969  0
               && ( !((ArrayFault)value).isFetched() ) ) )
 970  
             {
 971  
                 // do the same as if the source was null
 972  0
                 result = parentStore.objectsForSourceGlobalID( 
 973  0
                     aGlobalID, aRelationshipKey, this );
 974  
                     
 975  
                 // set our value since we have it
 976  0
                 if ( source instanceof EOKeyValueCoding )
 977  
                 {
 978  0
                     ((EOKeyValueCoding)source).takeStoredValueForKey( 
 979  0
                         result, aRelationshipKey );
 980  0
                 }
 981  
                 else // handle directly
 982  
                 {
 983  0
                     EOKeyValueCodingSupport.takeStoredValueForKey( 
 984  0
                         source, result, aRelationshipKey );
 985  
                 }
 986  0
             }
 987  
             else
 988  0
             if ( ( value instanceof ArrayFault ) 
 989  0
               && ( !((ArrayFault)value).isFetched() ) )
 990  
             {
 991  
                 // do the same as if the source was null
 992  0
                 result = parentStore.objectsForSourceGlobalID( 
 993  0
                     aGlobalID, aRelationshipKey, this );
 994  0
             }
 995  
             else
 996  0
             if ( value instanceof NSArray )
 997  
             {
 998  0
                 result = (NSArray) value;
 999  0
             }
 1000  
             else // not NSArray
 1001  0
             if ( value instanceof Collection )
 1002  
             {
 1003  
                 // convert to NSArray
 1004  0
                 result = new NSArray( (Collection) value );
 1005  0
             }
 1006  
             else
 1007  
             {
 1008  0
                 throw new WotonomyException( 
 1009  0
                     "Relationship key did not return a collection: " 
 1010  0
                     + aGlobalID + " : " + aRelationshipKey );   
 1011  
             }
 1012  
         }
 1013  
             
 1014  
         // if our context is not the specified context
 1015  0
         if ( aContext != this ) 
 1016  
         {
 1017  0
             result = (NSArray) clone( this, result, aContext );
 1018  
         }
 1019  
         
 1020  0
         return result;
 1021  
     }
 1022  
 
 1023  
     /**
 1024  
     * Returns a read-only List of objects the meet the criteria of
 1025  
     * the supplied specification.  This method simply calls
 1026  
     * objectsWithFetchSpecification on this editing context 
 1027  
     * with this editing context as the parameter.
 1028  
     */
 1029  
     public NSArray objectsWithFetchSpecification ( 
 1030  
         EOFetchSpecification aFetchSpec )
 1031  
     {
 1032  0
         return objectsWithFetchSpecification( aFetchSpec, this );
 1033  
     }
 1034  
 
 1035  
     /**
 1036  
     * Returns a read-only List of objects the meet the criteria of
 1037  
     * the supplied specification.  Faults are not allowed in the array.
 1038  
     * If any objects are already fetched, they should not be
 1039  
     * refetched. All objects should belong to the specified editing context.
 1040  
     * This implementation forwards the call to the parent object
 1041  
     * store, which will register each object in the specified editing
 1042  
     * context only if it does not already exist.
 1043  
     */
 1044  
     public NSArray objectsWithFetchSpecification ( 
 1045  
         EOFetchSpecification aFetchSpec,
 1046  
         EOEditingContext aContext)
 1047  
     {
 1048  0
         if ( aContext == this )
 1049  
         {
 1050  0
             Object result = notifyDelegate(
 1051  0
                 "editingContextShouldFetchObjects",
 1052  0
                 new Class[] { EOEditingContext.class, EOFetchSpecification.class },
 1053  0
                 new Object[] { aContext, aFetchSpec } );
 1054  0
             if ( result instanceof NSArray ) return (NSArray) result;
 1055  
         }
 1056  0
         return parentStore.objectsWithFetchSpecification( aFetchSpec, aContext );
 1057  
     }
 1058  
 
 1059  
     /**
 1060  
     * Returns the parent object store for this editing context.
 1061  
     * The result will not be null.
 1062  
     */
 1063  
     public EOObjectStore parentObjectStore ()
 1064  
     {
 1065  0
         return parentStore;
 1066  
     }
 1067  
 
 1068  
     /**
 1069  
     * Updates the inserted, updated, and deleted objects lists,
 1070  
     * and posts notifications about which objects have been changed.
 1071  
     * This method is called at the end of an event loop in which
 1072  
     * objects were modified.  This method is additionally called
 1073  
     * by saveChanges() so that any changes in the same event loop
 1074  
     * will be processed correctly before calling to the parent
 1075  
     * object store.
 1076  
     * This implementation updates those lists immediately, but
 1077  
     * only posts notifications when this method is called.
 1078  
     */
 1079  
     public void processRecentChanges ()
 1080  
     { // System.out.println( "EOEditingContext.processRecentChanges: " + invalidatedObjectsBuffer );
 1081  
         
 1082  
     /*
 1083  
     * This implementation actually updates those lists immediately,
 1084  
     * but keeps a separate buffer of changes in the current event 
 1085  
     * loop for the purposes of posting a notification.
 1086  
     * NOTE: to reenable buffering, uncomment lines from this method
 1087  
     * body and reenable the RecentChangesObserver in the constructor.
 1088  
     */
 1089  
 
 1090  
         // broadcast ObjectsChangedInStoreNotification
 1091  
         // for the benefit of child editing contexts
 1092  
         
 1093  0
         boolean postStoreInfo = 
 1094  0
             ( insertedObjectsBuffer.size() + 
 1095  0
               updatedObjectsBuffer.size() +
 1096  0
               deletedIDsBuffer.size() +
 1097  0
               invalidatedIDsBuffer.size() > 0 );
 1098  
         
 1099  0
         NSMutableDictionary storeInfo = new NSMutableDictionary();
 1100  0
         if ( postStoreInfo )
 1101  
         {
 1102  0
             storeInfo.setObjectForKey( 
 1103  0
                 globalIDsForObjects( insertedObjectsBuffer ), 
 1104  
     //            globalIDsForObjects( insertedObjects ), 
 1105  0
                 EOObjectStore.InsertedKey );
 1106  0
             storeInfo.setObjectForKey( 
 1107  0
                 globalIDsForObjects( updatedObjectsBuffer ), 
 1108  
     //            globalIDsForObjects( updatedObjects ), 
 1109  0
                 EOObjectStore.UpdatedKey );
 1110  0
             storeInfo.setObjectForKey( 
 1111  0
                 new NSArray( (Collection) deletedIDsBuffer ), 
 1112  
     //            globalIDsForObjects( deletedObjects ), 
 1113  0
                 EOObjectStore.DeletedKey );
 1114  0
             storeInfo.setObjectForKey( 
 1115  0
                 new NSArray( (Collection) invalidatedIDsBuffer ), 
 1116  0
                 EOObjectStore.InvalidatedKey );
 1117  
         }
 1118  
 
 1119  
         // broadcast ObjectsChangedInEditingContextNotification
 1120  
         // for the benefit of attached display groups
 1121  
         
 1122  0
         boolean postContextInfo = 
 1123  0
             ( insertedObjectsBuffer.size() + 
 1124  0
               updatedObjectsBuffer.size() +
 1125  0
               deletedObjectsBuffer.size() +
 1126  0
               invalidatedObjectsBuffer.size() > 0 );
 1127  
         
 1128  0
         NSMutableDictionary contextInfo = new NSMutableDictionary();
 1129  
 
 1130  0
         if ( postContextInfo )
 1131  
         {
 1132  
                 
 1133  0
             contextInfo.setObjectForKey( 
 1134  0
                new NSArray( (Collection) insertedObjectsBuffer ), 
 1135  
     //            new NSArray( (Collection) insertedObjects ), 
 1136  0
                 EOObjectStore.InsertedKey );
 1137  0
             contextInfo.setObjectForKey( 
 1138  0
                 new NSArray( (Collection) updatedObjectsBuffer ),  
 1139  
     //            new NSArray( (Collection) updatedObjects ),  
 1140  0
                 EOObjectStore.UpdatedKey );
 1141  0
             contextInfo.setObjectForKey( 
 1142  0
                 new NSArray( (Collection) deletedObjectsBuffer ),  
 1143  
     //            new NSArray( (Collection) deletedObjects ),  
 1144  0
                 EOObjectStore.DeletedKey );
 1145  0
             contextInfo.setObjectForKey( 
 1146  0
                 new NSArray( (Collection) invalidatedObjectsBuffer ),  
 1147  0
                 EOObjectStore.InvalidatedKey );
 1148  
         }
 1149  
     
 1150  
         // update the current snapshots
 1151  
         
 1152  
         Object o;
 1153  
         Iterator it;
 1154  0
         it = insertedObjectsBuffer.iterator();
 1155  0
         while ( it.hasNext() )
 1156  
         {
 1157  0
             o = it.next();
 1158  0
             registrar.setCurrentSnapshot( o, takeSnapshot( o ) );
 1159  0
         }
 1160  0
         it = updatedObjectsBuffer.iterator();
 1161  0
         while ( it.hasNext() )
 1162  
         {
 1163  0
             o = it.next();
 1164  0
             registrar.setCurrentSnapshot( o, takeSnapshot( o ) );
 1165  0
         }
 1166  
 
 1167  
         // clear buffers
 1168  
         
 1169  0
         insertedObjectsBuffer.removeAllObjects();
 1170  0
         updatedObjectsBuffer.removeAllObjects();
 1171  0
         deletedObjectsBuffer.removeAllObjects();
 1172  0
         deletedIDsBuffer.removeAllObjects();
 1173  0
         invalidatedObjectsBuffer.removeAllObjects();
 1174  0
         invalidatedIDsBuffer.removeAllObjects();
 1175  
 
 1176  
         // post notifications (does order matter?)
 1177  
         
 1178  0
         if ( postStoreInfo )
 1179  
         {
 1180  0
             NSNotificationCenter.defaultCenter().postNotification(
 1181  0
                 new NSNotification(
 1182  0
                     ObjectsChangedInStoreNotification, this, storeInfo ) );
 1183  
         }
 1184  
         
 1185  0
         if ( postContextInfo )
 1186  
         {
 1187  0
             NSNotificationCenter.defaultCenter().postNotification( 
 1188  0
                 new NSNotification(
 1189  0
                     ObjectsChangedInEditingContextNotification, this, contextInfo ) );
 1190  
         }
 1191  
 
 1192  0
     }
 1193  
 
 1194  
     /**
 1195  
     * Returns whether this editing context propagates deletes
 1196  
     * immediately after the event that triggered the delete.
 1197  
     * Otherwise, propagation occurs only before commit.
 1198  
     */
 1199  
     public boolean propagatesDeletesAtEndOfEvent ()
 1200  
     {
 1201  0
         return propagateDeletesAfterEvent;
 1202  
     }
 1203  
 
 1204  
     /**
 1205  
     * Registers the specified object in this editing context 
 1206  
     * for the specified id.  This method is called by an object
 1207  
     * store when fetching objects for a display group, or when
 1208  
     * objects are inserted into a display group.
 1209  
     * This implementation will re-register the object under the
 1210  
     * new id if it is already registered under a different id.
 1211  
     */
 1212  
     public void recordObject ( 
 1213  
         Object anObject,
 1214  
         EOGlobalID aGlobalID )
 1215  
     { 
 1216  
         // find state for re-registration
 1217  0
         boolean inserted = false;
 1218  0
         boolean updated = false;
 1219  0
         boolean deleted = false;
 1220  
 
 1221  
         // is the object already registered?
 1222  0
         EOGlobalID existingID = globalIDForObject( anObject );
 1223  0
         if ( existingID != null )
 1224  
         {
 1225  
             // remember object state
 1226  
             int index;
 1227  0
             index = insertedObjects.indexOfIdenticalObject( anObject );
 1228  0
             if ( index != NSArray.NotFound ) inserted = true;    
 1229  0
             index = updatedObjects.indexOfIdenticalObject( anObject );
 1230  0
             if ( index != NSArray.NotFound ) updated = true;    
 1231  0
             index = deletedObjects.indexOfIdenticalObject( anObject );
 1232  0
             if ( index != NSArray.NotFound ) deleted = true;    
 1233  
             // forget the object
 1234  0
             forgetObject( anObject );
 1235  
         }
 1236  
 
 1237  
         // is the global id already in use?
 1238  0
         Object existingObject = objectForGlobalID( aGlobalID );
 1239  0
         if ( existingObject != null )
 1240  
         {
 1241  
             // forget it (don't worry about state?)
 1242  0
             forgetObject( existingObject );
 1243  
         }
 1244  
 
 1245  0
         registrar.registerObject( anObject, aGlobalID );
 1246  
         
 1247  
         // restore state if necessary
 1248  0
         if ( inserted ) insertedObjects.addObject( anObject );
 1249  0
         if ( updated ) updatedObjects.addObject( anObject );
 1250  0
         if ( deleted ) deletedObjects.addObject( anObject );
 1251  0
     }
 1252  
 
 1253  
     /**
 1254  
     * Undoes the last undo operation.
 1255  
     */
 1256  
     public void redo ()
 1257  
     {
 1258  
         //TODO: not supported yet
 1259  0
         throw new UnsupportedOperationException("Not implemented yet.");
 1260  
     }
 1261  
 
 1262  
     /**
 1263  
     * Refaults this editing context, turning all unmodified
 1264  
     * objects into faults.  This implementation calls
 1265  
     * editingContextWillSaveChanges() on all editors, and
 1266  
     * then calls refaultObjects().
 1267  
     */
 1268  
     public void refault ()
 1269  
     {
 1270  0
         fireWillSaveChanges();
 1271  0
         refaultObjects();
 1272  0
     }
 1273  
 
 1274  
     /**
 1275  
     * Refaults the specified object, turning it into a fault
 1276  
     * for the specified global id in the specified context.
 1277  
     */
 1278  
     public void refaultObject ( 
 1279  
         Object anObject,
 1280  
         EOGlobalID aGlobalID,
 1281  
         EOEditingContext aContext)
 1282  
     {
 1283  0
         aContext.registrar.setCurrentSnapshot( anObject, null );
 1284  
 
 1285  0
         ignoreChanges = true;
 1286  0
         parentStore.refaultObject( anObject, aGlobalID, aContext );
 1287  0
         ignoreChanges = false;
 1288  
         
 1289  
                 // remove from updated objects if necessary
 1290  0
                 int i = updatedObjects.indexOfIdenticalObject( anObject );
 1291  0
                 if ( i != NSArray.NotFound )
 1292  
         {
 1293  0
             updatedObjects.removeObjectAtIndex( i );
 1294  
         }
 1295  
 
 1296  
         // add to invalidated notification queue
 1297  0
         invalidatedObjectsBuffer.addObject( anObject );
 1298  0
         invalidatedIDsBuffer.addObject( aGlobalID );
 1299  0
     }
 1300  
 
 1301  
     /**
 1302  
     * Turns all unmodified objects into faults, calling
 1303  
     * processRecentChanges() and then refaultObject() for
 1304  
     * each unmodified object.
 1305  
     */
 1306  
     public void refaultObjects ()
 1307  
     {
 1308  
         // is this call really needed?
 1309  
         // processRecentChanges();
 1310  
         
 1311  
         Object o;
 1312  
         EOGlobalID id;
 1313  0
         Iterator it = registeredObjects().iterator();
 1314  0
         while ( it.hasNext() )
 1315  
         {
 1316  0
             o = it.next();
 1317  0
             if ( ( updatedObjects.indexOfIdenticalObject( o ) == NSArray.NotFound )
 1318  0
             &&   ( insertedObjects.indexOfIdenticalObject( o ) == NSArray.NotFound )
 1319  0
             &&   ( deletedObjects.indexOfIdenticalObject( o ) == NSArray.NotFound ) )
 1320  
             {
 1321  0
                 id = globalIDForObject( o );
 1322  0
                 refaultObject( o, id, this );
 1323  0
             }
 1324  
         }
 1325  0
     }
 1326  
 
 1327  
     /** 
 1328  
     * Calls editingContextWillSaveChanges() on all editors,
 1329  
     * and then calls invalidateAllObjects().
 1330  
     */ 
 1331  
     public void refetch ()
 1332  
     {
 1333  0
         fireWillSaveChanges();
 1334  0
         invalidateAllObjects();
 1335  0
     }
 1336  
 
 1337  
     /**
 1338  
     * Returns a read-only List of all objects registered in this
 1339  
     * editing context.
 1340  
     */
 1341  
     public NSArray registeredObjects ()
 1342  
     {
 1343  0
         return registrar.registeredObjects();
 1344  
     }
 1345  
 
 1346  
     /**
 1347  
     * Unregisters the specified editor with this editing context.
 1348  
     */
 1349  
     public void removeEditor ( Object anObject )
 1350  
     {
 1351  0
         if ( anObject == null ) return;
 1352  
         
 1353  
         Object o;
 1354  0
         Iterator i = editorSet.iterator();
 1355  0
         while ( i.hasNext() )
 1356  
         {
 1357  0
             o = ((WeakReference)i.next()).get();
 1358  0
             if ( ( o == null ) || ( o == anObject ) )
 1359  
             {
 1360  0
                 i.remove();
 1361  0
             }
 1362  
         }
 1363  0
     }
 1364  
 
 1365  
     /**
 1366  
     * Unregisters all objects from this editing context,
 1367  
     * and resets the fetch timestamp.
 1368  
     */
 1369  
     public void reset ()
 1370  
     {
 1371  0
         Iterator it = registeredObjects().iterator();
 1372  0
         while ( it.hasNext() )
 1373  
         {
 1374  0
             forgetObject( it.next() );   
 1375  0
         }
 1376  0
         fetchTimestamp = 0; //FIXME: reset timestamp properly
 1377  0
     }
 1378  
 
 1379  
     /**
 1380  
     * Reverts the objects in this editing context to
 1381  
     * their original state.
 1382  
     * Calls editingContextWillSaveChanges on all editors,
 1383  
     * discards all inserted objects, restores deleted
 1384  
     * objects, and applies the fetch snapshot to all
 1385  
     * registered objects.
 1386  
     */
 1387  
     public void revert ()
 1388  
     {
 1389  0
         willChange();
 1390  0
         fireWillSaveChanges();
 1391  
 
 1392  
         Iterator it;
 1393  
         
 1394  
         // forget inserted objects
 1395  0
         it = new NSArray( insertedObjects ).iterator();
 1396  0
         while ( it.hasNext() )
 1397  
         {
 1398  0
             forgetObject( it.next() );
 1399  0
         }
 1400  
 
 1401  
         EOGlobalID id;
 1402  
         Object o;
 1403  
         byte[] snapshot;
 1404  
 
 1405  
         // re-initialize updated objects
 1406  0
         it = new NSArray( updatedObjects ).iterator();
 1407  0
         while ( it.hasNext() )
 1408  
         {
 1409  0
             o = it.next();
 1410  0
             snapshot = (byte[]) registrar.getCommitSnapshot( o );
 1411  0
             if ( snapshot != null )
 1412  
             {
 1413  0
                 applySnapshot( snapshot, o );
 1414  
             }
 1415  0
             registrar.setCommitSnapshot( o, null );
 1416  0
             updatedObjectsBuffer.addObject( o );
 1417  0
         }
 1418  
 
 1419  
         // re-initialize deleted objects
 1420  0
         it = new NSArray( deletedObjects ).iterator();
 1421  0
         while ( it.hasNext() )
 1422  
         {
 1423  0
             o = it.next();
 1424  0
             snapshot = (byte[]) registrar.getCommitSnapshot( o );
 1425  0
             if ( snapshot != null )
 1426  
             {
 1427  0
                 applySnapshot( snapshot, o );
 1428  
             }
 1429  0
             registrar.setCommitSnapshot( o, null );
 1430  0
             updatedObjectsBuffer.addObject( o );
 1431  0
         }
 1432  
 
 1433  
         // reset lists
 1434  0
         insertedObjects.removeAllObjects(); // unneccessary?
 1435  0
         deletedObjects.removeAllObjects();
 1436  0
         updatedObjects.removeAllObjects();
 1437  
 
 1438  
         // post notification
 1439  0
         processRecentChanges();        
 1440  0
     }
 1441  
 
 1442  
     /**
 1443  
     * Returns the root object store, which is the parent
 1444  
     * of all parent object stores of this editing context.
 1445  
     */
 1446  
     public EOObjectStore rootObjectStore ()
 1447  
     {
 1448  0
         EOObjectStore parent = parentObjectStore();
 1449  0
         while ( parent instanceof EOEditingContext )
 1450  
         {
 1451  0
             parent = ((EOEditingContext)parent).parentObjectStore();   
 1452  0
         }
 1453  0
         return parent;
 1454  
     }
 1455  
 
 1456  
     /**
 1457  
     * Calls editingContextWillSaveChanges on all editors,
 1458  
     * and commits all changes in this editing context to 
 1459  
     * the parent editing context by calling 
 1460  
     * saveChangesInEditingContext to the parent.
 1461  
     * Then posts EditingContextDidSaveChangeNotification.
 1462  
     */
 1463  
     public void saveChanges ()
 1464  
     {
 1465  
 //System.out.println( "EOEditingContext.saveChanges: " + this );        
 1466  0
         willChange();
 1467  
 
 1468  
         // process any changes
 1469  0
         processRecentChanges();
 1470  
         
 1471  
         // set up user info for notification to be posted.
 1472  0
         NSMutableDictionary userInfo = new NSMutableDictionary();
 1473  0
         userInfo.setObjectForKey( 
 1474  0
             new NSArray( (Collection) insertedObjects ), 
 1475  0
             EOObjectStore.InsertedKey );
 1476  0
         userInfo.setObjectForKey( 
 1477  0
             new NSArray( (Collection) updatedObjects ),  
 1478  0
             EOObjectStore.UpdatedKey );
 1479  0
         userInfo.setObjectForKey( 
 1480  0
             new NSArray( (Collection) deletedObjects ),  
 1481  0
             EOObjectStore.DeletedKey );
 1482  
         
 1483  
         // notify the editors
 1484  0
         fireWillSaveChanges();
 1485  
 
 1486  
         // notify the delegate
 1487  0
         notifyDelegate(
 1488  0
             "editingContextWillSaveChanges",
 1489  0
             new Class[] { EOEditingContext.class },
 1490  0
             new Object[] { this } );
 1491  
 
 1492  
         // needed for notification handling
 1493  0
         isInvalidating = true;
 1494  
         try
 1495  
         {
 1496  
             // ask parent to save us
 1497  0
             parentStore.saveChangesInEditingContext( this );
 1498  
         }
 1499  0
         catch ( RuntimeException e )
 1500  
         {
 1501  
             // unset save flag and rethrow
 1502  0
             isInvalidating = false;
 1503  0
             throw e;
 1504  0
         }
 1505  0
         isInvalidating = false;
 1506  
         
 1507  
         // no exceptions: proceed!
 1508  
         
 1509  
         Object o, key;
 1510  
         Iterator it;
 1511  
         
 1512  
         // update the committed snapshots
 1513  0
         it = insertedObjects.iterator();
 1514  0
         while ( it.hasNext() )
 1515  
         {
 1516  0
             o = it.next();
 1517  0
             registrar.setCommitSnapshot( o, null );
 1518  0
             registrar.setCurrentSnapshot( o, null );
 1519  0
         }
 1520  0
         it = updatedObjects.iterator();
 1521  0
         while ( it.hasNext() )
 1522  
         {
 1523  0
             o = it.next();
 1524  0
             registrar.setCommitSnapshot( o, null );
 1525  0
             registrar.setCurrentSnapshot( o, null );
 1526  0
         }
 1527  
         
 1528  
         // clear the lists
 1529  0
         updatedObjects.removeAllObjects();
 1530  0
         insertedObjects.removeAllObjects();
 1531  0
         it = new NSArray( deletedObjects() ).iterator();
 1532  0
         while ( it.hasNext() )
 1533  
         {   // parent is doing this as well?
 1534  0
             forgetObject( it.next() );   
 1535  0
         }
 1536  
         
 1537  
         // post notification
 1538  0
         NSNotificationCenter.defaultCenter().postNotification(
 1539  0
             new NSNotification(
 1540  0
                 EditingContextDidSaveChangesNotification, this, userInfo ) );
 1541  0
     }
 1542  
 
 1543  
     /**
 1544  
     * Commits all changes in the specified editing context
 1545  
     * to this one.  Called by child editing contexts in
 1546  
     * their saveChanges() method.
 1547  
     */
 1548  
     public void saveChangesInEditingContext ( 
 1549  
         EOEditingContext aContext)
 1550  
     {
 1551  
         Object o;
 1552  
         Iterator it;
 1553  
         
 1554  
         // process deletes
 1555  0
         List deletes = new NSArray( aContext.deletedObjects() );
 1556  0
         it = deletes.iterator();
 1557  0
         while ( it.hasNext() )
 1558  
         {
 1559  0
             o = it.next();
 1560  0
             EOGlobalID id = aContext.globalIDForObject( o );
 1561  0
             Object localVersion = objectForGlobalID( id );
 1562  0
             if ( localVersion == null )
 1563  
             {
 1564  
                 // make a local copy and register it
 1565  0
                 localVersion = registerClone( id, aContext, o, this );
 1566  0
                 if ( localVersion == null )
 1567  
                 {
 1568  0
                     throw new WotonomyException(
 1569  0
                         "Deleted object could not be serialized: " 
 1570  0
                         + id + " : " + o );
 1571  
                 }
 1572  
             }
 1573  
             else // we have a local version, copy changes
 1574  
             {
 1575  0
                 copy( aContext, o, this, localVersion );
 1576  
                 // copy marks the object as updated: will be on both lists
 1577  
             }
 1578  
             // delete our copy -- marks context as changed
 1579  0
             deleteObject( localVersion );
 1580  0
         }
 1581  
 
 1582  
         // process inserts - all inserts are new objects
 1583  0
         List inserts = new NSArray( aContext.insertedObjects() );
 1584  0
         it = inserts.iterator();
 1585  0
         while ( it.hasNext() )
 1586  
         {
 1587  0
             o = it.next();
 1588  
             // make a local copy and register it
 1589  0
             EOGlobalID id = aContext.globalIDForObject( o );
 1590  0
             willChange(); // need to mark editing context as changed
 1591  0
             Object localVersion = registerClone( id, aContext, o, this );
 1592  0
             if ( localVersion == null )
 1593  
             {
 1594  0
                 throw new WotonomyException(
 1595  0
                     "Inserted object could not be serialized: " 
 1596  0
                     + o );
 1597  
             }
 1598  
             // insert our copy manually so a new id is not generated
 1599  0
             insertedObjects.addObject( localVersion );
 1600  0
             insertedObjectsBuffer.addObject( localVersion );
 1601  0
         }
 1602  
 
 1603  
         // process updates
 1604  0
         List updates = new NSArray( aContext.updatedObjects() ); 
 1605  0
         it = updates.iterator();
 1606  0
         while ( it.hasNext() )
 1607  
         {
 1608  0
             willChange(); // need to mark editing context as changed
 1609  0
             o = it.next();
 1610  0
             EOGlobalID id = aContext.globalIDForObject( o );
 1611  0
             Object localVersion = objectForGlobalID( id );
 1612  0
             if ( localVersion == null )
 1613  
             {
 1614  
                 // make a local copy and register it
 1615  0
                 localVersion = registerClone( id, aContext, o, this );
 1616  0
                 if ( localVersion == null )
 1617  
                 {
 1618  0
                     throw new WotonomyException(
 1619  0
                         "Updated object could not be serialized: " 
 1620  0
                         + id + " : " + o );
 1621  
                 }
 1622  0
                 if ( id.isTemporary() )
 1623  
                 {
 1624  
                     // mark this object as inserted
 1625  0
                     insertedObjects.addObject( localVersion );
 1626  0
                     insertedObjectsBuffer.addObject( localVersion );
 1627  0
                 }
 1628  
                 else
 1629  
                 {
 1630  
                     // mark this object as updated
 1631  0
                     updatedObjects.addObject( localVersion );
 1632  
                     
 1633  
                     // notify of update only if not on deleted list
 1634  0
                     if ( deletedObjectsBuffer.indexOfIdenticalObject( 
 1635  0
                         localVersion ) == NSArray.NotFound )
 1636  
                     {
 1637  0
                         updatedObjectsBuffer.addObject( localVersion );
 1638  0
                     }
 1639  
                 }
 1640  
             }
 1641  
             else // we have a local version, copy changes
 1642  
             {
 1643  0
                 copy( aContext, o, this, localVersion );
 1644  
                 // copy marks the object as updated 
 1645  
             }
 1646  0
         }
 1647  
         
 1648  0
     }
 1649  
 
 1650  
     /**
 1651  
     * Sets the delegate for this editing context.
 1652  
     * Note: this implementation retains only a 
 1653  
     * weak reference to the specified object.
 1654  
     */
 1655  
     public void setDelegate ( 
 1656  
         Object anObject )
 1657  
     {
 1658  0
         if ( anObject == null ) delegate = null;
 1659  0
         delegate = new WeakReference( anObject );
 1660  0
     }
 1661  
 
 1662  
     /**
 1663  
     * Sets the fetch timestamp for this editing context.
 1664  
     */
 1665  
     public void setFetchTimestamp ( 
 1666  
         double aDouble )
 1667  
     {
 1668  0
         fetchTimestamp = aDouble;
 1669  0
     }
 1670  
 /*
 1671  
     public void setInvalidatesObjectsWhenFinalized ( 
 1672  
         boolean invalidatesObjects )
 1673  
     {
 1674  
         throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
 1675  
     }
 1676  
 
 1677  
     public void setInvalidatesObjectsWhenFreed ( 
 1678  
         boolean invalidatesObjects )
 1679  
     {
 1680  
         throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
 1681  
     }
 1682  
 */
 1683  
     /**
 1684  
     * Sets whether this editing context attempts to
 1685  
     * lock objects when they are first modified.
 1686  
     * Default is false.
 1687  
     */
 1688  
     public void setLocksObjectsBeforeFirstModification ( 
 1689  
         boolean locksObjects )
 1690  
     {
 1691  0
         lockBeforeModify = locksObjects;
 1692  0
     }
 1693  
 
 1694  
     /**
 1695  
     * Sets the message handler for this editing context.
 1696  
     * Note: this implementation retains only a 
 1697  
     * weak reference to the specified object.
 1698  
     */
 1699  
     public void setMessageHandler ( 
 1700  
         Object anObject )
 1701  
     {
 1702  0
         if ( anObject == null ) messageHandler = null;
 1703  0
         messageHandler = new WeakReference( anObject );
 1704  0
     }
 1705  
 
 1706  
     /**
 1707  
     * Sets whether this editing context propagates deletes
 1708  
     * immediately after the event that triggered the delete.
 1709  
     * Otherwise, propagation occurs only before commit.
 1710  
     * Default is true.
 1711  
     */
 1712  
     public void setPropagatesDeletesAtEndOfEvent ( 
 1713  
         boolean propagatesDeletes )
 1714  
     {
 1715  0
         propagateDeletesAfterEvent = propagatesDeletes;
 1716  0
     }
 1717  
 /*
 1718  
     public void setSharedEditingContext ( 
 1719  
         EOSharedEditingContext aSharedEditingContext )
 1720  
     {
 1721  
         throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
 1722  
     }
 1723  
 */
 1724  
     /**
 1725  
     * Sets whether validation is stopped after the
 1726  
     * first error occurs.  Otherwise, validation will
 1727  
     * continue for all other objects.
 1728  
     * Default is true.
 1729  
     */
 1730  
     public void setStopsValidationAfterFirstError ( 
 1731  
         boolean stopsValidation )
 1732  
     {
 1733  0
         stopValidationAfterError = stopsValidation;
 1734  0
     }
 1735  
 
 1736  
     /**
 1737  
     * Sets the undo manager to be used for this context.
 1738  
     * Note: This is currently javax.swing.undo.UndoManager,
 1739  
     * until we have a implementation of NSUndoManager.
 1740  
     */
 1741  
 /*
 1742  
     public void setUndoManager ( 
 1743  
         UndoManager anUndoManager )
 1744  
     {
 1745  
         undoManager = anUndoManager;
 1746  
     }
 1747  
 */
 1748  
 /*
 1749  
     public EOSharedEditingContext sharedEditingContext ()
 1750  
     {
 1751  
         throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
 1752  
     }
 1753  
 */
 1754  
     /**
 1755  
     * Returns whether validation is stopped after the
 1756  
     * first error occurs.  Otherwise, validation will
 1757  
     * continue for all other objects.
 1758  
     */
 1759  
     public boolean stopsValidationAfterFirstError ()
 1760  
     {
 1761  0
         return stopValidationAfterError;
 1762  
     }
 1763  
 
 1764  
     /**
 1765  
     * Reverts the last change on the undo stack.
 1766  
     */
 1767  
     public void undo ()
 1768  
     {
 1769  
         //TODO: not supported yet
 1770  0
         throw new UnsupportedOperationException("Not implemented yet.");
 1771  
     }
 1772  
 /*
 1773  
     public NSUndoManager undoManager ()
 1774  
     {
 1775  
         throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
 1776  
     }
 1777  
 */
 1778  
 /**
 1779  
     public void unlock ()
 1780  
     {
 1781  
         throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
 1782  
     }
 1783  
 */    
 1784  
     /**
 1785  
     * Returns a read-only list of all objects marked as modified,
 1786  
     * but not inserted or deleted, in this editing context.
 1787  
     */
 1788  
     public NSArray updatedObjects ()
 1789  
     {
 1790  0
         return updatedObjectsProxy;
 1791  
     }
 1792  
 
 1793  
     /**
 1794  
     * Notify editors of changes.
 1795  
     */    
 1796  
     private void fireWillSaveChanges()
 1797  
     {
 1798  0
         Object o = null;
 1799  0
         Iterator i = editors().iterator();
 1800  0
         while ( i.hasNext() )
 1801  
         {
 1802  
             try
 1803  
             {
 1804  0
                 o = i.next();
 1805  0
                 NSSelector.invoke( "editingContextWillSaveChanges", 
 1806  0
                     new Class[] { EOEditingContext.class }, o, this );
 1807  
             }
 1808  0
             catch ( NoSuchMethodException e )
 1809  
             {
 1810  
                 // ignore: not implemented
 1811  
             }
 1812  0
             catch ( Exception exc )
 1813  
             {
 1814  
                 // log to standard error
 1815  0
                 System.err.println( "Error while notifying editor of pending save: " + o );
 1816  0
                 exc.printStackTrace();
 1817  0
             }
 1818  0
         }
 1819  0
     }
 1820  
     
 1821  
     /**
 1822  
     * Handles notifications from parent store, looking for
 1823  
     * InvalidatedAllObjectsInStoreNotification and
 1824  
     * ObjectsChangedInStoreNotification.
 1825  
     * The former causes all objects in this store to be
 1826  
     * invalidated.
 1827  
     * The latter refaults the invalidated ids, merges changes
 1828  
     * from the updated ids, and forgets the deleted ids, then
 1829  
     * posts a ObjectsChangedInStoreNotification.
 1830  
     * Note: This method is not in the public specification.
 1831  
     */
 1832  
     public void handleNotification( NSNotification aNotification )
 1833  
     { // System.out.println( "EOEditingContext: " + this + " : " + aNotification );
 1834  
 
 1835  0
         willChange();
 1836  0
         if ( InvalidatedAllObjectsInStoreNotification
 1837  0
             .equals( aNotification.name() ) )
 1838  
         {
 1839  0
             refaultObjects();
 1840  
             
 1841  
             // relay notification
 1842  0
             NSNotificationCenter.defaultCenter().postNotification(
 1843  0
                 new NSNotification(
 1844  0
                     InvalidatedAllObjectsInStoreNotification, this ) );
 1845  0
         }
 1846  
         else
 1847  0
         if ( EOGlobalID.GlobalIDChangedNotification
 1848  0
             .equals( aNotification.name() ) )
 1849  
         {
 1850  0
             NSDictionary userInfo = aNotification.userInfo();
 1851  
             
 1852  
             // if any keys in userInfo are registered ids, 
 1853  
             // re-register with new permanent values.
 1854  
 
 1855  
             Object o;
 1856  
             EOGlobalID id;
 1857  0
             Enumeration e = userInfo.keyEnumerator();
 1858  0
             while ( e.hasMoreElements() )
 1859  
             {
 1860  0
                 id = (EOGlobalID) e.nextElement();
 1861  0
                 o = objectForGlobalID( id );
 1862  0
                 if ( o != null )
 1863  
                 {
 1864  
                     // record object is assumed to handle key updates
 1865  0
                     recordObject( o, (EOGlobalID) userInfo.objectForKey( id ) );
 1866  0
                 }
 1867  
             }
 1868  0
         }
 1869  
         else
 1870  0
         if ( EOObjectStore.ObjectsChangedInStoreNotification
 1871  0
             .equals( aNotification.name() ) )
 1872  
         {
 1873  
             //System.out.println( "EOEditingContext.handleNotification: " + aNotification + " : " + this );            
 1874  
             // post so child contexts are notified
 1875  0
             willChange();
 1876  
 
 1877  
             Object o;
 1878  
             EOGlobalID id;
 1879  
             Enumeration e;
 1880  0
             NSDictionary userInfo = aNotification.userInfo();
 1881  
             
 1882  
             // inserted objects are ignored
 1883  
             
 1884  
             // existing deleted objects are removed
 1885  0
             NSArray deletes = (NSArray) userInfo.objectForKey( 
 1886  0
                 EOObjectStore.DeletedKey );
 1887  0
             e = deletes.objectEnumerator();
 1888  0
             while ( e.hasMoreElements() )
 1889  
             {
 1890  0
                 id = (EOGlobalID) e.nextElement();
 1891  0
                 o = objectForGlobalID( id );
 1892  0
                 if ( o != null )
 1893  
                 {
 1894  
                     //System.out.println( "EOEditingContext: deleted: " + id );
 1895  0
                     forgetObject( o );
 1896  0
                     deletedObjectsBuffer.addObject( o );            
 1897  0
                     deletedIDsBuffer.addObject( id );            
 1898  0
                 }
 1899  
             }
 1900  
             
 1901  
             // existing updated objects are merged
 1902  0
             NSArray updates = (NSArray) userInfo.objectForKey( 
 1903  0
                 EOObjectStore.UpdatedKey );
 1904  0
             e = updates.objectEnumerator();
 1905  0
             while ( e.hasMoreElements() )
 1906  
             {
 1907  0
                 id = (EOGlobalID) e.nextElement();
 1908  0
                 o = objectForGlobalID( id );
 1909  0
                 if ( o != null )
 1910  
                 {
 1911  
                     //System.out.println( "EOEditingContext: updated: " + id );
 1912  0
                     if ( updatedObjects // only update if unchanged
 1913  0
                         .indexOfIdenticalObject( o ) == NSArray.NotFound )
 1914  
                     {
 1915  0
                         refaultObject( o, id, this );
 1916  0
                         updatedObjectsBuffer.addObject( o );            
 1917  0
                     }
 1918  
                     else
 1919  
                     {
 1920  
                         // notify user and/or merge
 1921  0
                         handleUpdateConflict( id, o );
 1922  
                     }
 1923  0
                 }
 1924  
             }
 1925  
                 
 1926  
             // existing invalidated objects are refaulted           
 1927  0
             NSArray invalidates = (NSArray) userInfo.objectForKey( 
 1928  0
                 EOObjectStore.InvalidatedKey );
 1929  0
             e = invalidates.objectEnumerator();
 1930  0
             while ( e.hasMoreElements() )
 1931  
             {
 1932  0
                 id = (EOGlobalID) e.nextElement();
 1933  0
                 o = objectForGlobalID( id );
 1934  0
                 if ( o != null )
 1935  
                 {
 1936  0
                     if ( updatedObjects // only invalidate if unchanged
 1937  0
                         .indexOfIdenticalObject( o ) == NSArray.NotFound )
 1938  
                     {
 1939  0
                         refaultObject( o, id, this );
 1940  0
                     }
 1941  
                     else
 1942  
                     {
 1943  
                         // notify user and/or merge
 1944  0
                         handleUpdateConflict( id, o );
 1945  
                     }
 1946  0
                     if ( invalidatedObjectsBuffer
 1947  0
                         .indexOfIdenticalObject( o ) == NSArray.NotFound )
 1948  
                     {
 1949  0
                         invalidatedObjectsBuffer.addObject( o ); 
 1950  
                     }
 1951  0
                     if ( invalidatedIDsBuffer
 1952  0
                         .indexOfIdenticalObject( id ) == NSArray.NotFound )
 1953  
                     {
 1954  0
                         invalidatedIDsBuffer.addObject( id );
 1955  0
                     }
 1956  
                 }
 1957  
             }
 1958  
                
 1959  
         }
 1960  0
     }
 1961  
     
 1962  
     /**
 1963  
     * Called by handleNotification to resolve the case where we have
 1964  
     * received notification that another user or context has updated
 1965  
     * an object that is currently marked as edited in this context.
 1966  
     * This implementation first asks the delegate if it should merge.
 1967  
     * If true or no delegate, the changes are merged. True or false, 
 1968  
     * the delegate is then sent editingContextDidMergeChanges.
 1969  
     */
 1970  
     private void handleUpdateConflict( EOGlobalID anID, Object anObject )
 1971  
     {
 1972  
         // if we're causing the invalidation, ignore
 1973  
         // (this is probably better handled by the caller...)
 1974  0
         if ( isInvalidating ) 
 1975  
         {
 1976  0
             ignoreChanges = true;
 1977  0
             parentStore.refaultObject( anObject, anID, this );
 1978  0
             ignoreChanges = false;
 1979  0
             return;
 1980  
         }
 1981  
 
 1982  0
         Boolean result = (Boolean) notifyDelegate( 
 1983  0
             "editingContextShouldMergeChangesForObject", 
 1984  0
             new Class[] { EOEditingContext.class, Object.class },
 1985  0
             new Object[] { this, anObject } );
 1986  
 
 1987  0
         if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) )         
 1988  
         {
 1989  
             // do merge
 1990  0
             mergeExternalChanges( anID, anObject );
 1991  
         }
 1992  
         else // Boolean.FALSE
 1993  
         {
 1994  
             // do nothing: don't lose the user's changes
 1995  
         }
 1996  
         
 1997  
         // notify merge did happen
 1998  0
         notifyDelegate( 
 1999  0
             "editingContextDidMergeChanges", 
 2000  0
             new Class[] { EOEditingContext.class },
 2001  0
             new Object[] { this } );
 2002  0
     }    
 2003  
     
 2004  
     /**
 2005  
     * For the currently modified object with the specified global id,
 2006  
     * this method merges changes with the updated version in the parent 
 2007  
     * object store.  This implementation looks at the fetch snapshot
 2008  
     * to determine which changes where made by the user, fetches the
 2009  
     * updated version of the object, and then determine what external
 2010  
     * changes were made.  If the changes do not overlap, the original
 2011  
     * changes are applied to the updated version.  If there is a conflict,
 2012  
     * notifies the user of the conflict.  
 2013  
     */
 2014  
     private boolean mergeExternalChanges( EOGlobalID anID, Object anObject )
 2015  
     {
 2016  
         try
 2017  
         {
 2018  
             Iterator i;
 2019  
             Object key;
 2020  
             
 2021  
             // get fetch snapshot
 2022  0
             Map fetchSnapshot = committedSnapshotForObject( anObject );
 2023  
             
 2024  
             // get current snapshot
 2025  0
             Map currentSnapshot = currentEventSnapshotForObject( anObject );
 2026  
             
 2027  
             // diff against fetch snapshot
 2028  0
             Map currentDiff = new HashMap();
 2029  0
             i = currentSnapshot.keySet().iterator();
 2030  0
             while ( i.hasNext() )
 2031  
             {
 2032  0
                 key = i.next();
 2033  0
                 if ( ! currentSnapshot.get( key ).equals( fetchSnapshot.get( key ) ) )
 2034  
                 {
 2035  0
                     currentDiff.put( key, currentSnapshot.get( key ) );
 2036  0
                 }
 2037  
             }
 2038  
             
 2039  
             // refault
 2040  0
             ignoreChanges = true;
 2041  0
             parentStore.refaultObject( anObject, anID, this );
 2042  0
             ignoreChanges = false;
 2043  
             
 2044  
             // get updated snapshot
 2045  0
             Map updatedSnapshot = convertSnapshotToDictionary( takeSnapshot( anObject ) );
 2046  
             
 2047  
             // diff against fetch snapshot
 2048  0
             Map updatedDiff = new HashMap();
 2049  0
             i = updatedSnapshot.keySet().iterator();
 2050  0
             while ( i.hasNext() )
 2051  
             {
 2052  0
                 key = i.next();
 2053  0
                 if ( ! updatedSnapshot.get( key ).equals( fetchSnapshot.get( key ) ) )
 2054  
                 {
 2055  0
                     updatedDiff.put( key, updatedSnapshot.get( key ) );
 2056  0
                 }
 2057  
             }
 2058  
             
 2059  
             // determine if there's a conflict
 2060  0
             boolean proceed = true;
 2061  0
             Set updatedKeys = updatedDiff.keySet();
 2062  0
             i = currentDiff.keySet().iterator();
 2063  0
            while ( i.hasNext() )
 2064  
             {
 2065  0
                 if ( updatedKeys.contains( i.next() ) )
 2066  
                 {
 2067  0
                     proceed = false;
 2068  0
                     break;
 2069  
                 }
 2070  
             }
 2071  
             
 2072  
             // if no conflicts, apply original diff to current object and exit
 2073  0
             if ( proceed )
 2074  
             {
 2075  0
                 KeyValueCodingUtilities.takeStoredValuesFromDictionary( anObject, currentDiff );
 2076  0
                 return true; // exit!
 2077  
             }
 2078  
         }
 2079  0
         catch ( Exception exc )
 2080  
         {
 2081  
             // log error to standard out
 2082  0
             exc.printStackTrace();
 2083  0
         }
 2084  
         
 2085  
         // notify user we're unable to merge
 2086  0
         notifyMessageHandler( MessageChangeConflict + anObject );
 2087  0
         return false;
 2088  
     }
 2089  
 
 2090  
     /**
 2091  
      * Sends the specified message to the message handler.
 2092  
      */
 2093  
     private void notifyMessageHandler( String aMessage )
 2094  
     {
 2095  0
         Object handler = null;
 2096  
         try
 2097  
         {
 2098  0
             handler = messageHandler();
 2099  0
             if ( handler == null ) return;
 2100  0
             NSSelector.invoke( "editingContextPresentErrorMessage", 
 2101  0
                 new Class[] { EOEditingContext.class, String.class }, 
 2102  0
                 handler, this, aMessage );
 2103  
         }
 2104  0
         catch ( NoSuchMethodException e )
 2105  
         {
 2106  
             // ignore: not implemented
 2107  
         }
 2108  0
         catch ( Exception exc )
 2109  
         {
 2110  
             // log to standard error
 2111  0
             System.err.println( 
 2112  0
                 "Error while notifying message handler: " + 
 2113  0
                     handler + " : " + aMessage );
 2114  0
             exc.printStackTrace();
 2115  0
         }
 2116  0
     }
 2117  
     
 2118  
     /**
 2119  
      * Sends the specified message to the delegate.
 2120  
      * Returns the return value of the method,
 2121  
      * or null if no return value or no delegate
 2122  
      * or no implementation.
 2123  
      */
 2124  
     private Object notifyDelegate( 
 2125  
         String aMethodName, Class[] types, Object[] params )
 2126  
     {
 2127  
         try
 2128  
         {
 2129  0
             Object delegate = delegate();
 2130  0
             if ( delegate == null ) return null;
 2131  0
             return NSSelector.invoke( 
 2132  0
                 aMethodName, types, delegate, params );
 2133  
         }
 2134  0
         catch ( NoSuchMethodException e )
 2135  
         {
 2136  
             // ignore: not implemented
 2137  
         }
 2138  0
         catch ( Exception exc )
 2139  
         {
 2140  
             // log to standard error
 2141  0
             System.err.println( 
 2142  0
                 "Error while messaging delegate: " + 
 2143  0
                     delegate + " : " + aMethodName );
 2144  0
             exc.printStackTrace();
 2145  0
         }
 2146  
         
 2147  0
         return null;
 2148  
     }
 2149  
     
 2150  
     // interface EOObserving
 2151  
         
 2152  
     /**
 2153  
     * Implementation of the EOObserving interface.
 2154  
     * Called before objects are modified.
 2155  
     */
 2156  
     public void objectWillChange ( 
 2157  
         Object anObject )
 2158  
     {
 2159  0
         if ( ignoreChanges ) return;
 2160  
 //NSNotificationCenter.defaultCenter().postNotification( "objectWillChange", this, new NSDictionary( "object", anObject ) );
 2161  
 //new RuntimeException().printStackTrace();
 2162  
 
 2163  0
         willChange();
 2164  
 
 2165  
         // mark as updated if not marked already
 2166  0
         int i = updatedObjects.indexOfIdenticalObject( anObject );
 2167  0
         if ( i == NSArray.NotFound )
 2168  
         {
 2169  
             // don't mark inserted objects as updated
 2170  0
             i = insertedObjects.indexOfIdenticalObject( anObject );
 2171  0
             if ( i == NSArray.NotFound )
 2172  
             {
 2173  0
                 i = deletedObjects.indexOfIdenticalObject( anObject );
 2174  0
                 if ( i == NSArray.NotFound )
 2175  
                 {
 2176  
                     // add object
 2177  0
                     updatedObjects.addObject( anObject );
 2178  
 
 2179  
                     // record revert snapshot
 2180  0
                     registrar.setCommitSnapshot( anObject, takeSnapshot( anObject ) );
 2181  
                 }
 2182  
             }
 2183  
         }
 2184  
         
 2185  
         // add to buffer
 2186  0
         if ( updatedObjectsBuffer.indexOfIdenticalObject( anObject )
 2187  0
                == NSArray.NotFound )
 2188  
         {
 2189  0
             updatedObjectsBuffer.addObject( anObject );
 2190  
         }
 2191  0
     }
 2192  
 
 2193  
     // static methods
 2194  
     
 2195  
     public static double defaultFetchTimestampLag ()
 2196  
     {
 2197  0
         return defaultFetchTimestampLag;
 2198  
     }
 2199  
 
 2200  
     /**
 2201  
     * Returns the default parent object store for all
 2202  
     * object stores created with the parameterless 
 2203  
     * constructor.
 2204  
     */
 2205  
     public static EOObjectStore defaultParentObjectStore ()
 2206  
     {
 2207  0
         return defaultParentObjectStore;
 2208  
     }
 2209  
 
 2210  
 /*
 2211  
     public static Object initObjectWithCoder ( 
 2212  
         Object anObject,
 2213  
         NSCoder aCoder )
 2214  
     {
 2215  
         throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
 2216  
     }
 2217  
 */
 2218  
 
 2219  
     /**
 2220  
     * Returns whether editing contexts are configured to retain strong
 2221  
     * references to their registered objects.  If false, editing contexts
 2222  
     * will only retain weak references to their registered objects.
 2223  
     */ 
 2224  
     public static boolean instancesRetainRegisteredObjects()
 2225  
     {
 2226  0
         return retainsRegisteredObjects;
 2227  
     }
 2228  
 
 2229  
     /**
 2230  
     * Sets the global default fetch timestamp lag.
 2231  
     */
 2232  
     public static void setDefaultFetchTimestampLag ( 
 2233  
         double aDouble )
 2234  
     {
 2235  0
         defaultFetchTimestampLag = aDouble;
 2236  0
     }
 2237  
 
 2238  
     /**
 2239  
     * Sets the global default parent object store,
 2240  
     * used for the parameterless constructor.
 2241  
     */
 2242  
     public static void setDefaultParentObjectStore ( 
 2243  
         EOObjectStore anObjectStore )
 2244  
     {
 2245  0
         defaultParentObjectStore = anObjectStore;
 2246  0
     }
 2247  
 
 2248  
     public static void setInstancesRetainRegisteredObjects ( 
 2249  
         boolean retainsObjects )
 2250  
     {
 2251  0
         retainsRegisteredObjects = retainsObjects;
 2252  0
     }
 2253  
 
 2254  
 /*
 2255  
     public static void setSubstitutionEditingContext ( 
 2256  
         EOEditingContext aContext)
 2257  
     {
 2258  
         throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
 2259  
     }
 2260  
 
 2261  
     public static void setUsesContextRelativeEncoding ( 
 2262  
         boolean usesRelativeEncoding )
 2263  
     {
 2264  
         throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
 2265  
     }
 2266  
 
 2267  
     public static EOEditingContext substitutionEditingContext ()
 2268  
     {
 2269  
         throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
 2270  
     }
 2271  
 
 2272  
     public static boolean usesContextRelativeEncoding ()
 2273  
     {
 2274  
         throw new net.wotonomy.util.WotonomyException("Not implemented yet.");
 2275  
     }
 2276  
 */
 2277  
 
 2278  
     public String toString()
 2279  
     {
 2280  0
         return "[EOEditingContext@"+Integer.toHexString(System.identityHashCode(this))+":"+
 2281  0
             " inserted="+idsForObjects(insertedObjects)+
 2282  0
             " updated="+idsForObjects(updatedObjects)+
 2283  0
             " deleted="+idsForObjects(deletedObjects)+
 2284  0
             " registered="+registrar.registeredGlobalIDs()+" ]";
 2285  
     }
 2286  
     private List idsForObjects( List objects )
 2287  
     {
 2288  0
         List result = new LinkedList();
 2289  0
         Iterator i = objects.iterator();
 2290  0
         while ( i.hasNext() ) result.add( globalIDForObject( i.next() ) );
 2291  0
         return result;
 2292  
     }
 2293  
     
 2294  
     // snapshots
 2295  
     
 2296  
     /**
 2297  
     * Returns a NSDictionary containing only the mutable properties 
 2298  
     * for the specified object and deep clones of their values.
 2299  
     * Nulls are represented by NSNull.nullValue().
 2300  
     */
 2301  
     private byte[] takeSnapshot( Object anObject )
 2302  
     { // System.out.println( "takeSnapshot: " + anObject );
 2303  0
         return KeyValueCodingUtilities.freeze( anObject, this, anObject, true );
 2304  
     }
 2305  
     
 2306  
     /**
 2307  
     * Applies the map of properties and values to the
 2308  
     * specified object.  Null values for properties must
 2309  
     * be represented by the NSNull.nullValue().
 2310  
     * Posts a willChange event before applying changes.
 2311  
     */
 2312  
     private void applySnapshot( byte[] aSnapshot, Object anObject )
 2313  
     { 
 2314  
         // must clone snapshot to avoid changing existing snapshot
 2315  0
         NSDictionary values = convertSnapshotToDictionary( aSnapshot ); 
 2316  
 
 2317  
 //System.out.println( "applySnapshot: " + aSnapshot + " : " + values );
 2318  
 //ignoreChanges = true;        
 2319  0
         willChange();
 2320  0
         KeyValueCodingUtilities.takeStoredValuesFromDictionary( anObject, values );        
 2321  
 //ignoreChanges = false;        
 2322  0
     }
 2323  
 
 2324  
     /**
 2325  
     * Snapshots are stored internally in binary format,
 2326  
     * but exposed to the user as NSDictionaries.
 2327  
     */
 2328  
     private NSDictionary convertSnapshotToDictionary( byte[] aSnapshot )
 2329  
     {
 2330  
         // get the object
 2331  0
         Object clone = KeyValueCodingUtilities.thaw( aSnapshot, this, true );
 2332  
         // get all keys for this object
 2333  0
         EOClassDescription classDesc =
 2334  0
             EOClassDescription.classDescriptionForClass( clone.getClass() );            
 2335  0
         List keys = new LinkedList();
 2336  0
         keys.addAll( classDesc.attributeKeys() ); 
 2337  0
         keys.addAll( classDesc.toOneRelationshipKeys() ); 
 2338  0
         keys.addAll( classDesc.toManyRelationshipKeys() ); 
 2339  
 
 2340  0
         return KeyValueCodingUtilities.valuesForKeys( clone, keys );
 2341  
     }
 2342  
     /**
 2343  
     * Creates a deep clone of the specified object.
 2344  
     * (Object.clone() only creates a shallow clone.)
 2345  
     * Returns null if operation fails.
 2346  
     */ 
 2347  
     static private Object clone( 
 2348  
         EOEditingContext aSourceContext,
 2349  
         Object aSource, 
 2350  
         EOEditingContext aDestinationContext )
 2351  
     { // System.out.println( "clone: " + aSource );
 2352  0
         return KeyValueCodingUtilities.clone( 
 2353  0
             aSourceContext, aSource, aDestinationContext );
 2354  
     }
 2355  
 
 2356  
     /**
 2357  
     * Creates a deep clone of the specified object.
 2358  
     * but does not transpose references.  This allows
 2359  
     * us to register an object before transposing
 2360  
     * references so that child objects will be able
 2361  
     * to resolve references to their parent.
 2362  
     * After recording the object, we copy the source
 2363  
     * object into the clone, which does transpose
 2364  
     * and resolve properly.
 2365  
     * Returns null if operation fails.
 2366  
     */ 
 2367  
     static private Object registerClone( 
 2368  
         EOGlobalID aGlobalID,
 2369  
         EOEditingContext aSourceContext,
 2370  
         Object aSource, 
 2371  
         EOEditingContext aDestinationContext )
 2372  
     { 
 2373  
         // while we'd like to just transpose/clone at the same time
 2374  
         //   we must record a clone without transposing: this
 2375  
         //   avoids a endless loop if the object graph has a cycle
 2376  0
         Object clone = KeyValueCodingUtilities.thaw( 
 2377  0
                 KeyValueCodingUtilities.freeze( 
 2378  0
                     aSource, aSourceContext, aSource, false ), 
 2379  0
                 aDestinationContext, false );
 2380  
             
 2381  0
         aDestinationContext.recordObject( clone, aGlobalID );
 2382  
         
 2383  
         // need to copy to transpose references into this context
 2384  
         //   while preserving the same instance of the object
 2385  0
         aDestinationContext.ignoreChanges = true;
 2386  0
         copy( aSourceContext, aSource, aDestinationContext, clone );
 2387  0
         aDestinationContext.ignoreChanges = false;
 2388  
 
 2389  
         // return our clone
 2390  0
         return clone;
 2391  
     }
 2392  
     
 2393  
     /**
 2394  
     * Copies values from one object to another.
 2395  
     * Returns the destination object, or throws exception
 2396  
     * if operation fails.
 2397  
     */ 
 2398  
     static private Object copy( 
 2399  
         EOEditingContext aSourceContext,
 2400  
         Object aSource, 
 2401  
         EOEditingContext aDestinationContext,
 2402  
         Object aDestination )
 2403  
     { // System.out.println( "copy: " );
 2404  0
         EOObserverCenter.notifyObserversObjectWillChange( aDestination );
 2405  0
         KeyValueCodingUtilities.copy( aSourceContext, aSource, aDestinationContext, aDestination );
 2406  0
         return aDestination;
 2407  
     }
 2408  
     
 2409  
     // process recent changes 
 2410  
     
 2411  
     /**
 2412  
     * Called to notify observers of changes.
 2413  
     * Also calls runLater().
 2414  
     */
 2415  
     private void willChange()
 2416  
     {
 2417  0
         EOObserverCenter.notifyObserversObjectWillChange( this );        
 2418  0
         runLater();
 2419  0
     }
 2420  
     
 2421  
         /**
 2422  
         * Called to ensure that processRecentChanges
 2423  
         * will be called on the next event loop.
 2424  
         */
 2425  
         private void runLater()
 2426  
         {
 2427  0
                 if ( ! willRunLater )
 2428  
                 {
 2429  0
                         willRunLater = true;
 2430  0
             NSRunLoop.currentRunLoop().performSelectorWithOrder(
 2431  0
                 runLaterSelector, this, null, EditingContextFlushChangesRunLoopOrdering, null );
 2432  
                 }
 2433  0
         }
 2434  
         
 2435  
         /**
 2436  
         * This method is called by the event queue run loop
 2437  
         * and calls processRecentChanges.
 2438  
         * NOTE: This method is not part of the specification.
 2439  
         */
 2440  
         public void flushRecentChanges( Object anObject )
 2441  
         {
 2442  
 //System.out.println( "EODelayedObserverQueue: running" );                
 2443  0
             processRecentChanges();
 2444  0
                 willRunLater = false;
 2445  0
         }
 2446  
         
 2447  
     // inner classes
 2448  
 
 2449  
     /**
 2450  
     * Gatekeeper for all access to registered objects.
 2451  
     */
 2452  
     static private class Registrar
 2453  
     {
 2454  
         EOEditingContext context;
 2455  
         NSMutableDictionary IDsToObjects;
 2456  
         NSMutableDictionary objectsToIDs;
 2457  
         NSMutableDictionary objectsToCommitSnapshots;
 2458  
         NSMutableDictionary objectsToCurrentSnapshots;
 2459  
 
 2460  
         ReferenceKey comparisonKey; //FIXME not thread safe!
 2461  
 
 2462  0
         public Registrar( EOEditingContext aContext )
 2463  0
         {
 2464  0
             context = aContext;
 2465  0
             IDsToObjects = new NSMutableDictionary();
 2466  0
             objectsToIDs = new NSMutableDictionary();
 2467  0
             objectsToCommitSnapshots = new NSMutableDictionary();
 2468  0
             objectsToCurrentSnapshots = new NSMutableDictionary();
 2469  0
             comparisonKey = new ReferenceKey();
 2470  0
         }
 2471  
         
 2472  
         public NSArray registeredObjects()
 2473  
         {
 2474  0
             return IDsToObjects.allValues();
 2475  
         }
 2476  
         
 2477  
         public NSArray registeredGlobalIDs()
 2478  
         {
 2479  0
             return IDsToObjects.allKeys();
 2480  
         }
 2481  
         
 2482  
         public Object objectForGlobalID( EOGlobalID aGlobalID )
 2483  
         {
 2484  0
             return IDsToObjects.objectForKey( aGlobalID );
 2485  
         }
 2486  
         
 2487  
         public EOGlobalID globalIDForObject( Object anObject )
 2488  
         {
 2489  0
             comparisonKey.set( anObject );
 2490  0
             return (EOGlobalID) objectsToIDs.objectForKey( comparisonKey );
 2491  
         }
 2492  
         
 2493  
         public byte[] getCommitSnapshot( Object anObject )
 2494  
         {
 2495  0
             comparisonKey.set( anObject );
 2496  0
             return (byte[]) objectsToCommitSnapshots.objectForKey( comparisonKey );
 2497  
         }
 2498  
         
 2499  
         public void setCommitSnapshot( Object anObject, byte[] aSnapshot )
 2500  
         {
 2501  0
             if ( aSnapshot == null )
 2502  
             {
 2503  0
                 comparisonKey.set( anObject );
 2504  0
                 objectsToCommitSnapshots.removeObjectForKey( comparisonKey );
 2505  0
             }
 2506  
             else
 2507  
             {
 2508  0
                 objectsToCommitSnapshots.setObjectForKey( 
 2509  0
                     aSnapshot, new ReferenceKey( anObject ) );
 2510  
             }
 2511  0
         }
 2512  
         
 2513  
         public byte[] getCurrentSnapshot( Object anObject )
 2514  
         {
 2515  0
             comparisonKey.set( anObject );
 2516  0
             return (byte[]) objectsToCurrentSnapshots.objectForKey( comparisonKey );
 2517  
         }
 2518  
         
 2519  
         public void setCurrentSnapshot( Object anObject, byte[] aSnapshot )
 2520  
         {
 2521  0
             if ( aSnapshot == null )
 2522  
             {
 2523  0
                 comparisonKey.set( anObject );
 2524  0
                 objectsToCurrentSnapshots.removeObjectForKey( comparisonKey );
 2525  0
             }
 2526  
             else
 2527  
             {
 2528  0
                 objectsToCurrentSnapshots.setObjectForKey( 
 2529  0
                     aSnapshot, new ReferenceKey( anObject ) );
 2530  
             }
 2531  0
         }
 2532  
         
 2533  
         public void registerObject( Object anObject, EOGlobalID aGlobalID )
 2534  
         {
 2535  0
             IDsToObjects.setObjectForKey( anObject, aGlobalID );
 2536  0
             objectsToIDs.setObjectForKey( aGlobalID, new ReferenceKey( anObject ) );
 2537  0
             EOObserverCenter.addObserver( context, anObject );
 2538  0
         }
 2539  
         
 2540  
         public void forgetObject( Object anObject )
 2541  
         {
 2542  0
             comparisonKey.set( anObject );
 2543  0
             Object id = objectsToIDs.objectForKey( comparisonKey );
 2544  0
             IDsToObjects.removeObjectForKey( id );
 2545  0
             objectsToIDs.removeObjectForKey( comparisonKey );
 2546  0
             EOObserverCenter.removeObserver( context, anObject );
 2547  0
         }
 2548  
         
 2549  
         public void disposeSnapshots( Object anObject )
 2550  
         {
 2551  0
             setCommitSnapshot( anObject, null );
 2552  0
             setCurrentSnapshot( anObject, null );
 2553  0
         }
 2554  
 
 2555  
     }
 2556  
     
 2557  
     /**
 2558  
     * Registrar that uses only WeakReferences.
 2559  
     * Used if retainsRegisteredObjects is false.
 2560  
     */
 2561  
     static private class WeakRegistrar extends Registrar
 2562  
     {
 2563  
         private WeakReferenceKey weakComparisonKey; //FIXME not thread safe!
 2564  
         
 2565  
         public WeakRegistrar( EOEditingContext aContext )
 2566  
         {
 2567  0
             super( aContext );
 2568  0
             weakComparisonKey = new WeakReferenceKey();
 2569  0
         }
 2570  
         
 2571  
         public NSArray registeredObjects()
 2572  
         {
 2573  
             Object object;
 2574  
             WeakReferenceKey weakKey;
 2575  0
             NSMutableArray result = new NSMutableArray();
 2576  0
             Enumeration e = new NSArray( objectsToIDs.allKeys() ).objectEnumerator();
 2577  0
             while ( e.hasMoreElements() )
 2578  
             {
 2579  0
                 weakKey = (WeakReferenceKey) e.nextElement();
 2580  0
                 object = weakKey.get();
 2581  0
                 if ( object != null )
 2582  
                 {
 2583  0
                     result.addObject( object );
 2584  0
                 }
 2585  
                 else
 2586  
                 {
 2587  
                     // object has been released: perform cleanup
 2588  0
                     disposeObject( null, weakKey );
 2589  
                 }
 2590  0
             }
 2591  0
             return result;
 2592  
         }
 2593  
         
 2594  
         public Object objectForGlobalID( EOGlobalID aGlobalID )
 2595  
         {
 2596  0
             WeakReference ref = (WeakReference) super.objectForGlobalID( aGlobalID );
 2597  0
             if ( ref == null ) return null;
 2598  0
             Object result = ref.get();
 2599  0
             if ( result == null )
 2600  
             {
 2601  
                 // clean up manually
 2602  0
                 IDsToObjects.removeObjectForKey( aGlobalID );
 2603  0
                 Iterator i = new LinkedList( objectsToIDs.allKeysForObject( ref ) ).iterator();
 2604  0
                 while ( i.hasNext() )
 2605  
                 {
 2606  0
                     objectsToIDs.removeObjectForKey( i.next() );
 2607  0
                 }
 2608  0
                 disposeSnapshots( aGlobalID ); 
 2609  
             }
 2610  0
             return result;
 2611  
         }
 2612  
         
 2613  
         public byte[] getCommitSnapshot( Object anObject )
 2614  
         {
 2615  0
             weakComparisonKey.set( anObject );
 2616  0
             return (byte[]) objectsToCommitSnapshots.objectForKey( weakComparisonKey );
 2617  
         }
 2618  
         
 2619  
         public void setCommitSnapshot( Object anObject, byte[] aSnapshot )
 2620  
         {
 2621  0
             if ( aSnapshot == null )
 2622  
             {
 2623  0
                 weakComparisonKey.set( anObject );
 2624  0
                 objectsToCommitSnapshots.removeObjectForKey( weakComparisonKey );
 2625  0
             }
 2626  
             else
 2627  
             {
 2628  0
                 objectsToCommitSnapshots.setObjectForKey( 
 2629  0
                     aSnapshot, new WeakReferenceKey( anObject ) );
 2630  
             }
 2631  0
         }
 2632  
         
 2633  
         public byte[] getCurrentSnapshot( Object anObject )
 2634  
         {
 2635  0
             weakComparisonKey.set( anObject );
 2636  0
             return (byte[]) objectsToCurrentSnapshots.objectForKey( weakComparisonKey );
 2637  
         }
 2638  
         
 2639  
         public void setCurrentSnapshot( Object anObject, byte[] aSnapshot )
 2640  
         {
 2641  0
             if ( aSnapshot == null )
 2642  
             {
 2643  0
                 weakComparisonKey.set( anObject );
 2644  0
                 objectsToCurrentSnapshots.removeObjectForKey( weakComparisonKey );
 2645  0
             }
 2646  
             else
 2647  
             {
 2648  0
                 objectsToCurrentSnapshots.setObjectForKey( 
 2649  0
                     aSnapshot, new WeakReferenceKey( anObject ) );
 2650  
             }
 2651  0
         }
 2652  
         
 2653  
         public void registerObject( Object anObject, EOGlobalID aGlobalID )
 2654  
         { // new net.wotonomy.ui.swing.ReferenceInspector( anObject );            
 2655  0
             IDsToObjects.setObjectForKey( new WeakReference( anObject ), aGlobalID );
 2656  0
             objectsToIDs.setObjectForKey( aGlobalID, new WeakReferenceKey( anObject ) );
 2657  0
             EOObserverCenter.addObserver( context, anObject );
 2658  0
         }
 2659  
         
 2660  
         public void forgetObject( Object anObject )
 2661  
         {
 2662  0
             disposeObject( anObject, null );
 2663  0
         }
 2664  
         
 2665  
         // must specify one or the other
 2666  
         private void disposeObject( Object anObject, WeakReferenceKey key )
 2667  
         {
 2668  0
             if ( key == null ) key = new WeakReferenceKey( anObject );
 2669  0
             EOGlobalID id = (EOGlobalID) objectsToIDs.objectForKey( key );
 2670  0
             if ( id != null ) IDsToObjects.removeObjectForKey( id );
 2671  0
             objectsToIDs.removeObjectForKey( key );
 2672  0
             disposeSnapshots( id );
 2673  0
             if ( anObject != null )
 2674  
             {
 2675  0
                 EOObserverCenter.removeObserver( context, anObject );
 2676  
             }
 2677  0
         }
 2678  
     }
 2679  
     
 2680  
     /**
 2681  
     * Private class used to force a hashmap to
 2682  
     * perform key comparisons by reference.
 2683  
     */
 2684  
         static private class ReferenceKey 
 2685  
     {
 2686  
         int hashCode;
 2687  
         Object referent;
 2688  
         
 2689  0
         public ReferenceKey()
 2690  0
         {
 2691  0
             referent = null;
 2692  0
             hashCode = -1;
 2693  0
         }
 2694  
         
 2695  0
         public ReferenceKey( Object anObject )
 2696  0
         {
 2697  0
             set( anObject );
 2698  0
         }
 2699  
         
 2700  
         public Object get()
 2701  
         {
 2702  0
             return referent;
 2703  
         }
 2704  
         
 2705  
         public void set( Object anObject )
 2706  
         {
 2707  0
             referent = anObject;
 2708  0
             hashCode = anObject.hashCode();
 2709  0
         }
 2710  
         
 2711  
         /**
 2712  
         * Returns the actual key's hash code.
 2713  
         */
 2714  
         public int hashCode()
 2715  
         {
 2716  0
             return hashCode;   
 2717  
         }
 2718  
         
 2719  
         /**
 2720  
         * Compares by reference.
 2721  
         */ 
 2722  
         public boolean equals( Object anObject )
 2723  
         {
 2724  0
             if ( anObject == this ) return true;
 2725  0
             if ( anObject instanceof ReferenceKey )
 2726  
             {
 2727  0
                 return ((ReferenceKey)anObject).get() == referent;
 2728  
             }
 2729  0
             return false;  
 2730  
         }
 2731  
     }
 2732  
 
 2733  
     /**
 2734  
     * Private class used to force a hashmap to
 2735  
     * perform key comparisons by reference.
 2736  
     */
 2737  
         static private class WeakReferenceKey extends ReferenceKey
 2738  
     {
 2739  
         public WeakReferenceKey()
 2740  
         {
 2741  0
             super();
 2742  0
         }
 2743  
         
 2744  
         public WeakReferenceKey( Object anObject )
 2745  
         {
 2746  0
             super( anObject );
 2747  0
         }
 2748  
         
 2749  
         public Object get()
 2750  
         {
 2751  0
             return ((WeakReference)referent).get();
 2752  
         }
 2753  
         
 2754  
         public void set( Object anObject )
 2755  
         {
 2756  0
             referent = new WeakReference( anObject );
 2757  0
             hashCode = anObject.hashCode();
 2758  0
         }
 2759  
         
 2760  
         /**
 2761  
         * Compares by reference.
 2762  
         */ 
 2763  
         public boolean equals( Object anObject )
 2764  
         {
 2765  0
             if ( anObject == this ) return true;
 2766  0
             if ( anObject instanceof ReferenceKey )
 2767  
             {
 2768  0
                 return ((ReferenceKey)anObject).get() == get();
 2769  
             }
 2770  0
             return false;  
 2771  
         }
 2772  
     }
 2773  
 
 2774  
     /**
 2775  
     * Key combining an object with a string.
 2776  
     * Object is compared by reference.
 2777  
     */ 
 2778  
         static private class CompoundKey 
 2779  
     {
 2780  
         private Object object;
 2781  
         private String string;
 2782  
         private int hashCode;
 2783  
         
 2784  
         /**
 2785  
         * Creates compound key.  
 2786  
         * Neither name nor object may be null.
 2787  
         */
 2788  0
         public CompoundKey ( 
 2789  
             Object anObject, String aString )
 2790  0
         {
 2791  0
             object = anObject;
 2792  0
             string = aString;
 2793  0
             hashCode = object.hashCode() + string.hashCode();
 2794  0
         }
 2795  
         
 2796  
         public int hashCode()
 2797  
         {
 2798  0
             return hashCode;
 2799  
         }
 2800  
         
 2801  
         public boolean equals( Object anObject )
 2802  
         {
 2803  0
             if ( anObject instanceof CompoundKey )
 2804  
             {
 2805  0
                 CompoundKey key = (CompoundKey) anObject;
 2806  0
                 return ( ( key.object == object ) && ( key.string.equals( string ) ) );
 2807  
             }
 2808  0
             return false;  
 2809  
         }
 2810  
         
 2811  
         public String toString()
 2812  
         {
 2813  0
             return "[CompoundKey:"+object+":"+string+"]";   
 2814  
         }
 2815  
     }
 2816  
         
 2817  
     /**
 2818  
     * Used by EditingContext to delegate behavior to another class.
 2819  
     * Note that EditingContext doesn't require its delegates to implement
 2820  
     * this interface: rather, this interface defines the methods that
 2821  
     * EditingContext will attempt to invoke dynamically on its delegate.
 2822  
     * The delegate may choose to implement only a subset of the methods
 2823  
     * on the interface.
 2824  
     */
 2825  
     public interface Delegate
 2826  
     {
 2827  
         /**
 2828  
         * Called after the editing context has completed merge operations
 2829  
         * on one or more objects after receiving an ObjectChangedInStore
 2830  
         * notification.
 2831  
         */
 2832  
         void editingContextDidMergeChanges( 
 2833  
             EOEditingContext anEditingContext );
 2834  
             
 2835  
         /**
 2836  
         * Called by objectsWithFetchSpecification.  
 2837  
         * If null, the editing context will pass the fetch specification
 2838  
         * on to its parent store, as normal.  Otherwise, the context
 2839  
         * will use the returned array to service the request.
 2840  
         */
 2841  
         NSArray editingContextShouldFetchObjects( 
 2842  
             EOEditingContext anEditingContext,
 2843  
             EOFetchSpecification fetchSpecification );
 2844  
             
 2845  
         /**
 2846  
         * Called to determine whether an object should be invalidated.
 2847  
         * Return false to prevent the object from being invalidated.
 2848  
         * Default is true.
 2849  
         */
 2850  
         boolean editingContextShouldInvalidateObject( 
 2851  
             EOEditingContext anEditingContext,
 2852  
             Object anObject, 
 2853  
             EOGlobalID aGlobalID );
 2854  
             
 2855  
         /**
 2856  
         * Called to determine whether the editing context should attempt
 2857  
         * to merge changes in the specified object that the parent store
 2858  
         * says has changed via an ObjectChangedInStore notification.
 2859  
         * Default is true.  Return false if you wish to handle the merge
 2860  
         * yourself, by extracting the values in the object now and comparing
 2861  
         * them to the values when editingContextDidMergeChanges is called.
 2862  
         */
 2863  
         boolean editingContextShouldMergeChangesForObject( 
 2864  
             EOEditingContext anEditingContext,
 2865  
             Object anObject );
 2866  
             
 2867  
         /**
 2868  
         * Returns whether the editing context should ask its message handler
 2869  
         * to display a message.  Return false if the delegate will display the error.
 2870  
         * Default is true.
 2871  
         */
 2872  
         boolean editingContextShouldPresentException( 
 2873  
             EOEditingContext anEditingContext,
 2874  
             Throwable exception );
 2875  
             
 2876  
         /**
 2877  
         * Returns whether the editing context should undo the most
 2878  
         * recent set of changes that resulted in a validation failure.
 2879  
         * Default is true.
 2880  
         */
 2881  
         boolean editingContextShouldUndoUserActionsAfterFailure( 
 2882  
             EOEditingContext anEditingContext );
 2883  
             
 2884  
         /**
 2885  
         * Returns whether the editing context should validate the
 2886  
         * most recent set of changes.  Default is true.
 2887  
         */
 2888  
         boolean editingContextShouldValidateChanges( 
 2889  
             EOEditingContext anEditingContext );
 2890  
             
 2891  
         /**
 2892  
         * Called before the editing context saves its changes
 2893  
         * to the parent object store.
 2894  
         */
 2895  
         void editingContextWillSaveChanges( 
 2896  
             EOEditingContext anEditingContext );
 2897  
 
 2898  
     }
 2899  
     
 2900  
     /**
 2901  
     * Editors register themselves with the editing context
 2902  
     * so that they may receive notification before the context
 2903  
     * commits changes.  This is useful for associations whose
 2904  
     * components do not immediately commit their changes to
 2905  
     * the object they are editing.
 2906  
     */
 2907  
     public interface Editor
 2908  
     {
 2909  
         /**
 2910  
         * Called before the editing context saves its changes
 2911  
         * to the parent object store.
 2912  
         */
 2913  
         void editingContextWillSaveChanges( 
 2914  
             EOEditingContext anEditingContext );
 2915  
 
 2916  
         /**
 2917  
         * Called to determine whether this editor has changes
 2918  
         * that have not been committed to the object in the context.
 2919  
         */
 2920  
         boolean editorHasChangesForEditingContext(
 2921  
             EOEditingContext anEditingContext );
 2922  
 
 2923  
     }
 2924  
             
 2925  
     /**
 2926  
     * Used by EditingContext to delegate messaging handling to another class,
 2927  
     * typically the display group that has the currently active association.
 2928  
     * Note that EditingContext doesn't require its message handlers to implement
 2929  
     * this interface: rather, this interface defines the methods that
 2930  
     * EditingContext will attempt to invoke dynamically on its delegate.
 2931  
     * The delegate may choose to implement only a subset of the methods
 2932  
     * on the interface.
 2933  
     */
 2934  
     public interface MessageHandler
 2935  
     {
 2936  
         /**
 2937  
         * Called to display a message for an error that occurred
 2938  
         * in the specified editing context.
 2939  
         */
 2940  
         void editingContextPresentErrorMessage( 
 2941  
             EOEditingContext anEditingContext,
 2942  
             String aMessage );
 2943  
 
 2944  
         /**
 2945  
         * Called by the specified object store to determine whether
 2946  
         * fetching should continue, where count is the current count
 2947  
         * and limit is the limit as specified by the fetch specification.
 2948  
         * Default is false.
 2949  
         */
 2950  
         boolean editingContextShouldContinueFetching(
 2951  
             EOEditingContext anEditingContext,
 2952  
             int count,
 2953  
             int limit,
 2954  
             EOObjectStore anObjectStore );
 2955  
 
 2956  
     }
 2957  
             
 2958  
 }
 2959  
 
 2960  
 /*
 2961  
  * $Log$
 2962  
  * Revision 1.2  2006/02/16 16:47:14  cgruber
 2963  
  * Move some classes in to "internal" packages and re-work imports, etc.
 2964  
  *
 2965  
  * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions.
 2966  
  *
 2967  
  * Revision 1.1  2006/02/16 13:19:57  cgruber
 2968  
  * Check in all sources in eclipse-friendly maven-enabled packages.
 2969  
  *
 2970  
  * Revision 1.86  2003/12/18 15:37:38  mpowers
 2971  
  * Changes to retain ability to work with objects that don't necessarily
 2972  
  * implement EOEnterpriseObject.  I would still like to preserve this case
 2973  
  * for general usage, however the access package is free to assume that
 2974  
  * those objects will be EOs and cast appropriately.
 2975  
  *
 2976  
  * Revision 1.85  2003/08/19 01:53:12  chochos
 2977  
  * EOObjectStore had some incompatible return types (Object instead of EOEnterpriseObject, in fault methods mostly). It's internally consistent but I hope it doesn't break anything based on this, even though fault methods mostly throw exceptions for now.
 2978  
  *
 2979  
  * Revision 1.84  2003/08/06 23:07:52  chochos
 2980  
  * general code cleanup (mostly, removing unused imports)
 2981  
  *
 2982  
  * Revision 1.83  2003/02/13 15:24:33  mpowers
 2983  
  * hasChanges is now derived, not tracked.
 2984  
  * refaultObject now more consistently removes object from updated list.
 2985  
  *
 2986  
  * Revision 1.82  2002/12/16 15:46:00  mpowers
 2987  
  * Major refactoring to implement setInstancesRetainRegisteredObjects().
 2988  
  *
 2989  
  * Revision 1.81  2002/11/18 22:10:58  mpowers
 2990  
  * Now resetting hasChanges flag on reset.
 2991  
  *
 2992  
  * Revision 1.80  2002/10/24 21:15:33  mpowers
 2993  
  * New implementations of NSArray and subclasses.
 2994  
  *
 2995  
  * Revision 1.79  2002/10/24 18:18:12  mpowers
 2996  
  * NSArray's are now considered read-only, so we can return our internal
 2997  
  * representation to reduce unnecessary object allocation.
 2998  
  *
 2999  
  * Revision 1.78  2002/06/21 21:44:33  mpowers
 3000  
  * No longer marking deleted objects as updated (thanks to dwang).
 3001  
  *
 3002  
  * Revision 1.77  2002/05/20 15:10:17  mpowers
 3003  
  * No longer refaulting if delegate does not wish to handle the merge.
 3004  
  *
 3005  
  * Revision 1.76  2002/03/26 21:46:06  mpowers
 3006  
  * Contributing EditingContext as a java-friendly convenience.
 3007  
  *
 3008  
  * Revision 1.75  2002/03/06 16:14:57  mpowers
 3009  
  * More attempts at ignoring update conflicts that come from ourself.
 3010  
  *
 3011  
  * Revision 1.74  2002/02/21 21:57:50  mpowers
 3012  
  * Implemented default merge behavior.
 3013  
  *
 3014  
  * Revision 1.73  2002/02/20 16:46:54  mpowers
 3015  
  * Implemented better support for EOEditingContext.Delegate.
 3016  
  *
 3017  
  * Revision 1.70  2002/02/19 22:26:05  mpowers
 3018  
  * Implemented EOEditingContext.MessageHandler support.
 3019  
  *
 3020  
  * Revision 1.69  2002/02/19 16:33:42  mpowers
 3021  
  * Implemented support for EditingContext.Editor
 3022  
  *
 3023  
  * Revision 1.68  2002/02/13 22:00:34  mpowers
 3024  
  * Fixed: invalidateAllObjects tries to invalidate inserted objects,
 3025  
  * typically causing class cast exceptions involving EOTemporaryGlobalID.
 3026  
  *
 3027  
  * Revision 1.67  2002/02/06 21:15:35  mpowers
 3028  
  * No longer refaulting a dirty object when we receive an invalidation notif.
 3029  
  *
 3030  
  * Revision 1.66  2002/01/08 19:31:03  mpowers
 3031  
  * refaultObject now correctly refaults the object.
 3032  
  *
 3033  
  * Revision 1.65  2001/12/20 18:56:15  mpowers
 3034  
  * Refinements to snapshotting and calling processRecentChanges.
 3035  
  *
 3036  
  * Revision 1.64  2001/12/10 15:11:41  mpowers
 3037  
  * Now only tracking a commit snapshot after an object has been modified.
 3038  
  *
 3039  
  * Revision 1.63  2001/11/14 00:08:10  mpowers
 3040  
  * Now marking context changed when objects are inserted or deleted
 3041  
  * and when child contexts save their changes into this context.
 3042  
  *
 3043  
  * Revision 1.62  2001/11/07 14:49:31  mpowers
 3044  
  * invalidateAllObjects now handles objects manually discarded in the course
 3045  
  * of invalidation.
 3046  
  *
 3047  
  * Revision 1.61  2001/10/26 20:02:49  mpowers
 3048  
  * No longer using NSNotificationQueue: all notifications are posted immed.
 3049  
  *
 3050  
  * Revision 1.60  2001/10/26 18:37:50  mpowers
 3051  
  * Now using NSRunLoop to correctly flush recent changes before delayed
 3052  
  * observers and AWT events.
 3053  
  *
 3054  
  * Revision 1.59  2001/10/23 22:29:59  mpowers
 3055  
  * Now posting notifications immediately.
 3056  
  * Recent changes are flushed at ObserverPrioritySixth, soon to change.
 3057  
  *
 3058  
  * Revision 1.58  2001/09/10 14:16:51  mpowers
 3059  
  * EditingContexts now relay InvalidatedAllObjectsInStore notifications.
 3060  
  *
 3061  
  * Revision 1.57  2001/06/18 14:11:15  mpowers
 3062  
  * Inserting a deleted object simply cancels the delete operation.
 3063  
  *
 3064  
  * Revision 1.56  2001/06/07 22:07:59  mpowers
 3065  
  * Now handling delete notifications before update notifications.
 3066  
  * Eliminated the case where deleted objects were also being put
 3067  
  * on the updated list when notifying child contexts.
 3068  
  *
 3069  
  * Revision 1.55  2001/05/18 21:04:33  mpowers
 3070  
  * Reimplemented EditingContext.initializeObject.
 3071  
  *
 3072  
  * Revision 1.54  2001/05/05 23:05:42  mpowers
 3073  
  * Implemented Array Faults.
 3074  
  *
 3075  
  * Revision 1.53  2001/05/05 15:00:06  mpowers
 3076  
  * Tested load-on-demand: still works.
 3077  
  * Now using registerClone for consistency.
 3078  
  * Editing context is temporarily posting notification on objectWillChange.
 3079  
  *
 3080  
  * Revision 1.52  2001/05/05 14:11:48  mpowers
 3081  
  * Implemented registerClone.
 3082  
  *
 3083  
  * Revision 1.51  2001/05/04 16:57:56  mpowers
 3084  
  * Now correctly transposing references to editing contexts when
 3085  
  * cloning/copying between editing contexts.
 3086  
  *
 3087  
  * Revision 1.50  2001/05/02 17:58:41  mpowers
 3088  
  * Removed debugging code, added comments.
 3089  
  *
 3090  
  * Revision 1.49  2001/05/02 15:47:40  mpowers
 3091  
  * Fixed the pernicious problem with reverts: recordObject was recording
 3092  
  * a snapshot of the clone before the transposition-copy happened,
 3093  
  * so the revert object would lose all of its transposed relationships.
 3094  
  *
 3095  
  * Revision 1.48  2001/05/02 12:39:05  mpowers
 3096  
  * Fixed a nasty problem with transpose-cloning and faultForGlobalID.
 3097  
  * Now we're forced to create a deep clone, registered it, and then
 3098  
  * transpose it after it has been registered.
 3099  
  *
 3100  
  * Revision 1.47  2001/04/30 13:15:24  mpowers
 3101  
  * Child contexts re-initializing objects invalidated in parent now
 3102  
  * propery transpose relationships.
 3103  
  *
 3104  
  * Revision 1.46  2001/04/29 22:02:45  mpowers
 3105  
  * Work on id transposing between editing contexts.
 3106  
  *
 3107  
  * Revision 1.45  2001/04/29 02:29:31  mpowers
 3108  
  * Debugging relationship faulting.
 3109  
  *
 3110  
  * Revision 1.44  2001/04/28 16:18:44  mpowers
 3111  
  * Implementing relationships.
 3112  
  *
 3113  
  * Revision 1.43  2001/04/28 14:12:23  mpowers
 3114  
  * Refactored cloning/copying into KeyValueCodingUtilities.
 3115  
  *
 3116  
  * Revision 1.42  2001/04/27 00:27:11  mpowers
 3117  
  * Provided description to not-implemented exceptions.
 3118  
  *
 3119  
  * Revision 1.41  2001/04/26 01:16:44  mpowers
 3120  
  * Major bug fix so we no longer accumulate objects in the all objects
 3121  
  * list with every InvalidateAllObjectsInStore.
 3122  
  *
 3123  
  * Revision 1.40  2001/04/21 23:07:49  mpowers
 3124  
  * Now only broadcasts notifications if there's actually a change.
 3125  
  *
 3126  
  * Revision 1.39  2001/04/13 16:33:11  mpowers
 3127  
  * Corrected the refaulting behavior.
 3128  
  *
 3129  
  * Revision 1.38  2001/04/09 21:42:10  mpowers
 3130  
  * Debugging and optimizing notifications.
 3131  
  *
 3132  
  * Revision 1.37  2001/04/08 20:59:47  mpowers
 3133  
  * objectsForFetchSpecification now relies on faultForGlobalID.
 3134  
  *
 3135  
  * Revision 1.36  2001/04/03 20:36:01  mpowers
 3136  
  * Fixed refaulting/reverting/invalidating to be self-consistent.
 3137  
  *
 3138  
  * Revision 1.35  2001/03/29 03:29:49  mpowers
 3139  
  * Now using KeyValueCoding and Support instead of Introspector.
 3140  
  *
 3141  
  * Revision 1.34  2001/03/28 14:06:29  mpowers
 3142  
  * Implemented snapshots.  Revert now uses snapshots.
 3143  
  *
 3144  
  * Revision 1.33  2001/03/20 23:20:33  mpowers
 3145  
  * invalidating all objects now sets the dirty flag to false.
 3146  
  *
 3147  
  * Revision 1.32  2001/03/19 21:44:36  mpowers
 3148  
  * Reverts reinitialize for now.
 3149  
  * Testing for inserted objects instead of temp id when invalidating object.
 3150  
  *
 3151  
  * Revision 1.31  2001/03/15 21:10:26  mpowers
 3152  
  * Implemented global id re-registration for newly saved inserts.
 3153  
  *
 3154  
  * Revision 1.30  2001/03/13 21:41:34  mpowers
 3155  
  * Broadcasting willChange for any change to hasChanges.
 3156  
  * Fixed major bug with inserted objects treated as updated objects
 3157  
  * in child display groups.
 3158  
  *
 3159  
  * Revision 1.29  2001/03/09 22:10:30  mpowers
 3160  
  * Fine tuned initializeObject.
 3161  
  *
 3162  
  * Revision 1.28  2001/03/06 23:23:55  mpowers
 3163  
  * objectForGlobalID now returns null if not found.
 3164  
  * objectsForFetchSpecification again does things the old way, for now.
 3165  
  *
 3166  
  * Revision 1.27  2001/03/02 16:31:45  mpowers
 3167  
  * Trying to better handle fetches from child contexts.
 3168  
  * No longer trying to invalidate temporary objects.
 3169  
  *
 3170  
  * Revision 1.26  2001/02/28 16:25:19  mpowers
 3171  
  * Now calling globalIDForObject internally.
 3172  
  *
 3173  
  * Revision 1.25  2001/02/27 17:36:55  mpowers
 3174  
  * Objects inserted from child now preserve the existing temporary id.
 3175  
  *
 3176  
  * Revision 1.24  2001/02/27 02:11:17  mpowers
 3177  
  * Now throwing exception when cloning fails.
 3178  
  * Removed debugging printlns.
 3179  
  *
 3180  
  * Revision 1.23  2001/02/26 22:41:51  mpowers
 3181  
  * Implemented null placeholder classes.
 3182  
  * Duplicator now uses NSNull.
 3183  
  * No longer catching base exception class.
 3184  
  *
 3185  
  * Revision 1.22  2001/02/26 21:18:45  mpowers
 3186  
  * Now marking edited objects from child contexts that were not already
 3187  
  * recorded in parent as changed in saveChangesInEditingContext.
 3188  
  *
 3189  
  * Revision 1.21  2001/02/26 15:53:22  mpowers
 3190  
  * Fine-tuning notification firing.
 3191  
  * Child display groups now update properly after parent save or invalidate.
 3192  
  *
 3193  
  * Revision 1.20  2001/02/24 17:03:22  mpowers
 3194  
  * Implemented the notification queue, and changed editing context to use it.
 3195  
  *
 3196  
  * Revision 1.19  2001/02/23 23:44:15  mpowers
 3197  
  * Fine-tuning notification handling.
 3198  
  *
 3199  
  * Revision 1.18  2001/02/22 22:56:57  mpowers
 3200  
  * Only refaulting edited objects on parent store invalidateAll.
 3201  
  *
 3202  
  * Revision 1.17  2001/02/22 20:54:39  mpowers
 3203  
  * Implemented notification handling.
 3204  
  *
 3205  
  * Revision 1.16  2001/02/21 22:10:55  mpowers
 3206  
  * Editing context is now posting appropriate notifications.
 3207  
  *
 3208  
  * Revision 1.15  2001/02/21 21:17:32  mpowers
 3209  
  * Now retaining a reference to the recent changes observer.
 3210  
  * Better documented need to retain reference.
 3211  
  * Started implementing notifications.
 3212  
  *
 3213  
  * Revision 1.14  2001/02/20 17:24:22  mpowers
 3214  
  * Now using reference keys in objectToID.
 3215  
  *
 3216  
  * Revision 1.13  2001/02/20 16:45:36  mpowers
 3217  
  * Child data sources now accept a data source instead of an editing context
 3218  
  * for more flexibility.  Child data sources now forward relationship
 3219  
  * methods to parent source.
 3220  
  *
 3221  
  * Revision 1.12  2001/02/16 22:51:29  mpowers
 3222  
  * Now deep-cloning objects passed between editing contexts.
 3223  
  *
 3224  
  * Revision 1.11  2001/02/16 18:34:19  mpowers
 3225  
  * Implementing nested contexts.
 3226  
  *
 3227  
  * Revision 1.9  2001/02/15 21:13:30  mpowers
 3228  
  * First draft implementation is complete.  Now on to debugging.
 3229  
  *
 3230  
  * Revision 1.7  2001/02/13 23:24:29  mpowers
 3231  
  * Implementing more of editing context.
 3232  
  *
 3233  
  * Revision 1.4  2001/02/09 22:09:34  mpowers
 3234  
  * Completed implementation of EOObjectStore.
 3235  
  *
 3236  
  * Revision 1.3  2001/02/06 15:24:11  mpowers
 3237  
  * Widened parameters on abstract method to fix build.
 3238  
  *
 3239  
  * Revision 1.2  2001/02/05 03:45:37  mpowers
 3240  
  * Starting work on EOEditingContext.
 3241  
  *
 3242  
  * Revision 1.1.1.1  2000/12/21 15:46:42  mpowers
 3243  
  * Contributing wotonomy.
 3244  
  *
 3245  
  */
 3246  
     
 3247